APP编程指南 (五) —— 实现特定应用程序功能的策略(一)

版本记录

版本号 时间
V1.0 2018.06.01

前言

我们在做一个APP时候需要注意哪些方面呢,接下来我们就看一下APP编程指南。里面有些可能大家在平时编程中都经历过,但是再系统的了解下也不是坏事。感兴趣的可以看上面写的几篇。
1. APP编程指南 (一) —— 基本概览(一)
2. APP编程指南 (二) —— 应用程序必须实现的行为(一)
3. APP编程指南 (三) —— 应用程序的后台执行(一)
4. APP编程指南 (四) —— 处理应用程序状态转换的策略(一)

Strategies for Implementing Specific App Features - 实现特定应用程序功能的策略

不同的应用程序有不同的需求,但某些行为对于许多类型的应用程序都很常见。以下各节提供了有关如何在您的应用中实现特定类型的功能的指导。


Privacy Strategies - 隐私策略

保护用户的隐私是设计应用程序的重要考虑因素。 隐私保护包括保护用户的数据,包括用户的身份和个人信息。 系统框架已经提供了用于管理联系人等数据的隐私控制,但是您的应用程序应该采取措施来保护您在本地使用的数据。

1. Protecting Data Using On-Disk Encryption - 使用磁盘加密保护数据

数据保护使用内置硬件将文件以加密格式存储在磁盘上并按需解密。当用户的设备被锁定时,受保护的文件无法访问,甚至创建它们的APP也无法访问。在应用程序可以访问其中一个受保护的文件之前,用户必须解锁该设备(通过输入适当的密码)。

数据保护在大多数iOS设备上可用,并且需要满足以下要求:

  • 用户设备上的文件系统必须支持数据保护。大多数设备支持这种行为。
  • 用户必须为设备设置有效的密码锁定。

为了保护文件,您需要向文件中添加一个属性,指出所需的保护级别。使用NSData类或NSFileManager类添加此属性。在编写新文件时,可以使用NSDatawriteToFile:options:error:方法,并将相应的保护值作为其中一个写入选项。对于现有文件,可以使用NSFileManagersetAttributes:ofItemAtPath:error:方法来设置或更改NSFileProtectionKey的值。使用这些方法时,请为文件指定以下保护级别之一:

如果您保护文件,则您的应用必须准备好无法访问该文件。启用完整文件保护时,当用户锁定设备时,应用程序将失去读取和写入文件内容的能力。您可以使用以下技术之一跟踪受保护文件状态的更改:

对于新文件,建议您在写入任何数据之前启用数据保护。如果使用writeToFile:options:error:方法将NSData对象的内容写入磁盘,则会自动发生。对于现有文件,添加数据保护会使用新的受保护版本替换未受保护的文件。

2. Identifying Unique Users of Your App - 识别您的应用程序的唯一用户

您应该识别应用的用户,只有在这样做时才为该用户提供明显的好处。 如果您只需要将应用程序的一个用户与另一个用户区分开来,则iOS会提供可帮助您完成此操作的标识符。 但是,如果您需要更高级别的安全性,则可能需要自行完成更多工作。 例如,提供金融服务的应用程序可能需要提示用户输入登录凭据,以确保用户有权访问特定帐户。

重要提示:识别用户时,请始终保持透明,了解您打算如何处理您获得的任何信息。 识别用户以便您可以秘密跟踪他们是不可接受的。

以下是可能需要您标识用户的一些常见情况,以及如何实施它们的解决方案。

  • You want to link a user to a specific account on your server - 您想要将用户链接到服务器上的特定帐户

    • 包括一个登录屏幕,要求用户安全地输入他们的帐户信息。始终保护您通过以加密形式存储从用户收集的帐户信息。
  • You want to differentiate instances of your app running on different devices - 您想区分在不同设备上运行的应用的实例

    • 使用UIDevice类的identifierForVendor属性可以获得一个ID,该ID将一个设备上的用户与其他设备上的用户区分开来。这种技术现在可以让你识别特定的用户。一个用户可以有多个设备,每个设备都有不同的ID值。
  • You want to identify a user for the purposes of advertising - 您想为广告目的识别用户

由于用户可以在所有iOS设备上运行App,因此Apple不提供在多个设备上识别同一用户的方法。如果您需要识别特定用户,则必须使用通用唯一ID(UUID),登录帐户或某种其他类型的识别系统提供您自己的解决方案。


Respecting Restrictions - 关于限制

用户可以设置限制,指定他们想要在应用中使用的媒体的评分。 如果您的应用根据限制播放媒体或修改其行为,则需要确定当前设置并在设置更改时作出响应。

要获取当前设置,请获取共享的standardUserDefaults对象,并使用objectForKey:方法查看以下键的值:

