Iphone 官方的开发指南

核心应用程序

所有的iPhone应用程序都是基于UIKit框架构建而成的,因此,它们在本质上具有相同的核心架构。UIKit负责提供运行应用程序和协调用户输入及屏幕显示所需要的关键对象。应用程序之间不同的地方在于如何配置缺省对象,以及如何通过定制对象来添加用户界面和行为。

虽然应用程序的界面和基本行为的定制发生在定制代码的内部,但是,还有很多定制需要在应用程序的最高级别上进行。这些高级的定制会影响应用程序和系统、以及和设备上的其它程序之间的交互方式,因此,理解何时需要定制、何时缺省行为就已经足够是很重要的。本章将概要介绍核心应用程序架构和高级别的定制点,帮助您确定什么时候应该定制,什么时候应该使用缺省的行为。

核心应用程序架构

从应用程序启动到退出的过程中,UIKit框架负责管理大部分关键的基础设施。iPhone应用程序不断地从系统接收事件,而且必须响应那些事件。接收事件是UIApplication对象的工作,但是,响应事件则需要您的定制代码来处理。为了理解事件响应需要在哪里进行,我们有必要对iPhone应用程序的整个生命周期和事件周期有一些理解。本文的下面部分将描述这些周期,同时还对iPhone应用程序开发过程中使用的一些关键设计模式进行总结。

应用程序的生命周期

应用程序的生命周期是由发生在程序启动到终止期间的一序列事件构成的。在iPhone OS中,用户可以通过轻点Home屏幕上的图标来启动应用程序。在轻点图标之后的不久,系统就会显示一个过渡图形,然后调用相应的main函数来启动应用程序。从这个点之后,大量的初始化工作就会交给UIKit,由它装载应用程序的用户界面和准备事件循环。在事件循环过程中,UIKit会将事件分发给您的定制对象及响应应用程序发出的命令。当用户进行退出应用程序的操作时,UIKit会通知应用程序,并开始应用程序的终止过程。

图1-1显示了一个简化了的iPhone应用程序生命周期。这个框图展示了发生在应用程序启动到退出过程中的事件序列。在应用程序初始化和终止的时候,UIKit会向应用程序委托对象发送特定的消息,使其知道正在发生的事件。在事件循环中,UIKit将事件派发给应用程序的定制事件处理器。有关初始化和终止事件的如何处理的信息,将在随后的“初始化和终止”部分进行讨论;事件处理的过程则在“事件处理周期”部分介绍,在后面的章节也还有更为详细的讨论。

图1-1  应用程序的生命周期

主函数

在iPhone的应用程序中,main函数仅在最小程度上被使用,应用程序运行所需的大多数实际工作由UIApplicationMain函数来处理。因此,当您在Xcode中开始一个新的应用程序工程时,每个工程模板都会提供一个main函数的标准实现,该实现和“处理关键的应用程序任务”部分提供的实现是一样的。main例程只做三件事:创建一个自动释放池,调用UIApplicationMain函数,以及使用自动释放池。除了少数的例外,您永远不应该改变这个函数的实现。

程序清单1-1  iPhone应用程序的main函数

#import 
 
int main(int argc, char *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}

请注意:自动释放池用于内存管理,它是Cocoa的一种机制,用于延缓释放具有一定功能的代码块中创建的对象。有关自动释放池的更多信息,请参见Cocoa内存管理编程指南;如果需要了解与自动释放池有关的具体内存管理规则,则请参见“恰当地分配内存”部分。

程序清单的核心代码是UIApplicationMain函数,它接收四个参数,并将它们用于初始化应用程序。传递给该函数的缺省值并不需要修改,但是它们对于应用程序启动的作用还是值得解释一下。除了传给main函数的argcargv之外,该函数还需要两个字符串参数,用于标识应用程序的首要类(即应用程序对象所属的类)和应用程序委托类。如果首要类字符串的值为nil, UIKit就缺省使用UIApplication类;如果应用程序委托类为nil,UIKit就会将应用程序主nib文件(针对通过Xcode模板创建的应用程序)中的某个对象假定为应用程序的委托对象。如果您将这些参数设置为非nil值,则在应用程序启动时,UIApplicationMain函数会创建一个与传入值相对应的类实例,并将它用于既定的目的。因此,如果您的应用程序使用了UIApplication类的定制子类(这种做法是不推荐的,但确实是可能的),就需要在第三个参数指定该定制类的类名。

应用程序的委托

监控应用程序的高级行为是应用程序委托对象的责任,而应用程序委托对象是您提供的定制类实例。委托是一种避免对复杂的UIKit对象(比如缺省的UIApplication对象)进行子类化的机制。在这种机制下,您可以不进行子类化和方法重载,而是将自己的定制代码放到委托对象中,从而避免对复杂对象进行修改。当您感兴趣的事件发生时,复杂对象会将消息发送给您定制的委托对象。您可以通过这种“挂钩”执行自己的定制代码,实现需要的行为。

重要提示:委托模式的目的是使您在创建应用程序的时候省时省力,因此是非常重要的设计模式。如果您需要概要了解iPhone应用程序中使用的重要设计模式,请参见“基本设计模式”部分;如果需要对委托和其它UIKit设计模式的详细描述,则请参见Cocoa基本原理指南部分。

应用程序的委托对象负责处理几个关键的系统消息。每个iPhone应用程序都必须有应用程序委托对象,它可以是您希望的任何类的实例,但需要遵循UIApplicationDelegate协议,该协议的方法定义了应用程序生命周期中的某些挂钩,您可以通过这些方法来实现定制的行为。虽然您不需要实现所有的方法,但是每个应用程序委托都应该实现“处理关键的应用程序任务”部分中描述的方法。

有关UIApplicationDelegate协议方法的更多信息请参见UIApplicationDelegate协议参考

主Nib文件

初始化的另一个任务是装载应用程序的主nib文件。如果应用程序的信息属性列表(Info.plist)文件中含有NSMainNibFile键,则作为初始化过程的一个部分,UIApplication对象会装载该键指定的nib文件。主nib文件是唯一一个自动装载的nib文件,其它的nib文件可以在稍后根据需要进行装载。

Nib文件是基于磁盘的资源文件,用于存储一或多个对象的快照。iPhone应用程序的主nib文件通常包含一个窗口对象和一个应用程序委托对象,还可能包含一个或多个管理窗口的其它重要对象。装载一个nib文件会使该文件中的对象被重新构造,从而将每个对象的磁盘表示转化为应用程序可以操作的内存对象。从nib文件中装载的对象和通过编程方式创建的对象之间没有区别。然而,对于用户界面而言,以图形的方式(使用Interface Builder程序)创建与用户界面相关联的对象并将它们存储在nib文件中通常比以编程的方式进行创建更加方便。

有关nib文件及其在iPhone应用程序中如何使用的更多信息,请参见“Nib文件”部分,有关如何为应用程序指定主nib文件的信息则请参见“信息属性列表”部分。

事件处理周期

在应用程序初始化之后,UIApplicationMain函数就会启动管理应用程序事件和描画周期的基础组件,如图1-2所示。在用户和设备进行交互的时候,iPhone OS会检测触摸事件,并将事件放入应用程序的事件队列。然后,UIApplication对象的事件处理设施会从队列的上部逐个取出事件,将它分发到最适合对其进行处理的对象。举例来说,在一个按键上发生的触摸事件会被分发到对应的按键对象。事件也可以被分发给控制器对象和应用程序中不直接负责处理触摸事件的其它对象。

图1-2  事件和描画周期

在iPhone OS的多点触摸事件模型中,触摸数据被封装在事件对象(UIEvent)中。为了跟踪触摸动作,事件对象中包含一些触摸对象(UITouch),每个触摸对象都对应于一个正在触摸屏幕的手指。当用户把手指放在屏幕上,然后四处移动,并最终离开屏幕的时候,系统通过对应的触摸对象报告每个手指的变化。

在启动一个应用程序时,系统会为该程序创建一个进程和一个单一的线程。这个初始线程成为应用程序的主线程,UIApplication对象正是在这个线程中建立主运行循环及配置应用程序的事件处理代码。图1-3显示了事件处理代码和主运行循环的关系。系统发送的触摸事件会在队列中等待,直到被应用程序的主运行循环处理

图1-3  在主运行循环中处理事件

请注意:运行循环负责监视指定执行线程的输入源。当输入源有数据需要处理的时候,运行循环就唤醒相应的线程,并将控制权交给输入源的处理器代码。处理器在完成任务后将控制权交回运行循环,然后,运行循环就处理下一个事件。如果没有其它事件,运行循环会使线程进入休眠状态。您可以通过Foundation框架的NSRunLoop类来安装自己的输入源,包括端口和定时器。更多有关NSRunLoop和运行循环的一般性讨论,请参见线程编程指南

