前言微信经过多次版本迭代,产生不少冗余代码和无用资源。之前微信也没有很好的手段知道哪个模块增量多少。另外去年10月微信开始做ARC支持,目的是为了减少野指针带来的Crash,但代价是可执行文件增大20%左右。而苹果规定今年6月提交给Appstore的应用必须支持64位,32位和64位两个架构的存在使得可执行文件增加了一倍多。安装包大小优化迫在眉睫。 Appstore安装包是由资源和可执行文件两部分组成,安装包瘦身也是从这两部分进行。(您也可以查看本文的iOS姊妹篇:《移动端IM实践:Android版微信界面卡顿监测方案》) 第一弹:引用资源“减肥”资源瘦身主要是去掉无用资源和压缩资源,资源包括图片、音视频文件、配置文件以及多语言wording。无用资源是指资源在工程文件里,但没有被代码引用。检查方法是,用资源关键字(通常是文件名,图片资源需要去掉@2x @3x),搜索代码,搜不到就是没有被引用。当然,有些资源在使用过程中是拼接而成的(如loading_xxx.png),需要手工过滤。 资源压缩主要对png进行无损压缩,用的是ImageOptim工具和compress命令(需要安装XQuartz-2.7.5.dm插件)。不建议对资源做有损压缩,有损压缩需要设计一个个检查,通常压缩后效果不尽人意。 第二弹:可执行文件减肥可执行文件瘦身问题,LinkMap文件可以帮助我们寻找优化点,关于“Xcode’s Link Map File”的说明请参考附录哦。 1找出无用的selector‘ 结合LinkMap文件的__TEXT.__text,通过正则表达式([+|-][.+/s(.+)]),我们可以提取当前可执行文件里所有objc类方法和实例方法(SelectorsAll)。再使用otool命令otool -v -s __DATA __objc_selrefs逆向__DATA.__objc_selrefs段,提取可执行文件里引用到的方法名(UsedSelectorsAll),我们可以大致分析出SelectorsAll里哪些方法是没有被引用的(SelectorsAll-UsedSelectorsAll)。注意,系统API的Protocol可能被列入无用方法名单里,如UITableViewDelegate的方法,我们只需要对这些Protocol里的方法加入白名单过滤即可。 另外第三方库的无用selector也可以这样扫出来的。 2找出无用Objective-C类查找无用oc类有两种方式,一种是类似于查找无用资源,通过搜索"[ClassName alloc/new"、"ClassName *"、"[ClassName class]"等关键字在代码里是否出现。另一种是通过otool命令逆向__DATA.__objc_classlist段和__DATA.__objc_classrefs段来获取当前所有oc类和被引用的oc类,两个集合相减就是无用oc类。 3Protobuf优化protobuf是Google推出的一种轻量高效的结构化数据存储格式,在微信用于网络协议和本地文件序列化。但google默认工具生成的代码比较冗余,像序列化、反序列化、计算序列化大小等方法都生成在具体的pb类里,每个类的实现大同小异。通过代码分析以及结合protobuf原理,要想把这些方法抽象到基类,派生类提供每个字段相关信息就够了:
typedef struct { Byte _fieldNumber; Byte _fieldLabel; Byte _fieldType; BOOL _isPacked; int _enumInitValue; union { __unsafe_unretained NSString* _messageClassName; __unsafe_unretained Class _messageClass; // ClassName对应的Class IsEnumValidFunc _isEnumValidFunc; // 检测枚举值是否合法函数指针 }; } PBFieldInfo; 另外通过无用selector列表,发现不少pb类属性的getter或setter没有被使用。原先的pb类属性是用@synthesize修饰,编译器会自动生成getter和setter。如果不想编译器生成,则要用@dynamic。甚至我们可以把pb类的成员变量去掉。做法如下:
class PBClassInfo { public: PBClassInfo(Class cls, PBFieldInfo* fieldInfo); ~PBClassInfo(); public: unsigned int _numberOfProperty; std::string* _propertyNames; size_t* _propertyNameHashes; std::string* _getterObjCTypes; std::string* _setterObjCTypes; PBFieldInfo* _fieldInfos; }; @interface WXPBGeneratedMessage () { uint32_t _has_bits_[3]; // 最多96个属性,表示属性是否有赋值 int32_t _serializedSize; PBClassInfo* _classInfo; id* _ivarValues; } - (NSMethodSignature*) methodSignatureForSelector:(SEL) aSelector; - (void) forwardInvocation:(NSInvocation*) anInvocation; - (void) setValue:(id) value forUndefinedKey:(NSString*) key; - valueForUndefinedKey:(NSString*) key; @end 把冗余代码去掉后,整个类清爽多了。像GameResourceReq只有3个属性的proto结构体,类方法代码行数由以前的127行变成现在的8行。protobuf精简改造中,精简类方法减少了可执行文件8.8M,去掉类成员变量和类属性改用@dynamic减少了2.5M。 message GameResourceReq { required BaseRequest BaseRequest = 1; required int32 PropsCount = 2; repeated uint32 PropsIdList = 3[packed=true]; } // 老实现 @implementation GameResourceReq @synthesize hasBaseRequest; @synthesize baseRequest; @synthesize hasPropsCount; @synthesize propsCount; @synthesize mutablePropsIdListList; @dynamic propsIdList; - (id) init {...} - (void) SetBaseRequest:(BaseRequest*) value {...} - (void) SetPropsCount:(int32_t) value {...} - (NSArray*) propsIdListList {...} - (NSMutableArray*)propsIdList {...} - (void)setPropsIdList:(NSMutableArray*) values {...} - (BOOL) isInitialized {...} - (void) writeToCodedOutputStream:(PBCodedOutputStream*) output {...} - (int32_t) serializedSize {...} + (GameResourceReq*) parseFromData:(NSData*) data {...} - (GameResourceReq*) mergeFromCodedInputStream:(PBCodedInputStream*) input {...} - (void) addPropsIdList:(uint32_t) value {...} - (void) addPropsIdListFromArray:(NSArray*) values {...} @end // 新实现 @implementation GameResourceReq PB_PROPERTY_TYPE baseRequest; PB_PROPERTY_TYPE opType; PB_PROPERTY_TYPE brandUserName; + (void) initialize { static PBFieldInfo _fieldInfoArray[] = { {1, FieldLabelRequired, FieldTypeMessage, NO, 0, ._messageClassName = STRING_FROM(BaseRequest)}, {2, FieldLabelRequired, FieldTypeInt32, NO, 0, 0}, {3, FieldLabelRepeated, FieldTypeUint32, NO, 0, 0}, }; initializePBClassInfo(self, _fieldInfoArray); } @end 4选择合理的编译设置
5其他可能的优化
6建立版本增量日志通过对LinkMap文件的分析,可以得知每个模块可执行文件占用大小。再对比两个版本,就知道业务模块的增量大小。参考如下: 全站即时通讯技术资料分类[1] 网络编程基础资料: [2] 有关IM/推送的通信格式、协议的选择: [3] 有关IM/推送的心跳保活处理: [4] 有关WEB端即时通讯开发: [5] 有关IM架构设计: [6] 有关IM安全的文章: [7] 有关实时音视频开发: [8] IM开发综合文章: [9] 开源移动端IM技术框架资料: [10] 有关推送技术的文章: [11] 更多即时通讯技术好文分类: 附录:有关QQ、微信的文章汇总[1] 有关QQ、微信的技术文章: [2] 有关QQ、微信的技术故事: (原文链接:点此查看) |
来源: – 即时通讯开发者社区! |
这篇文章之前看过。可以借鉴的主要是编译选项的设置,这倒是可以试试。 其它涉及代码的删减和优化主要跟代码的实现联系一起,最好还是能具体项目具体分析。 安装包减重总归是有办法的,只要死磕它。 |
iOS的安装包大小确实太夸张了,动不动一个IPA就是几十M,尤其需要同时兼容32位和64位,几乎是双倍大小。这确实很恶心。 |
使用图片资源的时候不需要@2x和@3x的图片了么? 那是不是说现在的app里只要一套图片就可以了? 求解~~ |
WeChat的工程名竟然是MicroMessenger |
另一种是通过otool命令逆向__DATA.__objc_classlist段和__DATA.__objc_classrefs段来获取当前所有oc类和被引用的oc类,两个集合相减就是无用oc类。 不知道这个咋操作??哭哭哭 |
楼主, 我最近在公司也是做的这么一个工作, 但是我的任务只是排布.o文件大小, 删除资源, 当然我是用的笨办法,关于下面这一段,读者看了还是不会操作, 你可再详细指导一下, 我的qq:914078998 多谢了。 结合LinkMap文件的__TEXT.__text,通过正则表达式([+|-][.+/s(.+)]),我们可以提取当前可执行文件里所有objc类方法和实例方法(SelectorsAll)。再使用otool命令otool -v -s __DATA __objc_selrefs逆向__DATA.__objc_selrefs段,提取可执行文件里引用到的方法名(UsedSelectorsAll),我们可以大致分析出SelectorsAll里哪些方法是没有被引用的(SelectorsAll-UsedSelectorsAll)。注意,系统API的Protocol可能被列入无用方法名单里,如UITableViewDelegate的方法,我们只需要对这些Protocol里的方法加入白名单过滤即可。 |
你理解错了文章里的意思了:作者说要要把@2x、@3x的图片的删除,意思是在删除不带@2x或@3x尾巴的图片时,不要忘了把@2x、@3x的同名高清图也删除,而不是说app里不需要这两种图了。 |
就是通过otool找出2个集合,相减得出的地址再去LinkMap file找出类名称。<br>而且还要减去__objc_superrefs段。<br>还存在2种误报情况:<br>1.NSClassFromString(“ABCClass”)动态调用;<br>2.被间接继承的基类; |
MicroMessenger 这是标准的名称 |
最近正好在关注瘦身相关技术文档,👍 |
原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/tech/pnotes/314498.html