一些名词
mach-O
哪些名词指的是Mach-O
Executable 可执行文件
Dylib 动态库
Bundle 无法被连接的动态库,只能通过dlopen()加载
Image 指的是Executable,Dylib或者Bundle的一种
Framework 动态库和对应的头文件和资源文件的集合
Virtual Memory
虚拟内存是在物理内存上建立的一个逻辑地址空间,它向上(应用)提供了一个连续的逻辑地址空间,向下隐藏了物理内存的细节。
虚拟内存使得逻辑地址可以没有实际的物理地址,也可以让多个逻辑地址对应到一个物理地址。
虚拟内存被划分为一个个大小相同的Page(64位系统上是16KB),提高管理和读写的效率。 Page又分为只读和读写的Page。
虚拟内存是建立在物理内存和进程之间的中间层。在iOS上,当内存不足的时候,会尝试释放那些只读的Page,因为只读的Page在下次被访问的时候,可以再从磁盘读取。如果没有可用内存,会通知在后台的App(也就是在这个时候收到了memory warning),如果在这之后仍然没有可用内存,则会杀死在后台的App。
Page fault
在应用执行的时候,它被分配的逻辑地址空间都是可以访问的,当应用访问一个逻辑Page,而在对应的物理内存中并不存在的时候,这时候就发生了一次Page fault。当Page fault发生的时候,会中断当前的程序,在物理内存中寻找一个可用的Page,然后从磁盘中读取数据到物理内存,接着继续执行当前程序。
从dyld开始
官网说明:
用于Darwin / OS X的动态加载程序称为dyld,它负责加载进程所需的所有框架,动态库和包(插件)。
什么是动态库:
iOS为了节省空间,将系统框架以动态库的形式,保存在dyld中,这样每个app都能使用这些库.也不需要每个app中都包含这些库.只需要在使用时调用就行.这个手机中就保存了一份.大大节省了内存.
动态库的形式:
动态库形式:.dylib和.framework
静态库形式:.a和.framework
dyld会首先读取mach—o文件的Header和load commands,每个程序依赖的动态库都需要通过dyld(位于/usr/lib/dyld)一个一个加载到内存,然而,很多系统库几乎是每个程序都会用到的,如果在每个程序运行的时候都重复的去加载一次,势必造成运行缓慢,为了优化启动速度和提高程序性能,共享缓存机制就应运而生。所有默认的动态链接库被合并成一个大的缓存文件,放到/System/Library/Caches/com.apple.dyld/目录下,按不同的架构保存分别保存着,原博客作者的iPhone6里面就有dyld_shared_cache_armv7s和dyld_shared_cache_armv64
- 加载dyld到App进程
2.加载动态库(包括所依赖的所有动态库)
3.Rebase
4.Bind
5.初始化Objective C Runtime
6.其它的初始化代码
创建一个动态库:可以跳过这部分。
framework可以是动态库也可以是静态库,默认是动态库。
虽然在iOS8之后我们可以创建动态库,但是用户的动态库也只能自己用用,所以相当于还是静态库。
在这个.h文件中
注释提醒我们,应该把要暴露的公共类写在这里。
在test类里我写了一个打印日期的方法。
可以看到public里只有一个项目.h文件,我们要使用test中的方法,就要把它拖到上面去。
然后选择真机或Genneric iOS Device
2.3.利用lipo -info 查看动态库所支持的CPU指令集,步骤如下
打开终端
cd 进入MangoSDK.framework,这里需要注意进入的是MangoSDK.framework,而不是MangoSDK.framework所在目录
在终端输入lipo -info staticLibTest
info后面是framework名字
通过以上三个步骤后,在终端会显示出MangoSDK.framework所支持的CPU指令集。
Architectures:指明选定Target要求被编译生成的二进制包所支持的指令集
Build Active Architecture Only: 指明是否只编译当前连接设备所支持的指令集,如果是,那么只编译出连接设备所对应的指令集,如果否,则编译出所有其它有效的指令集(由Architectures和Valid Architectures决定)
Valid Architectures:指明可能支持的指令集并非Architectures列表中指明的指令集都会被支持
编译产生的动态库所支持的指令集将由上面三个编译选项所影响,首先一个动态库要成功编译,则需要这三个编译选项的交集不为空,举几个例子:
示例1:
Architectures 为armv7、arm64
Valid Architectures 为armv7、armv7s、arm64
Build Active Architecture Only 为 debug:YES release:NO
链接设备:iPhone 6s (arm64架构的设备)
编译(command + shift + B,保证Build Active Architecture Only 为 debug:YES 生效)
结果:编译成功,生成的动态库支持的指令集为arm64
示例2:
Architectures 为armv7、arm64
Valid Architectures 为 armv7s
Build Active Architecture Only 为 debug:YES release:NO
链接设备:iPhone 6s (arm64架构的设备)
编译(command + shift + B,保证Build Active Architecture Only 为 debug:YES 生效)
结果:编译失败,因为当前是debug模式,在该模式下Build Active Architecture Only 为YES,表示只编译支持该指令集的动态库,
但是由于Architectures和Build Active Architecture Only的交集中并不存在arm64,故三者的交集为空,故编译失败,无法生成动态库。
示例3:
Architectures 为armv7、arm64
Valid Architectures 为armv7、armv7s、arm64
Build Active Architecture Only 为 debug:NO release:NO
链接设备:iPhone 6s (arm64架构的设备)
编译(command + shift + B,保证Build Active Architecture Only 为 debug:YES 生效)
结果:编译成功,因为当前是debug模式,在该模式下Build Active Architecture Only 为NO,
表示可以编译的结果可能为当前连接的设备所支持的指令集以及其向下兼容的指令集(armv64、armv7s、armv7)
关于debug和release可以自行百度。
刚刚我们查看的指令集为模拟机指令集,是因为我们Build Active Architecture Only debug为YES。
创建所有机型都支持的动态库
我这样写会报错
invalid iOS deployment version ‘–target=armv7s-apple-ios13.5’, iOS 10 is the maximum deployment target for 32-bit targets
我把项目版本改成iOS8.0就解决了,只是不知道原因,根据翻译,iOS10以后不支持32位了?
Build Active Architecture Only debug全为no,它就不会只支持模拟机指令集。
可以看到支持所有的指令集。
然后我们再弄模拟机的指令集。
改成这样然后用模拟机运行。
合并模拟器和真机动态库
这里我用了一个评论区的方法
解决了问题就是好方法
可以看到指令集成了 模拟机+真机。
将framework文件直接拖入新工程
以上为自己创建动态库的过程。
Rebase && Bind
这里设计到app提交到store的一些东西,
ASLR的全称是Address space layout randomization,翻译过来就是“地址空间布局随机化”。App被启动的时候,程序会被影射到逻辑的地址空间,这个逻辑的地址空间有一个起始地址,而ASLR技术使得这个起始地址是随机的。如果是固定的,那么黑客很容易就可以由起始地址+偏移量找到函数的地址。
其他的,暑假再了解吧。
OC
Objective C是动态语言,所以在执行main函数之前,需要把类的信息注册到一个全局的Table中。同时,Objective C支持Category,在初始化的时候,也会把Category中的方法注册到对应的类中,同时会唯一Selector,这也是为什么当你的Cagegory实现了类中同名的方法后,类中的方法会被覆盖。
另外,由于iOS开发时基于Cocoa Touch的,所以绝大多数的类起始都是系统类,所以大多数的Runtime初始化起始在Rebase和Bind中已经完成。
Initializers
+load方法和initialize
load是在类装载的时候执行,而initialize是在类第一次收到message前调用。
2017的dyld3它来了,在iOS13引入
dyld2是上面说的,在程序进程内执行的,也就是说只有应用程序被启动的时候,dyld2才能开始执行任务。
而dyld3,有一部分是在app安装和更新的时候就会执行。
- 分析Mach-o Headers
- 分析依赖的动态库
- 查找需要Rebase & Bind之类的符号
- 把上述结果写入缓存
这样,在应用启动的时候,就可以直接从缓存中读取数据,加快加载速度。
main函数之后
我们首先来分析下,从main函数开始执行,到你的第一个界面显示,这期间一般会做哪些事情。
执行AppDelegate的代理方法,主要是didFinishLaunchingWithOptions
初始化Window,初始化基础的ViewController结构(一般是UINavigationController+UITabViewController)
获取数据(Local DB/Network),展示给用户。
优化UIViewController
延迟初始化那些不必要的UIViewController。