写点什么

iOS 官方瘦身方案 ODR(二):换肤系统改造|践行 On-Demand Resources

用户头像
Ryukie
关注
发布于: 47 分钟前
iOS官方瘦身方案ODR(二):换肤系统改造|践行 On-Demand Resources

👋


前言

如果你还不太清楚 ODR: On-Demand Resources 是什么,可以看看https://xie.infoq.cn/article/084b0c7e826e6572a881ed128


既然知道了 ODR 能干什么了,那就拿自己的项目开个刀。


这里使用我的个人项目梦见账本,由于项目中有多套皮肤可以更换,所以存了很多套图标,这些图标就很适合使用 ODR 来优化。


关于 ODR 除了官方文档,也没找到很多实践的资料,这里就结合文档手摸手一起来实践一波吧。

先来个卖家秀

使用 ODR 之前



使用 ODR 之后!



TestFlight 上看的大小,不过和真实下载的不会有多少差别。

一、 启用 ODR


iOS9 开始就是默认开启的了

二、 创建标签

标签用于识别和管理一组 ODR。将一个或多个标签分配给项目中的资源可将其标识为 ODR。在运行时,所有 ODR 的访问都与标签(而非单个资源)配合工作。


在 Target 的 Resource tags 可以看到添加标签的入口,这里可以选择项目中的资源,以添加到标签下。



项目之前的资源组织方式:



这里为了方便标签管理,进行一下资源包的拆分:



2.1 为 Assets 添加标签

Assets 添加到 Tag 下:




2.2 确认资源标签设置成功

打开资源包查看资源信息,就可以看到这里匹配的资源标签了。(一个资源可以设置多个标签)



在构建项目时,Xcode 会检查项目中的所有标记项目,并生成供操作系统使用的资源包。

2.3 为单个资源快速添加标签

也可以通过直接输入联想,快速为单个资源添已有标签,或者快速创建一个标签。



三、 标签分类

通过切换到 Prefetched 我们看到了三种分类:



创建的标签的默认类别是仅按需下载ODR。标签可以在类别之间拖动。


  • ***Initial install tags. ***

  • 资源与应用程序同时下载。资源的大小包含在应用商店中应用的总大小中。当没有任何 NSBundleResourceRequest 访问时,可能会被清除。

  • ***Prefetch tag order. ***

  • 安装应用后,资源开始下载。按标签顺序下载。

  • ***Dowloaded only on demand. ***

  • 按需加载。当应用请求时,将下载这些标签。


当操作系统下载标签时,它只会下载设备上没有的资源。

3.1 设置标签分类

设置标签分类很简单,从默认的 ODR 列表中拖到相应的分类下即可。


这里我把默认的两个皮肤的资源设置为:Initial install



四、 大小限制

应用剪切后,标签中资源的总大小不得超过 512 MB。应用商店中存储的按需资源的总大小不得超过 20 GB


标签的理想大小不大于 64 MB。此大小在下载速度和本地存储之间提供了良好的平衡,以便在设备的本地存储处于低位时可以清除。



  • Slicing.

  • 复选标记(✓)表示大小反映的是应用剪切后的大小。

  • App bundle.

  • 下载到设备的剪切后的应用程序包的大小。

  • Asset packs.

  • 资产包由 Xcode 生成。

  • ***Initial install tags. ***

  • 标记为初始安装的标记的总剪切后的大小。

  • ***Initial install and prefetched tags.. ***

  • 标记为 Initial installPrefetch tag 的资源剪切后的总大小

  • ***In use on-demand resources. ***

  • 一次行最多可用 ODR 的剪切后的大小。

  • ***Hosted on-demand resources. ***

  • 所有 ODR 大小,不剪切的大小。

五、 管理 ODR

配置了标签后,直接运行项目。可以看出所有的 ODR 资源均没有下载。



5.1 ODR 更新时机确定

这里先描述下 梦见账本 的换肤逻辑,来帮助确定 ODR 的更新时机:


  • 皮肤分为两种模式:普通模式、暗黑模式。

  • 即:用户可以为两种模式分别设置皮肤,切换系统暗黑模式开关即可无缝切换

  • 设置皮肤后会重新设置根控制器

  • 这里是需要更新 ODR 的一个节点

  • 另一个检查更新的节点是应用启动的时候

  • 检查当前的两种皮肤,准备相关标签的资源

5.1 NSBundleResourceRequest 请求资源

一个标签可以被多个实例管理,下面列举三个重要的 API

初始化

  • tags: 标签集合

  • bundle: 一般是 main


public init(tags: Set<String>, bundle: Bundle)
复制代码

检查状态

检查请求的标签的资源是否全部都已经在设备中保存了。


