iOS | runtime和runloop

1,871

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…

image.png

1. Runtime基础知识
2. OC方法底层调用过程
3. 消息发送
4. 动态方法解析
5. 消息转发
6. 应用

1. Runtime基础知识

现行版本: iPhone程序和Mac OS X v10.5及以后的系统中的64位程序 和运行时系统的交互:

image.png

程序执行的大致过程: 代码--> 编译链接--->执行

c语言,程序编译成什么,运行就调用什么

oc语言,动态语言,程序编译成什么,程序运行的过程中可以动态改变(动态改变方法调用,动态添加方法)

image.png

具体代码在 github.com/tanghaitao/…

2. OC方法底层调用过程

借助clang编译器,在终端执行命令 clang -rewrite-objc main.m 然后将main.cpp文件拖入到工程,记得 取消勾选target,否则会参与编译compile sources 具体代码在 github.com/tanghaitao/…

image.png

objc_msgSend(p, sel_registerName("walk"));
NSLog(@"%p %p",@selector(walk),sel_registerName("walk"));
//0x100003fa2 0x100003fa2

image.png

从上面可以得出:

     oc方法调用,实际上是objc_msgSend函数的调用。
    本质是消息机制: 消息接收者  消息名称
    objc_msgSend的执行过程大致可以分成三部分
     1.消息发送
     2.动态方法解析
     3.消息转发

2.1 消息发送

下面我将结合objc源码 github.com/tanghaitao/… 对objc_msgSend消息发送底层做一个详细的分析:

objc底层源码主要由 汇编 c c++组成。

image.png

如上图所示:搜索objc_msgSend 找到objc-msg-arm64.s 汇编文件ENTRY _objc_msgSend 表示入口 x0 消息接收者 消息名称,比较nil check and tagged pointer check,如果没有消息接收者则返回。 接着找x13 =isa(此处的isa 不止是指针,多了一个与操作),找到了类。 类的缓存中找,CacheLookup Normal

如果找到了(即Hit),就从缓存中找,CacheHit,如下图所示:

image.png

缓存中直接调用imp

image.png

如果没有找到(即CheckMiss),就__objc_msgSend_uncached

image.png

搜索 ___objc_msgSend_uncached,找到 STATIC_ENTRY __objc_msgSend_uncached从MethodTableLookup 方法列表里面查找

image.png

接着调用 __class_lookupMethodAndLoadCache3,搜索后发现搜索不到,因为从汇编进入了c,少一根下划线才能搜索得到

image.png

image.png

上述是通过源码对objc_msgSend的底层原理做详细的分析,下面是总结:

image.png

image.png

2.2 动态方法解析

如果从类的缓存和方法列表,以及父类的缓存和方法列表里面都没找到imp,会执行一次动态方法解析,然后 goto retry,再从类的缓存和方法列表,以及父类的缓存和方法列表这样的步骤去查找一次。

image.png

动态方法解析过程如下图:

image.png

如果不是元类,就会调用实例方法_class_resolveInstanceMethod

如果是元类,就会调用类方法 _class_resolveClassMethod

单纯看源码太枯燥,下面结合具体的例子做讲解: 具体代码在github.com/tanghaitao/…

image.png

image.png

关于 实例对象, 类对象、元类对象我做了一个简单的总结,如下:

    // 实例对象、    类对象、       元类对象,
    //    p[p class],    object_getClass("类名")== 等价于if(p) p->getIsa()
    //   实例对象isa找到类,类通过isa找到元类,元类通过is找到根元类,
    // 实例对象存的是特殊的实例方法。   [p method0]
    // 类对象存的是所有特殊实例的方法 - 减号 [p1 method0] [p2 method0] [p3 method0]
    // 元类对象存的是所有的类方法。 + 加号[Person method1];
    
    

image.png

动态方法解析: 实例方法 c方法和oc方法 具体代码在github.com/tanghaitao/…

image.png

image.png

类方法的动态方法解析: 具体代码在github.com/tanghaitao/…

image.png

2.3 消息转发

如果消息执行imp动态方法解析``都没有找到,就会进入消息转发_objc_msgForward_impcache

image.png

image.png

image.png

image.png

消息转发源码在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;
}

image.png

打印结果会输出到 /private/tmp/msgSend-XXX的文件中。先找到/private/tmp,然后定位到末尾,再运行xcode工程,即可发现新添加的msgSend-XXX文件

声明一个walk的实例方法-(void)walk[[Person new] walk];运行后没有imp,动态方法解析resolve也没有,进入消息转发流程

image.png

image.png

msgSends-xxx具体内容:

image.png

image.png

image.png

类方法的消息转发 具体代码在github.com/tanghaitao/…

image.png

关于实例方法和类方法的 消息转发的不同点:
实例方法只能转发到实例方法
类方法可以转发到实例方法和类方法

3. API 应用

关于runtime的api 主要的代码: github.com/tanghaitao/…

例子代码在 github.com/tanghaitao/…

建议大家大概的跑一下,比较基础,不要觉得很难,其实就是一些API的调用而已。

3.1 类的应用

image.png

下面分析一下objc_class的源码

image.png class_rw_t 表示可以读写表示 在objc_registerClassPair类注册前和注册后都可以添加class_ro_t 表示 只读 read only,表示 只能在 objc_registerClassPair类注册前添加 从下面图可以看出,实例变量ivars是class_ro_t结构体中,只读,所以在类注册后,不能直接添加实例变量了,当然可以通过runtime的方式变相的添加。 image.png

3.2 方法的应用

image.png

tableview的设置,页面统计,MLeakFinder,数组越界保护等等都有应用。

image.png

3.3 属性变量的应用

看案例github.com/tanghaitao/… 此处省略一千字