App Programming Guide for iOS -&
为保存而标记视图控制器
UIKit只保存那些restorationIdentifier属性包含有有效字符串的视图控制器对象。在初始化这些视图控制器的时候,就对此属性进行赋值。如果是从storyboard或nib文件上加载的视图控制器,可以直接在这两处设置恢复标识符。
为恢复标识符选择恰当的值是很重要的。在恢复过程期间,代码使用恢复标识符来决定视图控制器是取回还是创建。如果每个视图控制器都基于不同的类,那你可以使用类名来作为恢复标识符。但是,如果你的视图控制器层级结构包含多个实例来自同一个类,你就需要根据每个视图的用途来选择不同的名字。
当UIKit要求你提供一个视图控制器的时候,它会提供视图控制器对象的恢复路径给你。恢复路径是视图控制器层次结构中从根视图控制器到当前对象的一系列恢复标识符。例如,想象你有一个恢复标识符为TabBarControllerID的选项栏控制器,它的第一个选项卡包含一个标识符为NavControllerID导航控制器,该导航控制器的根视图控制器的标识符为MyViewController。那么该根视图控制器的完整恢复路径是TabBarControllerID/NavControllerID/MyViewController。
恢复路径对于每个对象必须是独一无二的。如果视图控制器拥有两个子视图控制器,那么这两个子视图控制器必定有不同的恢复标识符。但是两个有着不同父对象的视图控制器可以有一个相同的恢复标识符,这时因为路径是不同的。一些UIKit视图控制器,例如导航控制器,自动消除子视图控制器的歧义,允许你为每个子视图控制器使用相同的恢复标识符。更多关于给定视图控制器的行为,参见相关的类参考。
在恢复的时候,你使用提供的恢复路径来决定哪个视图控制器返回给UIKit。更多关于使用恢复标识符以及恢复路径来恢复视图控制器的信息,参见Restoring Your View Controllers at Launch Time。
在启动时恢复视图控制器
在恢复过程期间,UIKit要求应用创建(或定位)视图控制器的对象,这些对象构成了你保存的用户界面。UIKit在视图定位视图控制器的时候会遵循如下过程:
- 如果视图控制器有一个恢复类,UIKit要求这个类提供视图控制器。UIKit调用相应恢复类的viewControllerWithRestorationIdentifierPath:coder:方法来取回视图控制器。如果该方法返回nil,则假定应用程序不想重新创建该视图控制器,UIKit停止搜寻它。
- 如果视图控制器没有恢复类,UIKit要求应用委托来提供视图控制器。UIKit调用应用委托的application:viewControllerWithRestorationIdentifierPath:coder:方法来查找没有恢复类的视图控制器。如果该方法返回nil,UIKit会尝试隐式的继续查找该视图控制器。
- 如果具有正确的恢复路径视图控制器已存在,UIKit将使用该对象。如果应用在启动时创建视图控制器(无论是编码方式还是从资源文件加载)并将恢复标识符分配给它们,UIKit可以通过恢复路径隐式的找到它们。
- 如果视图控制器最初是从storyboard文件加载的,UIKit使用保存的storyboard信息来定位和创建它。UIKit把关于视图控制器的storyboard的信息保存到恢复存档中。在恢复的时候,如果没有通过其他方式找到视图控制器,它将使用这些信息来定位相同的storyboard文件,并实例化相应的视图控制器。
值得注意的是,如果你给视图控制器指定了恢复类,UIKit就不会尝试隐式的查找视图控制器了。如果恢复类的viewControllerWithRestorationIdentifierPath:coder:方法返回nil,UIKit停止尝试定位视图控制器。这可以让你控制是否真创建视图控制器。如果你没有指定恢复类,UIKit会尽可能的查找视图控制器,并根据需要从应用的storyboard文件创建它。
如果你选择使用恢复类,就要实现viewControllerWithRestorationIdentifierPath:coder:类来创建该类的新实例,执行最小化的初始化,并返回生成的对象。代码清单5-1展示了一个如何使用该方法从storyboard加载视图控制器的例子。因为这个视图控制器最初是从storyboard加载的,所以这个方法使用UIStateRestorationViewControllerStoryboardKey键来从归档中获取storyboard。注意这个方法不会尝试配置这个视图控制器的数据字段。当视图控制器的状态被解码的时候才发生此步骤。
代码清单5-1 在恢复期间创建一个新的视图控制器
+ (UIViewController*) viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
coder:(NSCoder *)coder {
MyViewController* vc;
UIStoryboard* sb = [coder decodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb) {
vc = (PushViewController*)[sb instantiateViewControllerWithIdentifier:@"MyViewController"];
vc.restorationIdentifier = [identifierComponents lastObject];
vc.restorationClass = [MyViewController class];
}
return vc;
}
当创建新视图控制器的时候,重新分配恢复标识符和恢复类,如上例所示,是一个很好的习惯。取得恢复标识符的最简单方法是取出identifierComponents数组的最后一个元素,并把它分配给你的视图控制器。
对于在启动的时候已经从应用的主storyboard文件加载的对象,不用创建每个对象的新实例。相反,应该实现应用委托的application:viewControllerWithRestorationIdentifierPath:coder:方法,并使用它来返回合适的对象或让UIKit隐式的找到这些对象。
视图控制器状态的编码与解码
对于每个要保存的对象的状态,UIKit调用对象的encodeRestorableStateWithCoder:方法,给对象一个保存状态的机会。在解码过程期间,调用decodeRestorableStateWithCoder:方法,把状态匹配给对象。实现这些方法对于视图控制器不是必须的,但是推荐使用。你可以对以下信息类型使用它们进行保存和恢复。
- 任何被用于显示的数据的引用(而不是数据本身)
- 对于容器视图控制器,它的子视图控制器的引用
- 关于当前选择的信息
- 对于具有用户配置视图的视图控制器,关于视图当前配置的信息
在你的编码和解码方法中,你可以对编码器支持的所有值进行编码,包括其他对象。对于初了视图和视图控制器以外的所有对象,必须采用NSCoding协议,并使用这个协议的方法来写入状态,对于视图和视图控制器,编码器不使用NSCoding协议方法来保存对象状态。相反,编码器保存对象的恢复标识符,并把它添加到可保存对象的列表中,这导致对象的encodeRestorableStateWithCoder:方法被调用。
视图控制器的encodeRestorableStateWithCoder: 和 decodeRestorableStateWithCoder:方法在实现的时候应该总是调用super。调用super可以让父类又机会保存和恢复任何额外的信息。代码清单5-2展示了使用这些方法来对视图控制器数值的简单编解码。
Listing 5-2 Encoding and decoding a view controller’s state.
- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {
[super encodeRestorableStateWithCoder:coder];
[coder encodeInt:self.number forKey:MyViewControllerNumber];
}
- (void)decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
self.number = [coder decodeIntForKey:MyViewControllerNumber];
}
编码器对象在编解码的过程中不会进行共享。每个带有保存状态的对象都接受它们自己的编码器,它可以用来读写数据。使用独一无二的编码器意味着你不需要担心命名空间问题。但是你必须仍然要避免使用一些UIKit提供的特定的键名。具体来说,每个编码器包含UIApplicationStateRestorationBundleVersionKey 和 UIApplicationStateRestorationUserInterfaceIdiomKey 键,它们提供关于束版本和当前用户界面风格的信息。与视图控制相关的编码器也还包含UIStateRestorationViewControllerStoryboardKey键,它识别视图控制器起源的storyboard。
更多关于为视图控制器实现编解码方法的信息,参见UIViewController Class Reference。
保存视图的状态
如果视图有值的保存的状态信息,你可以将该状态和应用的其他视图控制器保存在一起。因为它们通常被它们自己的视图控制器配置,大多数视图不必保存状态信息。只有当视图自身能被用户通过某种方式改变的时候,你才需要保存视图的状态。这种改变的基础是视图拥有独立的数据和或视图控制器。例如,滚动视图保存当前的滚动位置,这个位置的信息不足以引起视图控制器的兴趣,但是它却影响了滚动视图自身的外观。
为了指定应该保存的视图状态,你需要做以下操作:
- 分配你一个有效的字符串给视图的restorationIdentifier属性。
- 使用该视图的视图控制器也要有有效的恢复标识符。
- 对于表视图和集合视图,分配一个采用UIDataSourceModelAssociation协议的数据源。
与视图控制器一样,分配一个恢复标识符给视图,告诉系统该视图对象有应用想要保存的状态。该恢复标识符也能被用于视图的定位。
就像视图控制器,视图定义方法来编解码它们的自定义状态。如果你创建值的保存状态的视图,你可以使用这些方法来读写相关的数据。
保存状态的UIKit视图
为了要保存状态的任何视图,包括自定和标准的系统视图,你必须给视图分配恢复标识符。没有恢复标识符的视图,不能被UIKit添加到保存对象列表中。
下面的UIKit视图有可以被保存的状态信息:
- UICollectionView
- UIImageView
- UIScrollView
- UITableView
- UITextField
- UITextView
- UIWebView
其他框架或许还有具有可保存状态的视图,关于一个视图是否保存状态信息,以及保存什么信息,参见相应类的参考。
保存自定义视图的状态
如果你正在实现一个带有可存储状态的自定义视图,那就实现 encodeRestorableStateWithCoder: 和 decodeRestorableStateWithCoder:方法,并使用它们编、解码这个状态。使用这些方法来保存那些不能通过其他方式轻松重配置的数据。例如,使用这些方法来保存那些用户和视图交互产生的修改的数据。不要使用这些方法来保存通过视图或视图控制器可以轻松配置的任何数据。
代码清单5-3展示了一个如何保存和恢复包含有可编辑文本的自定义视图选择的例子。在这个例子中,可以使用 selectionRange 和 setSelectionRange:方法来访问范围,这些是视图自定义的用于管理选择的方法。编码这些数据仅需要将其写入编码器对象中。恢复数据要求读取它并把它应用于视图。
代码清单5-3 保存自定义视图的选择
// Preserve the text selection
- (void) encodeRestorableStateWithCoder:(NSCoder *)coder {
[super encodeRestorableStateWithCoder:coder];
NSRange range = [self selectionRange];
[coder encodeInt:range.length forKey:kMyTextViewSelectionRangeLength];
[coder encodeInt:range.location forKey:kMyTextViewSelectionRangeLocation];
}
// Restore the text selection.
- (void) decodeRestorableStateWithCoder:(NSCoder *)coder {
[super decodeRestorableStateWithCoder:coder];
if ([coder containsValueForKey:kMyTextViewSelectionRangeLength] &&
[coder containsValueForKey:kMyTextViewSelectionRangeLocation]) {
NSRange range;
range.length = [coder decodeIntForKey:kMyTextViewSelectionRangeLength];
range.location = [coder decodeIntForKey:kMyTextViewSelectionRangeLocation];
if (range.length > 0)
[self setSelectionRange:range];
}
}
实现易于维护的数据源
因为表或集合视图显示的数据可以更改,所以只有当它们的数据源实现了UIDataSourceModelAssociation协议时,这两个类才可以保存关于当前选择和可见单元的信息。这个协议为表和集合视图提供一个方式来识别它们所包含的内容,而不必依赖这些内容的索引路径。因此,无论数据源在下一个启动周期如何放置项目的位置,该视图仍然具有查找该项目所需的信息。
为了成功实现UIDataSourceModelAssociation协议,你的数据源对象必须能够识别应用后续启动中的项目。这意味着任何你设计的识别方案对给定的数据必须是不变的。这是至关重要的,因为数据源必须能够在每次请求的时候为相同的标识符取回相同的数据。实现这个协议本身,是从数据项映射到唯一的ID并再次返回。
使用Core Data的应用可以通过利用对象标识符来实现协议。每个Core Data库中对象都有一个唯一的对象标识符,它可以被转化为URI,并用于定位对象,如果应用不是使用Core Data,如果你想让你的视图支持状态保存,你需要设计自己的唯一标识符的格式。
注意:记住,实现UIDataSourceModelAssociation协议仅需要保存属性,例如表或集合视图的当前选择。这个协议不用于保存由数据源管理的真实数据。确保数据在合适的时候保存是应用的责任。
保存应用的高级状态
除了通过应用的视图控制器和视图来保存数据之外,UIKit提供了钩子(hooks)来保存应用需要的任何杂项数据。具体来说,需要重写UIApplicationDelegate协议的下列两个方法:
- application:willEncodeRestorableStateWithCoder:
- application:didDecodeRestorableStateWithCoder:
如果应用包含不在视图控制器中的状态,但又需要保存,你可以使用上面的方法来保存和恢复。application:willEncodeRestorableStateWithCoder:方法在存储过程刚开始的时候调用,所以你可以在这里写入高级应用状态,例如当前的用户界面版本。application:didDecodeRestorableStateWithCoder:方法在恢复状态结束的时候调用,你可以在这里解码任何数据并根据应用要求执行最后的清理。
保存和恢复状态信息的提示
当你向应用添加状态保存和恢复的支持的时候,请考虑以下准则:
- 版本信息可以和应用的其他状态一起编码。在保存过程期间,建议对指明当前应用用户界面的版本字符串或数字进行编码。你可以在应用委托的application:willEncodeRestorableStateWithCoder:方法里面编码这个状态。当应用委托的application:shouldRestoreApplicationState:方法被调用的时候,你可以从提供的编码器中取回这些信息,并使用它来确定状态是否可以保存。
- 不要在应用的状态中包含来自数据模型的对象。应用应该保持吧数据保存在iCloud或者磁盘的本地文件上。不要使用状态恢复机制来保存数据。如果在恢复操作期间发生错误,有可能使保存的界面数据被删除。因此,你写入磁盘的任何与保存相关的数据都会被认为可清除。
- 状态保存系统期望你依照设计目的来使用视图控制器。视图控制器层次结构是依赖于视图控制器之间的控制和呈现创建的。如果应用通过其他方式显示视图控制器的视图,(例如,通过把它添加到另一个和相应的视图控制器没有控制关系的视图中,)保存系统将不能找到你的视图控制器并保存它。
- 记住你或许并不想保存所有的视图控制器。在某些情况下,保存视图控制器或许没有意义。例如,如果在修改密码的时候用户退出了应用,你可能希望能取消这个操作,并恢复到上一个屏幕。这种情况下,就不需要保存要求输入新密码信息的视图控制器。
- 避免在恢复过程期间交换视图控制器类。状态保存系统编码它保存的视图控制器类。在恢复期间,如果应用返回的对象的类和原来对象的类不匹配(或不是其子类),系统不会要求视图控制器解码任何状态信息。因此,交换了视图控制器的类,将不能恢复对象的完整状态。
- 当用户强退应用的时候,系统会自动删除应用保存的状态。当应用被杀死的时候,删除保存的状态信息是一种安全预防措施。(为了安全起见,如果系统连着启动两次都崩溃,系统也会删除保存的状态。)如果你想测试应用恢复它状态的功能,调试期间就不要使用多任务栏来杀死应用。相反,使用Xcode来杀死应用,或通过安装临时命令,或安装手势来按需调用退出函数来杀死应用。
开发VoIP应用的提示
互联网语音协议(VoIP)应用允许用户使用网络连接,而非设备的蜂窝服务来拨打电话。在iOS 8及更高的版本中,你可以使用APNS和PushKit框架的API来创建VoIP应用。以来推送通知来启用VoIP功能,意味着应用不需要维持相关服务的持续网络连接,或者配置供VoIP使用的套接字。当VoIP推送通知到达的时候,即使应用处于终止状态,应用也会花时间来处理该通知。
注意:VoIP的推送通知只发送给iOS 8及更高版本的设备。如果你需要支持较早版本的iOS设备,你需要处理处理兼容问题。
和任何后台音频应用一样,VoIP的音频会话必须正确配置,以便确保其他基于音频的应用能够平滑的运行。因为VoIP的音频播放和录制并不是一直运行的,所以创建只有在必要的时候才创建并配置应用的音频会话是非常重要的。例如,你创建音频会话来通知用户有电话呼入或者用户实际就在通话中。一旦通话结束,你应该移除对于音频会话的强引用,以便其他音频应用可以播放它们的音频。
关于如何为VoIP应用配置和管理音频会话的信息,参见Audio Session Programming Guide。想要学习更多关于使用VoIP推送通知以及用PushKit APIs创建VoIP应用的内容,参见Energy Efficiency Guide for iOS Apps。
在实现VoIP应用时有下列要求:
- 为应用启用Voice over IP 后台模式。(因为VoIP涉及音频内容,建议还要启用Audio and AirPlay后台模式。)你在Xcode项目的Capabilities选项卡中设置这些后台模式。
- 使用PushKit APIs来注册接收VoIP推送通知,并处理传入的通知。
- 配置你的音频会话来处理出、入内容的转换。
- 为了确保在iPhone上获得更好的用户体验,使用Core Telephony框架来调整与手机通话相关的行为。参见Core Telephony Framework Reference。
- 为了确保VoIP应用有良好的性能,使用System Configuration框架来检测网络改变,并运行应用尽可能的休眠。
- 申请VoIP Services证书来允许你的通知服务器连接VoIP服务。
启用VoIP后台模式,让系统知道它将允许应用必要时在后台运行,来管理其网络套接字。此键还允许应用在后台播放音频(尽管如此,还时鼓励启用Audio and AirPlay模式)。支持这个模式的应用在系统启动后立即在后台重启,以确保VoIP服务始终可用。
使用可达性(Reachbility)接口来提高用户体验
因为VoIP应用严重依赖网络,所以它们应该使用System Configuration框架的可达性接口来跟踪网络的状态,并调整它们的相应行为。可达性接口允许应用在网络条件发生变化的时候能收到通知。例如,当网络不可用的时候,VoIP应用可以关闭它的网络连接,而在网络恢复的时候,重新创建它们。应用还可以使用这些变化来通知用户VoIP连接现在状态。
为了使用可达性接口,你必须使用该框架来注册一个回调函数,并使用它来处理改变。通过以下步骤注册会掉函数:
- 为你的远程目标主机创建一个SCNetworkReachabilityRef结构。
- 分配一个回调函数被你的结构(使用SCNetworkReachabilitySetCallback函数),它处理目标的可达状态的改变。
- 使用SCNetworkReachabilityScheduleWithRunLoop 函数添加该目标到应用的一个活动的运行循环(例如主运行循环)
根据网络的可达性调整应用的行为,还可以有助于提高底层设备的电池寿命。让系统跟踪网络改变意味着应用可以让它自己尽可能的休眠。
更多关于可达性接口的信息,参见System Configuration Framework Reference。
(本节结束)