【3D技术宅公社】XR数字艺术论坛  XR技术讨论 XR互动电影 定格动画

 找回密码
 立即注册

QQ登录

只需一步,快速开始

调查问卷
论坛即将给大家带来全新的技术服务,面向三围图形学、游戏、动画的全新服务论坛升级为UTF8版本后,中文用户名和用户密码中有中文的都无法登陆,请发邮件到324007255(at)QQ.com联系手动修改密码

3D技术论坛将以计算机图形学为核心,面向教育 推出国内的三维教育引擎该项目在持续研发当中,感谢大家的关注。

查看: 2699|回复: 3

iPhone开发(2)进阶--- 深入理解iPhone OS/SDK与Objective-C 2.0

[复制链接]
发表于 2012-12-4 23:05:27 | 显示全部楼层 |阅读模式
工欲善其事,必先利其器。在开发iPhone应用程序的时候,深入理解iPhone OS/SDK与Objective-C 2.0是很重要的。iPhone OS
        iPhone OS 由4个主要部分组成。下面简单地罗列一下它们的功能。
Cocoa Touch
窗口和视图
        事件管理
        用户接口
        加速传感器
        照相机
Media
        Core Graphics(2维图形接口)
Core Animation(动画)
OpenGL
        Core Audio(声音)
OpenAL
        Media Player(MPEG4,MP3)
Core Services
        Address Book
        Core Foundation
        Core Location
        CFNetwork(http,https,ftp,SSL,TLS)