UIApplication对象用一个处理触摸事件的输入源来配置主运行循环,使触摸事件可以被派发到恰当的响应者对象。响应者对象是继承自UIResponder类的对象,它实现了一或多个事件方法,以处理触摸事件不同阶段发生的事件。应用程序的响应者对象包括UIApplicationUIWindowUIView、及所有UIView子类的实例。应用程序通常将事件派发给代表应用程序主窗口的UIWindow对象,然后由窗口对象将事件传送给它的第一响应者,通常是发生触摸事件的视图对象(UIView)。

除了定义事件处理方法之外,UIResponder类还定义了响应者链的编程结构。响应者链是为实现Cocoa协作事件处理而设计的机制,它由应用程序中一组链接在一起的响应者对象组成,通常以第一响应者作为链的开始。当发生某个事件时,如果第一响应者对象不能处理,就将它传递给响应者链中的下一个对象。消息继续在链中传递—从底层的响应者对象到诸如窗口、应用程序、和应用程序委托这样的高级响应者对象—直到事件被处理。如果事件最终没有被处理,就会被丢弃。

进行事件处理的响应者对象可能发起一系列程序动作,结果导致应用程序重画全部或部分用户界面(也可能导致其它结果,比如播放一个声音)。举例来说,一个控键对象(也就是一个UIControl的子类对象)在处理事件时向另一个对象(通常是控制器对象,负责管理当前活动的视图集合)发送动作消息。在处理这个动作消息时,控制器可能以某种方式改变用户界面或者视图的位置,而这又要求某些视图对自身进行重画。如果这种情况发生,则视图和图形基础组件会接管控制权,尽可能以最有效的方式处理必要的重画事件。

更多有关事件、响应者、和如何在定制对象中处理事件的信息,请参见“事件处理”部分;更多有关窗口及视图如何与事件处理机制相结合的信息,请参见“视图交互模型”部分;有关图形组件及视图如何被更新的更多信息,则请参见“视图描画周期”部分。

基本设计模式

UIKit框架的设计结合了很多在Mac OS X Cocoa应用程序中使用的设计模式。理解这些设计模式对于创建iPhone应用程序是很关键的,我们值得为此花上几分钟时间。下面部分将简要概述这些设计模式。

表1-1  iPhone应用程序使用的设计模式

设计模式

描述

模型-视图-控制器

模型-视图-控制器(MVC)模式将您的代码分割为几个独立的部分。模型部分定义应用程序的数据引擎,负责维护数据的完整性;视图部分定义应用程序的用户界面,对显示在用户界面上的数据出处则没有清楚的认识;控制器部分则充当模型和控制器的桥梁,帮助实现数据和显示的更新。

委托

委托模式可以对复杂对象进行修改而不需要子类化。与子类化不同的是,您可以照常使用复杂对象,而将对其行为进行修改的定制代码放在另一个对象中,这个对象就称为委托对象。复杂对象需要在预先定义好的时点上调用委托对象的方法,使其有机会运行定制代码。

目标-动作

控件通过目标-动作模式将用户的交互通知给您的应用程序。当用户以预先定义好的方式(比如轻点一个按键)进行交互时,控件就会将消息(动作)发送给您指定的对象(目标)。接收到动作消息后,目标对象就会以恰当的方式进行响应(比如在按动按键时更新应用程序的状态)。

委托内存模型

Objective-C使用引用计数模式来确定什么时候应该释放内存中的对象。当一个对象刚刚被创建时,它的引用计数是1。然后,其它对象可以通过该对象的retainrelease、或autorelease方法来增加或减少引用计数。当对象的引用计数变为0时,Objective-C运行环境会调用对象的清理例程,然后解除分配该对象。

有关这些设计模式更为详尽的讨论请参见Cocoa基本原理指南

应用程序运行环境

iPhone OS的运行环境被设计为快速而安全的程序执行环境。下面的部分这个运行环境的关键部分,并就如何在这个环境中进行操作提供一些指导。

启动过程快,使用时间短

iPhone OS设备的优势是它们的便捷性。用户通常从口袋里掏出设备,用上几秒或几分钟,就又放回口袋中了。在这个过程中,用户可能会打电话、查找联系人、改变正在播放的歌曲、或者取得一片信息。

在iPhone OS中,每次只能有一个前台应用程序。这意味着每次用户在Home屏幕上轻点您的应用程序图标时,您的程序必须快速启动和初始化,以尽可能减少延迟。如果您的应用程序花很长时间来启动,用户可能就不喜欢了。

除了快速启动,您的应用程序还必须做好快速退出的准备。每次用户离开您的应用程序时,无论是按下Home键还是通过软件提供的功能打开了另一个应用程序,iPhone OS会通知您的应用程序退出。在那个时候,您需要尽快将未保存的修改保存到磁盘上。如果您的应用程序退出的时间超过5秒,系统可能会立刻终止它的运行。

当用户切换到另一个应用程序时,虽然您的程序不是运行在后台,但是我们鼓励您使它看起来好像是在后台运行。当您的程序退出时,除了对未保存的数据进行保存之外,还应该保存当前的状态信息;而在启动时,则应该寻找这些状态信息,并将程序恢复到最后一次使用时的状态。这样可以使用户回到最后一次使用时的状态,使用户体验更加一致。以这种方式保存用户的当前位置还可以避免每次启动都需要经过多个屏幕才能找到需要的信息,从而节省使用的时间。

应用程序沙箱

由于安全的原因,iPhone OS将每个应用程序(包括其偏好设置信息和数据)限制在文件系统的特定位置上。这个限制是安全特性的一部分,称为应用程序的“沙箱”。沙箱是一组细粒度的控制,用于限制应用程序对文件、偏好设置、网络资源、和硬件等的访问。在iPhone OS中,应用程序和它的数据驻留在一个安全的地方,其它应用程序都不能进行访问。在应用程序安装之后,系统就通过计算得到一个不透明的标识,然后基于应用程序的根目录和这个标识构建一个指向应用程序家目录的路径。因此,应用程序的家目录具有如下结构:

  • /ApplicationRoot/ApplicationID/

在安装过程中,系统会创建应用程序的家目录和几个关键的子目录,配置应用程序沙箱,以及将应用程序的程序包拷贝到家目录上。将应用程序及其数据放在一个特定的地方可以简化备份-并-恢复操作,还可以简化应用程序的更新及卸载操作。有关系统为每个应用程序创建的专用目录、应用程序更新、及备份-并-恢复操作的更多信息,请参见“文件和数据管理”部分。

重要提示:沙箱可以限制攻击者对其它程序和系统造成的破坏,但是不能防止攻击的发生。换句话说,沙箱不能使您的程序避免恶意的直接攻击。举例来说,如果在您的输入处理代码中有一个可利用的缓冲区溢出,而您又没有对用户输入进行正当性检查,则攻击者可能仍然可以使您的应用程序崩溃,或者通过这种漏洞来执行攻击者的代码。

虚拟内存系统

在本质上,iPhone OS使用与Mac OS X同样的虚存系统。在iPhone OS中,每个程序都仍然有自己的虚拟地址空间,但其可用的虚拟内存受限于现有的物理内存的数量(这和Mac OS X不同)。这是因为当内存用满的时候,iPhone OS并不将非永久内存页面(volatile pages)写入到磁盘。相反,虚拟内存系统会根据需要释放永久内存(nonvolatile memory),确保为正在运行的应用程序提供所需的空间。内存的释放是通过删除当前没有正在使用或包含只读内容(比如代码页面)的内存页面来实现的,这样的页面可以在稍后需要使用的时候重新装载到内存中。

如果内存还是不够,系统也可能向正在运行的应用程序发出通告,要求它们释放额外的内存。所有的应用程序都应该响应这种通告,并尽自己所能减轻系统的内存压力。有关如何在应用程序中处理这种通告的更多信息,请参见“观察低内存警告”部分。

自动休眠定时器

iPhone OS试图省电的一个方法是使用自动休眠定时器。如果在一定的时间内没有检测到触摸事件,系统最初会使屏幕变暗,并最终完全关闭屏幕。大多数开发者都应该让这个定时器打开,但是,游戏和不使用触摸输入的应用程序开发者可以禁用这个定时器,使屏幕在应用程序运行时不会变暗。将共享的UIApplication对象的idleTimerDisabled属性设置为YES,就可以禁用自动休眠定时器。

