Apple 已经推出了很多种尺寸的 iPhone 了,从 iOS 方面,苹果也一直努力地消除这种硬件多元化给开发者带来的困扰,如 iOS 8 推出了 Adaptive Layout
, Trait Collections
, Universal split view controllers
,从此你不再需要为某个特定的设备开发独占 App,而是开发一个通用 App 适配所有设备。
然而这也带来一些挑战,因为要包含所有设备的资源文件,这个通用的 App 所占空间容量都比较大。所以 iOS 9 拿出了新的解决方案:
以上这三种技术加起来被称为 App Thinning
我们可以通过这种方式来对 App 瘦身过程进行量化
App slicing 可分为两部分: executable slicing
和 resource slicing
。 Executable slicing 就是 Apple 将根据不同的设备安装特定的 App,你不需要做太多事情,Apple 已经为你做好了一切。
Resource slicing要求你做的事情也很简单,将所有的资源文件放到 Asset Catalogs 下,并且按照相关特性进行组织,从 Xcode 7 开始,你可以根据 Memory 和 Graphics 来标记资源文件了
现在我们通过对资源的『按需使用』来削减内容尺寸(俗称 ODR),ODR 允许你将资源存储在 Apple 的服务器上,之后仅在需要时才会下载使用。
NSBundleResourceRequest
负责处理 ODR,通过这个类,可以通过 Tags
来控制内容的下载。对 tags 的使用,Apple 模糊了本地资源和远程资源的界限。
使用 ODR 可以包括 images,data,OpenGL shaders,SpriteKit Particles,Watchkit Complications 等
针对 NSBundles 可看做是 data 文件,因此对于 ODR 来说也是支持的。
现在是代码时间,将之前的本地载入资源文件( NSBundles 文件)改为远程异步载入,修改 downloadAndDisplayMapOverlay()
方法
// 1 guard let bundleTitle = mapOverlayData?.bundleTitle else { return } // 2 let bundleResource = NSBundleResourceRequest(tags: [bundleTitle]) // 3 bundleResource.beginAccessingResourcesWithCompletionHandler { [weak self] error in // 4 NSOperationQueue.mainQueue().addOperationWithBlock({ // 5 if error == nil { self?.displayOverlayFromBundle(bundleResource.bundle) } }) }
beginAccessingResourcesWithCompletionHandler(_:)
会在完成 on-demand
内容下载后,调用 completion block 将资源文件显示到屏幕上。
现在我们完成最后一步: 标记 tags :
现在运行程序,选中 LA(LA_Map.bundle 被标记过,所以会被存储在云端),此时将从 Apple 的服务器上下载相应的资源文件
LA 这个 bundle 相对来说还比较小,如果你尝试下 San Diego,会发现花很长时间来下载
对于已经通过 ODR 加载过的资源文件,再次显示的时候,ODR 会缓存来保证速度,除非触发了清空条件,否则将会一直缓存这些资源文件
为了避免应用被评为一星,你可以加一个进度条来告诉用户你的 app 正在下载内容,同样是回到 downloadAndDisplayMapOverlay()
方法
guard let bundleTitle = mapOverlayData?.bundleTitle else { return } let bundleResource = NSBundleResourceRequest(tags: [bundleTitle]) // 1 bundleResource.loadingPriority = NSBundleResourceRequestLoadingPriorityUrgent // 2 loadingProgressView.observedProgress = bundleResource.progress // 3 loadingProgressView.hidden = false UIApplication.sharedApplication() .networkActivityIndicatorVisible = true bundleResource.beginAccessingResourcesWithCompletionHandler { [weak self] error in NSOperationQueue.mainQueue().addOperationWithBlock({ // 4 self?.loadingProgressView.hidden = true UIApplication.sharedApplication() .networkActivityIndicatorVisible = false if error == nil { self?.displayOverlayFromBundle(bundleResource.bundle) } }) }
loadingProgressView 将会随下载进度进行实时更新
在真实世界,虽然你加上了进度条来做标记,但用户等待太长时间总归是件非常蛋疼的事情。因此你可以考虑将一些较大的资源文件设为初始数据打包进 IPA ,让他们一起下载安装
选择工程名称 -> Target -> Resource Tags ,将 All
切换为 Prefetched
,会发现 ODR 有三种类型的处理方式:
现在我们可以根据需要,添加相应的资源文件
想要测试实际效果,需要提交到 TestFlight Beta Testing ,然后用真实设备下载测试即可。
你可以帮助 iOS 系统在资源文件 不再需要时
从磁盘清除掉
设置一个 NSBundleResourceRequest
类型的属性 overlayBundleResource
用来标记下载到的资源文件
var overlayBundleResource: NSBundleResourceRequest?
将这个属性标记的指针指向获取到的资源文件,在资源文件下载显示方法 downloadAndDisplayMapOverlay()
中添加一条:
overlayBundleResource = bundleResource
最后在屏幕消失时告诉系统完成了对资源文件的访问
override func viewDidDisappear(animated: Bool) { super.viewDidDisappear(animated) overlayBundleResource?.endAccessingResources() }
具体的资源文件是否占用磁盘,可以通过下面的方式进行查看