在您的应用程序中启用状态保存和恢复
状态保存和恢复不是自动功能,应用程序必须选择使用它。应用程序通过在其应用程序委托中实施以下方法来表明他们对该功能的支持:
application:shouldSaveApplicationState:
application:shouldRestoreApplicationState:
通常,这些方法的实现只是返回YES,表示可以发生状态保存和恢复。但是,要在不发生操作的情况下,有条件地保留和恢复状态的应用程序可以返回NO。例如,在发布对您的应用程序的更新后,您可能希望从应用程序返回NO:application:shouldRestoreApplicationState:方法,如果您的应用程序无法从先前版本有效地还原状态。
保存您的视图控制器的状态
保存应用程序视图控制器的状态应该是您的主要目标。视图控制器定义用户界面的结构。他们管理呈现该界面所需的视图,并协调获取和设置支持这些视图的数据。要保留单个视图控制器的状态,您必须执行以下操作:
(必需)将修复标识符分配给视图控制器;请参阅标记您的视图控制器进行保存。Marking Your View Controllers for Preservation.
(必需)提供在启动时创建或定位新的视图控制器对象的代码;请参阅在启动时恢复视图控制器。Restoring Your View Controllers at Launch Time
(可选)实现encodeRestorableStateWithCoder:和decodeRestorableStateWithCoder:编码和恢复在后续启动期间无法重新创建的任何状态信息的方法;请参阅编码和解码视图控制器的状态。Encoding and Decoding Your View Controller’s State
标记您的视图控制器进行保存
UIKit仅保留其restoreIdentifier属性包含有效字符串对象的视图控制器。对于您知道要保留的视图控制器,在初始化视图控制器对象时,请设置此属性的值。如果从故事板或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调用应用程序:viewControllerWithRestorationIdentifierPath:您的应用程序委托的coder:方法来查找没有恢复类的视图控制器。如果该方法返回nil,则UIKit将尝试隐式查找视图控制器。
如果具有正确恢复路径的视图控制器已存在,则UIKit将使用该对象。如果您的应用程序在启动时创建视图控制器(以编程方式或通过从资源文件加载它们)并将恢复标识符分配给它们,则UIKit将通过其恢复路径隐式地找到它们。
如果视图控制器最初从故事板文件加载,则UIKit将使用保存的故事板信息来定位并创建它。 UIKit将关于视图控制器的故事板的信息保存在恢复存档中。在还原时,如果没有通过任何其他方式找到视图控制器,它将使用该信息来定位相同的故事板文件并实例化相应的视图控制器。
值得注意的是,如果为视图控制器指定了恢复类,UIKit不会隐含地找到您的视图控制器。如果您的还原类的viewControllerWithRestorationIdentifierPath:coder:方法返回nil,UIKit将停止尝试找到您的视图控制器。这可以控制您是否真的要创建视图控制器。如果您没有指定恢复类,则UIKit会尽力为您找到视图控制器,并根据需要从应用程序的故事板文件中进行创建。
如果您选择使用还原类,则您的viewControllerWithRestorationIdentifierPath:coder:method的实现应该创建一个类的新实例,执行一些最小化的初始化,并返回生成的对象。清单5-1显示了如何使用此方法从故事板加载视图控制器的示例。因为视图控制器最初是从故事板加载的,所以该方法使用UIStateRestorationViewControllerStoryboardKey键从存档中获取故事板。请注意,此方法不会尝试配置视图控制器的数据字段。当视图控制器的状态被解码时,该步骤发生。
清单5-1在恢复期间创建新的视图控制器
+ (UIViewController*)viewControllerWithRestorationIdentifierPath:(NSArray *)identifierComponents
coder:(NSCoder *)coder {
MyViewController* vc;
UIStoryboard* sb = [coderdecodeObjectForKey:UIStateRestorationViewControllerStoryboardKey];
if (sb) {
vc = (PushViewController*)[sbinstantiateViewControllerWithIdentifier:@"MyViewController"];
vc.restorationIdentifier =[identifierComponents lastObject];
vc.restorationClass =[MyViewController class];
}
return vc;
}
重新分配恢复标识符和恢复类,如上例所示,是创建新的视图控制器时采用的习惯。恢复恢复标识符的最简单的方法是抓取identifierComponents数组中的最后一个项目并将其分配给视图控制器。
对于在启动时已从应用程序的主要故事板文件加载的对象,请勿创建每个对象的新实例。相反,实现应用程序:viewControllerWithRestorationIdentifierPath:您的应用程序委托的方法,并使用它来返回相应的对象或让UIKit隐式查找这些对象。
编码和解码您的视图控制器的状态
对于要保存的每个对象,UIKit调用对象的encodeRestorableStateWithCoder:方法,以便有机会保存其状态。在解码过程中,对decodeRestorableStateWithCoder:方法进行匹配调用,以对该状态进行解码并将其应用于对象。这些方法的实现是可选的,但建议您的视图控制器使用。您可以使用它们来保存和恢复以下类型的信息:
引用显示的任何数据(不是数据本身)
对于容器视图控制器,对其子视图控制器的引用
有关当前选择的信息
对于具有用户可配置视图的视图控制器,有关该视图的当前配置的信息。
在编码和解码方法中,您可以编码编码器支持的任何值,包括其他对象。对于除视图和视图控制器之外的所有对象,对象必须采用NSCoding协议,并使用该协议的方法来写入其状态。对于视图和视图控制器,编码器不使用NSCoding协议的方法来保存对象的状态。相反,编码器保存对象的恢复标识符并将其添加到可保存对象的列表中,这导致该对象的encodeRestorableStateWithCoder:方法被调用。
encodeRestorableStateWithCoder:和decodeRestorableStateWithCoder:您的视图控制器的方法应始终在其实现中的某个时刻调用super。调用super方法使父类有机会保存和恢复任何其他信息。清单5-2显示了保存用于标识指定视图控制器的数值的这些方法的示例实现。
清单5-2编码和解码视图控制器的状态。
- (void)encodeRestorableStateWithCoder:(NSCoder*)coder {
[superencodeRestorableStateWithCoder:coder];
[coder encodeInt:self.numberforKey:MyViewControllerNumber];
}
- (void)decodeRestorableStateWithCoder:(NSCoder*)coder {
[superdecodeRestorableStateWithCoder:coder];
self.number = [coderdecodeIntForKey:MyViewControllerNumber];
}
编码对象在编码和解码过程中不共享。每个具有可保护状态的对象都可以接收它自己的编码器,它可以用来读取或写入数据。使用独特的编码器意味着您不必担心自己的对象之间的关键命名空间冲突。但是,您仍然必须避免使用UIKit提供的一些特殊键名称。具体来说,每个编码器都包含UIApplicationStateRateorationBundleVersionKey和UIApplicationStateRestorationUserInterfaceIdiomKey键,它们提供有关bundle版本和当前用户界面成语的信息。与视图控制器相关联的编辑器还可以包含UIStateRestorationViewControllerStoryboardKey键,它标识该视图控制器来源的故事板。
有关为视图控制器实现编码和解码方法的更多信息,请参阅UIViewController类参考。UIViewController Class Reference.
显示你的视图状态
如果视图具有值得保留的状态信息,则可以将该状态与应用程序的其他视图控制器保存在一起。因为它们通常由他们拥有的视图控制器配置,所以大多数视图不需要保存状态信息。您唯一需要保存视图状态的时间是视图本身可以由用户以独立于其数据或拥有视图控制器的方式进行更改。例如,滚动视图保存当前滚动位置,这是视图控制器不感兴趣的信息,但这影响了视图呈现的方式。
要指定视图的状态应该保存,请执行以下操作:
为视图的restoreIdentifier属性分配一个有效的字符串。
使用视图控制器的视图也具有有效的恢复标识符。
对于表视图和集合视图,分配一个采用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 {
[superencodeRestorableStateWithCoder:coder];
NSRange range = [selfselectionRange];
[coder encodeInt:range.lengthforKey:kMyTextViewSelectionRangeLength];
[coder encodeInt:range.locationforKey:kMyTextViewSelectionRangeLocation];
}
// Restore the text selection.
- (void) decodeRestorableStateWithCoder:(NSCoder*)coder {
[superdecodeRestorableStateWithCoder:coder];
if ([coder containsValueForKey:kMyTextViewSelectionRangeLength]&&
[codercontainsValueForKey:kMyTextViewSelectionRangeLocation]) {
NSRange range;
range.length = [coderdecodeIntForKey:kMyTextViewSelectionRangeLength];
range.location = [coderdecodeIntForKey:kMyTextViewSelectionRangeLocation];
if (range.length > 0)
[selfsetSelectionRange:range];
}
}
实施易于维护的数据源
因为表或集合视图显示的数据可以更改,所以只有当它们的数据源实现了UIDataSourceModelAssociation协议时,两个类都保存有关当前选择和可见单元的信息。此协议为表或集合视图提供了一种识别其所包含内容的方式,而不依赖该内容的索引路径。因此,无论数据源在下一个启动周期中放置项目的位置,该视图仍然具有查找该项目所需的所有信息。
为了成功实现UIDataSourceModelAssociation协议,您的数据源对象必须能够识别应用程序后续启动时的项目。这意味着您设计的任何识别方案对于给定的数据必须是不变的。这是至关重要的,因为数据源必须能够在每次请求时为相同标识符检索同一条数据。实现协议本身是从数据项映射到其唯一ID并再次返回的问题。
使用Core Data的应用程序可以通过利用对象标识符来实现协议。 Core Data存储中的每个对象都有一个唯一的对象标识符,可以将其转换为URI,并用于稍后查找对象。如果您的应用程序不使用CoreData,则如果要支持视图的状态保留,则需要设计自己的唯一标识符形式。
注意:请记住,实现UIDataSourceModelAssociation协议只需要保留属性,例如表或集合视图中的当前选择。此协议不用于保留由数据源管理的实际数据。您的应用程序有责任确保其数据在适当的时间保存。
保护您的应用程序的高级别状态
除了由应用程序的视图控制器和视图保留的数据之外,UIKit还提供了钩子来保存应用程序所需的任何其他数据。具体来说,UIApplicationDelegate协议包括以下方法来覆盖:
应用:willEncodeRestorableStateWithCoder:
应用:didDecodeRestorableStateWithCoder:
如果您的应用程序不包含在视图控制器中的状态,但需要保留的状态,则可以使用上述方法保存并还原它。应用程序:willEncodeRestorableStateWithCoder:方法在保存过程的开始时被调用,以便您可以写出任何高级应用程序状态,如当前版本的用户界面。应用程序:didDecodeRestorableStateWithCoder:方法在恢复状态结束时调用,以便您可以解码任何数据并执行应用程序所需的最终清理。
保存和恢复状态信息的技巧
当您为您的应用程序添加对状态保存和恢复的支持时,请考虑以下准则:
编辑版本信息以及您应用程序的其余状态。在保存过程中,建议您编码用于标识应用程序用户界面当前版本的版本字符串或数字。您可以在应用程序中编码此状态:willEncodeRestorableStateWithCoder:应用程序委托的方法。当您的应用程序委托应用程序:shouldRestoreApplicationState:方法被调用时,您可以从提供的编码器检索该信息,并使用它来确定状态是否可以保存。
不要在您的应用程式的状态中包含数据模型中的对象。应用程序应继续将其数据单独保存在iCloud或磁盘上的本地文件中。切勿使用状态恢复机制来保存数据。如果在恢复操作期间出现问题,则可以删除保留的接口数据。因此,您写入磁盘的任何保存相关数据都应被视为可清除的。
状态保存系统期望您以设计使用方式使用视图控制器。视图控制器层次结构通过视图控制器包含的组合和通过从另一个呈现一个视图控制器来创建。如果您的应用程序通过其他方式显示视图控制器的视图,例如,通过将其添加到另一个视图中,而不在相应的视图控制器之间创建一个包含关系,则保存系统将无法找到您的视图控制器来保留视图控制器。
请记住,您可能不想保留所有视图控制器。在某些情况下,保留视图控制器可能没有意义。例如,如果用户在显示视图控制器以更改用户密码时离开了您的应用程序,则可能需要取消操作并将应用程序还原到上一屏幕。在这种情况下,您不会保留要求输入新密码信息的视图控制器。
避免在恢复过程中交换视图控制器类。状态保存系统编码它保留的视图控制器的类。在恢复期间,如果您的应用程序返回其类与主对象不匹配(或不是子类)的对象,则系统不会要求视图控制器解码任何状态信息。因此,将旧视图控制器交换出完全不同的视图控制器不会恢复对象的完整状态。
当用户强制退出应用程序时,系统会自动删除应用程序的保留状态。当应用程序被杀死时,删除保留的状态信息是一种安全预防措施。(为了安全起见,如果应用程序在启动期间崩溃了两次,系统也会删除保留的状态。)如果要测试应用程序恢复其状态的能力,则不要在调试过程中使用多任务栏来杀死应用程序。相反,使用Xcode杀死应用程序或通过安装临时命令或手势需调用退出方式以程序方式杀死应用程序。
开发VoIP应用程序的技巧
互联网语音协议(VoIP)应用允许用户使用因特网连接而不是设备的蜂窝服务进行电话呼叫。在iOS8及更高版本中,您可以使用Apple Push Notification服务(APN)和PushKit框架的API来创建VoIP应用程序。依靠推送通知启用VoIP功能意味着您的应用程序不必维护与相关服务的持续网络连接或配置用于VoIP使用的套接字。当VoIP推送通知到达时,您的应用程序有时间处理通知,即使该应用程序目前已被终止。
注意:VoIP推送通知仅发送到运行iOS 8或更高版本的设备。如果您需要支持运行早期版本的iOS的设备,那么您需要维护兼容的实现。
与任何背景音频应用程序一样,VoIP应用程序的音频会话必须正确配置,以确保应用程序与其他基于音频的应用程序平滑运行。因为VoIP应用程序的音频播放和记录不会一直使用,所以特别重要的是,只有在需要时才能创建和配置应用程序的音频会话对象。例如,您将创建音频会话以通知用户来电或用户实际在通话中。一旦通话结束,您将删除对音频会话的强引用,并为其他音频应用程序提供播放音频的机会。
有关如何配置和管理VoIP应用程序的音频会话的信息,请参阅音频会话编程指南Audio Session Programming Guide。要了解有关使用VoIP推送通知和PushKitAPI创建VoIP应用程序的更多信息,请参阅iOS Apps的“能效指南”。Energy Efficiency Guide for iOS Apps
实施VoIP应用程序有以下几个要求:
为您的应用启用IP语音背景模式。 (因为VoIP应用程序涉及音频内容,建议您还启用Audio和AirPlay背景模式。)您可以在Xcode项目的Capabilities选项卡中启用后台模式。
使用PushKit API注册接收VoIP推送通知并处理传入的通知。
配置您的音频会话以处理到和使用的转换。
为了确保iPhone上更好的用户体验,请使用核心电话框架来调整与基于单元的电话有关的行为;请参阅核心电话框架参考。Core Telephony Framework Reference
为了确保您的VoIP应用程序的良好性能,请使用“系统配置”框架来检测网络更改,并允许您的应用程序尽可能睡眠。
请求VoIP服务证书以允许您的通知服务器连接到VoIP服务。
启用VoIP背景模式让系统知道它应该允许应用程序在后台运行以管理其网络套接字。此键还允许您的应用程序播放背景音频(尽管仍然鼓励音频和播放模式)。支持此模式的应用程序在系统启动后立即重新启动,以确保VoIP服务始终可用。
使用可达性接口来改善用户体验
由于VoIP应用程序严重依赖于网络,因此应使用“系统配置”框架的可达性界面来跟踪网络可用性并相应地调整其行为。可达性接口允许在网络条件变化时通知应用程序。例如,当网络变得不可用时,VoIP应用可以关闭其网络连接,并在再次可用时重新创建它们。该应用程序还可以使用这些更改来保持用户了解VoIP连接的状态。
要使用可达性界面,您必须在框架中注册一个回调函数,并使用它来跟踪更改。 注册回调函数:
为目标远程主机创建SCNetworkReachabilityRef结构。
为您的结构(使用SCNetworkReachabilitySetCallback函数)分配回调函数,以处理目标可达性状态的更改。
使用SCNetworkReachabilityScheduleWithRunLoop函数将该目标添加到应用程序的活动运行循环(如主运行循环)。
根据网络的可用性调整应用的行为也有助于提高底层设备的电池寿命。 让系统跟踪网络更改意味着您的应用程序可以让自己更频繁地休息。
有关可达性接口的更多信息,请参阅系统配置框架参考。System Configuration Framework Reference