Media rating key Value
com.apple.content-rating.ExplicitBooksAllowed Boolean。 如果此键的值为NO,则不允许explicit books
com.apple.content-rating.ExplicitMusicPodcastsAllowed Boolean,如果此键的值为NO,则不允许显式音乐,电影和播客。
com.apple.content-rating.AppRating NSNumber。此键的值范围为0到1000,不允许评级高于当前键值的应用程序。
com.apple.content-rating.MovieRating NSNumber。此键的数值范围为0到1000,不允许评级高于当前键值的电影。
com.apple.content-rating.TVShowRating NSNumber。此键的数值范围为0到1000,不允许评级高于当前键值的电视节目。

注意:如果objectForKey:对于特定键返回nil,则表示关于此特定限制的信息不可用。 在这种情况下,您的应用可以使用自己的政策来确定适当的评分。

要检测用户何时更改限制,请注册NSUserDefaultsDidChangeNotification通知。 共享standardUserDefaults对象在检测到位于其中一个持久域中的偏好设置发生变化时将此通知发送给您的应用程序。

应用评分是为美国国家代码定义的,并且通用。 表5-1显示了与每个美国应用评级相关的值

Table 5-1 App ratings

Rating name Numerical value
4+ 100
9+ 200
12+ 300
17+ 600

电影和电视的评分因国家而异。 如果某个国家或地区未指定电影或电视节目的评分系统,则应用应使用自己的政策来确定适当的评分。 虽然大部分地区都定义电影评级,但只有少数定义电视节目评级。

区域可以定义多个评级级别,每个评级级别都与描述评级的名称以及范围在0到1000之间的数字相关联。例如,美国使用字符串“G”和数字100来指定最低电影评级等级。

即使您的应用不播放媒体,您也可能希望将自己的评分系统映射到电影或电视节目评级系统。 例如,游戏可能仅在美国电影评级“R”被允许时才启用某些功能。 要查看当前评分列表,请下载本文档的配套文件(链接位于页面顶部附近)。


Supporting Multiple Versions of iOS - 支持多种版本的iOS

支持最新版本的iOS以及一个或多个较早版本的应用程序必须使用运行时检查,以防止在较早版本的iOS上使用较新的API。 运行时检查可防止您的应用在尝试使用当前操作系统上不可用的功能时崩溃。

您可以进行几种类型的检查:

  • 要确定某个类是否存在,请查看它的Class对象是否为nil。 对于任何未知的类对象,链接器都返回nil,从而可以使用类似于以下内容的条件检查:
if ([UIPrintInteractionController class]) {
   // Create an instance of the class and use it.
}
else {
   // The print interaction controller is not available so use an alternative technique.
}
  • 要确定某个方法是否可用于现有类,请使用instancesRespondToSelector:class方法或respondsToSelector:instance方法。

  • 要确定基于C的函数是否可用,请将函数名称与NULL进行比较。 如果符号不是NULL,则可以调用该函数。 例如:

if (UIGraphicsBeginPDFPage != NULL) {
    UIGraphicsBeginPDFPage();
}

有关如何编写支持多个部署目标的代码的更多信息和示例,请参阅SDK Compatibility Guide


Preserving Your App’s Visual Appearance Across Launches - 在启动时保留应用程序的外观

即使您的应用程序支持后台执行,它也无法永久运行。在某些时候,系统可能需要终止您的应用程序以释放当前前台应用程序的内存。但是,用户不应该在意应用程序是否已经运行或被终止。从用户的角度来看,退出应用程序应该看起来像是暂时的中断。当用户返回到应用程序时,该应用程序应始终将用户返回到最后一个使用点,以便用户可以继续执行正在进行的任何任务。这种行为为用户提供了更好的体验,并且内置到UIKit中的状态恢复支持相对容易实现。

UIKit中的状态保存系统提供了一个简单但灵活的基础结构,用于保存和恢复应用视图控制器和视图的状态。基础工作是在适当的时候推动保存和恢复过程。要做到这一点,UIKit需要你的应用程序的帮助。只有你了解你的应用的内容,所以只有你可以编写保存和恢复该内容所需的代码。而当你更新应用程序的用户界面时,只有你知道如何将保存较旧的内容映射到界面中较新的对象。

有三个地方你必须考虑在你的应用中保存状态:

  • 您的应用程序委托对象,用于管理应用程序的顶级状态
  • 您应用的视图控制器对象,用于管理应用用户界面的整体状态
  • 您的应用的自定义视图,可能有一些需要保留的自定义数据

UIKit允许您选择要保留的用户界面的哪些部分。如果您已经有处理状态保存的自定义代码,您可以继续使用该代码,并根据需要将部分迁移到UIKit状态保存系统。