由于禁用休眠定时器会导致更大的电能消耗,所以开发者应该尽一切可能避免这样做。只有地图程序、游戏、以及不依赖于触摸输入而又需要在设备屏幕上显示内容的应用程序才应该考虑禁用休眠定时器。音频应用程序不需要禁用这个定时器,因为在屏幕变暗之后,音频内容可以继续播放。如果您禁用了定时器,请务必尽快重新激活它,使系统可以更省电。有关应用程序如何省电的其它贴士,请参见“减少电力消耗”部分。

应用程序的程序包

当您连编iPhone程序时,Xcode会将它组织为程序包程序包是文件系统中的一个目录,用于将执行代码和相关资源集合在一个地方。iPhone应用程序包中包含应用程序的执行文件和应用程序需要用到的所有资源(比如应用程序图标、其它图像、和本地化内容)。表1-2列出了一个典型的iPhone应用程序包中的内容(为了便于说明,我们称之为MyApp)。这个例子只是为了演示,表中列出的一些文件可能并不出现在您自己的应用程序包中。

表1-2  一个典型的应用程序包

文件

描述

MyApp

包含应用程序代码的执行文件,文件名是略去.app后缀的应用程序名。这个文件是必需的。

Settings.bundle

设置程序包是一个文件包,用于将应用程序的偏好设置加入到Settings程序中。这种程序包中包含一些属性列表和其它资源文件,用于配置和显示您的偏好设置。更多信息请参见“显示应用程序的偏好设置”部分。

Icon.png

这是个57 x 57像素的图标,显示在设备的Home屏幕上,代表您的应用程序。这个图标不应该包含任何光亮效果。系统会自动为您加入这些效果。这个文件是必须的。更多有关这个图像文件的信息,请参见“应用程序图标和启动图像”部分。

Icon-Settings.png

这是一个29 x 29像素的图标,用于在Settings程序中表示您的应用程序。如果您的应用程序包含设置程序包,则在Settings程序中,这个图标会显示在您的应用程序名的边上。如果您没有指定这个图标文件,系统会将Icon.png文件按比例缩小,然后用做代替文件。有关这个图像文件的更多信息,青参见“显示应用程序的偏好设置”部分。

MainWindow.nib

这是应用程序的主nib文件,包含应用程序启动时装载的缺省用户界面对象。典型情况下,这个nib文件包含应用程序的主窗口对象和一个应用程序委托对象实例。其它界面对象则或者从其它nib文件装载,或者在应用程序中以编程的方式创建(主nib文件的名称可以通过Info.plist文件中的NSMainNibFile键来指定,进一步的信息请参见“信息属性列表”部分)。

Default.png

这是个480 x 320像素的图像,在应用程序启动的时候显示。系统使用这个文件作为临时的背景,直到应用程序完成窗口和用户界面的装载。有关这个图像文件的信息请参见“应用程序图标和启动图像”部分。

iTunesArtwork

这是个512 x 512的图标,用于通过ad-hoc方式发布的应用程序。这个图标通常由App Store来提供,但是通过ad-hoc方式分发的应用程序并不经由App Store,所以在程序包必须包含这个文件。iTunes用这个图标来代表您的程序(如果您的应用程序在App Store上发布,则在这个属性上指定的文件应该和提交到App Store的文件保持一致(通常是个JPEG或PNG 文件),文件名必须和左边显示的一样,而且不带文件扩展名)。

Info.plist

这个文件也叫信息属性列表,它是一个定义应用程序键值的属性列表,比如程序包ID、版本号、和显示名称。进一步的信息请参见“信息属性列表”部分。这个文件是必需的。

sun.png (或其它资源文件)

非本地化资源放在程序包目录的最上层(在这个例子中,sun.png表示一个非本地化的图像)。应用程序在使用非本地化资源时,不需要考虑用户选择的语言设置。

en.lproj

fr.lproj

es.lproj

其它具体语言的工程目录

本地化资源放在一些子目录下,子目录的名称是ISO 639-1定义的语言缩写加上.lproj后缀组成的(比如en.lprojfr.lproj、和es.lproj目录分别包含英语、法语、和西班牙语的本地化资源)。更多信息请参见“国际化您的应用程序”部分。

iPhone应用程序应该是国际化的。程序支持的每一种语言都有一个对应的语言.lproj文件夹。除了为应用程序提供定制资源的本地化版本之外,您还可以本地化您的应用程序图标(Icon.png)、缺省图像(Default.png)、和Settings图标(Icon-Settings.png),只要将同名文件放到具体语言的工程目录就可以了。然而,即使您提供了本地化的版本,也还是应该在应用程序包的最上层包含这些文件的缺省版本。当某些的本地化版本不存在的时候,系统会使用缺省版本。

您可以通过NSBundle类的方法或者与CFBundleRef类型相关联的函数来获取应用程序包中本地化和非本地化图形及声音资源的路径。举例来说,如果您希望得到图像文件sun.png(显示在“响应中断”部分中)的路径并通过它创建一个图像文件,则需要下面两行Objective-C代码:

NSString* imagePath = [[NSBundle mainBundle] pathForResource:@"sun" ofType:@"png"];
UIImage* sunImage = [[UIImage alloc] initWithContentsOfFile:imagePath];

代码中的mainBundle类方法用于返回一个代表应用程序包的对象。有关资源装载的信息请参见资源编程指南

信息属性列表

信息属性列表是一个名为Info.plist的文件,通过Xcode创建的每个iPhone应用程序都包含一个这样的文件。属性列表中的键值对用于指定重要的应用程序运行时配置信息。信息属性列表的元素被组织在一个层次结构中,每个结点都是一个实体,比如数组、字典、字符串、或者其它数值类型。

在Xcode中,您可以通过在Project菜单中选择Edit Active Target TargetName命令、然后在目标的Info窗口中点击Properties控件来访问信息属性列表。Xcode会显示如图1-4所示的信息面板。

图1-4  目标Info窗口的属性面板

属性面板显示的是程序包的一些属性,但并不是所有属性都显示在上面。当您选择“Open Info.plist as File” 按键或在Xcode工程中选择Info.plist文件时,Xcode会显示如图1-5所示的属性列表编辑器窗口,您可以通过这个窗口来编辑属性值和添加键-值对。您还可以查看添加到Info.plist文件中的实际键名,具体操作是按住Control键的同时点击编辑器中的信息属性列表项目,然后选择上下文菜单中的Show Raw Keys/Values命令。

图1-5  信息属性列表编辑器

Xcode会自动设置某些属性的值,其它属性则需要显式设置。表1-3列出了一些重要的键,供您在自己的Info.plist文件中使用(在缺省情况下,Xcode不会直接显示实际的键名,因此,下表在括号中列出了这些键在Xcode中显示的字符串。您可以查看所有键的实际键名,具体做法是按住Control键的同时点击编辑器中的信息属性列表项目,然后选择上下文菜单中的Show Raw Keys/Values命令)。有关属性列表文件可以包含的完整属性列表及系统如何使用这些属性的信息,请参见运行环境配置指南

表1-3   Info.plist文件中重要的键

CFBundleDisplayName (程序包显示名)

显示在应用程序图标下方的名称。这个值应该本地化为所有支持的语言。

CFBundleIdentifier (程序包标识)

这是由您提供的标识字符串,用于在系统中标识您的应用程序。这个字符串必须是一个统一的类型标识符(UTI),仅包含字母数字(A-Za-z0-9),连字符(-),和句号(.);且应该使用反向DNS格式。举例来说,如果您的公司的域名为Ajax.com,且您创建的应用程序名为Hello,则可以将字符串com.Ajax.Hello作为应用程序包的标识。

程序包的标识用于验证应用程序的签名。

CFBundleURLTypes (URL类型)

这是应用程序能够处理的URL类型数组。每个URL类型都是一个字典,定义一种应用程序能够处理的模式(如httpmailto)。应用程序可以通过这个属性来注册定制的URL模式。

CFBundleVersion (程序包版本号)

这是一个字符串,指定程序包的连编版本号。它的值是单调递增的,由一或多个句号分隔的整数组成。这个值不能被本地化。

LSRequiresIPhoneOS

这是一个Boolean值,用于指示程序包是否只能运行在iPhone OS 系统上。Xcode自动加入这个键,并将它的值设置为true。您不应该改变这个键的值。

NSMainNibFile (主nib文件的名称)

这是一个字符串,指定应用程序主nib文件的名称。如果您希望使用其它的nib文件(而不是Xcode为工程创建的缺省文件)作为主nib文件,可以将该nib文件名关联到这个键上。nib文件名不应该包含.nib扩展名。

UIStatusBarStyle

这是个字符串,标识程序启动时状态条的风格。这个键的值基于UIApplication.h头文件中声明的UIStatusBarStyle常量。缺省风格是UIStatusBarStyleDefault。在启动完成后,应用程序可以改变状态条的初始风格。

