0. Runloop
关于runloop部分我就在稍微提一下,可以优酷上搜索孙源Runloop iOS线下分享《RunLoop》by 孙源@sunnyxx,配合孙源博客的 深入理解RunLoop,再去git或其他地方找些案例练习一下, 优酷上的视频是重点,网上很多关于runloop的讲解或多或少都有问题.
本篇文章主要从实操角度出发,切入点分析,切勿死记硬背。例如runtime消息打印的msgSend文件内容是什么,为什么会有object_setClass,method_setImplementation两种不同的方法; 什么情况会用到动态方法解析,什么时候用消息发送消息转发.
- 消息发送机制: 使用了运行时, 通过selector快速去查找IMP的一个过程.
- 消息转发机制: IMP找不到的时候, 通过一些方法做转发处理
runtime应用参考:www.jianshu.com/p/6bcff1f9f…
强烈建议学完以后好好读一遍英文源码: developer.apple.com/library/arc…
1. Runtime基础知识
2. OC方法底层调用过程
3. 消息发送
4. 动态方法解析
5. 消息转发
6. 应用
1. Runtime基础知识
现行版本: iPhone程序和Mac OS X v10.5及以后的系统中的64位程序
和运行时系统的交互:
程序执行的大致过程: 代码--> 编译链接--->执行
c语言,程序编译成什么,运行就调用什么
oc语言,动态语言,程序编译成什么,程序运行的过程中可以动态改变(动态改变方法调用,动态添加方法
)
具体代码在 github.com/tanghaitao/…
2. OC方法底层调用过程
借助clang编译器,在终端执行命令 clang -rewrite-objc main.m
然后将main.cpp文件拖入到工程,记得 取消勾选target,否则会参与编译compile sources
具体代码在 github.com/tanghaitao/…
objc_msgSend(p, sel_registerName("walk"));
NSLog(@"%p %p",@selector(walk),sel_registerName("walk"));
//0x100003fa2 0x100003fa2
从上面可以得出:
oc方法调用,实际上是objc_msgSend函数的调用。
本质是消息机制: 消息接收者 消息名称
objc_msgSend的执行过程大致可以分成三部分
1.消息发送
2.动态方法解析
3.消息转发
2.1 消息发送
下面我将结合objc源码 github.com/tanghaitao/… 对objc_msgSend消息发送底层做一个详细的分析:
objc底层源码主要由 汇编
c
c++
组成。
如上图所示:搜索objc_msgSend
找到objc-msg-arm64.s 汇编文件
,ENTRY _objc_msgSend 表示入口
x0 消息接收者 消息名称,比较nil check and tagged pointer check,如果没有消息接收者则返回。
接着找x13 =isa(此处的isa 不止是指针,多了一个与操作),找到了类。
类的缓存中找,CacheLookup Normal
如果找到了(即Hit),就从缓存中找,CacheHit
,如下图所示:
缓存中直接调用imp
如果没有找到(即CheckMiss)
,就__objc_msgSend_uncached
搜索 ___objc_msgSend_uncached
,找到 STATIC_ENTRY __objc_msgSend_uncached
,从MethodTableLookup
方法列表里面查找
接着调用 __class_lookupMethodAndLoadCache3
,搜索后发现搜索不到,因为从汇编进入了c,少一根下划线才能搜索得到
。
上述是通过源码对objc_msgSend的底层原理做详细的分析,下面是总结:
2.2 动态方法解析
如果从类的缓存和方法列表
,以及父类的缓存和方法列表
里面都没找到imp
,会执行一次动态方法解析
,然后 goto retry
,再从类的缓存和方法列表
,以及父类的缓存和方法列表
这样的步骤去查找一次。
动态方法解析过程如下图:
如果不是元类,就会调用实例方法_class_resolveInstanceMethod
如果是元类,就会调用类方法 _class_resolveClassMethod
单纯看源码太枯燥,下面结合具体的例子做讲解: 具体代码在github.com/tanghaitao/…
关于 实例对象, 类对象、元类对象我做了一个简单的总结,如下:
// 实例对象、 类对象、 元类对象,
// p 、[p class], object_getClass("类名")== 等价于if(p) p->getIsa()
// 实例对象isa找到类,类通过isa找到元类,元类通过is找到根元类,
// 实例对象存的是特殊的实例方法。 [p method0]
// 类对象存的是所有特殊实例的方法 - 减号 [p1 method0] [p2 method0] [p3 method0]
// 元类对象存的是所有的类方法。 + 加号[Person method1];
动态方法解析: 实例方法 c方法和oc方法
具体代码在github.com/tanghaitao/…
类方法的动态方法解析:
具体代码在github.com/tanghaitao/…
2.3 消息转发
如果消息执行imp
和动态方法解析``都没有找到
,就会进入消息转发_objc_msgForward_impcache
消息转发源码在objc源码中没有暴露,需要通过api打印出具体的消息转发流程,(当然你也可以使用砸壳、hopper等工具逆向得出,个人不推荐):
具体代码在github.com/tanghaitao/…
extern void instrumentObjcMessageSends(BOOL);
int main(int argc, const char * argv[]) {
@autoreleasepool {
instrumentObjcMessageSends(YES);
[[TZPerson new] walk];
instrumentObjcMessageSends(NO);
}
return 0;
}
打印结果会输出到 /private/tmp/msgSend-XXX
的文件中。先找到/private/tmp
,然后定位到末尾
,再运行xcode工程,即可发现新添加的msgSend-XXX文件
。
声明一个walk的实例方法-(void)walk
,[[Person new] walk]
;运行后没有imp,动态方法解析resolve也没有
,进入消息转发流程
msgSends-xxx具体内容:
类方法的消息转发 具体代码在
github.com/tanghaitao/…
关于实例方法和类方法的 消息转发的不同点:
实例方法只能转发到实例方法
类方法可以转发到实例方法和类方法
3. API 应用
关于runtime的api 主要的代码: github.com/tanghaitao/…
例子代码在 github.com/tanghaitao/…
建议大家大概的跑一下,比较基础,不要觉得很难,其实就是一些API的调用而已。
3.1 类的应用
下面分析一下objc_class的源码
class_rw_t
表示可以读写
,表示 在objc_registerClassPair类注册前和注册后都可以添加
。
class_ro_t
表示 只读 read only
,表示 只能在
objc_registerClassPair类注册前
添加
从下面图可以看出,实例变量ivars是class_ro_t结构体中,只读,所以在类注册后,不能直接添加实例变量了,当然可以通过runtime的方式变相的添加。
3.2 方法的应用
tableview的设置,页面统计,MLeakFinder,数组越界保护等等都有应用。
3.3 属性变量的应用
看案例github.com/tanghaitao/… 此处省略一千字