1. Enabling State Preservation and Restoration in Your App - 在您的应用程序中启用状态保存和恢复

状态保存和恢复不是自动功能,应用程序必须选择使用它。 应用程序通过在其应用程序委托中实现以下方法来表明他们对该功能的支持:

通常情况下,这些方法的实现只是返回YES以指示状态保存和恢复可能发生。 但是,如果应用程序有条件地保留并恢复其状态,则在操作不应发生的情况下可以返回NO。 例如,向应用程序发布更新后,如果您的应用程序无法有效恢复以前版本的状态,则可能需要从application:shouldRestoreApplicationState:方法返回NO:shouldRestoreApplicationState:方法。

2. The Preservation and Restoration Process - 保存和恢复过程

状态保护和恢复是一个选择加入功能,并需要您的应用程序的帮助。您的应用程序会标识应该保留的对象,UIKit会在适当的时候执行保留和恢复这些对象的工作。因为UIKit处理这么多的过程,所以它有助于理解它在幕后做了什么,以便您了解自定义代码如何适应整体方案。

在考虑状态保护和恢复时,首先分离这两个过程是有好处的。 UIKit会在适当的时候保留您的应用程序的状态,例如您的应用程序从前台移动到后台时。当UIKit确定需要新的状态信息时,它会查看应用程序的视图和视图控制器以查看应保留哪些信息。对于这些对象中的每一个,UIKit都会将保存相关数据写入加密的磁盘文件。下次您的应用程序从头开始启动时,UIKit会查找该文件,如果该文件存在,则使用该文件尝试并恢复应用程序的状态。由于该文件是加密的,因此状态保存和恢复仅在设备解锁时才会发生。

在恢复过程中,UIKit使用保留的数据重新构建界面,但创建实际对象由你的代码处理。由于您的应用程序可能会自动从storyboard文件加载对象,因此只有您的代码知道需要创建哪些对象以及哪些对象可能已经存在并且可以简单地返回。在创建每个对象后,UIKit使用保存的状态信息初始化它们。

在保存和恢复过程中,您的应用程序有一些责任。

  • 在保存期间,您的应用程序负责:

    • 告诉UIKit它支持状态保存。
    • 告诉UIKit应该保留哪些视图控制器和视图。
    • 编码任何保存对象的相关数据。
  • 在恢复期间,您的应用程序负责:

    • 告诉UIKit它支持状态恢复。
    • 提供(或创建)UIKit请求的对象。
    • 解码您保存的对象的状态并使用它将对象返回到之前的状态。

在您的应用程序的负责内容中,最重要的是告诉UIKit在后续启动期间保留哪些对象并提供这些对象。这两种行为是设计应用程序保存和恢复代码时应花费大部分时间的地方。他们也是您对实际流程拥有最大控制权的地方。要理解为什么是这样,看一个例子可能有助于你的理解。

图5-1显示了用户与多个选项卡交互后,tabbar界面的视图控制器层次结构。正如你所看到的,一些视图控制器作为应用程序的主要故事板文件的一部分被自动加载,但是一些视图控制器被呈现或推送到不同选项卡中的视图控制器上。在没有状态恢复的情况下,只有主故事板文件中的视图控制器才会在后续启动过程中恢复。通过向应用程序添加对状态还原的支持,可以保留所有视图控制器。

Figure 5-1 A sample view controller hierarchy

UIKit仅保留具有已分配的恢复标识符的对象。 恢复标识符restoration identifier是一个字符串,用于标识UIKit和您的应用的视图或视图控制器。 此字符串的值仅对您的代码有意义,但此字符串的存在会告知UIKit它需要保留标记的对象。 在保存过程中,UIKit将遍历应用的视图控制器层次结构,并保留具有恢复标识符的所有对象。 如果视图控制器没有恢复标识符,则不保留该视图控制器及其所有视图和子视图控制器。 图5-2显示了先前视图层次结构的更新版本,现在使用适用于大多数(但不是全部)视图控制器的恢复标识。

Figure 5-2 Adding restoration identifies to view controllers

根据您的应用程序,保留每个视图控制器可能有意义也可能没有。如果视图控制器显示暂时信息,则可能不想在还原时返回到同一点,而是选择将用户返回到界面中更稳定的点。

对于您选择保留的每个视图控制器,还需要决定以后如何恢复它。 UIKit提供了两种重新创建对象的方法。您可以让应用程序委托重新创建它,也可以将恢复类分配给视图控制器,并让该类重新创建它。恢复类实现UIViewControllerRestoration协议,并负责在还原时查找或创建指定对象。以下是关于何时使用它们的一些提示:

  • If the view controller is always loaded from your app’s main storyboard file at launch time, do not assign a restoration class - 如果视图控制器始终在启动时从应用程序的主要storyboard文件加载,请勿分配恢复类

    • 相反,让您的应用程序委托查找对象或利用UIKit的支持隐式查找已恢复的对象。
  • For view controllers that are not loaded from your main storyboard file at launch time, assign a restoration class - 对于在启动时未从主要故事板文件加载的视图控制器,请分配恢复类

    • 最简单的选择是让每个视图控制器都有自己的恢复类。

