本文来自网易云社区html
做者:饶梦云ios
Activating this setting causes the -dead_strip flag to be passed to ld(1) via cc(1) to turn on dead code stripping. Remove functions and data that are unreachable by the entry point or exported symbolsgit
Xcode 默认会开启此选项,C/C++/Swift 等静态语言编译器会在 link 的时候移除未使用的代码,可是对于 Objective-C 等动态语言是无效的。由于 Objective-C 是创建在运行时上面的,底层暴露给编译器的都是 Runtime 源码编译结果,全部的部分应该都是会被判别为有效代码。github
扫描无用代码的基本思路都是查找已经使用的方法/类和全部的类/方法,而后从全部的类/方法当中剔除已经使用的方法/类剩下的基本都是无用的类/方法,可是因为 Objective-C 是动态语言,可使用字符串来调用类和方法,因此检查结果通常都不是特别准确,须要二次确认。目前市面上的扫描的思路大体能够分为 3 种:swift
基于 Clang 扫描xcode
基于可执行文件扫描缓存
基于源码扫描安全
基本思路是基于 clang AST。追溯到函数的调用层级,记录全部定义的方法/类和全部调用的方法/类,再取差集。具体原理参考 如何使用 Clang Plugin 找到项目中的无用代码,目前只有思路没有现成的工具。微信
Mach-O 文件中的 (__DATA,__objc_classlist) 段表示全部定义的类, (__DATA.__objc_classrefs) 段表示全部引用的类(继承关系是在 __DATA.__objc_superrefs 中);使用的方法和引用的方法也是相似原理。所以咱们使用 otool 等命令逆向可执行文件中引用到的类/方法和全部定义的类/方法,而后计算差集。具体参考iOS微信安装包瘦身,目前只有思路没有现成的工具。app
通常都是对源码文件进行字符串匹配。例如将 A *a、[A xxx]、NSStringFromClass("A")、objc_getClass("A") 等归类为使用的类,@interface A : B 归类为定义的类,而后计算差集。
基于源码扫描 有个已经实现的工具 - fui,可是它的实现原理是查找全部 #import "A" 和全部的文件进行比对,因此结果相对于上面的思路来讲可能更不许确。
AppCode 提供了 Inspect Code 来诊断代码,其中含有查找无用代码的功能。
它能够帮助咱们查找出 AppCode 中无用的类、无用的方法甚至是无用的 import ,可是没法扫描经过字符串拼接方式来建立的类和调用的方法,因此说仍是上面所说的 基于源码扫描 更加准确和安全。
经过扫描的方式去检查无用代码有个痛点就是 类的方法调用是一种引用关系,以上所说的四种思路都是查找到引用末端的未使用的代码,咱们很难经过一次扫描就定位到全部未使用的类,自动化实现起来也较难。举个例子来讲,假如 A 是一个未使用到的类,可是 A 引用了 B,因此首次检查结果是 A 未被引用,B 被无用类 A 引用了,咱们须要把 A 删除了以后咱们才能了解到 B 是不是无用的类。固然若是你从新去实现一个引用树的话就另当别论了。
因为扫描无用类实现起来较为麻烦,而且其检查结果也不是特别准确。因此建议仍是让开发者养成一个良好的习惯,在迭代或者重构代码的时候把老的代码删除,不要等到量变引发质变的时候才回头去优化。
重复代码堆积太多,不只意味着 Bad Code Smell,咱们的包大小也会受到影响,咱们可使用 PMD 来检查项目中的重复代码,而且作选择性的重构。
Cocoapods 的 project 文件在每次 pod install 或者 pod update 会重置,因此须要 hook pod install 来设置 Pods 中每一个 Target 的编译选项:
post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['ENABLE_BITCODE'] = 'NO' config.build_settings['STRIP_INSTALLED_PRODUCT'] = 'YES' config.build_settings['SWIFT_COMPILATION_MODE'] = 'wholemodule' if config.name == 'Debug' config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Onone' config.build_settings['GCC_OPTIMIZATION_LEVEL'] = '0' else config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Osize' config.build_settings['GCC_OPTIMIZATION_LEVEL'] = 's' end end endend
本文中的资源分为图片资源、音频资源、视频资源、视图资源、字体资源、网页资源等等。资源都是弱类型的,会随着项目工程的增加而持续增加,维护起来费时费力,因此通常都是但愿能有工具化、自动化的解决方案。
Xcode 提供的给咱们两个编译选项来帮助压缩 PNG 资源:
Compress PNG Files:打包的时候自动对图片进行无损压缩,使用的工具为 pngcrush,压缩比仍是至关高的,比较流行的压缩软件 ImageOptim 也是使用 pngcrush 进行压缩 PNG 的。
Remove Text Medadata From PNG Files:能帮助咱们移除 PNG 资源的文本字符,好比图像名称、做者、版权、创做时间、注释等信息。
项目引进的 PNG 资源都是自动被 Xcode 进行压缩了,因此彻底不须要本身再去用工具压缩一遍。当除非你是使用 bundle 管理的资源,由于 bundle 是直接拷贝进项目,并不会被 Xcode 进行压缩;JPG 或者其余类型的图片资源可使用 ImageOptim 进行无损压缩而后导入到 Xcode 中,为了提升效率建议仍是提供 PNG 格式的图片。
iOS 9 中引入的 App Thinning 中提到过 Slicing 的技术,当咱们把一个完整的安装包提交给 App Store 后,App Store 会为不一样的设备准备不一样的变体(Variant),设备的在下载 App 的时候它能帮助咱们自动选择合适的 Variant 进行下载。
可执行文件的 Slicing 技术就是上面所说的 BitCode,一样资源文件也是支持 Slicing 的。好比 iPhone 6 下载的安装包中就只会包含 2x 图,iPhone 6 Plus 下载的安装包就只会包含 3x 图,可是只有使用 asset catelogs(也就是 XCAssets) 管理的资源才支持 Slicing,因此尽可能仍是使用 XCAsset 来管理资源图片。同时 XCAsset 也支持 PDFs 矢量图,在上传到 App Store 以后,会根据矢量图自动生成 1x, 2x, 3x 图,而后进行 Slicing。
固然 XCAsset 也有它的存在的问题:
使用 XCAsset 管理的资源会被压缩并打包成一个 Asset.car 文件,咱们没法获取相应图片的物理路径,所以咱们没法使用 [UIImage imageWithContentsOfFile:] 的方式来获取图片。对于那些须要使用物理路径的方式来访问的图片,建议仍是直接拖拽到 App 中进行管理。
iOS 10.3 推出的更换 App Icon 的资源文件只能放在 App 根目录下进行管理。
使用 XCAsset 管理图片后,Xib/Storyboard 中设置的带后缀 .png 图片在 Interface Builder 是不可见的,都是显示的问号,可是运行起来是没有问题的。最好的作法是全局搜索并去掉后缀保证更好的开发体验。
未使用的资源可使用脚原本进行删除。强烈推荐使用 FengNiao 来自动删除图片,由于其相对比较新,是 2017 年开始开发的,而且是使用 swift 语言开发的,方便进行二次开发。FengNiao 的基本原理是查找出项目中全部使用到的字符串和项目中全部的资源文件。二者进行匹配(彻底匹配和模式匹配,模式匹配支持带数字资源的前缀/中缀/后缀匹配),计算差集就为未使用的资源。
相比于以前流行的 LSUnusedResources,FengNiao 支持模式匹配会更增强大:好比咱们导入 image_01 image_02 image_03 这样的图片资源做为帧动画素材,使用的时候是 image_%d 或者 image_\(index) 方式,FengNiao 会把这些图片资源做为使用中的资源,不会出现误删的状况。固然若是你仍是用了其余 Pattern,能够考虑扩展 FengNiao。
除了这些以外,FengNiao 是命令行工具,咱们能够给 Xcode 添加 Run Script,在每次构建的时候自动检测/清理未使用的资源。
因为基于源码的扫描工具结果不是百分百准确的,因此建议最好的作法是在项目编译的时候提供出显式的 Warning,而后再次确认以后再去删除。同时也能够配合资源命名规范来优化工具,若是大家的命名规范和工具的检测规范可以保持一致的话,搜索的结果无疑是最为准确的。
之因此要使用自动化工具来检测重复资源的缘由是由于资源是弱类型,咱们在项目迭代过程中手动去维护是至关麻烦的一个过程。转换一下思惟,若是资源变成强类型了,那咱们维护起来就至关容易了。目前就有这样一个工具-R.swift,相似于 Android 开发中的 R 文件,有兴趣的能够去尝试。
这里所说的重复资源是资源内容相同可是命名不相同的一些资源,对于此类资源,咱们可使用 fdupes 来进行扫描并去除,fdupes 的原理是对比不一样文件的签名,签名相同的文件就会断定为重复资源。
而后咱们就能够在 Xcode 中添加 Run Script,对于扫描到的相同的资源,咱们能够显式的报出 Warning,而后咱们在开发阶段解决资源重复的问题。
On-Demand Resources 是 Apple 在 iOS 9 跟 App Thinning 一块儿引进的一项减小安装包体积技术,大体的概念是苹果帮你把全部 App 中的资源管理在 App Store 云端上,而后你须要把资源标记为不一样的 Tag,须要的时候才去下载相应 Tag 的图片。引用苹果文档中的一张图表示。
这种机制对于许多图片资源都放在本地的 App 就会比较有用,好比游戏中的不一样关卡能够分为不一样的 tag,在用户通关了一关以后才下载下一关资源。
视频/音频等图片资源相对图片来讲会大不少,因此建议把视频/音频放在服务端,客户端在使用的时候进行下载或者使用流播放。
H5 资源也是建议放在服务端,若是对 H5 加载和离线访问有要求的话,可使用离线缓存的方式来缓存网页资源到本地。
这里所说的视图资源是指 xib/storyboard。xib 在打包时会被压缩为 nib 文件,storyboard 文件会被压缩为 storyboardc 文件,storyboardc 是个压缩包,内部包含了相应的 nib 和 一个 plist 文件。通常的 nib 文件压缩后在几 KB 到几十 KB 大小,这部分包大小的影响相对于 xib 能提升开发效率来讲影响是微乎及微的,网易漫画 App 中使用到了 257 个 xib 文件,可是其在 payload 中的数据仅仅只有 1.7M 大小。
Framework 文件夹存放的是 Embedded Framework,它在打包的时候最终会被拷贝进 Target App Bundle 中的 Framework 文件夹中,在 App 启动的时候才会被连接和加载。Embedded Framework 主要分类两类:
SwiftSupport:Framework 文件夹中前缀是 libSwift 的一些 framework。因为目前 Swift ABI 还未稳定,咱们发布应用的时候还须要带上一份本身应用中使用到的 Swift 标准库代码,这部分占用最终 ipa 的大小可能在 10M 左右。
其余依赖库:使用 Cocoapods 管理依赖而且设置了 user_framework! 时三方库源码都会打包成 framework,而后导入到工程当中。
这里所说的 Framework 表示的是: 静态库(.a) Framework(Static Library)
目前绝大部分的 Framework 的作法是直接将资源放进 bundle 中进行管理的,在主工程打包的时候,Xcode 会将这部分资源直接拷贝进 App Target Bundle 中,这样作就存在2个问题:
使用 bundle 管理的资源是不会被 Xcode 优化的(图片压缩等)
使用 bundle 管理的资源不享受 App Thinning/Slicing。
因此尽可能仍是选择 XCAsset 进行 Framework 的资源管理,静态库和动态库的管理方式有所不一样:
静态库(.a)/Framework(Static Library): 静态库的目标文件(.a/.framework) 中是不能包含资源文件的,因此这部分只能使用 bundle 来管理。可是因为 bundle 直拷贝的特性,咱们须要把 xib/storyboard/asset catalog 编译后的产物(nib/storyboardc/Asset.car)放进 bundle 里。比较广泛的一个作法是借助 Bundle Target 来编译咱们的资源文件,具体作法看这篇文章。
动态库: 动态库相对来讲要简单一点,由于动态库自己就是一个 bundle。因此咱们直接把资源文件放在目标文件(.framework)中就能够了。
若是你是使用 Cocoapods 管理你的源码,也可使用 XCAsset 来管理资源,参考 在 Cocoapods 中使用 XCAsset。
这部分能够参考以上的可执行文件瘦身。
Plugin 内部主要存放的就是 App Extension,App Extension 是独立打包签名,而后再拷贝进 Target App Bundle 的。
静态库最终会打包进可执行文件内部,因此若是 App Extension 依赖了三方静态库,同时主工程也引用了相同的静态库的话,最终 App 包中可能会包含两份三方静态库的体积。
动态库是在运行的时候才进行加载连接的,因此 Plugin 的动态库是能够和主工程共享的,把动态库的加载路径 Runpath Search Paths 修改成跟主工程一致就能够共享主工程引入的动态库。
在 Swift ABI 稳定以前,Swift 标准库会被拷贝进 App 当中。Swift 标准库是动态连接库,也是能够在主工程和其余的 App Extensions 之间共享的,前提固然是全部 Target 使用的 Swift 版本是一致的,不然就会出现意料以外的 bug。 设置共享分为两步:
设置 Extension 中的 Always Embed Swift Standard Libraries 为 NO,让编译器再也不为 Extension 生成 Swift 标准库
设置 Extension 中的动态库的查找路径为主工程的 Framework 文件夹
咱们在网易漫画 App 中逐渐进行了实践,这次主要进行的是可执行文件的瘦身:编译优化以及去除符号;资源瘦身:冗余资源清除以及主工程图片分片(App Thinning)。下面是优化先后的部分数据:
优化前 | 优化后 | 效果 | |
---|---|---|---|
Executable | 35.4M | 17.1M | 52.1% |
Embedded Framework(除去 Swift STL) | 27.8M | 19M | 31.7% |
Images(FengNiao) | 13.3M | 11.7M | 12% |
Executable 的数据显示的比较夸张的主要缘由是咱们在作瘦身的时候同时去除了以前使用到的直播 SDK,实际可执行文件的提高效果应该也在 30% 左右。IPA 文件从最初的 70+M 到如今 39.4M,总的来讲效果仍是至关明显。
固然以上并不是这次瘦身的所有内容,Framework 瘦身,冗余代码清除等比较难的点后续也会陆续展开实践,同时也会在本文中进行更新。
将 Build Settings -> Clang/LLVM Generate Code -> Optimize Level 设置为 Fastest, Smallest(-Os)。
将 Build Settings -> Swift/LLVMGenerate Code -> Optimize Level 设置为 Optimize for Size(-Osize)。
将 Build Settings -> Strip Linked Product 和 Strip Swift Symbols 设置为 YES,Deployment Postprocessing 设置为 NO,发布代码的时候也须要勾选 Strip Swift Symbols。
Strip Debug Symbols During Copy 在 Release 模式下设置为 YES。
有条件的话,适配 BitCode。
不管在主工程或者 Framework 中都使用 XCAsset 来管理资源。
使用工具扫描删除无用资源,推荐选择 FengNiao,并添加 Run Scripts。
使用工具扫描重复资源,推荐选择 fdupes,并添加 Run Scripts。
若是你大量资源都放在本地,推荐使用 On-Demand Resources 方式来管理资源。
在 Swift ABI 稳定以前 Extension 和主 App 之间共享 Swift Standard Libraries。
开启 Compress PNG Files/Remove Text Metadata From PNG Files。
将 Dead Code Stripping 设置为 YES。
使用工具扫描和清理无用代码,同时养成良好习惯,在迭代或者重构的时候删除旧的代码。
使用工具扫描重复代码并重构它。
视频/音频/H5 等资源远端化。
使用 xib/storyboard 来开发视图界面会必定程序增长安装包的大小。
使用 Swift 来开发程序会必定程序增长安装包的大小,对包大小有严格要求的话也能够衡量是否使用 Swift。
若是你对包大小有严格要求的话,选择合适大小的三方库来进行开发。
本文来自网易云社区,经做者饶梦云受权发布
网易云免费体验馆,0成本体验20+款云产品!
更多网易研发、产品、运营经验分享请访问网易云社区。
相关文章:
【推荐】 玩转数据产品设计-小屏幕下的大数据
【推荐】 Innodb实践总结(一)
【推荐】 年轻设计师如何作好商业设计