UIStatusBarHidden

这个一个Boolean值,指定在应用程序启动的最初阶段是否隐藏状态条。将这个键值设置为true将隐藏状态条。缺省值为false

UIInterfaceOrientation

这是个字符串,标识应用程序用户界面的初始方向。这个键的值基于UIApplication.h头文件中声明的UIInterfaceOrientation 常量。缺省风格是UIInterfaceOrientationPortrait

有关将应用程序启动为景观模式的更多信息,请参见“以景观模式启动”部分。

UIPrerenderedIcon

这个一个Boolean值,指示应用程序图标是否已经包含发光和斜面效果。这个属性缺省值为false。如果您不希望系统在您的原图上加入这些效果,则将它设置为true。

UIRequiredDeviceCapabilities

这是个信息键,作用是使iTunes和App Store知道应用程序运行需要依赖于哪些与设备相关的特性。iTunes和移动App Store程序使用这个列表来避免将应用程序安装到不支持所需特性的设备上。

这个键的值可以是一个数组或者字典如果您使用的是数组,则数组中存在某个键就表示该键对应的特性是必需的;如果您使用的是字典,则必须为每个键指定一个Boolean值,表示该键是否需要。无论哪种情况,不包含某个键表示该键对应的特性不是必需的。

如果您需要可包含在这个字典中的键列表,请参见表1-4。这个键在iPhone OS 3.0及更高版本上才被支持。

UIRequiresPersistentWiFi

这是个Boolean值,用于通知系统应用程序是否使用Wi-Fi网络进行通讯。如果您的应用程序需要在一段时间内使用Wi-Fi,则应该将这个键值设置为true;否则,为了省电,设备会在30分钟内关闭Wi-Fi连接。设置这个标志还可以让系统在Wi-Fi网络可用但未被使用的时候显示网络选择对话框。这个键的缺省值是false

请注意,当设备处于闲置状态(也就是屏幕被锁定的状态)时,这个属性的值为true是没有作用的。这种情况下,应用程序会被认为是不活动的,虽然它可能在某些级别上还可以工作,但是没有Wi-Fi连接。

UISupportedExternalAccessoryProtocols

这是个字符串数组,标识应用程序支持的配件协议。配件协议是应用程序和连接在iPhone或iPod touch上的第三方硬件进行通讯的协议。系统使用这个键列出的协议来识别当配件连接到设备上时可以打开的应用程序。

有关配件和协议的更多信息,请参见“和配件通讯”部分。这个键只在iPhone OS 3.0和更高版本上支持。

UIViewGroupOpacity

这是个Boolean值,用于指示Core Animation子层是否继承其超层的不透明特性。这个特性使开发者可以在仿真器上进行更为复杂的渲染,但是对性能会有显著的影响。如果属性列表上没有这个键,则其缺省值为NO

这个键只在iPhone OS 3.0和更高版本上支持。

UIViewEdgeAntialiasing

这是个Boolean值,用于指示在描画不和像素边界对齐的层时,Core Animation层是否进行抗锯齿处理。这个特性使开发者可以在仿真器上进行更为复杂的渲染,但是对性能会有显著的影响。如果属性列表上没有这个键,则其缺省值为NO

这个键只在iPhone OS 3.0和更高版本上支持。

如果信息属性文件中的属性值是显示在用户界面上的字符串,则应该进行本地化,特别是当Info.plist中的字符串值是与本地化语言子目录下InfoPlist.strings文件中的字符串相关联的键时。更多信息请参见“国际化您的应用程序”部分。

表1-4列出了和UIRequiredDeviceCapabilities键相关联的数组或字典中可以包含的键。您应该仅包含应用程序确实需要的键。如果应用程序可以通过不执行某些代码路径来适应设备特性不存在的情况,则不需要使用对应的键。

表1-4   UIRequiredDeviceCapabilities键的字典键

描述

telephony

如果您的应用程序需要Phone程序,则包含这个键。如果您的应用程序需要打开tel模式的URL,则可能需要这个特性。

sms

如果您的应用程序需要Messages程序,则包含这个键。如果您的应用程序需要打开sms模式的URL,则可能需要这个特性。

still-camera

如果您的应用程序使用UIImagePickerController接口来捕捉设备照相机的图像时,需要包含这个键。

auto-focus-camera

如果您的应用程序需要设备照相机的自动对焦能力,则需要包含这个键。虽然大多数开发者应该不需要,但是如果您的应用程序支持微距摄影,或者需要更高锐度的图像以进行某种处理,则可能需要包含这个键。

video-camera

如果您的应用程序使用UIImagePickerController接口来捕捉设备摄像机的视频时,需要包含这个键。

wifi

当您的应用程序需要设备的网络特性时,包含这个键。

accelerometer

如果您的应用程序使用UIAccelerometer接口来接收加速计事件,则需要包含这个键。如果您的程序仅需要检测设备的方向变化,则不需要。

location-services

如果您的应用程序使用Core Location框架来访问设备的当前位置,则需要包含这个键(这个键指的是一般的位置服务特性。如果您需要GPS级别的精度,则还应该包含gps键)。

gps

如果您的应用程序需要GPS(或者AGPS)硬件,以获得更高精度的位置信息,则包含这个键。如果您包含了这个键,就应该同时包含location-services键。如果您的程序需要更高精度的位置数据,而不是由蜂窝网络或Wi-fi信号提供的数据,则应该要求只接收GPS数据。

magnetometer

如果您的应用程序使用Core Location框架接收与方向有关的事件时,则需要包含这个键。

microphone

如果您的应用程序需要使用内置的麦克风或支持提供麦克风的外设,则包含这个键。

opengles-1

如果您的应用程序需要使用OpenGL ES 1.1 接口,则包含这个键。

opengles-2

如果您的应用程序需要使用OpenGL ES 2.0 接口,则包含这个键。

应用程序图标和启动图像

显示在用户Home屏幕上的图标文件的缺省文件名为Icon.png(虽然通过Info.plist文件中的CFBundleIconFile属性可以进行重命名)。它应该是一个位于程序包最上层目录的PNG文件。应用程序图标应该是一个57 x 57像素的图像,不带任何刨光和圆角斜面效果。典型情况下,系统在显示之前会将这些效果应用到图标上。然而,在应用程序的Info.plist文件中加入UIPrerenderedIcon键可以重载这个行为,更多信息请参见表1-3

请注意:如果您以ad-hoc的方式(而不是通过App Store)将应用程序发布给本地用户,则程序包中还应该包含一个512 x 512像素版本的应用程序图标,命名为iTunesArtwork。在分发您的应用程序时,iTunes需要显示这个文件提供的图标。

应用程序的启动图像文件的文件名为Default.png。这个图像应该和应用程序的初始界面比较相似;系统在应用程序准备好显示用户界面之前显示启动文件,使用户觉得启动速度很快。启动图像也应该是PNG图像文件,位于应用程序包的顶层目录。如果应用程序是通过URL启动的,则系统会寻找名为Default-scheme.png的启动文件,其中scheme是URL的模式。如果该文件不存在,才选择Default.png文件。

将一个图像文件加入到Xcode工程的具体做法是从Project菜单中选择Add to Project命令,在浏览器中定位目标文件,然后点击Add按键。

请注意:除了程序包顶层目录中的图标和启动图像,您还可以在应用程序中具体语言的工程子目录下包含这些图像文件的本地化版本。更多有关应用程序本地化资源的信息请参见“国际化您的应用程序”部分。

Nib文件

nib文件是一种数据文件,用于存储可在应用程序需要时使用的一些“冻结”的对象。大多数情况下,应用程序使用nib文件来存储构成用户界面的窗口和视图。当您将nib文件载入应用程序时,nib装载代码会将文件中的内容转化为应用程序可以操作的真正对象。通过这个机制,nib文件省去了用代码创建那些对象的工作。

Interface Builder是一个可视化的设计环境,您可以用它来创建nib文件。您可以将标准对象(比如UIKit框架中提供的窗口和视图)和Xcode工程中的定制对象放到nib文件中。在Interface Builder中创建视图层次相当简单,只需要对视图对象进行简单拖拽就可以了。您也可以通过查看器窗口来配置每个对象的属性,以及通过创建对象间的连接来定义它们在运行时的关系。您所做的改变最终都会作为nib文件的一部分存储到磁盘上。

在运行时,当您需要nib文件中包含的对象时,就将nib文件装载到程序中。典型情况下,装载nib文件的时机是当用户界面发生变化和需要在屏幕上显示某些新视图的时候。如果您的应用程序使用视图控制器,则视图控制器会自动处理nib文件的装载过程,当然,您也可以通过NSBundle类的方法自行装载。