在保存过程中,UIKit识别要保存的对象,并将每个受影响对象的状态写入磁盘。每个视图控制器对象都有机会写出想要保存的数据。例如,tab view controller保存选定的tab的标识。 UIKit还将诸如视图控制器的恢复类的信息保存到磁盘。如果任何视图控制器的视图具有恢复标识符,则UIKit会要求他们保存其状态信息。

下次启动应用程序时,UIKit像往常一样加载应用程序的主要storyboard或Nib文件,调用应用程序委托的应用程序:application:willFinishLaunchingWithOptions:方法,然后尝试恢复应用程序的先前状态。它所做的第一件事就是要求您的应用程序提供与保存的视图控制器对象相匹配的一组视图控制器对象。如果给定的视图控制器有一个指定的恢复类,则要求该类提供该对象;否则,应用程序代理被要求提供它。

(1) Flow of the Preservation Process - 保存过程的流程

图5-3显示了状态保存期间发生的高级事件,并显示了应用程序的对象如何受到影响。 甚至在发生保存之前,UIKit会通过调用application:shouldSaveApplicationState:方法来请求应用程序代理。 如果该方法返回YES,UIKit将开始收集和编码应用程序的视图和视图控制器。 完成后,它将编码数据写入磁盘。

下次启动应用程序时,系统会自动查找保存的状态文件,如果存在,则使用它来恢复您的界面。 由于此状态信息只与应用的以前和当前启动周期相关,因此通常会在您的应用完成启动后放弃该文件。 该文件在任何时候恢复您的应用程序出现错误时也会被丢弃。 例如,如果您的应用程序在恢复过程中崩溃,则系统会在下次启动周期中自动丢弃状态信息,以避免再次发生崩溃。

(2) Flow of the Restoration Process - 恢复过程的流程

图5-4显示了状态恢复期间发生的高级事件,并显示了应用程序的对象如何受到影响。 标准初始化和UI加载完成后,UIKit会询问您的应用程序代理通过调用application:shouldRestoreApplicationState:方法判断是否应该执行还原状态。 这是您的应用程序代理检查保存的数据并确定状态恢复是否可行的机会。 如果可以,UIKit使用应用程序委托和恢复类来获取对应用程序视图控制器的引用。 然后为每个对象提供它自己恢复到之前状态所需的数据。

Figure 5-4 High-level flow for restoring your user interface

尽管UIKit有助于恢复单个视图控制器,但它不会自动恢复这些视图控制器之间的关系。相反,每个视图控制器负责对足够的状态信息进行编码以使其自身恢复到之前的状态。例如,导航控制器对其导航堆栈上的视图控制器的顺序信息进行编码。然后它会使用这些信息将这些视图控制器返回到它们在堆栈上的先前位置。其他具有嵌入子视图控制器的视图控制器同样负责编码他们以后需要恢复子视图的任何信息。

注意:并非所有视图控制器都需要对其子视图控制器进行编码。例如,tabbar控制器不会编码有关其子视图控制器的信息。相反,假设您的应用程序遵循创建tabbar控制器本身之前创建适当的子视图控制器的通常模式。

由于您有责任重新创建应用的视图控制器,因此在恢复过程中可以灵活地更改界面。例如,您可以重新排列tabbar控制器中的tab,并仍然使用保留的数据将每个选项卡返回到其先前的状态。当然,如果您对视图控制器层次结构进行重大更改(例如在应用程序更新期间),则可能无法使用保存的数据。

3. What Happens When You Exclude Groups of View Controllers? - 排除视图控制器组时发生了什么?

当视图控制器的恢复标识符为nil时,该视图控制器及其管理的任何子视图控制器都不会自动保留。 例如,在图5-5中,因为导航控制器没有恢复标识符,所以它的所有子视图控制器和视图都从保存的数据中省略。

Figure 5-5 Excluding view controllers from the automatic preservation process

即使您决定不保留视图控制器,也并不意味着所有这些视图控制器都会从视图层次结构中完全消失。 在启动时,您的应用程序可能仍会创建视图控制器作为其默认设置的一部分。 例如,如果任何视图控制器是从您的应用程序的storyboard文件自动加载的,它们仍然会出现,尽管处于它们的默认配置中,如图5-6所示。

Figure 5-6 Loading the default set of view controllers

