戴维营教育翻译:感谢Davis Allie的An Introduction to On-Demand Resources on iOS and tvOS
介绍
与iOS 9和watchOS 2一起,苹果引入了一套新的内容分发API,以便节约设备空间,这就是按需加载资源。通过使用按需加载资源,我们可以将特定的应用程序资源托管在苹果的服务器上,然后在需要的时候进行加载。在这个教程中,我将通过开发一个图片查看应用介绍一下按需加载资源的基本用法。
准备工作
这个教程需要使用Xcode 7+并且熟悉iOS开发。可以到Github上下载初始项目。
1. 按需加载资源
益处
iOS 9和watchOS 2引入按需加载资源的主要目的是减少单个应用程序所占的空间。另外一个好处是我们的应用程序能够更快的被用户下载下来并且启动。
通过使用Xcode中的asset pack所唯一分配的tags来获取按需加载资源。这些资源包可用包含任何asset中的内容(图片、SpriteKit纹理、数据等),甚至其它文件如OpenGL和Metal的着色器、SpriteKit和SceneKit的场景文件以及粒子系统。
当我们将应用提交到App Store时,这些资源也会被上传和托管,以便需要的时候下载。程序运行的时候,只要使用在Xcode中设置的tag就可以非常方便的获取到资源。
类别
使用按需加载资源最多的两个方面是app bundle,包含可执行代码和重要的资源,例如UI图标和asset packs。
在Xcode中可以将这些资源分为三个主要的类别:
- 初始安装: 第一次运行需要的内容,过后可以删除。一般可以包含游戏的开始几关,当游戏进度足够远的时候就可以删除了。
- 预取: 这个类别的内容在安装完应用后就需要立即下载。这个类型的数据应该应用在那些非必需的资源上,但是如果安装了可以获取更好的用户体验。比如游戏的新手教程。
- 按需加载: 在程序运行过程中下载,不会影响程序的运行。这是我们使用最多的按需加载类型。
限制
支持按需加载资源的应用程序需要遵循以下限制:
- iOS应用程序不能超过2GB
- 初始安装的tag不能超过2GB
- 预取tag不能超过2GB
- 正在使用的资源不能超过2GB。这一点只有当应用正在运行并且使用了按需加载资源的时候才起作用。
- 每个asset pack不能给你超过512MB。如果超过了这个限制,Xcode将给出警告并且允许我们继续测试和开发程序,但是提交到App Store时会失败。
- 苹果为每个应用提供最多20GB的空间进行托管,这也是每个程序一次最多可以下载的资源数量。当然,应用程序一次最多只能使用2GB的资源。
应用分片
注意20GB的限制是所有应用程序分片的总和,而是总共20GB。那什么是应用程序分片呢?应用程序分片是iOS 9引入的,用于降低应用大小的一个特性。当应用被安装后,它只会查找与设备对应的资源。例如,当资源被正确使用的时候,iPhone 6 Plus或6s Plus只会加载3x的图标,而不会下载1x和2x的。所以整个按需加载资源的总的大小是20GB,包含我们上传到App Store服务器的所有设备类型需要的资源总和。除此以外其它限制条件都是针对特定的设备类型单独设置的。
删除按需加载资源
已经下载的按需加载资源只有当设备空间不够时才会被清理。当空间不够时,按需加载资源系统会查看设备上的所有应用,并且根据资源的最后使用时间决定删除哪个。如果应用程序正在运行,它拥有的资源永远都不会被清理。
2. 分配和指定Tag
使用Xcode打开启动项目并且在模拟器中运行程序。它包含一组图片,由三种颜色(红、绿、蓝)和四种形状(圆、正方形、星形和六边形)组合而成。当程序运行的时候,选择Colors > Red,我们可以看到一个红色的圆。
我们在程序中设置7个asset pack,每个包含一种颜色和一个形状。按需请求资源的另外一个特性是每个资源可以设置多个Tag。比如红色的圆可以同时是Red和Circle包的一部分。
按需请求资源API对同一个资源不会下载两次。换句话说,就是如果已经下载Red包,那么在下载Circle包的时候,不会再次下载红色圆形这个图片。
在Xcode中,打开Assets.xcassets。我们可以看到12张图片。
下一步,选择蓝色方块图片集,并且打开Attributes Inspector。
在Attributes Inspector中包含一个新的On Demand Resource Tags组,可以用来设置资源的标签(Tag)。我们给蓝色方块设置Blue和Square标签。
设置好资源的Tag后,打开Xcode左侧的Project Navigator,点击Resource Tags标签并且选择Prefetched进行过滤。
我们可以非常方便的看到每个资源包的大小以及里面所包含的内容。Prefetched中显示每个类别中的资源,并且允许在不同的类别中进行移动:
- 初始安装
- 预取
- 按需下载
这正是我之前所提到的三个类别。Prefetched Tag Order组中的资源在显示的时候自动开始下载。
设置好了所有的图片资源后,就可以开始访问这些内容了。
3. 访问按需请求资源
我们使用NSBundleResourceRequest
获取App Store服务器托管的资源包。使用需要获取的资源的Tag创建request对象。它会告诉系统我们所需要使用的资源包是哪个。而释放这些对象则告诉系统我们不再需要这些资源包了。一定要注意不能超过2GB的资源限制。
在项目中,打开DetailViewController.swift并且在DetailViewController
中添加一下属性。
var request: NSBundleResourceRequest!
下一步,替换viewDidAppear(_:)
方法。
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
request = NSBundleResourceRequest(tags: [tagToLoad])
request.beginAccessingResourcesWithCompletionHandler { (error: NSError?) -> Void in
// Called on background thread
if error == nil {
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.displayImages()
})
}
}
}
刚才的代码中我们用Tag初始化了一个request
对象。tagToLoad
属性是在前一个页面设置的,表示我们要显示的内容是什么。
下一步,我们通过调用beginAccessingResourcesWithCompletionHandler(_:)
开始下载特定Tag标明的资源包。这个方法自动访问Tag对应的所有资源,并且开始下载。一旦获取到资源后,其它代码一切照旧。
注意,如果我们只希望访问已经下载的资源,而不想继续加重内容。可以调用conditionallyBeginAccessingResourcesWithCompletionHandler(_:)
。
需要提醒的是,上面方法的执行完后,handler是在子线程中执行的,因此需要切换到主线程再更新UI。
我们已经成功的在程序里使用了按需加载资源,简单吧!
Xcode 7的有一个重要的调试特性就是能够查看当前下载了那些资源包。点开Debug Navigator并且选择Disk就可以看到了。
我们可以改变一下资源的下载优先级,这样有些内容就会总是立马下载。同时还可以改变资源的保存优先级,比如Hexagon和Star会在Circle和Square之前清理。将代码改成一下样子:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
request = NSBundleResourceRequest(tags: [tagToLoad])
request.loadingPriority = NSBundleResourceRequestLoadingPriorityUrgent
NSBundle.mainBundle().setPreservationPriority(1.0, forTags: ["Circle", "Square"])
NSBundle.mainBundle().setPreservationPriority(0.5, forTags: ["Hexagon", "Star"])
request.beginAccessingResourcesWithCompletionHandler { (error: NSError?) -> Void in
// Called on background thread
if error == nil {
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.displayImages()
})
}
}
}
初始化request对象后,我们可以设置loadingPriority
属性为NSBundleResourceRequestLoadingPriorityUrgent
。当然,也可以设置0-1之间的任意值来表明加载优先级。
这个常量自动设置请求为最高加载优先级,但是这取决于当前的CPU活动情况。在某些情况下,如果CPU任务繁忙,资源的下载可能会被推迟。
下一步我们通过mainBundle的setPreservationPriority(_:forTags:)
设置四个形状的保存优先级。这时我们就可以确认,一旦按需加载系统需要清理一些资源的时候,就会先删除Hexagon和Star。
4. 最佳实践
现在我们知道了如何在iOS应用里使用按需加载资源。下面我想给大家介绍一些需要注意的地方。
让每个Tag保持最小
为了减少资源的加载时间,使得每个资源更容易访问,应该让每个资源包保持尽可能的小。这样就不会导致被过度清理。
例如,系统本身需要释放50MB的空间,但是如果最合适被删除的包是400MB,这样就会导致350MB的内容被不必要的删除。这就意味着下次需要的时候又要多下350MB。建议每个资源包的大小应该接近64MB。
提前下载资源
如果你的应用有一个非常容易预见的用户操作过程,最好是提前就开始下载资源。这样可以提高用户体验,而不需要他们等待太久。
游戏是一个非常常见的场景。如果一个玩家已经完成了前5关,我们最好在他玩第6关的时候,开始下载第7关。
正确的停止对资源的访问
如果不在需要访问某个资源包,确保释放NSBundleResourceRequest
对象或者调用endAccessingResources
方法。
这样不仅可以防止我们的应用达到2GB的限制,而且可以帮助系统知道什么时候需要访问这些资源,从而更好的决定如何删除资源来获取更多空间。
5. tvOS上的按需访问资源
在我的另外一篇关于tvOS的博客提到tvOS的应用限制。比如最大程序大小为200MB,因此更应该使用按需方法资源。
tvOS和iOS的按需访问资源的其它限制是一样的,只是要注意tvOS仅使用1x的图片,因此并不会因为应用程序分片的存在而减少空间。
总结
iOS 9和tvOS上的按需加载资源是用来减少程序大小和提供更好用户体验的一个非常重要的方法。它非常容易使用和设置,但是要注意它的加载时间和数据清理所带来的一些问题。