有关如何设计应用程序用户界面的更多信息,请参见iPhone用户界面指南。有关如何创建nib文件的信息则参见Interface Builder用户指南

处理关键的应用程序任务

本部分将描述几个所有iPhone应用程序都应该处理的任务。这些任务是整个应用程序生命周期的一部分,因此也是将应用程序集成到iPhone OS系统的重要方面。在最坏的情况下,没有很好地处理其中的某些任务甚至可能会导致应用程序被操作系统终止。

初始化和终止

在初始化和终止过程中,UIApplication类会向应用程序的委托发送恰当的消息,使其执行必要的任务。虽然系统并不要求您的应用程序响应这些消息,但是,几乎所有的iPhone应用程序都应该处理这些消息。初始化是您为应用程序准备用户界面及使其进入初始运行状态的阶段。类似地,在终止阶段,您应该把未保存的数据和关键的应用程序状态写入磁盘。

由于一个iPhone应用程序必须在其它应用程序启动之前退出,所以花在初始化和终止阶段的执行时间要尽可能少。初始化阶段并不适合装载大的、却又不需要马上使用的数据结构。在开始阶段,您的目标应该是尽可能快地显示应用程序的用户界面,最好是使它进入最后一次退出的状态。如果您的应用程序在启动过程中需要更多的时间来装载网络数据,或者执行一些可能很慢的任务,则应该首先显示出用户界面并运行起来,然后在后台线程中执行速度慢的任务。这样,您就有机会向用户显示进度条和其它反馈信息,指示应用程序正在装载必要的数据,或者正在执行重要的任务。

表1-5列举出UIApplicationDelegate协议定义的方法,您在应用程序委托中需要实现这些协议方法,以处理初始化和终止的事务。表中还列出了您在每个方法中应该执行的关键事务。

表1-5  应用程序委托的责任

委托方法

描述

applicationDidFinishLaunching:

使用这个方法来将应用程序恢复到上一个会话的状态。您也可以在这个方法中执行应用程序数据结构和用户界面的定制初始化。

applicationWillTerminate:

使用这个方法来将未存数据或关键的应用程序状态存入磁盘。您也可以在这个方法中执行额外的清理工作,比如删除临时文件。

响应中断

除了Home按键可以终止您的应用程序之外,系统也可以暂时中断您的应用程序,使用户得以响应一些重要的事件。举例来说,应用程序可能被呼入的电话、SMS信息、日历警告、或者设备上的Sleep按键所打断。按下Home按键会终止您的应用程序,而上述这些中断则只是暂时的。如果用户忽略这些中断,您的应用程序可以象之前那样继续运行;然而,如果用户决定接电话或回应SMS信息,系统就会开始终止您的程序。

图1-6显示了在电话、SMS信息、或者日历警告到来时发生的事件序列。紧接在图后面的步骤说明更为详细地描述了事件序列的关键点,包括您在响应每个事件时应该做的事项。这个序列并不反映当用户按下Sleep/Wake按键时发生的情景;该场景的事件序列在步骤说明之后的部分进行描述。

图1-6  中断过程的事件流程

  1. 系统检测到有电话、SMS信息、或者日历警告发生。

  2. 系统调用应用程序委托的applicationWillResignActive:方法,同时禁止将触摸事件发送给您的应用程序。

    中断会导致应用程序暂时失去控制权。如果控制权的丢失会影响程序的行为或导致不好的用户体验,您就应该在委托方法中采取恰当的步骤进行规避。举例来说,如果您的程序是个游戏,就应该暂停。您还应该禁用定时器、降低OpenGL的帧率(如果正在使用OpenGL的话),通常还应该使应用程序进行休眠状态。在这休眠状态下,您的应用程序继续运行,但是不应该做任何重要的工作。

  3. 系统显示一个带有事件信息的警告窗口。用户可以选择忽略或响应该事件。

  4. 如果用户忽略该事件,系统就调用应用程序委托的applicationDidBecomeActive:方法,并重新开始向应用程序传递触摸事件。 

    您可以在这个方法中重新激活定时器、提高OpenGL的帧率、以及将应用程序从休眠状态唤醒。对于处于暂停状态的游戏,您应该考虑使它停在当时的状态上,等待用户做好重新玩的准备。举例来说,您可以显示一个警告窗口,而窗口中带有重新开始的控件。

  5. 如果用户选择响应该事件(而不是忽略),则系统会调用应用程序委托的applicationWillTerminate:方法。您的应用程序应该正常终止,保存所有必要的上下文信息,使应用程序在下一次启动的时候可以回到同样的位置。

    在您的应用程序终止之后,系统就开始启动负责中断的应用程序。

根据用户对中断的不同响应,系统可能在中断结束之后再次启动您的应用程序。举例来说,如果用户接听一个电话并在完成后挂断,则系统会重新启动您的应用程序;如果用户在接听电话过程中回到Home屏幕或启动另一个程序,则系统就不再启动您的应用程序了。

重要提示:当用户接听电话并在通话过程中重新启动您的应用程序时,状态条的高度会变大,以反映当前用户正在通话中。类似地,当用户结束通话的时候,状态条的高度会缩回正常尺寸。您的应用程序应该为状态条高度的变化做好准备,并据此调整内容区域的尺寸。视图控制器会自动处理这个行为,然而,如果您通过代码进行用户界面的布局,就需要在视图布局以及通过layoutSubviews方法处理动态布局变化时考虑状态条的高度。

在运行您的应用程序时,如果用户按下设备的休眠/唤醒按键,系统会调用应用程序委托的applicationWillResignActive:方法,停止触摸事件的派发,然后使设备进入休眠状态。之后,当用户唤醒设备时,系统会调用应用程序委托的applicationDidBecomeActive:方法,并再次开始向应用程序派发事件。如同处理其它中断一样,您应该使用这些方法来使应用程序进入休眠状态(或者暂停游戏)及再次唤醒它们。在休眠时,您的应用程序应该尽可能少用电力。

观察低内存警告

当系统向您的应用程序发送低内存警告时,您需要加以注意。当可用内存的数量降低到安全阈值以下时,iPhone OS会通知最前面的应用程序。如果您的应用程序收到这种警告,就必须尽可能多地释放内存,即释放不再需要的对象或清理易于在稍后进行重建的缓存。

UIKit提供如下几种接收低内存警告的方法:

  • 在应用程序委托中实现applicationDidReceiveMemoryWarning:方法。

  • 在您的UIViewController子类中实现didReceiveMemoryWarning方法。

  • 注册UIApplicationDidReceiveMemoryWarningNotification通告

一旦收到上述的任何警告,您的处理代码就应该立即响应,释放所有不需要的内存。视图控制器应该清除当前离屏的视图对象,您的应用程序委托则应该释放尽可能多的数据结构,或者通知其它应用程序对象释放其拥有的内存。

如果您的定制对象知道一些可清理的资源,则可以让该对象注册UIApplicationDidReceiveMemoryWarningNotification通告,并在通告处理器代码中直接释放那些资源。如果您通过少数对象来管理大多数可清理的资源,且适合清理所有的这些资源,则同样可以让这些对象进行注册。但是,如果您有很多可清理的对象,或者仅希望释放这些对象的一个子集,则在您的应用程序委托中进行释放可能更好一些。

重要提示:和系统的应用程序一样,您的应用程序总是需要处理低内存警告,即使在测试过程中没有收到那些警告,也一样要进行处理。系统在处理请求时会消耗少量的内存。在检测到低内存的情况时,系统会将低内存警告发送给所有正在运行的进程(包括您的应用程序),而且可能终止某些后台程序(如果必要的话),以减轻内存的压力。如果释放后内存仍然不够—可能因为您的应用程序发生泄露或消耗太多内存—系统仍然可能会终止您的应用程序。

定制应用程序的行为

有几种方法可以对基本的应用程序行为进行定制,以提供您希望的用户体验。本文的下面部分将描述一些必须在应用程序级别进行的定制。

以景观模式启动

为了配合Home屏幕的方向,iPhone OS的应用程序通常以肖像模式启动。如果您的应用程序既可以以景观模式运行,也可以以肖像模式运行,那么,一开始应该总是以纵向模式启动,然后由视图控制器根据设备的方向旋转用户界面。但是,如果您的应用程序只能以景观模式启动,则必须执行下面的步骤,使它一开始就以景观模式启动。

  • 在应用程序的Info.plist文件中加入UIInterfaceOrientation键,并将它的值设置为景观模式。您可以将这个键值设置为UIInterfaceOrientationLandscapeLeft或者UIInterfaceOrientationLandscapeRight

  • 以景观模式布局您的视图,并确保正确设置视图的自动尺寸调整选项。

  • 重载视图控制器的shouldAutorotateToInterfaceOrientation:方法,使它仅在期望的景观方向时返回YES,而在肖像方向时返回NO