还有一点需要认识的是,即使视图控制器不能自动保存,仍然可以编码对视图控制器的引用并手动保存。 在图5-5中,第一个导航控制器的三个子视图控制器具有恢复标识符,即使父导航控制器没有。 如果您的应用程序代理(或任何保留的对象)对这些视图控制器的引用进行编码,则其状态将保留。 即使他们在导航控制器中的顺序没有保存,仍然可以使用这些引用重新创建视图控制器,并在后续的启动周期中将其安装在导航控制器中。

4. Checklist for Implementing State Preservation and Restoration - 实施状态保存和恢复的清单

支持状态保存和恢复需要修改您的应用程序代理和视图控制器对象以对状态信息进行编码和解码。如果您的应用程序具有任何也具有可保存状态信息的自定义视图,则还需要修改这些对象。

在为代码添加状态保存和恢复时,使用以下列表来提醒您需要编写的代码。

5. Enabling State Preservation and Restoration in Your App - 在您的应用程序中启用状态保存和恢复

状态保存和恢复不是自动功能,应用程序必须选择使用它。 应用程序通过在其应用程序委托中实现以下方法来表明他们对该功能的支持:

通常情况下,这些方法的实现只是返回YES以指示状态保存和恢复可能发生。 但是,如果应用程序有条件地保留并恢复其状态,则在操作不应发生的情况下可以返回NO。 例如,向应用程序发布更新后,如果您的应用程序无法有效恢复以前版本的状态,则可能需要从application:shouldRestoreApplicationState:方法返回NO。

6. Preserving the State of Your View Controllers - 保持您的视图控制器的状态

保持你的应用视图控制器的状态应该是你的主要目标。 视图控制器定义您的用户界面的结构。 他们管理呈现该界面所需的视图,并协调获取和设置支持这些视图的数据。 要保留单个视图控制器的状态,您必须执行以下操作:

(1) Marking Your View Controllers for Preservation - 标记您的视图控制器用来保存

UIKit仅保留其restorationIdentifier属性包含有效字符串对象的视图控制器。对于您知道要保留的视图控制器,请在初始化视图控制器对象时设置此属性的值。如果从故事板或nib文件加载视图控制器,则可以在其中设置恢复标识。

为恢复标识符选择适当的值非常重要。在恢复过程中,您的代码使用恢复标识符来确定要检索或创建哪个视图控制器。如果每个视图控制器对象都基于不同的类,则可以使用类名作为恢复标识符。但是,如果您的视图控制器层次结构包含同一类的多个实例,则可能需要根据每个视图用法选择不同的名称。

当它要求您提供视图控制器时,UIKit会为您提供视图控制器对象的恢复路径。恢复路径是从根视图控制器开始并沿视图控制器分层结构走向当前对象的恢复标识符序列。例如,假设您有一个tab bar控制器,其恢复标识符为TabBarControllerID,第一个标签包含一个导航控制器,其标识符为NavControllerID,并且其根视图控制器的标识符为MyViewController。根视图控制器的完整恢复路径将为TabBarControllerID / NavControllerID / MyViewController

每个对象的恢复路径必须是唯一的。如果视图控制器有两个子视图控制器,则每个子视图都必须具有不同的恢复标识。但是,具有不同父对象的两个视图控制器可以使用相同的恢复标识符,因为恢复路径的其余部分提供了所需的唯一性。某些UIKit视图控制器(例如导航控制器)会自动消除其子视图控制器的歧义,使您可以为每个孩子使用相同的恢复标识符。有关给定视图控制器行为的更多信息,请参阅相应的类参考。

恢复时,您使用提供的恢复路径来确定要返回到UIKit的视图控制器。有关如何使用恢复标识符和恢复路径来恢复视图控制器的更多信息,请参阅Restoring Your View Controllers at Launch Time

(2) Restoring Your View Controllers at Launch Time - 在启动时恢复视图控制器

在恢复过程中,UIKit会要求您的应用创建(或定位)包含您保留的用户界面的视图控制器对象。尝试定位视图控制器时,UIKit遵循以下过程:

  • If the view controller had a restoration class, UIKit asks that class to provide the view controller - 如果视图控制器具有恢复类,UIKit会要求该类提供视图控制器

  • If the view controller did not have a restoration class, UIKit asks the app delegate to provide the view controller - 如果视图控制器没有恢复类,UIKit会要求应用程序委托提供视图控制器

  • If a view controller with the correct restoration path already exists, UIKit uses that object - 如果已经存在具有正确还原路径的视图控制器,UIKit将使用该对象

    • 如果您的应用程序在启动时创建视图控制器(以编程方式或通过从资源文件加载它们)并为其分配恢复标识符,则UIKit会通过其恢复路径隐式找到它们。
  • If the view controller was originally loaded from a storyboard file, UIKit uses the saved storyboard information to locate and create it - 如果视图控制器最初是从故事板文件加载的,则UIKit使用保存的故事板信息来定位和创建它

    • UIKit将有关视图控制器故事板的信息保存在恢复归档中。在还原时,如果通过任何其他方式未找到视图控制器,它将使用该信息来定位相同的故事板文件并实例化相应的视图控制器。