open func conditionallyBeginAccessingResources(completionHandler: @escaping (Bool) -> Void)
复制代码

开始请求资源

请求不在本地的标签资源。


open func beginAccessingResources(completionHandler: @escaping (Error?) -> Void)
复制代码

六、改造「梦见账本」

6.1 封装 OdrTool

protocol OdrTagProtocol {    var tagName: String { get }}
struct OdrTool { static func requestODR(of tags: OdrTagProtocol..., in bundle: Bundle = Bundle.main) { var tagsSet: Set<String> = [] tags.forEach { tagsSet.insert($0.tagName) } let request = NSBundleResourceRequest(tags: tagsSet, bundle: bundle) request.conditionallyBeginAccessingResources { hasCache in if hasCache == false { // 这些资源不全在本地 request.beginAccessingResources { error in if let _ = error { // 下载失败 } else { // 下载成功 } } } else { // 这些资源已经在本地了 } } }}
复制代码

6.2 为皮肤系统 AppearanceStyle 拓展 Odr

AppearanceStyle 是真个换肤系统的核心,主体是一个 枚举


  • 这里让原先的皮肤都匹配一下自己的标签名就好了。

  • 然后添加一个 reloadOdr 方法来更新当前两个皮肤的 ODR


extension AppearanceStyle: OdrTagProtocol {    static func reloadOdr() {        OdrTool.requestODR(of: currentLightStyle, currentDarkStyle)    }        var tagName: String {        switch self {        case .Charge, .ChargeMain:            return "Charge"        case .ChargeLight:            return "ChargeLight"        case .Eternal, .Hachimitsu:            return "Eternal"        case .Spark:            return "Sakura"        case .SparkMain:            return "Spark"        case .NYXL:            return "NYXL"        case .Justice, .Dream, .Phoenix:            return "Justice"        case .Kiwi:            return "Kiwi"        case .Banshee:            return "Banshee"        case .Punk:            return "Punk"        }    }}
复制代码

6.3 在合适节点更新 Odr 资源

节点一:应用启动


AppearanceStyle.reloadOdr()
复制代码


节点二:设置皮肤


static func reloadApp() {    guard let tabVC = UIStoryboard(name: "Home", bundle: nil).instantiateInitialViewController() else {        return    }    UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController = tabVC    reloadOdr()}
复制代码

6.4 运行项目,检查效果

Xcode 中显示我设置的两个皮肤的资源已经下载完成了。



但是打开 App,发现还是空白?



6.5 解决问题

通过之前的了解,如果 ODR 资源没有被任何 NSBundleResourceRequest 使用的话就会被清理。而上面的封装并没有持有 NSBundleResourceRequest。对代码进行一下调整。


OdrTool


struct OdrTool {    static func requestODR(of tags: OdrTagProtocol..., in bundle: Bundle = Bundle.main) -> NSBundleResourceRequest {        ...        let request = NSBundleResourceRequest(tags: tagsSet, bundle: bundle)        ...        // 把 NSBundleResourceRequest 抛出        return request    }}
复制代码


AppearanceStyle


extension AppearanceStyle: OdrTagProtocol {    // 添加一个静态变量保存    static var odrRequest: NSBundleResourceRequest?        static func reloadOdr() {        odrRequest = OdrTool.requestODR(of: currentLightStyle, currentDarkStyle)    }        var tagName: String {        switch self {            ...        }    }}
复制代码

6.6 检查修改后的效果

完美~ 又测了下切换皮肤的功能也是没问题的



再看 Xcode 发现,标签的状态不一样了,两个正在使用中的标签成了 In use 正在使用。



6.7 使用资源的地方需要改动么?

不用

七、 总结

体验下来还是很顺滑的,感觉接入成本不高。个人项目自己比较熟,而且换肤体系封的比较好😜也为接入 ODR 带来了很大便利。而且我单个皮肤的资源自由 2M 左右下载也挺快。如果是一些较大的资源加载可能就不能像我处理的这么简单了。而且 NSBundleResourceRequest 还有很多设置我没用,想深入了解的可以看下官方文档,这里我就抛个砖🧱。


而且任何优化方案的选择还是要结合实际项目来,能一股脑照搬的方案是不存在的。苹果的这个 ODR 挺好的,却感觉一直没有进入到广大 iOSer 的视野中,感觉挺可惜,如果感觉合适你的项目真的可以用起来哦~

参考

发布于: 47 分钟前阅读数: 6
用户头像

Ryukie

关注

还未添加个人签名 2018.05.07 加入

还未添加个人简介

评论

发布
暂无评论
iOS官方瘦身方案ODR(二):换肤系统改造|践行 On-Demand Resources