网络安全
SQLite(SQL数据库
XML
        Core OS
多线程
        网络应用(BSD套接字)
        文件系统
Bonjour(利用无线网络连接其他机器)
iPhone SDK
        iPhone SDK 中主要包含下列4个工具。
Xcode - 项目管理、代码编辑、编译、调试(IDE)
Interface Builder - GUI 设计
iPhone Simulator - 模拟器
Instrument - 性能测试、调整
        实际开发的过程中,基本上是在使用 Xcode 与 Interface Builder 来进行的。调试则是使用模拟器或者实际设备。要注意的是在PC上模拟程序,由于PC的主频,性能高于实际设备,所以不能只在模拟器上调试。除此之外,一些类,功能在模拟器上也是不能使用的,比如 NSDateCalendar 类,或者是照相机功能。
Objective-C 2.0
内存管理
        虽然 Objective-C 2.0 已经支持了垃圾收集了,但是 iPhone OS 中却不能使用它。所以我们需要自己来管理内存。Objective-C 的内存管理方式与使用引用计数的方式,就是说对象有一个计数器,引用对象一次,计数器加一,当计数器为0的时候,该对象的内存被释放。
        创建对象实例的时候(init,alloc)应用计数加一,执行过程中,别的对象如果需要该对象,需要用(retain)来引用它,这时,该对象的应用计数器加一。不需要对象的时候用(release)来释放,这时引用计数器减一,当计数器为0的时候,释放该对象内存。
init,alloc - 计数器 +1
        retain - 计数器 +1
        release - 计数器 -1
另外如果不使用 retain,release,可以使用(autorelease)来自动释放对象。
        容器
Objective-C 中的容器主要有以下3种:
数组
        字典
Set
向容器中添加的内容不能直接用 int 或 float,需要通过 NSNumber 等封装类来实现。Objective-C 2.0 开始可以使用迭代子(Enumerator),来顺序访问容器中的元素。
Notification
        Notification是消息通知的功能。具体使用 NSNotificationCenter 类。将需要接受通知的对象,方法,事件注册到该类上。
        归档(Archive)
        归档是指将对象的内存布局原样地保存到文件系统上。同样对应的由文件中的数据生成对象叫做UnAchive。在 iPhone SDK 中使用 NSKeyedArchiver 和 NSKeyedUnarchiver 类来实现。
        一般在程序结束的时候,保存当前的状态,再次启动的时候UnAchive一下,就又回到了刚才退出时的状态。下面是一个例子:
// MyKeyedArchiver.h
        #import <Cocoa/Cocoa.h>
        @interface NSKeyedArchiver (MyKeyedArchiver)
- (void)encodeValueOfObjCType:(const char *)valueType at:(const void *)address;
        @end
         #import "MyKeyedArchiver.h"
        @implementation NSKeyedArchiver (MyKeyedArchiver)
- (void)encodeValueOfObjCType:(const char *)valueType at:(const void *)address
        {
            NSMutableData *datas = [NSMutableData data];
            NSArchiver *arch = [[NSArchiver alloc] initForWritingWithMutableData:datas];
            [arch encodeValueOfObjCType:valueType
                                     at:address];
            [self encodeObject:[NSData dataWithData:datas]];
            [arch release];
        }
        @end
         // MyKeyedUnarchiver.h
        #import <Cocoa/Cocoa.h>
        @interface NSKeyedUnarchiver (MyKeyedUnarchiver)
- (void)decodeValueOfObjCType:(const char *)valueType at:(void *)data;
        @end
         #import "MyKeyedUnarchiver.h"
        @implementation NSKeyedUnarchiver (MyKeyedUnarchiver)
- (void)decodeValueOfObjCType:(const char *)valueType at:(void *)data
        {
            NSData *datas = [self decodeObject];
            NSUnarchiver *unarch = [[NSUnarchiver alloc] initForReadingWithData:datas];
            [unarch decodeValueOfObjCType:valueType
                                       at:data];
            [unarch release];
        }
        @end

开发iPhone程序,首先接触到的不是源代码,而是项目工程文件,目录。我们来看看它有怎样的构成。
iPhone应用程序目录构成
iPhone应用程序被放入一个叫做沙盒(sandbox)的具有安全性的构造中。程序只能访问自己沙盒中的资源。
iPhone 应用程序与 Mac OS 上的程序基本上相同、 只是程序目录下有一些不同。可以通过 AddressBook 等构造访问其他的功能或构造体。
iPhone 应用程序的目录构造如下所示:
/Applications/
 [Application1]/
  Application1.app
  Documents/
  Library/
  tmp/
 [Application2]/
  Application2.app
  Documents/
  Library/
  tmp/
工程项目的构成
        工程项目的构成虽然根据程序不同而不同,但基本上都是基于MVC模型,所以按照 Model、Controller、View 来组织目录形式。
        比如以下的目录构成:
Classes
        Libraries (各种中间件,程序库等)
JSON
        ImageStore
其他程序模块
Controllers (与 View Controller 相关的类)
UIApplicationDelegate
        UIViewController
        Views (定制的视图,程序界面)
UITableViewCell的子类
UIView的子类
        项目文件构成
        接下来我们再看看程序工程中还有什么:
HelloWorld
        |-- Classes
        |   |-- HelloWorldAppDelegate.h
        |   |-- HelloWorldAppDelegate.m
        |   |-- HelloWorldViewController.h
        |   `-- HelloWorldViewController.m
        |-- HelloWorld.xcodeproj
        |-- HelloWorldViewController.xib
        |-- HelloWorld_Prefix.pch
        |-- Info.plist
        |-- MainWindow.xib
        |-- build
        |   `-- HelloWorld.build
        `-- main.m
         
        .pch
预编译头文件,win32里经常会碰到,这里也有,包含了常用的头文件。
.plist
包含了项目自身的特性,比如说项目名称,默认加载的nib file,版本等。
.xib
程序的资源文件。用于简化编码过程,提高开发效率。
main.m
        iphone程序的入口,类似于C/C++中的main函数。
main函数如下所示:
int main(int argc, char *argv[]) {
            NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
            int retVal = UIApplicationMain(argc, argv, nil, nil);
            [pool release];
            return retVal;
        }
        main函数的参数 argc 以及 argv[] 与C语言中的意思一样,支持命令行的输入。
        接下来创建一个NSAutoreleasePool对象,用来自动管理程序的内存。
1 NSAutoreleasePool * pool = NSAutoreleasePool alloc] init];
最主要的是下面的 UIApplicationMain 的调用,通过它完成系统启动的过程,并形成一个事件驱动。
1 int retVal = UIApplicationMain(argc, argv, nil, nil);
关于 iPhone 应用程序具体的启动过程,我们下回讲解
我们看到了iPhone的入口函数main,这之后它有是怎样启动应用程序,初始化的呢,这些都是通过 UIApplicationMain 来实现的。
        其启动的流程图大致如下图所示:


1 int retVal = UIApplicationMain(argc, argv, nil, nil);

通过上面的语句,创建UIApplication实例。同时,查看应用程序的 Info.plist 文件(该文件记录了一些应用程序的基础信息,比如程序名称,版本,图标等)。该文件还包含应用程序资源文件的名称(nib文件,名称用NSMainNibFile键指定)。如下所示:
<key>NSMainNibFile</key>
            <string>MainWindow</string>

上面的意思是指,在应用程序启动的时候,需要从nib文件中加载名为 MainWindow 的资源。
        其实,nib文件也是参照项目中Resources组中MainWindow.xib文件,我们双击该文件,启动Interface Builder后可以看到下面的图示:

Interface Builder 中有以下4个项目:
File’s Owner 对象,实际上就是 UIApplication 的实例。
First Responder 对象。每个程序都会有一个第一响应者,比如鼠标事件,键盘事件等,它就是对应的那个对象。比如多文档程序中,menu的响应事件一般都是连接到FirstResponder中去的,因为主界面一般都在别的nib里面,此时的FirstResponder就是你的那个主nib的FileOwner。
Delegate 对象。
Window。应用程序启动的时候所显示的窗口。
        应用程序启动之后,像下面图一样,你可以定制自己的行为。

程序启动之后,会发送消息给 UIApplicationDelegate 的 applicationDidFinishLaunching 方法,在这里我们完成自己的初始化过程。如下面的代码。
- (void)applicationDidFinishLaunching:(UIApplication *)application {
            // Override point for customization after app launch
            [window addSubview:viewController.view];
            [window makeKeyAndVisible];
        }
- (void)dealloc {
            [viewController release];
            [window release];
            [super dealloc];
        }

        [window addSubview:viewController.view] 表示 XXXXXXViewController.xib 、[window makeKeyAndVisible] 是显示该窗口。
        总结以上的内容,iPhone应用程序的引导过程如下所示:
1 main.m → MainWindow.xib → XXXXXXDelegate.m → XXXXXXViewController.m → XXXXXXViewController.xib

或者看下面的图来理解。

 楼主| 发表于 2012-12-4 23:07:00 | 显示全部楼层
Xcode 也支持以命令行形式来编译 iPhone 程序。另外还可以手动的编写 Makefile 文件,实现编译→安装的自动化批处理过程。如果你习惯了命令行的操作方式(linux,unix),那么这样的操作还是很方便的。        首先看看 Xcode 的命令行格式:
xcodebuild -target Project_Name
        xcodebuild install -target Project_Name

下面我们来实现程序的编译,并通过 ldid 转换编码格式,最后用 ssh 将编译好的程序安装到 iPhone 上的 /Applications/目录下。
        首先安装 ssh 的公开密匙到 iPhone 上
1). 在Mac的终端上产生密匙
ssh-keygen -t rsa
        Generating public/private rsa key pair.
        Enter file in which to save the key (/home/xxxx/.ssh/id_rsa):
        Created directory '/home/xxxx/.ssh'.
        Enter passphrase (empty for no passphrase): xxx
        Enter same passphrase again: xxx
        Your identification has been saved in /home/xxxx/.ssh/id_rsa.
        Your public key has been saved in /home/xxxx/.ssh/id_rsa.pub.
        The key fingerprint is:
        e4:e8:b7:05:06:b3:f0:ff:af:13:fc:50:6a:5b:d1:b5 xxxx@localhost.localdomain

过程中会提问你通行证(passphrase),输入你常用的秘密。
2). 在 iPhone 上创建.ssh目录(iPhone的IP地址是10.0.2.2)
1 ssh root@10.0.2.2 'mkdir -p .ssh'

如果问道你iPhone root password,输入 alpine。
3). 拷贝刚才生成的公开密匙到 iPhone
        1 cat ~/.ssh/id_rsa.pub | ssh root@10.0.2.2 'cat >> .ssh/authorized_keys'

如果问道你iPhone root password,输入 alpine。
4). 在 iPhone 上编辑 /etc/ssh/sshd_config 文件
#将
#StrictModes yes
        #PubkeyAuthentication yes
        #AuthorizedKeysFile .ssh/authorized_keys
        #替换为
StrictModes no
        PubkeyAuthentication yes
        AuthorizedKeysFile .ssh/authorized_keys

        5). 重新启动iPhone
接下来,编译生成ldid工具
wget http://svn.telesphoreo.org/trunk/data/ldid/ldid-1.0.610.tgz
        tar -zxf ldid-1.0.610.tgz
        # 如果是 PowerPC 下载下面的补丁
# wget -qO- http://fink.cvs.sourceforge.net/viewvc/*checkout*/fink/dists/10.4/unstable/crypto/finkinfo/ldid.patch?revision=1.1 | patch -p0
        cd ldid-1.0.610
        g++ -I . -o util/ldid{,.cpp} -x c util/{lookup2,sha1}.c
        sudo cp -a util/ldid /usr/bin

最后,让我们看看Makefile中都有什么
        项目中的文件如下所示:
Classes : source code (.m .c .cpp etc)
        Resources : png file and other support files
        Project folder : *.xib Info.plist
        Makefile: Select all
        PREFIX  = arm-apple-darwin9-
###/////////////////////////////////////////////////////////////
        ###                      Executable files
        ###/////////////////////////////////////////////////////////////
        CC      = $(PREFIX)gcc
        CXX     = $(PREFIX)g++
        LD      = $(CC)
        AR      = $(PREFIX)ar
        STRIP   = $(PREFIX)strip
        OBJCOPY = $(PREFIX)objcopy
        ####################################################################################
        ## debug/release
        DEBUG   ?= n
        DEVEL   ?= n
        ## SDK版本
SDKVER    = 3.1.2
        ## iPhone的IP地址
IPHONE_IP = 10.0.2.2
        ## iPhone SDK路径
IPHONESDK = /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS$(SDKVER).sdk
        ## include 路径
INCPATH += -I"$(IPHONESDK)/usr/include"
        INCPATH += -I"/Developer/Platforms/iPhoneOS.platform/Developer/usr/lib/gcc/arm-apple-darwin9/4.2/include/"
        INCPATH += -I"/Developer/Platforms/iPhoneOS.platform/Developer/usr/include/"
        INCPATH += -I"/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator$(SDKVER).sdk/usr/include"
        ## 标准库或者框架的设置
LDFLAGS=    -lobjc \
                    -bind_at_load \
                    -multiply_defined suppress \
                    -w
        LDFLAGS += -framework CoreFoundation
        LDFLAGS += -framework Foundation
        LDFLAGS += -framework UIKit
        LDFLAGS += -framework CoreGraphics
        #LDFLAGS += -framework AddressBookUI
        #LDFLAGS += -framework AddressBook
        #LDFLAGS += -framework QuartzCore
        #LDFLAGS += -framework GraphicsServices
        #LDFLAGS += -framework CoreSurface
        #LDFLAGS += -framework CoreAudio
        #LDFLAGS += -framework Celestial
        #LDFLAGS += -framework AudioToolbox
        #LDFLAGS += -framework WebCore
        #LDFLAGS += -framework WebKit
        #LDFLAGS += -framework SystemConfiguration
        #LDFLAGS += -framework CFNetwork
        #LDFLAGS += -framework MediaPlayer
        #LDFLAGS += -framework OpenGLES
        #LDFLAGS += -framework OpenAL
        LDFLAGS += -F"$(IPHONESDK)/System/Library/Frameworks"
        LDFLAGS += -F"$(IPHONESDK)/System/Library/PrivateFrameworks"
        ## 编译开关
CFLAGS  += $(INCPATH) \
                -std=c99 \
                -W -Wall \
                -funroll-loops \
                -Diphoneos_version_min=2.0 \
                -Wno-unused-parameter \
                -Wno-sign-compare
        ifeq ($(DEBUG), y)
        CFLAGS  += -O0 -g -DDEBUG_MUTEX
        else
        CFLAGS  += -O3 -DNDEBUG
        ifeq ($(DEVEL), y)
        CFLAGS  += -g
        endif
        endif
        CFLAGS += -F"$(IPHONESDK)/System/Library/Frameworks"
        CFLAGS += -F"$(IPHONESDK)/System/Library/PrivateFrameworks"
        ####################################################################################
        BUILDDIR =./build/3.0
        SRCDIR   =./Classes
        RESDIR   =./Resources
        ###/////////////////////////////////////////////////////////////
        ###                        Source files
        ###/////////////////////////////////////////////////////////////
        OBJS     = $(patsubst %.m,%.o,$(wildcard $(SRCDIR)/*.m))
        OBJS    += $(patsubst %.m,%.o,$(wildcard ./*.m))
        OBJS    += $(patsubst %.c,%.o,$(wildcard $(SRCDIR)/*.c))
        OBJS    += $(patsubst %.cpp,%.o,$(wildcard $(SRCDIR)/*.cpp))
        NIBS     = $(patsubst %.xib,%.nib,$(wildcard *.xib))
        RESOURCES= $(wildcard $(RESDIR)/*)
        APPFOLDER= $(TARGET).app
        .PHONY: all
        all: $(TARGET) bundle
        $(TARGET): $(OBJS)
            $(LD) $(LDFLAGS) -o $@ $^
        %.o:    %.m
            $(CC) -c $(CFLAGS) $< -o $@
        %.o:    %.c
            $(CC) -c $(CFLAGS) $< -o $@
        %.o:    %.cpp
            $(CXX) -x objective-c++ $(CFLAGS) $< -o $@
        %.nib:  %.xib
            ibtool $< --compile $@
        bundle: $(TARGET)
            @rm -rf $(BUILDDIR)
            @mkdir -p $(BUILDDIR)/$(APPFOLDER)
            @cp -r $(RESDIR)/* $(BUILDDIR)/$(APPFOLDER)
            @cp Info.plist $(BUILDDIR)/$(APPFOLDER)/Info.plist
            @echo "APPL" > $(BUILDDIR)/$(APPFOLDER)/PkgInfo
            mv $(NIBS) $(BUILDDIR)/$(APPFOLDER)
        #    export CODESIGN_ALLOCATE=/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin/codesign_allocate
            @ldid -S $(TARGET)
            @mv $(TARGET) $(BUILDDIR)/$(APPFOLDER)/$(TARGET)_
        install: bundle
            @ssh root@$(IP) "cd /Applications/$(APPFOLDER) && rm -R * || echo 'not found' "
            @scp -rp $(BUILDDIR)/$(APPFOLDER) root@$(IP):/Applications
            @ssh root@$(IP) "cd /Applications/$(APPFOLDER) ; ldid -S $(TARGET)_; killall SpringBoard"
            @echo "Application $(APPFOLDER) installed"
        uninstall:
            ssh root@$(IPHONE_IP) 'rm -fr /Applications/$(APPFOLDER); respring'
            @echo "Application $(APPFOLDER) uninstalled, please respring iPhone"
        install_respring:
            scp respring_arm root@$(IPHONE_IP):/usr/bin/respring
        .PHONY: clean
        clean:
            @rm -f $(OBJS) $(TARGET)
            @rm -rf $(BUILDDIR)

然后执行下面的make命令,我们就可以直接在 iPhone 上测试我们的程序了。
make install_respring
        make
        make install

第三讲中看到的,即使不使用 XIB 文件,也可以通过重写 viewDidLoad 函数来配置任意的view或者是Controller。这里我们看看怎样编程定制这样的view和Controller。
        首先如果 UIViewController 的 init 方法找不到 XIB 文件的话,会自动创建一个自己的 UView 对象,使用 viewDidLoad 将自己登录。所以,我们可以在定制 UIViewController 时实现 viewDidLoad 方法、将 view 作为 subview。
        例子中 view 的背景为蓝色,在其上设置一个 UIButton。
        第一步,在 CustomViewControllerAppDelegate.m 文件中定义 CustomViewController 类。
@interface CustomViewController : UIViewController {
        }
        @end

同时,在 CustomViewControllerAppDelegate.h 文件中实现该实例。
  @class CustomViewController;
        @interface CustomViewControllerAppDelegate : NSObject  {
            UIWindow *window;
            CustomViewController*   controller;
        }
        @class CustomViewController 类似与C++中的类先声明。
        因为不需要外部对象的访问,所以没有 @property 宣言。

CustomViewController 的实例在 CustomViewControllerAppDelegate 类的成员函数 applicationDidFinishLaunching 中生成,然后用 addSubview 将 CustomViewController实例中的 view 添加进去。最后在 CustomViewControllerAppDelegate 释放的时候(dealloc)中释放其实例。代码如下所示:
  - (void)applicationDidFinishLaunching:(UIApplication *)application {
            viewController = [[CustomViewController alloc]init];
            [window addSubview:viewController.view];
            [window makeKeyAndVisible];
        }
- (void)dealloc {
            [window release];
            [controller release];
            [super dealloc];
        }
用 window addSubview 表示最初的view。

        然后像下面简单地声明和实现 CustomViewController。在 CustomViewController 的 viewDidLoad 函数中设置背景色为蓝色。
@interface CustomViewController : UIViewController {
        }
        @end
        @implementation CustomViewController
- (void)viewDidLoad {
            [super viewDidLoad];
            self.view.backgroundColor = [UIColor blueColor];
        }
        @end

编译以后执行一下,看到下面的结果。

接下来我们再来添加按钮,我们动态生成一个 UIButtonTypeInfoLight 类型的按钮,设置了按钮的 frame 后,用addSubview 添加到 view 上。
@implementation CustomViewController
- (void)viewDidLoad {
            [super viewDidLoad];
            self.view.backgroundColor = [UIColor blueColor];
            UIButton* button = [UIButton buttonWithType:UIButtonTypeInfoLight];
            button.frame = CGRectMake(100,100,100,100);
            [self.view addSubview:button];
        }
        @end

最终的效果如下:

下一讲我们来具体定制按钮动作

介绍了不使用 XIB 文件来定义 UIViewController 的方法。这一回说一说自动创建 UIButton 而不使用 XIB 文件。
        通过这一节的学习,我们可以掌握不通过 XIB (InterfaceBuilder) 来使用 UIControl 的 addTarget 方法、对应相应的事件动作。
        具体的例子是基于上一讲中的 CustomViewController 类,按钮按下是计数器加一,并显示在视图上。
        首先,在 CustomViewController 类中添加技术用的变量 count。
  @interface CustomViewController : UIViewController {
            int count;  // 计数器变量。
}
        @end

接下来,添加按钮按下时调用的方法。
-(void)countup:(id)inSender {
            count++;                        //  计数器自加
    //  inSender 是被点击的 Button 的实例,下面设置其标题
    [inSender setTitle:[NSString
                stringWithFormat:@"count:%d", count]
                forState:UIControlStateNormal];
        }

        setTitle 方法设定 UIButton 的标题。使用 forState: 来指定该标题显示的状态(按下,弹起,通常),这里指定通常状态显示的标题。当然,使用 UIControlStateNormal 也是可以的。
        注册按钮按下时的事件函数可以通过 UIControl 类中的 addTarget:action:forControlEvents: 方法(UIButton 继承了UIControl 类,所以可以直接使用)。如下所示:
- (void)viewDidLoad {
            [super viewDidLoad];
            self.view.backgroundColor = [UIColor blueColor];
            UIButton* button = [UIButton buttonWithType:UIButtonTypeInfoLight];
            button.frame = CGRectMake(100,100,100,100);
            // 注册按钮按下时的处理函数
    [button addTarget:self action:@selector(countup:)
                forControlEvents:UIControlEventTouchUpInside];
            [self.view addSubview:button];
        }

        forControlEvents: 中设定 UIControlEventTouchUpInside 是指在按钮上按下时响应。
        因为动作函数(countup)的类型是
1 -(void)countup:(id)inSender

则在注册的时候需要写 countup: 。
        而如果函数类型是
1 -(void)countup

的话,则是 countup ,这时 addTarget 接收的函数类型如下所示:
1 - (void) countup:(id)sender forEvent:(UIEvent *)event

同一响应,也可以注册多个处理,比如下面的代码,将上面两种类型的动作函数都注册了:
// 第一种处理方法
-(void)countup:(id)inSender {
            count++;
            [inSender setTitle:[NSString
                stringWithFormat:@"count:%d", count]
                forState:UIControlStateNormal];
        }
        // 第二种处理方法
-(void)countup {
            count++;
        }
-(void)countup:(id)inSender forEvent:(UIEvent *)event {
            count++;
            [inSender setTitle:[NSString
                stringWithFormat:@"count:%d", count]
                forState:UIControlStateNormal];
        }
- (void)viewDidLoad {
            [super viewDidLoad];
            self.view.backgroundColor = [UIColor blueColor];
            UIButton* button = [UIButton buttonWithType:UIButtonTypeInfoLight];
            button.frame = CGRectMake(100,100,100,100);
            // 注册第一种方法
    [button addTarget:self action:@selector(countup:)
                forControlEvents:UIControlEventTouchUpInside];
            // 注册第二种方法
    [button addTarget:self action:@selector(countup)
                forControlEvents:UIControlEventTouchUpInside];
            [button addTarget:self action:@selector(countup:forEvent:)
                forControlEvents:UIControlEventTouchUpInside];
            [self.view addSubview:button];
        }

编译以后,显示如下:

 楼主| 发表于 2012-12-4 23:08:52 | 显示全部楼层
当程序中含有多个view,需要在之间切换的时候,可以使用UINavigationController,或者是ModalViewController。UINabigationController 是通过向导条来切换多个view。而如果view 的数量比较少,且显示领域为全屏的时候,用ModalViewController 就比较合适(比如需要用户输入信息的view,结束后自动回复到之前的view)。今天我们就看看ModalViewController 的创建方法。
ModalViewController 并不像UINavigationController 是一个专门的类,使用UIViewController 的presentModalViewController 方法指定之后就是ModalViewController 了。

这里使用上两回做成的CustomViewController(由UIViewController继承)来实现ModalViewController 的实例。

首先,准备ModalViewController 退出时的函数。调用UIViewController 的dismissModalViewController:Animated: 方法就可以了,如下所示:
// 这里按钮按下的时候退出ModalViewController
-(void)dismiss:(id)inSender {
            //  如果是被presentModalViewController 以外的实例调用,parentViewController 将是nil,下面的调用无效
    [self.parentViewController dismissModalViewControllerAnimated:YES];
        }
接下来,生成另一个CustomViewController 的实例,用来表示ModalViewController,并将其对应的view 设置成红色。然后传递给presentModalViewController: Animated: 显示ModalViewController 的view。

- (void)applicationDidFinishLaunching:(UIApplication *)application {
            controller = [[CustomViewController alloc] init];
            [window addSubview:controller.view];
            [window makeKeyAndVisible];
            // 生成ModalViewController
            CustomViewController* controllerB = [[CustomViewController alloc] init];
            // 设置view 的背景为红色
    controllerB.view.backgroundColor = [UIColor redColor];
            // 显示ModalViewController view
            [controller presentModalViewController:controllerB animated:YES];
            // presentModalViewController 已经被controller 管理,这里可以释放该实例了
    [controllerB release];
        }
编译执行以后,首先启动的是红色背景的ModalViewController view、按下按钮后恢复到蓝色背景的通常view 上。

也可以在显示ModalViewController view 之前设置UIViewContrller 的modalTransitionStyle 属性,使其以动画形式显示。

1
        controllerB.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
以上的实现只是单一地实现了ModalViewController view 的功能,除了程序开始提醒用户一些信息外什么也做不了。另外由于是放入了applicationDidFinishLaunching 中的原因,也不能反复的显示。另外,在ModalViewController view 上设置的内容也不能反映到原来的view 上。


接下来我们将实现这些功能。

首先,从ModalViewController view 退出的时候,需要通知原先的view。这里使用iPhone/Cocoa 应用程序中经常使用的Delegate 设计模式(也是推荐使用的)。

实际上,系统所提供的图像选择控制类UIImagePickerController
或者是参照地址簿时的ABPeoplePickerNavigationController 类,都用到了Delegate 模式。
        基于上一讲的中的例子,这里我们追加为3个按钮,分别是绿色,灰色和取消。
- (void)viewDidLoad {
            [super viewDidLoad];
            self.view.backgroundColor = [UIColor blueColor];
            UIButton* button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
            button.frame = CGRectMake(100,100,100,100);
            button.tag = 1;
            [button setTitle:@"绿色" forState:UIControlStateNormal];
            //  按钮事件对应函数
    [button addTarget:self action:@selector(dismiss:)
                forControlEvents:UIControlEventTouchUpInside];
            [self.view addSubview:button];

            button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
            button.frame = CGRectMake(100,200,100,100);
            button.tag = 2;
            [button setTitle:@"灰色" forState:UIControlStateNormal];
            //  按钮事件对应函数
    [button addTarget:self action:@selector(dismiss:)
                forControlEvents:UIControlEventTouchUpInside];
            [self.view addSubview:button];

            button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
            button.frame = CGRectMake(100,300,100,100);
            button.tag = 0;
            [button setTitle:@"取消" forState:UIControlStateNormal];
            //  按钮事件对应函数
    [button addTarget:self action:@selector(dismiss:)
                forControlEvents:UIControlEventTouchUpInside];
            [self.view addSubview:button];
        }
程序启动的时候依然是先显示ModalViewController view,按下任何一个按钮,将关闭该view。按下“绿色”按钮,设置背景为绿色,按下“灰色”按钮时,设置背景为灰色。“取消”的时候什么也不做。

委托处理用下面的函数实现,当参数inColor 为nil 的时候代表取消。

-(void)selectColor:(UIColor*)inColor;
委托代理的实例用id 变量表示。

        @interface CustomViewController : UIViewController {
            id  colorSelectDelegate;
        }
设置该变量的函数如下。
-(void)setColorSelectDelegate:(id)inDelegate {
            colorSelectDelegate = inDelegate;
        }
另外如上面viewDidLoad 所示,按钮的tag 分别为0、1、2。按钮按下时调用的函数中由不同的tag 来发送不同的UIColor实例到colorSelectDelegate 上。

-(void)dismiss:(id)inSender {
            UIView* view = (UIView*)inSender;
            UIColor* requestColor = nil;
            if (view.tag == 1)
                requestColor = [UIColor greenColor];
            if (view.tag == 2)
                requestColor = [UIColor grayColor];
            [colorSelectDelegate selectColor:requestColor];
        }
这是不使用UIButton* 而是用UIView* ,是因为tag 属性被定义在UIView 类中,不需要必须转换为UIButton 类。
        另外这样一来,该函数在UIButton 以外的情况下也能被使用。
        如果想检查id 是什么类性的可以使用isKindOfClass: 方法。
接收到具体的参数inColor 更换背景色,并关闭ModalViewController view。

-(void)selectColor:(UIColor*)inColor {
            if (inColor != nil)
                self.view.backgroundColor = inColor;
            [self dismissModalViewControllerAnimated:YES];
        }
另外,在调用presentModalViewController 之前(显示ModalViewController view 之前),需要设定委托的实例。
- (void)applicationDidFinishLaunching:(UIApplication *)application {
            controller = [[CustomViewController alloc] init];
            [window addSubview:controller.view];
            [window makeKeyAndVisible];
            //  创建ModalViewController view 的Controller
            CustomViewController* controllerB = [[CustomViewController alloc] init];
            //  设置背景色为红色
    controllerB.view.backgroundColor = [UIColor redColor];
            //  设置委托实例
    [controllerB setColorSelectDelegate:controller];
            //  显示ModalViewController view
            [controller presentModalViewController:controllerB animated:YES];
            [controllerB release];
        }
编译一下,程序启动后显示红色背景的ModalViewController view,点击绿色按钮后,原先的view的背景变为绿色,点击灰色,显示灰色的背景,而点击取消,那么将显示原先蓝色的背景。

这样的形式,就是将按钮的动作委托给原先view的Controller 来处理了。根据送来的UIColor 来设置不同的背景色。
这一回来定制UIView 上的触摸事件,作为例子,只是简单地检测出触摸事件并显示当前坐标在控制台上。

首先添加新文件,如下图:

      

在显示的对话框中选中Cocoa Touch Class 的Objective C class &#8658;UIView

              在项目的添加菜单中选择Touch 。检测触摸时间需要实现下面的函数。


         - (void)touchesBegan:(NSSet *)touches
        withEvent:(UIEvent *)event;
这个函数由用户触摸屏幕以后立刻被调到。为了自定义他的行为,我们像下面来实现:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
            UITouch* touch = [touches anyObject];
            CGPoint pt = [touch locationInView:self];
            printf("point = %lf,%lf\n", pt.x, pt.y);
        }
上面的代码将触摸点的坐标取出,并打印到控制台上。

如果需要得到多点触摸(不只是一根手指)的信息,需要使用anyObject 实例指定UIView。

另外,TouchAppDelegate 的applicationDidFinishLaunching 函数像下面一样实现:

         - (void)applicationDidFinishLaunching:(UIApplication *)application {
            TouchView* view = [[TouchView alloc]
                initWithFrame:CGRectMake(100, 100, 200, 200)];
            view.backgroundColor = [UIColor greenColor];
            [window addSubview:view];
            [window makeKeyAndVisible];
            [view release];
        }
这里用intiWithFrame 指定的矩形区域可以任意。另外为了明确触摸的区域大小,设定其view.backgroundColor。

虽然通过initWithFrame 在TouchAppDelegate 内创建了TouchView 的实例、但是通过addSubview:view 将管理责任交给了window 。就是说,TouchAppDelegate 与window 两个实例都对TouchView 实例实施管理。所以这里用[view release] 释放TouchAppDelegate 的管理责任

今天我们来看看iPhone 中数据库的使用方法。iPhone 中使用名为SQLite 的数据库管理系统。它是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如Tcl、PHP、Java等,还有ODBC接口,同样比起Mysql、PostgreSQL这两款开源世界著名的数据库管理系统来讲,它的处理速度比他们都快。

其使用步骤大致分为以下几步:

创建DB文件和表格
        添加必须的库文件(FMDB for iPhone, libsqlite3.0.dylib)
通过FMDB 的方法使用SQLite
创建DB文件和表格
$ sqlite3 sample.db
        sqlite> CREATE TABLE TEST(
           ...>  id INTEGER PRIMARY KEY,
           ...>  name VARCHAR(255)
           ...> );
简单地使用上面的语句生成数据库文件后,用一个图形化SQLite管理工具,比如Lita 来管理还是很方便的。

然后将文件(sample.db)添加到工程中。

添加必须的库文件(FMDB for iPhone, libsqlite3.0.dylib)
首先添加Apple 提供的sqlite 操作用程序库ibsqlite3.0.dylib 到工程中。

位置如下
/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS${VER}.sdk/usr/lib/libsqlite3.0.dylib

这样一来就可以访问数据库了,但是为了更加方便的操作数据库,这里使用FMDB for iPhone。

svn co http://flycode.googlecode.com/svn/trunk/fmdb fmdb
如上下载该库,并将以下文件添加到工程文件中:

FMDatabase.h
        FMDatabase.m
        FMDatabaseAdditions.h
        FMDatabaseAdditions.m
        FMResultSet.h
        FMResultSet.m
通过FMDB 的方法使用SQLite
使用SQL 操作数据库的代码在程序库的fmdb.m 文件中大部分都列出了、只是连接数据库文件的时候需要注意 — 执行的时候,参照的数据库路径位于Document 目录下,之前把刚才的sample.db 文件拷贝过去就好了。

位置如下
/Users/xxxx/Library/Application Support/iPhone Simulator/User/Applications/xxxx/Documents/sample.db
以下为链接数据库时的代码:

         BOOL success;
        NSError *error;
        NSFileManager *fm = [NSFileManager defaultManager];
        NSArray  *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
        NSString *documentsDirectory = [paths objectAtIndex:0];
        NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:@"sample.db"];
        success = [fm fileExistsAtPath:writableDBPath];
        if(!success){
          NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"sample.db"];
          success = [fm copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
          if(!success){
            NSLog([error localizedDescription]);
          }
        }

        // 连接DB
        FMDatabase* db = [FMDatabase databaseWithPath:writableDBPath];
        if ([db open]) {
          [db setShouldCacheStatements:YES];

          // INSERT
          [db beginTransaction];
          int i = 0;
          while (i++ < 20) {
            [db executeUpdate:@"INSERT INTO TEST (name) values (?)" , [NSString stringWithFormat:@"number %d", i]];
            if ([db hadError]) {
              NSLog(@"Err %d: %@", [db lastErrorCode], [db lastErrorMessage]);
            }
          }
          [db commit];

          // SELECT
          FMResultSet *rs = [db executeQuery:@"SELECT * FROM TEST"];
          while ([rs next]) {
            NSLog(@"%d %@", [rs intForColumn:@"id"], [rs stringForColumn:@"name"]);
          }
          [rs close];
          [db close];
        }else{
          NSLog(@"Could not open db.");
        }
接下来再看看用DAO 的形式来访问数据库的使用方法,代码整体构造如下。



首先创建如下格式的数据库文件:
$ sqlite3 sample.db
        sqlite> CREATE TABLE TbNote(
           ...>  id INTEGER PRIMARY KEY,
           ...>  title VARCHAR(255),
           ...>  body VARCHAR(255)
           ...> );
创建DTO(Data Transfer Object)
//TbNote.h
        #import <Foundation/Foundation.h>

        @interface TbNote : NSObject {
          int index;
          NSString *title;
          NSString *body;
        }

        @property (nonatomic, retain) NSString *title;
        @property (nonatomic, retain) NSString *body;

- (id)initWithIndex:(int)newIndex Title:(NSString *)newTitle Body:(NSString *)newBody;
- (int)getIndex;

        @end

        //TbNote.m
        #import "TbNote.h"

        @implementation TbNote
        @synthesize title, body;

- (id)initWithIndex:(int)newIndex Title:(NSString *)newTitle Body:(NSString *)newBody{
          if(self = [super init]){
            index = newIndex;
            self.title = newTitle;
            self.body = newBody;
          }
          return self;
        }

- (int)getIndex{
          return index;
        }

- (void)dealloc {
          [title release];
          [body release];
          [super dealloc];
        }

        @end
创建DAO(Data Access Objects)
        这里将FMDB 的函数调用封装为DAO 的方式。

         //BaseDao.h
        #import <Foundation/Foundation.h>

        @class FMDatabase;

        @interface BaseDao : NSObject {
          FMDatabase *db;
        }

        @property (nonatomic, retain) FMDatabase *db;

-(NSString *)setTable:(NSString *)sql;

        @end

        //BaseDao.m
        #import "SqlSampleAppDelegate.h"
        #import "FMDatabase.h"
        #import "FMDatabaseAdditions.h"
        #import "BaseDao.h"

        @implementation BaseDao
        @synthesize db;

- (id)init{
          if(self = [super init]){
            // 由AppDelegate 取得打开的数据库
    SqlSampleAppDelegate *appDelegate = (SqlSampleAppDelegate *)[[UIApplication sharedApplication] delegate];
            db = [[appDelegate db] retain];
          }
          return self;
        }
        // 子类中实现
-(NSString *)setTable:(NSString *)sql{
          return NULL;
        }

- (void)dealloc {
          [db release];
          [super dealloc];
        }

        @end
下面是访问TbNote 表格的类。
//TbNoteDao.h
        #import <Foundation/Foundation.h>
        #import "BaseDao.h"

        @interface TbNoteDao : BaseDao {
        }

-(NSMutableArray *)select;
-(void)insertWithTitle:(NSString *)title Body:(NSString *)body;
-(BOOL)updateAt:(int)index Title:(NSString *)title Body:(NSString *)body;
-(BOOL)deleteAt:(int)index;

        @end

        //TbNoteDao.m
        #import "FMDatabase.h"
        #import "FMDatabaseAdditions.h"
        #import "TbNoteDao.h"
        #import "TbNote.h"

        @implementation TbNoteDao

-(NSString *)setTable:(NSString *)sql{
          return [NSString stringWithFormat:sql,  @"TbNote"];
        }
        // SELECT
-(NSMutableArray *)select{
          NSMutableArray *result = [[[NSMutableArray alloc] initWithCapacity:0] autorelease];
          FMResultSet *rs = [db executeQuery:[self setTable:@"SELECT * FROM %@"]];
          while ([rs next]) {
            TbNote *tr = [[TbNote alloc]
                      initWithIndex:[rs intForColumn:@"id"]
                      Title:[rs stringForColumn:@"title"]
                      Body:[rs stringForColumn:@"body"]
                      ];
            [result addObject:tr];
            [tr release];
          }
          [rs close];
          return result;
        }
        // INSERT
-(void)insertWithTitle:(NSString *)title Body:(NSString *)body{
          [db executeUpdate:[self setTable:@"INSERT INTO %@ (title, body) VALUES (?,?)"], title, body];
          if ([db hadError]) {
            NSLog(@"Err %d: %@", [db lastErrorCode], [db lastErrorMessage]);
          }
        }
        // UPDATE
-(BOOL)updateAt:(int)index Title:(NSString *)title Body:(NSString *)body{
          BOOL success = YES;
          [db executeUpdate:[self setTable:@"UPDATE %@ SET title=?, body=? WHERE id=?"], title, body, [NSNumber numberWithInt:index]];
          if ([db hadError]) {
            NSLog(@"Err %d: %@", [db lastErrorCode], [db lastErrorMessage]);
            success = NO;
          }
          return success;
        }
        // DELETE
- (BOOL)deleteAt:(int)index{
          BOOL success = YES;
          [db executeUpdate:[self setTable:@"DELETE FROM %@ WHERE id = ?"], [NSNumber numberWithInt:index]];
          if ([db hadError]) {
            NSLog(@"Err %d: %@", [db lastErrorCode], [db lastErrorMessage]);
            success = NO;
          }
          return success;
        }

- (void)dealloc {
          [super dealloc];
        }

        @end
为了确认程序正确,我们添加一个UITableView。使用initWithNibName 测试DAO。
//NoteController.h
        #import <UIKit/UIKit.h>

        @class TbNoteDao;

        @interface NoteController : UIViewController <UITableViewDataSource, UITableViewDelegate>{
          UITableView *myTableView;
          TbNoteDao *tbNoteDao;
          NSMutableArray *record;
        }

        @property (nonatomic, retain) UITableView *myTableView;
        @property (nonatomic, retain) TbNoteDao *tbNoteDao;
        @property (nonatomic, retain) NSMutableArray *record;

        @end

        //NoteController.m
        #import "NoteController.h"
        #import "TbNoteDao.h"
        #import "TbNote.h"

        @implementation NoteController
        @synthesize myTableView, tbNoteDao, record;

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
          if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
            tbNoteDao = [[TbNoteDao alloc] init];
            [tbNoteDao insertWithTitle:@"TEST TITLE" Body:@"TEST BODY"];
        //    [tbNoteDao updateAt:1 Title:@"UPDATE TEST" Body:@"UPDATE BODY"];
        //    [tbNoteDao deleteAt:1];
            record = [[tbNoteDao select] retain];
          }
          return self;
        }

- (void)viewDidLoad {
          [super viewDidLoad];
          myTableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]];
          myTableView.delegate = self;
          myTableView.dataSource = self;
          self.view = myTableView;
        }

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
          return 1;
        }

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
          return [record count];
        }

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
          static NSString *CellIdentifier = @"Cell";

          UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
          if (cell == nil) {
            cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
          }
          TbNote *tr = (TbNote *)[record objectAtIndex:indexPath.row];
          cell.text = [NSString stringWithFormat:@"%i %@", [tr getIndex], tr.title];
          return cell;
        }

- (void)didReceiveMemoryWarning {
          [super didReceiveMemoryWarning];
        }

- (void)dealloc {
          [super dealloc];
        }

        @end
最后我们开看看连接DB,和添加ViewController 的处理。这一同样不使用Interface Builder。
//SqlSampleAppDelegate.h
        #import <UIKit/UIKit.h>

        @class FMDatabase;

        @interface SqlSampleAppDelegate : NSObject <UIApplicationDelegate> {
          UIWindow *window;
          FMDatabase *db;
        }

        @property (nonatomic, retain) IBOutlet UIWindow *window;
        @property (nonatomic, retain) FMDatabase *db;

- (BOOL)initDatabase;
- (void)closeDatabase;

        @end

        //SqlSampleAppDelegate.m
        #import "SqlSampleAppDelegate.h"
        #import "FMDatabase.h"
        #import "FMDatabaseAdditions.h"
        #import "NoteController.h"

        @implementation SqlSampleAppDelegate

        @synthesize window;
        @synthesize db;

- (void)applicationDidFinishLaunching:(UIApplication *)application {
          if (![self initDatabase]){
            NSLog(@"Failed to init Database.");
          }
          NoteController *ctrl = [[NoteController alloc] initWithNibName:nil bundle:nil];
          [window addSubview:ctrl.view];
          [window makeKeyAndVisible];
        }

- (BOOL)initDatabase{
          BOOL success;
          NSError *error;
          NSFileManager *fm = [NSFileManager defaultManager];
          NSArray  *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
          NSString *documentsDirectory = [paths objectAtIndex:0];
          NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:@"sample.db"];

          success = [fm fileExistsAtPath:writableDBPath];
          if(!success){
            NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"sample.db"];
            success = [fm copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
            if(!success){
              NSLog([error localizedDescription]);
            }
            success = NO;
          }
          if(success){
            db = [[FMDatabase databaseWithPath:writableDBPath] retain];
            if ([db open]) {
              [db setShouldCacheStatements:YES];
            }else{
              NSLog(@"Failed to open database.");
              success = NO;
            }
          }
          return success;
        }

- (void) closeDatabase{
          [db close];
        }

- (void)dealloc {
          [db release];
          [window release];
          [super dealloc];
        }

        @end
 楼主| 发表于 2012-12-4 23:12:19 | 显示全部楼层
这一回简单地介绍一下GPS的使用方法。使用GPS大致分下面两步。

添加CoreLocation.framework。
        生成CLLocationManager 测量位置。
        测试代码如下:
// LocationViewCtrl.h
        #import <UIKit/UIKit.h>
        #import <CoreLocation/CoreLocation.h>
        @interface LocationViewCtrl : UIViewController <CLLocationManagerDelegate>{
          CLLocationManager *man;
        }
        @property(nonatomic, retain) CLLocationManager *man;
        @end

        LocationViewCtrl.m
        #import "LocationViewCtrl.h"
        #import <CoreLocation/CoreLocation.h>

        @implementation LocationViewCtrl
        @synthesize man;

- (void)viewDidLoad {
          [super viewDidLoad];
          man = [[CLLocationManager alloc] init];

          // 如果可以利用本地服务时
  if([man locationServicesEnabled]){
            // 接收事件的实例
    man.delegate = self;
            // 发生事件的的最小距离间隔(缺省是不指定)
    man.distanceFilter = kCLDistanceFilterNone;
            // 精度(缺省是Best)
            man.desiredAccuracy = kCLLocationAccuracyBest;
            // 开始测量
    [man startUpdatingLocation];
          }
        }

        // 如果GPS测量成果以下的函数被调用
- (void)locationManager:(CLLocationManager *)manager
          didUpdateToLocation:(CLLocation *)newLocation
              fromLocation:(CLLocation *)oldLocation{

          // 取得经纬度
  CLLocationCoordinate2D coordinate = newLocation.coordinate;
          CLLocationDegrees latitude = coordinate.latitude;
          CLLocationDegrees longitude = coordinate.longitude;
          // 取得精度
  CLLocationAccuracy horizontal = newLocation.horizontalAccuracy;
          CLLocationAccuracy vertical = newLocation.verticalAccuracy;
          // 取得高度
  CLLocationDistance altitude = newLocation.altitude;
          // 取得时刻
  NSDate *timestamp = [newLocation timestamp];

          // 以下面的格式输出format: <latitude>, <longitude>> +/- <accuracy>m @ <date-time>
          NSLog([newLocation description]);

          // 与上次测量地点的间隔距离
  if(oldLocation != nil){
            CLLocationDistance d = [newLocation getDistanceFrom:oldLocation];
            NSLog([NSString stringWithFormat:@"%f", d]);
          }
        }

        // 如果GPS测量失败了,下面的函数被调用
- (void)locationManager:(CLLocationManager *)manager
             didFailWithError:(NSError *)error{
          NSLog([error localizedDescription]);
        }
        ...
测量精度有以下几类,精度越高越消耗电力。

kCLLocationAccuracyNearestTenMeters     10m
        kCLLocationAccuracyHundredMeters  100m
        kCLLocationAccuracyKilometer    1km
        kCLLocationAccuracyThreeKilometers3km
因为在模拟器上不能设置经纬度,所以只能在实际设备中测试你的GPS程序
这一回,主要介绍一下iPhone SDK中多线程的使用方法以及注意事项。虽然现在大部分PC应用程序都支持多线程/多任务的开发方式,但是在iPhone上,Apple并不推荐使用多线程的编程方式。但是多线程编程毕竟是发展的趋势,而且据说即将推出的iPhone OS4将全面支持多线程的处理方式。所以说掌握多线程的编程方式,在某些场合一定能挖掘出iPhone的更大潜力。

从例子入手
        先从一个例程入手,具体的代码参考了这里。还有例程可以下载

多线程程序的控制模型可以参考这里,一般情况下都是使用 管理者/工人模型, 这里,我们使用iPhone SDK中的NSThread 来实现它。

首先创建一个新的View-based application 工程,名字为"TutorialProject" 。界面如下图所示,使用UILabel实现两部分的Part(Thread Part和Test Part),Thread Part中包含一个UIProgressView和一个UIButton;而Test Part包含一个值和一个UISlider。


接下来,在TutorialProjectViewController.h 文件中创建各个UI控件的IBOutlets.


        @interface TutorialProjectViewController : UIViewController {

            // ------ Tutorial code starts here ------

            // Thread part
            IBOutlet UILabel *threadValueLabel;
            IBOutlet UIProgressView *threadProgressView;
            IBOutlet UIButton *threadStartButton;

            // Test part
            IBOutlet UILabel *testValueLabel;

            // ------ Tutorial code ends here ------

        }
同时,也需要创建outlets变量的property.

        @property (nonatomic, retain) IBOutlet UILabel *threadValueLabel;
        @property (nonatomic, retain) IBOutlet UIProgressView *threadProgressView;
        @property (nonatomic, retain) IBOutlet UIProgressView *threadStartButton;
        @property (nonatomic, retain) IBOutlet UILabel *testValueLabel;
接下来定义按钮按下时的动作函数,以及slider的变化函数。

- (IBAction) startThreadButtonPressed:(UIButton *)sender;
- (IBAction) testValueSliderChanged:(UISlider *)sender;
然后在TutorialProjectViewController.m 文件中synthesize outlets,并在文件为实现dealloc释放资源。
@synthesize threadValueLabel, threadProgressView, testValueLabel, threadStartButton;

        ...

- (void)dealloc {

            // ------ Tutorial code starts here ------

            [threadValueLabel release];
            [threadProgressView release];
            [threadStartButton release];

            [testValueLabel release];

            // ------ Tutorial code ends here ------

            [super dealloc];
        }
现在开始线程部分的代码,首先当thread button 被按下的时候,创建新的线程.

- (IBAction) startThreadButtonPressed:(UIButton *)sender {
            threadStartButton.hidden = YES;
            threadValueLabel.text = @"0";
            threadProgressView.progress = 0.0;
            [NSThread detachNewThreadSelector:@selector(startTheBackgroundJob) toTarget:self withObject:nil];
        }
该按钮被按下后,隐藏按钮以禁止多次创建线程。然后初始化显示值和进度条,最后创建新的线程,线程的函数为startTheBackgroundJob.

具体的startTheBackgroundJob 函数定义如下.
- (void)startTheBackgroundJob {

            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
            // 线程开始后先暂停3秒(这里只是演示暂停的方法,你不是必须这么做的)
    [NSThread sleepForTimeInterval:3];
            [self performSelectorOnMainThread:@selector(makeMyProgressBarMoving) withObject:nil waitUntilDone:NO];
            [pool release];

        }
在第1行,创建了一个NSAutoreleasePool 对象,用来管理线程中自动释放的对象资源。这里NSAutoreleasePool 在线程退出的时候释放。这符合Cocoa GUI 应用程序的一般规则。

最后一行,阻塞调用(waitUntilDone状态是ON)函数makeMyProgressBarMoving。
- (void)makeMyProgressBarMoving {

            float actual = [threadProgressView progress];
            threadValueLabel.text = [NSString stringWithFormat:@"%.2f", actual];
            if (actual < 1) {
                threadProgressView.progress = actual + 0.01;
                [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(makeMyProgressBarMoving) userInfo:nil repeats:NO];
            }
            else threadStartButton.hidden = NO;

        }
这里计算用于显示的进度条的值,利用NSTimer ,每0.5秒自增0.01,当值等于1的时候,进度条为100%,退出函数并显示刚才被隐藏的按钮。

最后,添加UISlider 的实现函数,用来更改主线程中Test Part 中的label 值。

- (IBAction) testValueSliderChanged:(UISlider *)sender {

            testValueLabel.text = [NSString stringWithFormat:@"%.2f", sender.value];

        }
编译执行,按下线程开始按钮,你将看到进度条的计算是在后台运行。



使用线程的注意事项
        线程的堆栈大小
iPhone设备上的应用程序开发也是属于嵌入式设备的开发,同样需要注意嵌入式设备开发时的几点问题,比如资源上限,处理器速度等。

iPhone 中的线程应用并不是无节制的,官方给出的资料显示iPhone OS下的主线程的堆栈大小是1M,第二个线程开始都是512KB。并且该值不能通过编译器开关或线程API函数来更改。

你可以用下面的例子测试你的设备,这里使用POSIX Thread(pthread),设备环境是iPhone 3GS(16GB)、SDK是3.1.3。

        #include "pthread.h"

        void *threadFunc(void *arg) {
            void*  stack_base = pthread_get_stackaddr_np(pthread_self());
            size_t stack_size = pthread_get_stacksize_np(pthread_self());
            NSLog(@"Thread: base:%p / size:%u", stack_base, stack_size);
            return NULL;
        }

- (void)applicationDidFinishLaunching:(UIApplication *)application {
            void*  stack_base = pthread_get_stackaddr_np(pthread_self());
            size_t stack_size = pthread_get_stacksize_np(pthread_self());
            struct rlimit limit;
            getrlimit(RLIMIT_STACK, &limit);
            NSLog(@"Main thread: base:%p / size:%u", stack_base, stack_size);
            NSLog(@"  rlimit-> soft:%llu / hard:%llu", limit.rlim_cur, limit.rlim_max);

            pthread_t thread;
            pthread_create(&thread, NULL, threadFunc, NULL);

            // Override point for customization after app launch
            [window addSubview:viewController.view];
            [window makeKeyAndVisible];
        }
结果如下:

模拟器
Main thread: base:0xc0000000 / size:524288
        rlimit-> soft:8388608 / hard:67104768
        Thread: base:0xb014b000 / size:524288
设备
Main thread: base:0x30000000 / size:524288
        rlimit-> soft:1044480 / hard:1044480
        Thread: base:0xf1000 / size:524288
由此可见,当你测试多线程的程序时,模拟器和实际设备的堆栈大小是不一样的。如果有大量递归函数调用可要注意了。

Autorelease
如果你什么都不考虑,在线程函数内调用autorelease 、那么会出现下面的错误:

NSAutoReleaseNoPool(): Object 0x********* of class NSConreteData autoreleased with no pool in place ….
一般,在线程中使用内存的模式是,线程最初

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
而在线程结束的时候[pool drain] 或[pool release]。1

子线程中描画窗口
        多线程编程中普遍遵循一个原则,就是一切与UI相关的操作都有主线程做,子线程只负责事务,数据方面的处理。那么如果想在子线程中更新UI时怎么做呢?如果是在windows下,你会PostMessage 一个描画更新的消息,在iPhone中,需要使用performSelectorOnMainThread 委托主线程处理。

比如,如果在子线程中想让UIImageView 的image 更新,如果直接在线程中
imageView.image = [UIImage imageNamed:@"Hoge.png"];
这么做,什么也不会出现的。需要将该处理委托给主线程来做,像下面:
[delegate performSelectorOnMainThread:@selector(theProcess:) withObject:nil waitUntilDone:YES];
就OK了!

到此为止,《iPhone开发进阶》系列就告一段落了,接下来将针对不同的开发领域,总结一些小技巧与应用技术,希望您能继续关注。

注释:drain 与release 的区别前提是你的系统中是否有GC,如果有,-drain 需要送一个消息给GC (objc_collect_if_needed),而如果没有GC,drain = release

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|3D数字艺术论坛 ( 沪ICP备14023054号 )

GMT+8, 2025-5-6 19:48

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表