值得注意的是,如果你为视图控制器指定了一个恢复类,UIKit不会隐式地查找你的视图控制器。如果恢复类的viewControllerWithRestorationIdentifierPath:coder:方法返回nil,则UIKit将停止尝试查找您的视图控制器。这使您可以控制是否真的想要创建视图控制器。如果不指定恢复类,UIKit会尽其所能为您查找视图控制器,并根据需要从应用程序的故事板文件中创建视图控制器。

如果您选择使用恢复类,则viewControllerWithRestorationIdentifierPath:coder:方法的实现应该创建该类的新实例,执行一些最小化初始化并返回结果对象。Listing 5-1显示了一个如何使用此方法从故事板加载视图控制器的示例。由于视图控制器最初是从故事板加载的,因此此方法使用UIStateRestorationViewControllerStoryboardKey键从归档中获取故事板。请注意,此方法不会尝试配置视图控制器的数据字段。当视图控制器的状态被解码时,该步骤稍后发生。

// Listing 5-1  Creating a new view controller during restoration

+ (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数组中的最后一项并将其分配给您的视图控制器。

对于在启动时已从应用程序的主要故事板文件加载的对象,请勿创建每个对象的新实例。 相反,实现您的应用程序代理application:viewControllerWithRestorationIdentifierPath:coder的方法,并使用它返回适当的对象,或让UIKit隐式地查找这些对象。

(3) Encoding and Decoding Your View Controller’s State - 编码和解码您的视图控制器的状态

对于要保存的每个对象,UIKit会调用对象的encodeRestorableStateWithCoder:方法,以使其有机会保存其状态。 在解码过程中,相应的调用decodeRestorableStateWithCoder:方法以解码该状态并将其应用于该对象。 这些方法的实现是可选的,但对于您的视图控制器推荐使用这两个方法。 您可以使用它们来保存和恢复以下类型的信息:

  • 对所显示的任何数据的引用(不是数据本身)
  • 对于容器视图控制器,引用其子视图控制器
  • 关于当前选择的信息
  • 对于具有用户可配置视图的视图控制器,提供关于该视图当前配置的信息。

在编码和解码方法中,您可以编码编码器支持的任何值,包括其他对象。 对于除视图和视图控制器以外的所有对象,对象必须采用NSCoding协议并使用该协议的方法来写入其状态。 对于视图和视图控制器,编码器不使用NSCoding协议的方法来保存对象的状态。 相反,编码器保存对象的恢复标识符并将其添加到可保存对象的列表中,这会导致调用该对象的encodeRestorableStateWithCoder:方法。

视图控制器的encodeRestorableStateWithCoder:decodeRestorableStateWithCoder:方法应该在其实现的某个时刻始终调用super。 调用super可以让父类有机会保存和恢复任何附加信息。 Listing 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提供的一些特殊键名称。 具体而言,每个编码器都包含UIApplicationStateRestorationBundleVersionKeyUIApplicationStateRestorationUserInterfaceIdiomKey键,它们提供有关bundle版本和当前用户界面idiom的信息。 与视图控制器相关联的编码器也可以包含UIStateRestorationViewControllerStoryboardKey键,该键用于标识storyboard,视图控制器就来自它。

有关为视图控制器实现编码和解码方法的更多信息,请参阅UIViewController Class Reference

7. Preserving the State of Your Views - 保持你的视图的状态

如果视图具有值得保留的状态信息,则可以使用应用程序的其余视图控制器保存该状态。由于它们通常由其拥有的视图控制器进行配置,因此大多数视图不需要保存状态信息。唯一需要保存视图状态的时间是视图本身可以由用户以独立于其数据或拥有视图控制器的方式进行更改。例如,scroll views保存当前的滚动位置,这是视图控制器不感兴趣的信息,但它影响视图呈现方式。

要指定应保存视图的状态,请执行以下操作:

  • 为视图的restorationIdentifier属性分配一个有效的字符串。

  • 使用也具有有效恢复标识符的视图控制器中的视图。

  • 对于table views and collection视图,请分配一个采用UIDataSourceModelAssociation协议的数据源。

与视图控制器一样,将恢复标识符分配给视图可告诉系统视图对象具有应用程序想要保存的状态。恢复标识符也可用于稍后查看视图。

与视图控制器类似,视图定义了编码和解码其自定义状态的方法。如果您创建一个值得保存状态的视图,则可以使用这些方法来读取和写入任何相关数据。

(1) UIKit Views with Preservable State - 具有可保存状态的UIKit视图

为了保存任何视图的状态,包括自定义和标准系统视图,您必须为视图分配一个恢复标识符。 没有恢复标识符的视图不会被UIKit添加到可保存的对象列表中。

以下UIKit视图具有可保留的状态信息:

其他框架也可能具有可维护状态的视图。 有关视图是否保存状态信息以及保存的状态的信息,请参阅相应类的参考。

(2) Preserving the State of a Custom View - 保持自定义视图的状态

如果要实现具有可恢复状态的自定义视图,请实现encodeRestorableStateWithCoder:decodeRestorableStateWithCoder:方法,并使用它们对该状态进行编码和解码。 使用这些方法仅保存不能通过其他方式轻松重新配置的数据。 例如,使用这些方法保存用户与视图交互所修改的数据。 请勿使用这些方法保存视图呈现的数据或拥有视图控制器可轻松配置的任何数据。

Listing 5-3显示了如何保留和恢复包含可编辑文本的自定义视图的选择的示例。 在该示例中,可以使用selectionRangesetSelectionRange:方法来访问范围,这些方法是视图用于管理选择的自定义方法。 编码数据只需要将其写入所提供的编码器对象。 恢复数据需要读取它并将其应用于视图。

Listing 5-3  Preserving the selection of a custom text view

// 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];
   }
}