重要提示:上面描述的步骤假定您的应用程序使用视图控制器来管理视图层次。视图控制器为处理方向改变和复杂的视图相关事件提供了大量的基础设施。如果您的应用程序不使用视图控制器—游戏和其它基于OpenGL ES的应用程序可能是这样的—就必须根据需要旋转绘图表面(或者调整绘图命令),以便将您的内容以景观模式展示出来。

UIInterfaceOrientation属性提示iPhone OS在启动时应该配置应用程序状态条(如果有的话)的方向,就象配置视图控制器管理下的视图方向一样。在iPhone OS 2.1及更高版本的系统中,视图控制器会尊重这个属性,将视图的初始方向设置为指定的方向。使用这个属性相当于在applicationDidFinishLaunching:方法的一开始执行UIApplicationsetStatusBarOrientation:animated:方法。

请注意:在v2.1之前的iPhone OS系统中,如果要以景观模式启动基于视图控制器的应用程序,需要在上文描述的所有步骤的基础上对应用程序根视图的转换矩阵进行一个90度的旋转。在iPhone OS 2.1之前,视图控制器并不会根据UIInterfaceOrientation键的值自动进行旋转,当然在iPhone OS 2.1及更高版本的系统中不需要这个步骤。

和其它应用程序进行通讯

如果一个应用程序支持一些已知类型的URL,您就可以通过对应的URL模式和该程序进行通讯。然而,在大多数情况下,URL只是用于简单地启动一个应用程序并显示一些和调用方有关的信息。举例来说,对于一个用于管理地址信息的应用程序,您就可以在发送给它的URL中包含一个Maps程序可以处理的地址,以便显示相应的位置。这个级别的通讯为用户创造一个集成度高得多的环境,减少应用程序重新实现设备上其它程序已经实现的功能的必要性。

苹果内置支持httpmailtotel、和sms这些URL模式,还支持基于http的、指向Maps、YouTube、和iPod程序的URL。应用程序也可以自己注册定制的URL模式。您的应用程序可以和其它应用程序通讯,具体方法是用正确格式的内容创建一个NSURL对象,然后将它传给共享UIApplication对象openURL:方法。openURL:方法会启动注册接收该URL类型的应用程序,并将URL传给它。当用户最终退出该应用程序时,系统通常会重新启动您的应用程序,但并不总是这样。系统会考虑用户在URL处理程序中的动作及在用户看来返回您的应用程序是否合理,然后做出决定。

下面的代码片断展示了一个程序如何请求另一个程序提供的服务(假定这个例子中的“todolist”是由应用程序注册的定制模式):

NSURL *myURL = [NSURL URLWithString:@"todolist://www.acme.com?Quarterly%20Report#200806231300"];
[[UIApplication sharedApplication] openURL:myURL];

重要提示:如果您的URL类型包含的模式和苹果定义的一样,则启动的是苹果提供的程序,而不是您的程序。如果有多个第三方的应用程序注册处理同样的URL模式,则该类型的URL由哪个程序处理是没有定义的。

如果您的应用程序定义了自己的URL模式,则应该实现对该模式进行处理的方法,具体信息在“实现定制的URL模式”部分中进行描述。有关系统支持的URL处理,包括如何处理URL的格式,请参见苹果的URL模式参考

实现定制的URL模式

您可以为自己的应用程序注册包含定制模式的URL类型。定制的URL模式是第三方应用程序和其它程序及系统进行交互的机制。通过定制的URL模式,应用程序可以将自己的服务提供给其它程序。

注册定制的URL模式

在为您的应用程序注册URL类型时,必须指定CFBundleURLTypes属性的子属性,我们已经在“信息属性列表”部分中介绍过这个属性了。CFBundleURLTypes属性是应用程序的Info.plist文件中的一个字典数组,每个字典负责定义一个应用程序支持的URL类型。表1-6描述了CFBundleURLTypes字典的键和值。

表1-6   CFBundleURLTypes属性的键和值

CFBundleURLName

这是个字符串,表示URL类型的抽象名。为了确保其唯一性,建议您使用反向DNS风格的标识,比如com.acme.myscheme

这里提供的URL类型名是一个指向本地化字符串的键,该字符串位于本地化语言包子目录中的InfoPlist.strings文件中。本地化字符串是人类可识别的URL类型名称,用相应的语言来表示。

CFBundleURLSchemes

这是个URL模式的数组,表示归属于这个URL类型的URL。每个模式都是一个字符串。属于指定URL类型的URL都带有它们的模式组件。

图1-7显示了一个正在用内置的Xcode编辑器编辑的Info.plist文件。在这个图中,左列中的URL类型入口相当于您直接加入到Info.plist文件的CFBundleURLTypes键。类似地,“URL identifier”和“URL Schemes”入口相当于CFBundleURLNameCFBundleURLSchemes键。

图1-7  Info.plist文件中定义一个定制的URL模式

您在对CFBundleURLTypes属性进行定义,从而注册带有定制模式的URL类型之后,可以通过下面的方式来进行测试:

  1. 连编、安装、和运行您的应用程序。

  2. 回到Home屏幕,启动Safari(在iPhone仿真器上,在菜单上选择Hardware > Home命令就可以回到Home屏幕)。

  3. 在Safari的地址栏中,键入使用定制模式的URL。

  4. 确认您的应用程序是否启动,以及应用程序委托是否收到application:handleOpenURL:消息。

处理URL请求

应用程序委托在application:handleOpenURL:方法中处理传递给应用程序的URL请求。如果您已经为自己的应用程序注册了定制的URL模式,则务必在委托中实现这个方法。

基于定制模式的URL采用的协议是请求服务的应用程序能够理解的。URL中包含一些注册模式的应用程序期望得到的信息,这些信息是该程序在处理或响应URL请求时需要的。传递给application:handleOpenURL:方法的NSURL对象表示的是Cocoa Touch框架中的URL。NSURL遵循RFC 1808规范,该类中包含一些方法,用于返回RFC 1808定义的各个URL要素,包括用户名、密码、请求、片断、和参数字符串。与您注册的定制模式相对应的“协议”可以使用这些URL要素来传递各种信息。

在程序清单1-2显示的application:handleOpenURL:方法实现中,传入的URL对象在其请求和片断部分带有具体应用程序的信息。应用程序委托抽出这些信息—在这个例子中,是指一个to-do任务的名称和到期日—并根据这些信息创建应用程序的模型对象。

