这是阅读该指南的一些笔记,说说是笔记,其实就是把一些自己决定重要的知识给翻译了一遍,因为英文读着读着就把前面的给忘了。。。所以就打算记录一点。该文章会慢慢更新,这段时间打算将几篇重要的指南都重新看一遍,每天都会更新这些笔记,英语不大好,可能有翻译错误的地方,请大家指出,谢谢!
APP状态
在任何时间上,你的APP只有一个状态,看表Table 2-3.系统切换APP状态来响应系统事件.举例,当用户按下Home键,一个电话打来,或者其他中断的行为,当前运行的APP改变状态来响应.Figure 2-3 显示一个APP改变状态的路径.
状态 | 描述 |
---|---|
没有运行 | 该APP没有运行或者正在运行但被系统终止 |
不活跃的 | 该APP在前台运行,但没有接受到事件,APP一般都处在这个状态 |
活跃 | 该APP在前台运行并且接受事件,这是前台运行APP正常状态 |
后台 | 该APP在后台且在执行代码,大部分APP在该状态会停留一段事件 |
暂停 | 该APP在后台且没有执行代码,系统会自动将APP设置到这个状态并不会通知APP.暂停后,APP保留在内存内但不会执行代码.当内存低时,系统会杀死APP来使前台有更多内存 |
大多数状态的改变都伴随着系统方法的调用。你可以在这些方法内响应状态。
-
application:willFinishLaunchingWithOptions
: 启动时执行第一次代码 -
application:didFinishLaunchingWithOptions
:允许在程序显示之前进行最终的初始化 -
applicationDidBecomeActive
: 即将成为前台应用 -
applicationWillResignActive
: 过渡状态,即将不活跃 -
applicationDidEnterBackground
: 正在后台运行,会随时暂停 -
applicationWillEnterForeground
: 应用从后台到前台,但不活跃 -
applicationWillTerminate
: 应用正在被终止,暂停不会调用这个方法
本节讲了应用各种运行的状态和appdelgate里一些方法具体是什么时候被调用的
APP终止
APP应该随时准备被终止而不应该等待保存用户数据或者执行关键任务,系统发起的终止是APP生命周期里的一个正常环节。
如果APP正在后台运行且没有暂停,系统会在应用终止前调用applicationWillTerminate
,如果系统重启,不会调用这个方法
线程和并发
系统创建主要的线程,你也可以创建额外的线程。对于iOS APP,你应该首选GCD,操作队列和其他的异步接口而不是自己创建和管理线程,使用GCD可以让你更好的专注于工作,让系统去处理线程相关的事情。
在思考线程和并发时,你应该注意以下几点
- 包括视图,核心动画,还有很多UIKit的类通常都运行在主线程上,但也有例外,譬如图像的操作就在后台
- 耗时操作应该在后台处理,包括网络请求,文件访问或者大量的数据处理都应该使用异步的GCD或者操作队列管理。
- 在启动时,应该尽快的显示界面,除了必须的建立视图的操作,其他的耗时操作都应该放在后台
更多的GCD或者操作队列信息请看 Concurrency Programming Guide
本节讲了耗时操作能放在子线程就丢到子线程,除了创建视图,刷新视图等等。。
Strategies for Handling App State Transitions
可以在UIApplicationDelegate
协议方法里面知道状态改变。
What to Do at Launch Time
当APP启动时(无论是前台还是后台),在application:willFinishLaunchingWithOptions:
和application:didFinishLaunchingWithOptions:
方法内做以下几点:
- 检查Info.plist文件
- 初始化关键数据
- 准备显示视图
- OpenGL ES应该在
applicationDidBecomeActive:
方法内绘制 - 显示窗口在
application:willFinishLaunchingWithOptions:
方法内,显示视图在application:didFinishLaunchingWithOptions:
方法内
- OpenGL ES应该在
在启动时候要尽快完成,超过5秒系统会自动杀死该进程。
The Launch Cycle
应用进入运行状态流程图
应用在后台会处理事件,并会在某个时间点暂停。它仍会加载界面文件但不会显示
可以通过applicationState
属性区别当前应用状态,在前台时为UIApplicationStateInactive
,后台时为UIApplicationStateBackground
Launching in Landscape Mode
应用必须设置朝向,如果支持横向和纵向,那么默认是纵向。如果只支持横向,需要做到以下几点:
- 在info.plist文件中添加
UIInterfaceOrientation
Key值,设置属性为UIInterfaceOrientationLandscapeLeft
或者UIInterfaceOrientationLandscapeRight.
- 确保约束是正确的
What to Do When Your App Is Interrupted Temporarily
当应用暂时中断时,系统仍在前台但是不能接受触摸事件,但可以接受类似陀螺仪事件,你需要在applicationWillResignActive:
方法内做以下操作:
- 保存数据或者关键信息
- 停止定时器或者其他周期性任务
- 停止数据查询
- 不初始化新的任务
- 停止视频(除了AirPlay)
- 停止游戏
- 终止GCD或者操作队列
在应用进入活跃状态时,在applicationWillResignActive
方法内你应该重启定时器,恢复队列。游戏不应该恢复,这应该让用户手动开始。
Responding to Temporary Interruptions
假如有电话打来,应用会进入inactive状态,直到用户结束通话,应用回到活跃状态或者后台。
用户拉下通知横幅会造成应用进入inactive状态。
用户按下锁屏键,系统禁用触摸事件并且使应用进入后台,这时候数据文件会被加密保护起来。
What to Do When Your App Enters the Foreground
当应用进入前台状态时,applicationWillEnterForeground:
方法会撤销所有applicationDidEnterBackground:
方法里的事情,并在applicationDidBecomeActive:
方法里做激活任务。
UIApplicationWillEnterForegroundNotification
这个枚举也可以用来监听应用进入前台状态。
Be Prepared to Process Queued Notifications
Event | Notifications |
---|---|
配件断开或链接 | EAAccessoryDidConnectNotification |
屏幕旋转 | UIDeviceOrientationDidChangeNotification |
完整一天过去 | UIApplicationSignificantTimeChangeNotification |
偏好设置改变 | NSUserDefaultsDidChangeNotification |
语言改变或者区域改变 | NSCurrentLocaleDidChangeNotification |
应用在后台时仍然能调用setNeedsDisplay
或者setNeedsDisplayInRect:
,但是因为视图是不显示的,所以会在下次应用进入前台时更新视图。
What to Do When Your App Enters the Background
从前台进入后台,在applicationDidEnterBackground:
方法里做以下几点:
- 给你应用准备一张图片。如果用户退出前台前的那个页面包含了敏感信息,你应该隐藏或者修改这张图片在
applicationDidEnterBackground:
方法返回前。 - 保存有关应用的状态信息
- 释放不需要的内存。因为系统会杀掉占用内存大的应用,所以应该让你的应用在进入后台前释放图片资源,缓存和一些不需要的数据。
应用在applicationDidEnterBackground:
方法里大概有5秒的时间来完成任务。实际上,这个方法应该尽可能快的返回。如果方法没有在时间耗尽前返回,系统会杀掉应用并释放内存。如果你需要长时间进行任务,你应该在beginBackgroundTaskWithExpirationHandler:
方法里进行,并且在secondary线程上。不管你在applicationDidEnterBackground:
方法内进行任何操作都必须在5秒内返回。
Note:
UIApplicationDidEnterBackgroundNotification
这个枚举可以知道系统进入后台
The Background Transition Cycle
当用户按下Home键,关机键或者打开其他应用,前台应用会变到inactive状态然后到background状态。这些转变结果会调用到applicationWillResignActive:
和applicationDidEnterBackground:
方法。在applicationDidEnterBackground:
方法返回后,大多数应用会在不久后变到suspended
状态。如果应用需要特殊的后台任务(像播放音乐)或者执行额外的长时间任务,background状态会延长。
Prepare for the App Snapshot
在applicationDidEnterBackground:
方法返回不久后,系统会生成一张应用快照用于显示。同样,当应用被唤醒来处理一些后台任务,系统也许需要生成一张新的快照来反映改变。
如果你想对显示的快照做更改,你需要调用snapshotViewAfterScreenUpdates:
来更新视图,这个方法能马上更新视图,调用setNeedsDisplay
方法是没用的,这个上面文档讲过了。
Strategies for Implementing Specific App Features
不同的应用有不同的需要,但是有些行为应该是相同的。下面的章节会介绍这些特定的功能如何实现。
Privacy Strategies
保护用户隐私对于应用来说是很重要的。系统已经提供了保护隐私的方法。
Protecting Data Using On-Disk Encryption
设备锁屏以后,是不能访问被保护的文件的,就算文件是应用创建的也不可以。想访问保护文件必须在设备解锁之后。
数据保护在大部分设备上是可用的,只需要遵循以下几点:
- 文件系统必须支持数据保护,这在大部分设备上是可行的
- 用户必须设置密码锁
使用NSData
和NSFileManager
类可以通过添加属性来设置保护等级。当写入新文件时,你可以使用NSData
的writeToFile:options:error:
方法。对于已存在的文件,你可以使用NSFileManager
的setAttributes:ofItemAtPath:error:
方法,使用这些方法,你可以设置以下几个保护等级:
- No protection -> 文件被加密但是当设备被锁屏时不能被密码保护。选择
NSDataWritingFileProtectionNone
这个枚举。 - Complete -> 文件被加密且不能被访问当设备被锁屏。选择
NSDataWritingFileProtectionComplete
枚举。 - Complete unless already open -> 文件被加密且不能被访问当设备被锁屏,当文件被访问时设备锁屏,应用仍然可以在锁屏状态下访问文件。选择
NSDataWritingFileProtectionCompleteUnlessOpen
枚举 - Complete until first login -> 文件被加密且不能访问直到设备启动,用户解锁一次。
如果你保护了文件,那么应用会随时不能访问文件。当设备锁屏时,你可以通过几点知道是否可以访问文件:
- 可以在app delegate里实现
applicationProtectedDataWillBecomeUnavailable:
和applicationProtectedDataDidBecomeAvailable:
方法 - 任何对象都可以注册
UIApplicationProtectedDataWillBecomeUnavailable
和UIApplicationProtectedDataDidBecomeAvailable
通知 - 可以通过
UIApplication
的protectedDataAvailable
属性来知道当前是否可以访问文件
反正就是推荐文件保护。
Identifying Unique Users of Your App
你应该区别每一个用户,并且这一行为应该是透明的。
在以下几种情况下,也许你需要这样做
- 登录
- 给不同的用户观看不同的广告
如果你需要在不同设备上辨认是否是一个用户,你需要提供自己的一套识别系统。
Supporting Multiple Versions of iOS
一个在多个版本运行的应用应该检查系统版本,防止在旧系统上使用了新系统的API。
有几个检查方法你可以选择:
- 确定这个类是否存在
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:
类方法或者respondsToSelector:
实例方法。 - 判断基于C语言的函数是否可用
if (UIGraphicsBeginPDFPage != NULL) {
UIGraphicsBeginPDFPage();
}
想知道这方面更多的知识,可以看 SDK Compatibility Guide.
Preserving Your App’s Visual Appearance Across Launches
Enabling State Preservation and Restoration in Your App
状态保存和恢复不是自动的,系统必须选择使用。如果要使用这些功能,需要实现以下方法
application:shouldSaveApplicationState:
,application:shouldRestoreApplicationState:
一般来说,返回YES是表明功能可用。但是有些时候需要返回NO,比如应用更新了,你不应该恢复旧的页面。
The Preservation and Restoration Process
在保存和恢复过程中,应用也有少量的任务:
- 保存过程
- 告诉UIKit支持保存
- 告诉UIKit什么控制器和视图需要被保存
- 给保存的对象编码
- 恢复过程
- 告诉UIKit支持恢复
- 提供需要的UIKit对象
- 让保存的对象恢复原样
Figure 5-1 显示了简单的视图控制器的层次结构。如果没有状态恢复,只有main storyboard文件里的控制器在随后的启动中恢复。当你的应用支持状态恢复,你可以保存所有的控制器
UIKit保存对象需要一个保存identifier,假如控制器没有这个identifier,他和他的子视图都不会被保存。Figure 5-2 展示了部分拥有identifier的控制器
这些功能对于应用来说也许是毫无意思的,因为UIKit本身就能简单的保存和恢复。
对于你保存的所有控制器 ,你也需要决定如果去恢复它们。UIKit 提供了两种方式去创建对象。你可以去重新创建它们或者通过 view controller 去恢复它们。restoration class实现UIViewControllerRestoration
协议,并且在恢复的时候去负责寻找或者创建指定的对象。这里有几个建议:
- 如果你的控制器是通过 main storyboard 文件加载的,就不用这样搞了,让UIKit自己去恢复吧
- 如果不是第一种情况,那么最简单恢复办法就是让每一个控制器都实现协议。
在保存过程中,UIKit 保存对象并且写入到磁盘。每个控制器也有一次机会来保存数据。
Flow of the Preservation Process
Figure 5-3 展示了直到保存状态前的高级事件和应用是如何被影响的。在保存之前,UIKit 会调用application:shouldSaveApplicationState:
方法,如果返回YES的话会开始保存视图。
下次应用启动的时候会自动寻找保存状态的文件,如果有的话就恢复。因为这些文件只适用于上次和当前的周期,在启动后会删除上次的保存状态文件。在恢复过程中有错误也会删除保存状态文件。举例,在一次恢复过程中应用崩溃了,那么系统会自动删除文件防止下次恢复再崩溃。
What Happens When You Exclude Groups of View Controllers?
Figure 5-5 展示了当导航控制器没有保存identifier,他的子控制器和视图都不会被保存
即使你不保存控制器,也不意味着所有控制器从视图层次中消失了。举个例子,如果有控制器是从 storyboard 文件中加载的,他会一直显示。
Checklist for Implementing State Preservation and Restoration
你如果想通过代码来实现保存和恢复状态,你应该看看以下几点:
- 实现
application:shouldSaveApplicationState:
和application:shouldRestoreApplicationState:
方法 - 给你想要保存的控制器添加非空字符串的
restorationIdentifier
属性 - 在
application:willFinishLaunchingWithOptions:
展示窗口 - 给适当的控制器指定 restoration classes
- 使用
encodeRestorableStateWithCoder:
和decodeRestorableStateWithCoder:
编码解码控制器状态 - 编码解码任意的版本信息或者状态信息使用
application:willEncodeRestorableStateWithCoder:
和application:didDecodeRestorableStateWithCoder:
方法 - 数据源实现
UIDataSourceModelAssociation
协议,虽然这不是必须的,但是这个协议可以帮助我们保存选中的和可见的视图
Preserving the State of Your View Controllers
保存单独的控制器状态,你需要做到以下几点:
- 必须有 restoration identifier
- 必须在启动的时候创建或者找到新的控制器
- 可选的来实现
encodeRestorableStateWithCoder:
和decodeRestorableStateWithCoder:
方法
Marking Your View Controllers for Preservation
UIKit 只会恢复拥有有效 restorationIdentifier 的对象。
恢复路径是从上往下。
Restoring Your View Controllers at Launch Time
在恢复过程中,UIKit 会通过几种办法去恢复:
- 如果控制器有 restoration class ,那么 UIKit 会去恢复控制器。调用
viewControllerWithRestorationIdentifierPath:coder:
方法,如果返回nil的话就代表不想创建控制器,UIKit 会放弃寻找。 - 如果控制器没有 restoration class ,UIKit 会让 app delegate 去恢复控制器。调用
application:viewControllerWithRestorationIdentifierPath:coder:
- 如果控制器已经存在正确的恢复路径,UIKit 会使用这个对象
- 如果控制器是通过 storyboard 去加载的,UIKit 会通过 storyboard 去寻找和创建控制器。
以下代码展示了如果在恢复过程中创建控制器。
+ (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;
}
Inter-App Communication
应用之间可以通过 URL 来进行通信。
Supporting AirDrop
AirDrop 可以发送图片,文件,URLs,和其他类型的数据给附近的设备。AirDrop 通过 peer-to-peer 网络来寻找附近的设备。
Sending Files and Data to Another App
如果想通过 AirDrop 发送文件和数据,使用UIActivityViewController
对象。当你创建了这个控制器,
你可以指定你想显示什么。你也可以显示自定义对象,只要遵守UIActivityItemSource
协议。
你可以通过excludedActivityTypes
属性来指定不显示的类型。当显示一个 activity view controller 在iPad上时,你必须使用popover
。
在iPhone上显示 activity view controller
- (void)displayActivityControllerWithDataObject:(id)obj {
UIActivityViewController* vc = [[UIActivityViewController alloc]
initWithActivityItems:@[obj] applicationActivities:nil];
[self presentViewController:vc animated:YES completion:nil];
}
Receiving Files and Data Sent to Your App
使用 AirDrop 接受文件你需要注意以下几点:
- 在 Xcode 里,声明你的应用能打开的文件
- 在 app delegate 里,实现
application:openURL:sourceApplication:annotation:
方法来接受数据 - 在 Documents/Inbox 里查看文件,如果需要的话把文件移出来(需要修改的话)
传输到 Documents/Inbox 中的文件你只能读取或者删除,但不能修改
Using URL Schemes to Communicate with Apps
Apple 内置了很多 URL schemes。如果你的 URL 定义了一个和 Apple 相同的东西,那么在你应用启动时会打开 Apple 的应用。
Sending a URL to Another App
当你实现自定义的 URL scheme 来发送数据给别的应用,调用openURL:
方法。
以下代码展示了一个应用如何打开另一个应用
NSURL *myURL = [NSURL URLWithString:@"todolist://www.acme.com?Quarterly%20Report#200806231300"];
[[UIApplication sharedApplication] openURL:myURL];
如果应用自定义了一个 URL scheme ,请看Implementing Custom URL Schemes
PS: iOS9新增了一个白名单,苹果规定开发者只能设置50个 URL scheme 来打开别的应用,在openURL:
之前还需要做个判断canOpenURL:
,返回到前一个应用不用这样做。
Implementing Custom URL Schemes
如果你的应用可以接收特定的 URLs,你应该在系统里注册相应的 URL schemes。
Registering Custom URL Schemes
在应用里注册 URL 类型,你应该在 Info.plist 文件中加入CFBundleURLTypes
key。这个 key 包含一个字典数组,每一个都定义了一个 URL schemes。
Key | Value |
---|---|
CFBundleURLName | 字符串包含了一个 URL scheme。为了确保唯一性,推荐指定一个反向域名格式的标识,举个例子, com.acme.myscheme。 |
CFBundleURLSchemes | 字符串数组包含了 URL scheme 名字,举个例子,http, mailto, tel, 和 sms。 |
Note: 如果多个第三方应用使用了相同的 URL scheme,目前还没有好的办法来解决。
Handling URL Requests
每个应用都有自己自定义的 URL scheme 并且也该知道如何去处理它们。你应该在 delegate 中实现以下方法:
- 使用
application:willFinishLaunchingWithOptions:
和application:didFinishLaunchingWithOptions:
方法来检查 URL 的信息并决定是否打开。如果其中一个方法返回 NO,你应用处理 URL 的代码不会被调用。 - 使用
application:openURL:sourceApplication:annotation:
方法来打开文件。(iOS9废弃了,应该使用application:openURL:options:
)
Figure 6-1 展示了在一个要求打开网址的应用上显示修改好的启动顺序
Figure 6-2 展示了切换到前台状态打开 URL
Displaying a Custom Launch Image When a URL is Opened
应用支持自定义的 URL schemes 可以提供自定义的启动图片。当系统启动你的应用通过 URL 时并没有有效的快照,他会显示你指定的启动图片。指定一个启动图片,提供一张使用以下名字格式的 PNG 图片:
basename代表了基本的图片名字,假如名字是 Default,url_scheme 是你的 URL scheme 的一部分名字,假如 URL scheme 是 myapp,那么你的启动图片名字就是 Default-myapp@2x.png。
Performance Tips
本章讲述整体性能的几点。
Reduce Your App’s Power Consumption
功耗始终是移动设备上的一个大问题。你可以通过优化以下几个功能来提高电池寿命:
- CPU
- WiFi,蓝牙,基带
- 定位
- 加速剂
- 访问硬盘
你应该经常使用 Instruments 来优化算法。但是即使最优算法也会对电池寿命有意向。你应该在写代码的时候注意以下几点:
- 避免使用 polling。polling 不会让 CPU 进入休眠。应该使用
NSRunLoop
或者NSTime
代替。 - 设置
idleTimerDisabled
属性为 NO,这个属性默认就是 NO,当用户不再输入时会关闭屏幕。如果你需要屏幕不被关闭,你应该修改代码而不是设置属性为 YES。 - 尽可能的合并一些任务来增加 CPU 空闲时间。因为一些很小的任务常常会唤醒 CPU。
- 避免经常访问硬盘。
- 需要多少内容绘制多少内容。绘制很耗电,不要依靠硬件来控制帧数,应该是内容需要几帧绘制几帧。
- 如果你使用
UIAccelerometer
来接收加速计时间。PS:看了下好像这个类在5.0就废弃了。
访问网络数据是很耗电的,通过以下几点来使访问数据次数最小化:
- 仅在需要的时候访问网络并且不 poll 服务。
- 当你必须访问网络时,发送最少的数据量。
- 发送数据而不是发送数据包。系统会在空闲的时候关闭 WiFi 和 无线电。当你使用
NSURLSession
来进行多个上传或下载任务时,应该让他们同时进行而不是一个个来。系统会自动管理队列。 - 尽可能的使用WiFi来访问网络。
- 如果使用定位,你应该使用最适合的 distance filter 和精确度
Instruments 应用包含了几个收集电池信息的 Instruments。还可以收集指定硬件耗电量。你也可以使用诊断记录来收集信息。PS:这个在手机设置里可以看。
Use Memory Efficiently
系统的可用内存会影响应用的性能。
Observe Low-Memory Warnings
系统发出内存警告时应该移除不需要的对象。回应这个警告是很重要的否则系统可能会终止应用。系统通过以下几个 API 来发送内存警告:
- app delegate 的
applicationDidReceiveMemoryWarning:
-
UIViewController
的didReceiveMemoryWarning
-
UIApplicationDidReceiveMemoryWarningNotificationnotification
通知 - GCD 的
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE
类型。这是你唯一能用来辨别内存压力大不大。
收到内存警告你应该释放掉不需要的内存。例如清理缓存,释放图片。如果你有一个不用的大数据,应该把它写进磁盘。
你可以通过UIApplicationDidReceiveMemoryWarningNotification
通知来直接删除不需要的资源。
Reduce Your App’s Memory Footprint
Table 7-1 减少应用内存占用空间
Tip | Actions to take |
---|---|
清楚内存泄露 | 在iOS中内存是很关键的资源,你的应用不应该有内存泄露。使用 Instruments 查看是否有内存泄露。 |
资源文件尽可能的小 | 文件写入到磁盘之前会在内存中存在。尽可能的压缩图片文件(PNG 图片是 iOS 首选的图片格式)。 |
懒加载资源 | 在需要使用要资源时再加载。 |
后面这些很多都在之前的文档有讲到,感兴趣的朋友可以自己去翻阅一下。