(3) Implementing Preservation-Friendly Data Sources - 实施保存友好的数据源

因为table or collection view显示的数据可能会更改,所以只有当数据源实现UIDataSourceModelAssociation协议时,这两个类才会保存有关当前选择和可见单元格的信息。该协议为表或集合视图提供了一种方法,可以在不依赖该内容的索引路径的情况下识别其包含的内容。因此,无论数据源在下一个启动周期中放置项目的位置如何,视图仍然可以找到该项目所需的所有信息。

为了成功实现UIDataSourceModelAssociation协议,您的数据源对象必须能够识别应用程序后续启动之间的项目。这意味着您设计的任何识别方案对于给定的数据都必须是不变的。这是非常重要的,因为数据源必须能够在每次请求时为同一标识符检索相同的数据。实现协议本身就是将数据项映射到其唯一ID并再返回的问题。

使用Core Data的应用程序可以通过利用对象标识符来实现协议。Core Data存储中的每个对象都有唯一的对象标识符,可以将其转换为URI并用于稍后定位对象。如果您的应用程序不使用Core Data,则需要设计自己的唯一标识符形式,以便支持视图的状态保存。

注意:请记住,实现UIDataSourceModelAssociation协议仅用于保留属性,例如table or collection view中的当前选择。此协议不用于保存您的数据源管理的实际数据。您的应用程序有责任确保其数据在适当的时间保存。

8. Preserving Your App’s High-Level State - 保留您的应用程序的高级状态

除了应用视图控制器和视图保存的数据之外,UIKit还为您提供hooks以保存应用所需的任何杂项数据。 具体而言,UIApplicationDelegate协议包含以下用于重写的方法:

如果您的应用程序包含的状态不在视图控制器中,但需要保留,您可以使用上述方法来保存和恢复它。 application:willEncodeRestorableStateWithCoder:方法在保存过程开始时被调用,以便您可以写出任何高级应用程序状态,例如当前版本的用户界面。 application:didDecodeRestorableStateWithCoder:方法在恢复状态结束时调用,以便您可以解码任何数据并执行应用程序需要的任何最终清理。

9. Tips for Saving and Restoring State Information - 保存和恢复状态信息的提示

在为应用添加对状态保存和恢复的支持时,请考虑以下准则:

  • Encode version information along with the rest of your app’s state - 编码版本信息以及应用程序状态的其余部分

  • Do not include objects from your data model in your app’s state - 请勿在您的应用程序状态中包含数据模型中的对象

    • 应用程序应该继续将其数据分别存储在iCloud或磁盘上的本地文件中。切勿使用状态恢复机制来保存该数据。如果在还原操作期间出现问题,则可以删除保留的界面数据。因此,您写入磁盘的任何与保存相关的数据应视为可清除。
  • The state preservation system expects you to use view controllers in the ways they were designed to be used - 状态保护系统希望你以他们设计的方式使用视图控制器

    • 视图控制器层次结构是通过视图控制器遏制和从另一个视图控制器呈现一个视图控制器的组合创建的。如果您的应用程序通过其他方式显示视图控制器的视图(例如,将其添加到另一个视图而不在相应的视图控制器之间创建包含关系),保存系统将无法找到您的视图控制器来保留它。
  • Remember that you might not want to preserve all view controllers - 请记住,您可能不想保留所有视图控制器

    • 在某些情况下,保留视图控制器可能没有意义。例如,如果用户在显示视图控制器以更改用户密码时离开您的应用程序,则可能需要取消操作并将应用程序恢复到上一个​​屏幕。在这种情况下,您不会保留要求输入新密码信息的视图控制器。
  • Avoid swapping view controller classes during the restoration process - 避免在恢复过程中交换视图控制器类

    • 状态保存系统编码它保存的视图控制器的类。在恢复过程中,如果您的应用程序返回其类不匹配(或不是原始对象的子类)的对象,则系统不会要求视图控制器解码任何状态信息。因此,换出完全不同的旧视图控制器不会恢复对象的完整状态。
  • The system automatically deletes an app’s preserved state when the user force quits the app - 当用户强制退出应用程序时,系统会自动删除应用程序的保留状态

    • 在应用程序被杀死时删除保存的状态信息是一种安全预防措施。 (为了安全起见,如果应用程序在启动期间崩溃了两次,系统也会删除保留状态。)如果您想测试应用程序恢复其状态的能力,则不应在调试期间使用多任务栏来终止应用程序。相反,使用Xcode杀死应用程序或通过安装临时命令或手势来按需要调用exit来以编程方式终止应用程序。