程序清单1-2  处理基于定制模式的URL请求

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url {
    if ([[url scheme] isEqualToString:@"todolist"]) {
        ToDoItem *item = [[ToDoItem alloc] init];
        NSString *taskName = [url query];
        if (!taskName || ![self isValidTaskString:taskName]) { // must have a task name
            [item release];
            return NO;
        }
        taskName = [taskName stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
 
        item.toDoTask = taskName;
        NSString *dateString = [url fragment];
        if (!dateString || [dateString isEqualToString:@"today"]) {
            item.dateDue = [NSDate date];
        } else {
            if (![self isValidDateString:dateString]) {
                [item release];
                return NO;
            }
            // format: yyyymmddhhmm (24-hour clock)
            NSString *curStr = [dateString substringWithRange:NSMakeRange(0, 4)];
            NSInteger yeardigit = [curStr integerValue];
            curStr = [dateString substringWithRange:NSMakeRange(4, 2)];
            NSInteger monthdigit = [curStr integerValue];
            curStr = [dateString substringWithRange:NSMakeRange(6, 2)];
            NSInteger daydigit = [curStr integerValue];
            curStr = [dateString substringWithRange:NSMakeRange(8, 2)];
            NSInteger hourdigit = [curStr integerValue];
            curStr = [dateString substringWithRange:NSMakeRange(10, 2)];
            NSInteger minutedigit = [curStr integerValue];
 
            NSDateComponents *dateComps = [[NSDateComponents alloc] init];
            [dateComps setYear:yeardigit];
            [dateComps setMonth:monthdigit];
            [dateComps setDay:daydigit];
            [dateComps setHour:hourdigit];
            [dateComps setMinute:minutedigit];
            NSCalendar *calendar = [NSCalendar currentCalendar];
            NSDate *itemDate = [calendar dateFromComponents:dateComps];
            if (!itemDate) {
                [dateComps release];
                [item release];
                return NO;
            }
            item.dateDue = itemDate;
            [dateComps release];
        }
 
        [(NSMutableArray *)self.list addObject:item];
        [item release];
        return YES;
    }
    return NO;
}

请务必对传入的URL输入进行验证。如果您希望了解如何避免URL处理的相关问题,请参见安全编码指南文档中的验证输入部分。如果要了解苹果定义的URL模式,请参见苹果的URL模式参考

显示应用程序的偏好设置

如果您的应用程序通过偏好设置来控制其行为的不同方面,那么,以何种方式向用户提供偏好设置就取决于它们是否为程序的必需部分。

  • 如果偏好设置是程序使用的必需部分(且直接实现起来足够简单),那么应该直接通过应用程序的定制界面来呈现。

  • 如果偏好设置不是必需的,且要求相对复杂的界面,则应该通过系统的Settings程序来呈现。

在确定一组偏好设置是否为程序的必需部分时,请考虑您为程序设计的使用模式。如果您希望用户相对频繁地修改偏好设置,或者这些偏好设置对程序的行为具有相对重要的影响,则可能就是必需部分。举例来说,游戏中的设置通常都是玩游戏的必需部分,或者是用户希望快速改变的项目。然而,由于Settings程序是一个独立的程序,所以只能用于处理用户不频繁访问的偏好设置。

如果您选择在应用程序内进行偏好设置管理,则可以自行定义用户界面及编写代码来实现。但是,如果您选择使用Settings程序,则必须提供一个设置包(Settings Bundle)来进行管理。

设置包是位于应用程序的程序包目录最顶层的定制资源,它是一个封装了的目录,名字为Settings.bundle。设置包中包含一些具有特别格式的数据文件(及其支持资源),其作用是告诉Settings程序如何显示您的偏好设置。这些文件还告诉Settings程序应该把结果值存储在偏好设置数据库的什么位置上,以便应用程序随后可以通过NSUserDefaultsCFPreferences API来进行访问。

如果您通过设置包来实现偏好设置管理,则还应该提供一个定制的图标。Settings程序会在您的应用程序包的最顶层寻找名为Icon-Settings.png的图像文件,并将该图像显示在应用程序名称的边上。该文件应该是一个29 x 29像素的PNG图像文件。如果您没有在应用程序包的最顶层提供这个文件,则Settings程序会缺省使用缩放后的应用程序图标(Icon.png)。

有关如何为应用程序创建设置包的更多信息,请参见“应用程序的偏好设置”部分。

关闭屏幕锁定

如果一个基于iPhone OS的设备在某个特定时间段中没有接收到触摸事件,就会关闭屏幕,并禁用触摸传感器。以这种方式锁定屏幕是省电的重要方法。因此,除非您确实需要在应用程序中避免无意的行为,否则应该总是打开屏幕锁定功能。举例来说,如果您的应用程序不接收屏幕事件,而是使用其它特性(比如加速计)来进行输入,则可能需要禁用屏幕锁定功能。

将共享的UIApplication对象的idleTimerDisabled属性设置为YES,就可以禁止屏幕锁定。请务必在程序不需要禁止屏幕锁定功能时将该属性重置为NO。举例来说,您可能在用户玩游戏的时候禁止了屏幕锁定,但是,当用户处于配置界面或没有处于游戏活跃状态时,应该重新打开这个功能。

国际化您的应用程序

理想情况下,iPhone应用程序显示给用户的文本、图像、和其它内容都应该本地化为多种语言。比如,警告对话框中显示的文本就应该以用户偏好的语言显示。为工程准备特定语言的本地化内容的过程就称为国际化。工程中需要本地化的候选组件包括:

  • 代码生成的文本,包括与具体区域设置有关的日期、时间、和数字格式。

  • 静态文本—比如装载到web视图、用于显示应用程序帮助的HTML文件。

  • 图标(包括您的应用程序图标)及其它包含文本或具体文化意义的图像。

  • 包含发声语言的声音文件。

  • Nib文件

通过Settings程序,用户可以从Language偏好设置视图(参见图1-8)中选择希望在用户界面上看到的语言。您可以访问General设置,然后在International组中找到该视图。

图1-8  语言偏好设置视图

用户选择的语言和程序包中的一个子目录相关联,该子目录名由两个部分组成,分别是ISO 639-1定义的语言码和.lproj后缀。您还可以对语言码进行修改,使之包含具体的地区,方法是在后面(在下划线之后)加入ISO 3166-1定义的区域指示符。举例来说,如果要指定美国英语的本地化资源,程序包中的子目录应该命名为en_US.lproj。我们约定,本地化语言子目录称为lproj文件夹。

请注意:您也可以使用ISO 639-2语言码,而不一定使用ISO 639-1的定义。有关语言和区域代码的信息,请参见国际化编程主题文档中的“语言和地域的指定”部分。

一个lproj文件夹中包含所有指定语言(还可能包含指定地区)的本地化内容。您可以用NSBundle类或CFBundleRef封装类型提供的工具来(在应用程序的lproj文件夹)定位当前选定语言的本地化资源。列表1-3给出一个包含英语(en)本地化内容的目录。

列表1-3  本地化语言子目录的内容

en.lproj/
    InfoPlist.strings
    Localizable.strings
    sign.png

这个例子目录有下面几个项目:

  • InfoPlist.strings文件,包含与Info.plist文件中特定键(比如CFBundleDisplayName)相关联的本地化字符串值。比如,一个英文名称为Battleship的应用程序,其CFBundleDisplayName键在fr.lproj子目录的InfoPlist.strings文件中有如下的入口:

    CFBundleDisplayName = "Cuirassé";
  • Localizable.strings文件,包含应用程序代码生成的字符串的本地化版本。

  • 本例子中的sign.png,是一个包含本地化图像的文件。

为了本地化,我们需要国际化代码中的字符串,具体做法是用NSLocalizedString宏来代替字符串。这个宏的定义如下:

NSString *NSLocalizedString(NSString *key, NSString *comment);

第一个参数是一个唯一的键,指向给定lproj文件夹中Localizable.strings文件里的一个本地化字符串;第二个参数是一个注释,说明字符串如何使用,因此可以为翻译人员提供额外的上下文。举例来说,假定您正在设置用户界面中一个标签(UILabel对象)的内容,则下面的代码可以国际化该标签的文本:

label.text = NSLocalizedString(@"City", @"Label for City text field");

然后,您就可以为给定语言创建一个Localizable.strings文件,并将它加入到相应的lproj文件夹中。对于上文例子中的键,该文件中应该有如下入口:

"City" = "Ville";

请注意:另一种方法是在代码中恰当的地方插入NSLocalizedString调用,然后运行genstrings命令行工具。该工具会生成一个Localizable.strings文件的模板,包含每个需要翻译的键和注释。更多有关genstrings的信息,请参见genstrings(1)的man页面。

更多有关国际化的信息,请参见国际化编程主题

性能和响应速度的调优

在应用程序开发过程的每一步,您都应该考虑自己所做的设计对应用程序总体性能的影响。由于iPhone和iPod touch设备的移动本质,iPhone应用程序的操作环境受到更多的限制。本文的下面部分将描述在开发过程中应该考虑哪些因素。

不要阻塞主线程

您应该认真考虑在应用程序主线程上执行的任务。主线程是应用程序处理触摸事件和其它用户输入的地方。为了确保应用程序总是可以响应用户,我们不应该在主线程中执行运行时间很长或可能无限等待的任务,比如访问网络的任务。相反,您应该将这些任务放在后台线程。一个推荐的方法是将每个任务都封装在一个操作对象中,然后加入操作队列。当然,您也可以自己创建显式的线程。

将任务转移到后台可以使您的主线程继续处理用户输入,这对于应用程序的启动和退出尤其重要。在这些时候,系统期望您的应用程序及时响应事件。如果应用程序的主线程在启动过程中被阻塞住了,系统甚至可能在启动完成之前将它杀死;如果主线程在退出时被阻塞了,则应用程序可能来不及保存关键用户数据就被杀死了。

更多有关如何使用操作对象和线程的信息,请参见线程编程指南

有效地使用内存

由于iPhone OS的虚存模型并不包含磁盘交换区空间,所以应用程序在更大程度上受限于可供使用的内存。对内存的大量使用会严重降低系统的性能,可能导致应用程序被终止。因此,在设计阶段,您应该把减少应用程序的内存开销放在较高优先级上。

应用程序的可用内存和相对性能之间有直接的联系。可用内存越少,系统在处理未来的内存请求时就越可能出问题。如果发生这种情况,系统总是先把代码页和其它非易失性资源从内存中移除。但是,这可能只是暂时的修复,特别是当系统在短时间后又再次需要那些资源的时候。相反,您需要尽可能使内存开销最小化,并及时清除自己使用的内存。

本文的下面部分将就如何有效使用内存和在只有少量内存时如何反应方面提供更多的指导。

减少应用程序的内存印迹

表1-7列出一些如何减少应用程序总体内存印迹的技巧。在开始时将内存印迹降低了,随后就可以有更多的空间用于需要操作的数据。

表1-7  减少应用程序内存印迹的技巧

技巧

采取的措施

消除内存泄露

由于内存是iPhone OS的关键资源,所以您的应用程序不应该有任何的内存泄露。存在内存泄露意味着应用程序在之后可能没有足够的内存。您可以用Instruments程序来跟踪代码中的泄露,该程序既可以用于仿真器,也可以用于实际的设备。有关如何使用Instruments的更多信息,请参见Instruments用户指南

使资源文件尽可能小

文件驻留在磁盘中,但在使用时需要载入内存。属性列表文件和图像文件是通过简单的处理就可以节省空间的两种资源类型。您可以通过NSPropertyListSerialization类将属性列表文件存储为二进制格式,从而减少它们的使用空间;对于图像,可以将所有图像文件压缩得尽可能小(PNG图像是iPhone应用程序的推荐图像格式,可以用pngcrush工具来进行压缩)。

使用Core Data或SQLite来处理大的数据集合

如果您的应用程序需要操作大量的结构化数据,请将它存储在Core Data的持久存储或SQLite数据库,而不是使用扁平文件。Core Data和SQLite都提供了管理大量数据的有效方法,不需要将整个数据一次性地载入内存。

Core Data的支持是在iPhone OS 3.0系统上引入的。

延缓装载资源

在真正需要资源文件之前,永远不应该进行装载。预先载入资源文件表面看好象可以节省时间,但实际上会使应用程序很快变慢。此外,如果您最终没有用到那些资源,预先载入将只是浪费内存。

将程序连编为Thumb格式

加入-mthumb开关可以将代码的尺寸减少最多达35%。但是,对于具有大量浮点数运算的代码模块,请务必将这个选项关闭,因为对那样的模块使用Thumb反而会导致性能的下降。

恰当地分配内存

iPhone应用程序使用委托内存模式,因此,您必须显式保持和释放内存。表1-8列出了一些在程序中分配内存的技巧。

表1-8  分配内存的技巧

技巧

采取的措施

减少自动释放对象的使用

通过autorelease方法释放的对象会留在内存中,直到显式清理自动释放池或者程序再次回到事件循环。在任何可能的时候,请避免使用autorelease方法,而是通过release方法立即收回对象占用的空间。如果您必须创建一定数量的自动释放对象,则请创建局部的自动释放池,以便在返回事件循环之前定期对其进行清理,回收那些对象的内存。

为资源设置尺寸限制

避免装载大的资源文件,如果有更小的文件可用的话。请用适合于iPhone OS设备的恰当尺寸图像来代替高清晰度的图像。如果您必须使用大的资源文件,需要考虑仅装载当前需要的部分。举例来说,您可以通过mmapmunmap函数来将文件的一部分载入内存或从内存卸载,而不是操作整个文件。有关如何将文件映射到内存的更多信息,请参见文件系统性能指南

避免无边界的问题集

无边界的问题集可能需要计算任意大量的数据。如果该集合需要的内存比当前系统能提供的还要多,则您的应用程序可能无法进行计算。您的应用程序应该尽可能避免处理这样的集合,而将它们转化为内存使用极限已知的问题。

有关如何在iPhone应用程序中分配内存及使用自动释放池的详细信息,请参见Cocoa基本原理指南文档的Cocoa对象部分。

浮点数学运算的考虑

iPhone–OS设备上的处理器有能力在硬件上处理浮点数计算。如果您目前的程序使用基于软件的定点数数学库进行计算,则应该考虑对代码进行修改,转向使用浮点数数学库。典型情况下,基于硬件的浮点数计算比对应的基于软件的定点数计算快得多。

重要提示:当然,如果您的代码确实广泛地使用浮点数计算,请记住不要使用-mthumb选项来编译代码。Thumb选项可以减少代码模块的尺寸,但是也会降低浮点计算代码的性能。

减少电力消耗

移动设备的电力消耗一直是个问题。iPhone OS的电能管理系统保持电能的方法是关闭当前未被使用的硬件功能。此外,要避免CPU密集型和高图形帧率的操作。您可以通过优化如下组件的使用来提高电池的寿命:

  • CPU

  • Wi-Fi和基带(EDGE, 3G)无线信号

  • Core Location框架

  • 加速计

  • 磁盘

您的优化目标应该是以尽可能有效的方式完成大多数的工作。您应该总是采用Instruments和Shark工具对应用程序的算法进行优化。但是,很重要的一点是,即使最优化的算法也可能对设备的电池寿命造成负面的影响。因此,在写代码的时候应该考虑如下的原则:

  • 避免需要轮询的工作,因为轮询会阻止CPU进入休眠状态。您可以通过NSRunLoop或者NSTimer类来规划需要做的工作,而不是使用轮询。

  • 尽一切可能使共享的UIApplication对象的idleTimerDisabled属性值保持为NO。当设备处于不活动状态一段时间后,空闲定时器会关闭设备的屏幕。如果您的应用程序不需要设备屏幕保持打开状态,就让系统将它关闭。如果关闭屏幕给您的应用程序的体验带来负面影响,则需要通过修改代码来消除那些影响,而不是不必要地关闭空闲定时器。

  • 尽可能将任务合并在一起,以便使空闲时间最大化。每隔一段时间就间歇性地执行部分任务比一次性完成相同数量的所有任务开销更多的电能。间歇性地执行任务会阻止系统在更长时间内无法关闭硬件。

  • 避免过度访问磁盘。举例来说,如果您需要将状态信息保存在磁盘上,则仅当该状态信息发生变化时才进行保存,或者尽可能将状态变化合并保存,以避免短时间频繁进行磁盘写入操作。

  • 不要使屏幕描画速度比实际需求更快。从电能消耗的角度看,描画的开销很大。不要依赖硬件来压制应用程序的帧率,而是应该根据程序实际需要的帧率来进行帧的描画。

  • 如果你通过UIAccelerometer类来接收常规的加速计事件,则当您不再需要那些事件时,要禁止这些事件。类似地,请将事件传送的频率设置为满足应用程序需要的最小值。更多信息请参见“访问加速计事件”部分。

您向网络传递的数据越多,就需要越多的电能来进行无线发射。事实上,访问网络是您所能进行的最耗电的操作,您应该遵循下面的原则,使网络访问最小化:

  • 仅在需要的时候连接外部网络,不要对服务器进行轮询。

  • 当您需要连接网络时,请仅传递完成工作所需要的最少数据。请使用紧凑的数据格式,不要包含可被简单忽略的额外数据。

  • 尽可能快地以群发(in burst)方式传递数据包,而不是拉长数据传输的时间。当系统检测到设备没有活动时,就会关闭Wi-Fi和蜂窝无线信号。您的应用程序以较长时间传输数据比以较短时间传输同样数量的数据要消耗更多的电能。

  • 尽可能通过Wi-Fi无线信号连接网络。Wi-Fi耗电比基带无线少,是推荐的方式。

  • 如果您通过Core Location框架收集位置数据,则请尽可能快地禁止位置更新,以及将位置过滤器和精度水平设置为恰当的值。Core Location通过可用的GPS、蜂窝、和Wi-Fi网络来确定用户的位置。虽然Core Location已经努力使无线信号的使用最小化了,但是,设置恰当的精度和过滤器的值可以使Core Location在不需要位置服务的时候完全关闭硬件。更多信息请参见“获取用户的当前位置”部分。

代码的优化

和iPhone OS一起推出的还有几个应用程序的优化工具。它们中的大部分都运行在Mac OS X上,适合于调整运行在仿真器上的代码的某些方面。举例来说,您可以通过仿真器来消除内存泄露,确保总的内存开销尽可能小。借助这些工具,您还可以排除代码中可能由低效算法或已知瓶颈引起的计算热点。

在仿真器上进行代码优化之后,还应该在设备上用Instruments程序进行进一步优化。在实际设备上运行代码是对其进行完全优化的唯一方式。因为仿真器运行在Mac OS X上,而运行Mac OS X的系统具有更快的CPU和更多的可用内存,所以其性能通常比实际设备的性能好很多。在实际设备上用Instruments跟踪代码可能会发现额外的性能瓶颈,您需要进行优化。

更多有关Instruments的使用信息,请参见Instruments用户指南

你可能感兴趣的:(object-c,iphone,xcode,url,设计模式,cocoa,语言)