目录
- 对Objective-C语言的理解
- iOS 程序 main 函数之前发生了什么
- 静态库与动态库
- 系统框架图
1. 对Objective-C语言的理解
编程的核心
编程的无非两件事,数据和运算。
放在计算机硬件,是内存和CPU;
放在C语言,是结构体和函数(基本类型本质上就是一个只有一个字段的结构体);
放在面向对象的语言,是类和消息;
放在函数式语言,就是值和函数了
objc所有类和对象都是c结构体,category当然也一样
oc关于对象模型的设计
消息机制就不一样了,要实现向一个 target ( class / instance ) 发送消息名 ( selector ) 动态寻找到函数实现地址 ( IMP ) 并调用的过程,还要处理消息向父类传递、消息转发( Smalltalk 中叫 “Message-Not-Understood”)等,
便形成了最原始的 Runtime。
所以最初的 Objective-C = C + Preprocessor(预处理器,Clang) + Runtime
Runtime 中只要实现几个最基础的函数(如 objc_msgSend)即可
Runtime 的精髓并非在于平日里很少接触的那些所谓“黑魔法” Runtime API、也并非各种 Swizzle 大法
2. iOS 程序 main 函数之前发生了什么
一个 iOS App 的 main 函数位于 main.m 中,这是我们熟知的程序入口。但对 objc 了解更多之后发现,程序在进入我们的 main 函数前已经执行了很多代码,比如熟知的 + load 方法等。本文将跟随程序执行顺序,刨根问底,从 dyld 到 runtime ,看看 main 函数之前都发生了什么。
动态链接库
iOS 中用到的所有系统 framework 都是动态链接的,类比成插头和插排,静态链接的代码在编译后的静态链接过程就将插头和插排一个个插好,运行时直接执行二进制文件;而动态链接需要在程序启动时去完成“插插销”的过程,所以在我们写的代码执行前,动态连接器需要完成准备工作。
这些 framework 将会在动态链接过程中被加载
清楚的看到整个调用栈和顺序:
dyld 开始将程序二进制文件初始化
交由 ImageLoader 读取 image,其中包含了我们的类、方法等各种符号
由于 runtime 向 dyld 绑定了回调,当 image 加载到内存后,dyld 会通知 runtime 进行处理
runtime 接手后调用 map_images 做解析和处理,接下来 load_images 中调用 call_load_methods 方法,遍历所有加载进来的 Class,按继承层级依次调用 Class 的 +load 方法和其 Category 的 +load 方法
至此,可执行文件中和动态库所有的符号(Class,Protocol,Selector,IMP,…)都已经按格式成功加载到内存中,被 runtime 所管理,再这之后,runtime 的那些方法(动态添加 Class、swizzle 等等才能生效)
简单总结
整个事件由 dyld (动态链接器)主导,完成运行环境的初始化后,配合 ImageLoader 将二进制文件按格式加载到内存,
动态链接依赖库,并由 runtime 负责加载成 objc 定义的结构,所有初始化工作结束后,dyld 调用真正的 main 函数。
值得说明的是,这个过程远比写出来的要复杂,这里只提到了 runtime 这个分支,还有像 GCD、XPC 等重头的系统库初始化分支没有提及(当然,有缓存机制在,它们也不会玩命初始化),总结起来就是 main 函数执行之前,系统做了茫茫多的加载和初始化工作,但都被很好的隐藏了,我们无需关心。
3. 静态库与动态库
库简介:
- 库是共享程序代码的方式,一般分为静态库和动态库。
- 库作用:模块化,方便共享代码,便于合理使用,开发第三方sdk,别人看不到源码
- 苹果不让使用自己的动态库,否则审核就无法通过
区别:
静态库:
- 链接时完整地拷贝至可执行文件中,被多次使用就有多份冗余拷贝。
- .a和.framework(自己制造的)
动态库:
- 链接时不复制,程序运行时由系统动态加载到内存,供程序调用,系统只加载一次,多个程序共用,节省内存。
- .tbd(以前叫.dylib)和.framework(系统)
a与.framework区别:
.a + .h + sourceFile = .framework
- .a是一个纯二进制文件,不能直接使用,要有.h文件配合
- .framework中除了有二进制文件之外还有资源文件, 可以直接使用。
指令集:
平时项目开发中,可能使用第三方提供的静态库.a,如果.a提供方技术不成熟,使用的时候就会出现问题,例如:
在真机上编译报错:No architectures to compile for (ONLY_ACTIVE_ARCH=YES, active arch=x86_64, VALID_ARCHS=i386).
在模拟器上编译报错:No architectures to compile for (ONLY_ACTIVE_ARCH=YES, active arch=armv7s, VALID_ARCHS=armv7 armv6).
要解决以上问题,就要了解一下Apple移动设备处理器指令集相关的一些细节知识。
ARM
ARM处理器,特点是体积小、低功耗、低成本、高性能,所以几乎所有手机处理器都基于ARM,在嵌入式系统中应用广泛。
ARM处理器指令集
- iPhone处理器的指令集:
armv6|armv7|armv7s|arm64都是ARM处理器的指令集,这些指令集都是向下兼容的,例如armv7指令集兼容armv6,只是使用armv6的时候无法发挥出其性能,无法使用armv7的新特性,从而会导致程序执行效率没那么高。 机器对指令集的支持是向下兼容的,因此armv7的指令集是可以运行在iphone5S的,只是效率没那么高而已。 - Mac处理器的指令集:
i386|x86_64 ,i386是针对intel通用微处理器32架构的。x86_64是针对x86架构的64位处理器。所以当使用iOS模拟器的时候会遇到i386|x86_64,iOS模拟器没有arm指令集。
目前iOS移动设备指令集
arm64: iPhone7 | iPhone6 |iPhone5S| iPad Air| iPad mini2(iPad mini with Retina Display)
armv7s:iPhone5|iPhone5C|iPad4(iPad with Retina Display)
armv7:iPhone3GS|iPhone4|iPhone4S|iPad|iPad2|iPad3(The New iPad)|iPad mini|iPod Touch 3G|iPod Touch4
armv6 设备: iPhone, iPhone2, iPhone3G, 第一代、第二代 iPod Touch(一般不需要去支持)
制作静态库:(后续...)
- .a文件
- .framework文件