Tips for Developing a VoIP App - 开发VoIP应用程序的技巧

通过Voice over Internet Protocol (VoIP)应用程序,用户可以使用互联网连接而不是设备的手机服务拨打电话。在iOS 8和更高版本中,您可以使用Apple Push Notification服务(APN)PushKit框架的API来创建VoIP应用程序。依靠推送通知来启用VoIP功能意味着您的应用程序无需与相关服务保持持久的网络连接,也无需为VoIP使用配置套接字。当VoIP推送通知到达时,即使应用程序当前已终止,您的应用程序也有时间处理通知。

注意:VoIP推送通知仅发送到运行iOS 8或更高版本的设备。如果您需要支持运行早期版本iOS的设备,则需要负责维护兼容的实现。

与任何后台音频应用程序一样,VoIP应用程序的音频会话必须正确配置才能确保该应用程序与其他基于音频的应用程序顺畅协作。由于VoIP应用程序的音频回放和录制并非始终使用,因此仅在需要时才创建和配置应用程序的音频会话对象,这一点尤为重要。例如,您将创建音频会话以通知用户来电或用户实际正在通话。一旦通话结束,您将删除对音频会话的强引用,并让其他音频应用程序有机会播放其音频。

有关如何配置和管理VoIP应用的音频会话的信息,请参阅Audio Session Programming Guide。要了解有关使用VoIP推送通知和PushKit API创建VoIP应用程序的更多信息,请参阅Energy Efficiency Guide for iOS Apps

实施VoIP应用有几个要求:

  • 为您的应用启用Voice over IP后台模式。 (因为VoIP应用程序涉及音频内容,建议您还启用AudioAirPlay后台模式。)在Xcode项目的Capabilities选项卡中启用后台模式。
  • 使用PushKit API注册以接收VoIP推送通知并处理传入的通知。
  • 配置您的音频会话以处理往返使用的转换。
  • 为了确保在iPhone上获得更好的用户体验,请使用Core Telephony框架来调整与基于蜂窝电话呼叫相关的行为,请参阅 Core Telephony Framework Reference
  • 为了确保您的VoIP应用程序具有良好的性能,请使用System Configuration框架检测网络更改并尽可能让您的应用程序进入睡眠状态。
  • 请求VoIP服务证书以允许您的通知服务器连接到VoIP服务。

启用VoIP后台模式让系统知道它应该允许应用程序根据需要在后台运行以管理其网络套接字。此键还允许您的应用播放后台音频(尽管仍然鼓励启用AudioAirPlay模式)。支持此模式的应用程序在系统引导后立即在后台重新启动,以确保VoIP服务始终可用。

1. Using the Reachability Interfaces to Improve the User Experience - 使用可达性接口改善用户体验

由于VoIP应用程序严重依赖网络,因此应使用System Configuration框架的可访问性接口来跟踪网络可用性并相应地调整其行为。可达性接口允许在网络状况发生变化时通知应用程序。例如,一个VoIP应用程序可能会在网络不可用时关闭其网络连接,并在再次变为可用时重新创建它们。该应用程序还可以使用这些类型的更改来让用户了解VoIP连接的状态。

要使用可访问性接口,您必须在框架中注册回调函数并使用它来跟踪更改。要注册一个回调函数:

根据网络的可用性调整应用程序的行为也有助于提高基础设备的电池寿命。让系统跟踪网络变化意味着您的应用可以让自己更频繁地进入睡眠状态。

有关可达性接口的更多信息,请参阅System Configuration Framework Reference

后记

本篇主要介绍了实现特定应用程序功能的策略,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容