App的启动一般是指从用户点击App开始到AppDelegate
的didFinishLaunching
方法执行完成为止,一般又将启动分为冷启动和热启动。
冷启动: 是指App启动前它的进程不在系统里,需要系统分配一个进程给它启动的情况,这是一次完成的启动(一般启动优化都是优化冷启动的过程)
热启动: 是指App在冷启动后将App退后台,App的进程还在系统里,内存中海油App的数据的情况下,再次启动App的过程,这个过程做的事情也非常少
上文也说了一般启动优化主要优化的是冷启动的过程,热启动做的事情也非常少。所以这里只讲解冷启动过程的优化。冷启动过程又被分为main
函数执行之前和main
函数执行之后- ##### main
函数执行之前
操作系统加载App可执行文件到内存,执行一系列的加载&链接工作,可以通过添加添加环境变量DYLD_PRINT_STATISTICS
来查看main
函数执行之前都做了什么,同时也可以看出对应消耗的时间
不难发现main
函数执行之前主要做了以下几种事情
动态库的加载 对应的是dylib loading time
可以发现加载时间为48.41毫秒
优化建议 : 这里主要的优化建议是减少动态库的加载,苹果公司建议更少的使用动态库,并且建议动态库的数量较多的时候,尽量将多个动态库合并,数量上苹果公司最多支持6个非系统动态库的合并
偏移修正和符号绑定 对应的是rebase/binding time
,耗时9.18毫秒
偏移修正 任何App生成的二进制文件中的方法、函数都会有个地址,而这个地址是相对于当前二进制文件中的偏移地址,但是到了运行时系统会随机生成一个数值添加到二进制文件的头部(ASLR
安全机制下文中会有讲解),所以此时函数、方法的地址就是 随机分配的数值+偏移地址 这个过程就是偏移修正
符号绑定 动态库不像是静态库,静态库实在编译时期就将对应使用到的代码一起打包生成了mach-o
文件,所以此时使用到的静态库的方法、函数其实就和自定义的方法、函数差不多了,能够直接获取到对应的地址,
但是动态库在编译阶段是不会被打包进mach-o
文件的,但是此时又用到了动态库中的方法,例如用到了NSLog
方法,此时就会生成一个!NSLog
符号此时这个符号会随机指向一个地址,
当运行时,此时动态库被加载到内存,此时就可以拿到动态库对应的方法、函数的地址,所以此时就需要将!NSLog
这个符号绑定到相应的地址上去(dyld
做的),这个过程就叫做符号绑定
类的注册
对应的是ObjC setup time
,耗时10.86毫秒
优化建议
删除启动后不会去使用的类
执行load和构造函数
对应的是initializer time
,耗时110.79毫秒
优化建议 减少使用load
方法相应的可以将load
中的实现放在+initialize()
方法中去,应为一般一个load
方法的执行需要耗时4毫秒,而且如果类中实现了load
那么相对应类的加载就要提前到read_image
方法中去执行,如果没有实现load
类的加载则会方法第一次发送消息的时候加载,
main
函数执行之后这个阶段主要是指main函数执行开始到首屏渲染完成方法执行完毕。这个阶段主要做的工作包括:
第三方SDK初始化
自定义工具类初始化
首屏数据的加载
首屏渲染的一些计算这个地方的优化建议主要有一下几点
上文主要是针对特定的阶段做一些优化处理,除了删除的优化方案还有一种优化,就是二进制重排,在讲解二进制重排之前先将几个概念性的东西:
就是运行内存,是指计算机上安装的内存,通俗的将其实就是内存条的大小。 早期的操作系统没有虚拟内存,程序寻址用的都是物理地址,所以没启动一个程序开辟一个进程都要相应的分配一段物理内存给这个程序,这就造成了如下几个问题:
a. 当物理内存被分配完成的时候此时其他程序就不能再被加载到内存(也就是不能运行),此时就需要等待其他程序退出释放内存,此时才能运行新的程序
b. 程序指令都是在物理内存上操作的,那么我这个进程就可以修改其他进程的数据,甚至会修改内核地址空间的数据针对以上的问题也就引出了虚拟内存
指的是把硬盘中的一部分空间用来当做内存使用 进程和物理内存之间增加一个中间层,这个中间层就是所谓的虚拟内存,主要用于解决当多个进程同时存在时,对物理内存的管理。提高了CPU的利用率,使多个进程可以同时、按需加载。
所以虚拟内存其本质就是一张虚拟地址和物理地址对应关系的映射表.每个进程都有一个独立的虚拟内存,其地址都是从0开始,大小是4G固定的。 进程开始要访问一个地址,它可能会经历下面的过程:
a. 每次我要访问地址空间上的某一个地址,但是进程间是无法互相访问的,保证了进程间数据的安全(一个进程只能访问给定的这篇虚拟内存的地址)。都需要把地址翻译为实际物理内存地址
b. 所有进程共享这整一块物理内存,每个进程只把自己目前需要的虚拟地址空间映射到物理内存上
c. 每个虚拟内存会划分一个一个页存储(页的大小在iOS中是16K,其他的是4K),进程需要知道哪些地址空间上的数据在物理内存上,哪些不在(可能这部分存储在磁盘上),还有在物理内存上的哪里,这就需要通过页表来记录
d. 页表的每一个表项分两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址(如果在的话)
e. 当进程访问某个虚拟地址的时候,就会先去看页表,如果发现对应的数据不在物理内存上,就会发生缺页异常
f. 缺页异常的处理过程,操作系统立即阻塞该进程,并将硬盘里对应的页换入内存,然后使该进程就绪,如果内存已经满了,没有空地方了,那就找一个页覆盖,至于具体覆盖的哪个页,就需要看操作系统的页面置换算法是怎么设计的了。
如下图所示,虚拟内存与物理内存间的关系 如果物理内存被占满,此时又有新的页需要被加载进来,此时新页就会吧长时间没有使用的页覆盖掉
3. ASLR
应为虚拟内存的起始地址与大小都是固定的,这意味着,当我们访问时,其数据的地址也是固定的,这会导致我们的数据非常容易被破解, 为了解决这个问题,所以苹果为了解决这个问题,在iOS4.3开始引入了ASLR技术,其实现原理就是在虚拟内存的头部随机加上一块地址,这样每次启动时虚拟地址的其实址就不一样,所以在程序启动的时候需要做偏移修正。
从上文的知识中可以知道,ios程序在加载到虚拟内存的时候会被分成很多很多页,如果此时访问的虚拟地址的一个page,对应的物理地址不存在,则会缺页异常,此时会阻塞进程将这一页加载到物理内存然后在访问。
这里可以通过instruments
的System Trace
来查看你的项目的缺页异常的数量如下:
步骤:先点击启动->首页加载完成后暂停->然后找到你的项目找到主线程
发现启动之前有两百多个缺页异常,此时我们再看项目在编译时期的默认排列顺序,此时我们写一个简单的demo如下图:
就是写了几个简单的方法,然后项目中选择Build-setting
搜索link map
然后配置
此时会发现对应配置的文件夹中生成了对应的link-map
文件,
发现方法、函数等都是按照在文件中的实现顺序来的,而文件的顺序是按照comple source
中的顺序来的如图:
这种情况就造成了每个页有可能只有一个方法是有用的,其他方法、函数等都不是在启动阶段调用的,这就造成了在启动时期缺页异常的数量会很多,也就造成了启动时间变长的情况。
这也就是需要进行二进制重排的原因
上文分析了二进制重排的原因,就是应为页中空间的浪费没有充分利用每个页的空间造成缺页异常数量增多,二进制重排的原理其实就是将启动阶段用到的方法、函数全部排在最前面,这样就能充分利用每个页的空间,与此同时也降低了缺页异常的数量。如下图所示:
明显减少了一大半的缺页异常的数量
通过上面的原理分析可以知道,如果做二进制重排只需要改变编译时期方法、函数等的排列顺序就行。其本质就是就是对启动加载的符号进行重新排列。
Xcode
是用的链接器叫做ld
,ld
有一个参数叫Order File
, 我们可以通过这个参数配置一个 order
文件的路径 .
我们可以通过在Build Settings
-> Order File
配置一个后缀为order
的文件路径。在这个order
文件中,将所需要的符号按照顺序写在里面,在项目编译时,会按照这个文件的顺序进行加载,以此来达到我们的优化,所以二进制重排的关键点在于Order File
文件的生成Order File
文件的方法Order File
文件。hook``objc_msgSend
,但是由于objc_msgSend
的参数是可变的,需要通过汇编获取,使用门槛比较高。而且也只能拿到OC和swift中@objc
后的方法Mach-O
特定段和节里面所存储的符号以及函数数据Clang
插桩:即批量hook,可以实现100%符号覆盖,即完全获取swift、OC、C、block
函数Clang
插桩
llvm
内置了一个简单的代码覆盖率检测(SanitizerCoverage
)。它在函数级、基本块级和边缘级插入对用户定义函数的调用,相应文档
具体步骤:SanitizerCoverage
,在build setting
中搜索Other C Flags
,如下图
如果是OC项目则添加-fsanitize-coverage=func,trace-pc-guard
,如果是swift项目则添加-sanitize-coverage=func
和-sanitize=undefined
2 . 添加hook
方法
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
uint32_t *stop) {
static uint64_t N; // Counter for the guards.
if (start == stop || *start) return; // Initialize only once.
printf("INIT: %p %p\n", start, stop);
for (uint32_t *x = start; x < stop; x++)
*x = ++N; // Guards should start from 1.
}
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
//guard 是一个哨兵,告诉我们是第几个被调用的
// 这个地方 是过滤掉了load方法,所以这里需要注释掉
if (!*guard) return;
/*
- PC 当前函数返回上一个调用的地址
- 0 当前这个函数地址,即当前函数的返回地址
- 1 当前函数调用者的地址,即上一个函数的返回地址
*/
void *PC = __builtin_return_address(0);
char PcDescr[1024];
printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}
主要的方法在于__sanitizer_cov_trace_pc_guard
方法,在这里我们可以取到对应方法的地址,为什么方法执行之前会先调用__sanitizer_cov_trace_pc_guard
方法呢,可通过断点调试查看,在一个方法或者函数的起始处大断点,再看汇编代码如下图:
发现在方法执行之前插入了__sanitizer_cov_trace_pc_guard
方法,所有的函数执行都会限制性__sanitizer_cov_trace_pc_guard
方法,在block前面也打个断点发现
block
执行前也会被插入__sanitizer_cov_trace_pc_guard
方法,继续查看swift-oc
混编是swift
方法是否会被hook
也会被hook
,所以也验证了clang插桩的方法能覆盖所有方法、函数。
3. 获取符号 上述hook
方法中我们知道可以拿到当前方法或者函数的地址,拿到地址之后我们可以通过dladdr
方法去除对应方法或者函数的信息具体代码如下图:
发现dli_sname
就是我们想要的符号,接下来的操作主要就是把这些符号存储下来然后生成order然后工程再配置对应的Order file
就算完成了。
4. 输出order文件
上文中已经可以拿到符号了,最后的工作就是输出order文件。
具体思路:我们可以在__sanitizer_cov_trace_pc_guard
将函数地址信息存储下来然后给app
添加一个点击屏幕的监听事件,等到首屏加载完毕说明启动完成所有所需要加载的方法也就加载完成,此时我们再在这个方法遍历地址信息,输出符号。
我这里借用的链表存储,所以先要建立一个节点如下图:
然后再通过OSQueueHead
创建原子队列,其目的是保证读写安全。
通过OSAtomicEnqueue
方法将node
入队,通过链表的next
指针可以访问下一个符号
此刻地址的储存完成下一步就是读取写入order文件:具体代码如下
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//定义数组
NSMutableArray<NSString *> * symbolNames = [NSMutableArray array];
while (YES) {//一次循环!也会被HOOK一次!!
SYNode * node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next));
if (node == NULL) {
break;
}
Dl_info info = {0};
dladdr(node->pc, &info);
// printf("%s \n",info.dli_sname);
NSString * name = @(info.dli_sname);
free(node);
BOOL isObjc = [name hasPrefix:@"+["]||[name hasPrefix:@"-["];
//需要注意如果不是OC方法需要添加下划线
NSString * symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
[symbolNames addObject:symbolName];
}
//反向数组
NSEnumerator * enumerator = [symbolNames reverseObjectEnumerator];
//创建一个新数组
NSMutableArray * funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
NSString * name;
//去重!
while (name = [enumerator nextObject]) {
if (![funcs containsObject:name]) {//数组中不包含name
[funcs addObject:name];
}
}
[funcs removeObject:[NSString stringWithFormat:@"%s",__FUNCTION__]];
//数组转成字符串
NSString * funcStr = [funcs componentsJoinedByString:@"\n"];
//字符串写入文件
//文件路径
NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tudou.order"];
//文件内容
NSData * fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
}
运行完成发现生成了order文件 5. Xcode配置order文件 如图配置文件 6. 查看二进制重排结果 最后同样的查看生成的link map文件: 没有二进制重排之前: 发现是按照文件按照方法的顺序来的。 二进制重排之后: 发现此时就是按照我们的order文件的顺序来的
- EOF -
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/NgGUL09rGxxj74zqE9uRYQ
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。