概述: runtime又叫运行时,是一套底层C语言API,是iOS系统的核心之一。开发者在编码过程中,可以给任意一个对象发送消息,在编译阶段只是确定了要向接受着发送这条消息,而接受者如何响应和处理这条消息,就要看运行时来决定了
C语言中,在编译器就确定要调用哪个函数,而OC的函数,属于动态调用过程,在编译器并不能真正决定调用哪个函数,只有在真正的运行时才会根据函数的名称找到对应的函数来调用。OC是一个动态语言,这意味着它不仅要一个编译器,也需要一个运行时系统来动态创建类和对象、进行消息传递和发送
1.消息转发
Runtime的特性主要是消息传递,如果消息在对象中找不到,就进行转发。Objective-C是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态创建类和对象、进行消息传递和转发。Runtime的核心是消息传递。
(1)消息传递的过程
一个对象的方法[obj test],编译器转成消息发送objc_msgSend(obj,test),Runtime执行的流程是这样的
a.首先通过obj的isa指针找到它的class
b.在class的method list找test
c.如果class中没找到test,继续往它的superclass中找
d.一旦找到test这个函数,就去执行它的IMP
由于效率问题,每个消息都遍历一次objc_method_list并不合理,所以需要把经常被调用的函数缓存下来,去提高函数查询的效率。这也就是objc_class中另一个重要的成员objc_cache做的事情。找到test之后,将test的method_name作为key,method_imp作为value。当再次收到test消息的时候,可以直接在cache里找。
类对象(objc_class)
Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。struct objc_class结构体里面定义了很多变量。结构体里保存了指向父类的指针、类的名字(name)、版本、实例变量列表(ivars)、方法列表(methodLists)、缓存(cache)、遵守的协议列表(protocols),由此可见,类对象就是一个结构体struct objc_class,这个结构体存放的数据就是元数据
理解Runtime就是理解iOS在运行时他的数据存储以及他的类、实例、类对象、元类她们之间的关系和作用。
(2)消息转发机制
归根到底,Objective-C中所有的方法调用本质就是向对象发送消息
1.类中创建方法-(void)test
2.iOS系统为这个方法创建一个编号,SEL(test)并添加到方法列表里面
3.当调用这个方法的时候系统去方法列表里查找这个方法,找到了就执行
所以,调用一个方法就会进行一次发送消息也就是在这个类的方法列表里找,如果在该类中找不到就到该类的父类里找,如果父类还找不到就一直搜索到继承树的根部,如果找不到或者消息转发不成功那就会报unrecognized selector错。
1.动态方法解析
Objective-C运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:让你有机会提供一个函数实现,如果你添加了函数并且返回YES,那么运行时就会重新启动一次消息发送的过程。如下图:
虽然没有foo:的实现函数,但是通过class_addMethod()动态添加了fooMethod函数,并执行了这个函数并且打印成功。如果reslove返回NO运行时就会移到下一步:forwardingTargetSelector
2.直接消息转发
如果目标对象实现了forwardingTargetSelector,Runtime这时就会调用这个方法,给你把这个消息转发给其他对象的机会
从图中可以看出我们通过forwardingTargetForSelector方法将当前类的方法转给Father类实现了,打印成功。
3.完整消息转发
如果在上一步还不能处理未知消息,那唯一能做的就是启动消息转发机制。首先它会发送methodSignatureForSelector消息获得函数的参数和返回值类型。如果methodSignatureForSelector返回nil,Runtime则会发出doesNotRecognizeSelector。如果返回一个签名函数,Runtime就会创建一个NSInvocation对象并发送forwardInvocation消息给目标对象。
Runtime的实际应用
1.使用Runtime交换方法
2.动态添加方法(目前不是很懂)
3.给分类添加属性
4.消息转发(热更新)解决Bug(JSPatch)
b、const 关键字:
c、sizeof 关键字:sizeof 是在编译阶段处理,且不能被编译为机器码。sizeof 的结果等于对象或类型所占的内存字节数。sizeof 的返回值类型为 size_t。
d、@dynamic 关键字:意味着编译器不会帮助我们自动合成 setter 和 getter 方法。我们需要手动实现、这里就涉及 到 Runtime 的动态添加方法的知识点。
e、@autoreleasePool 的数据结构:简单说是双向链表,每张链表头尾相接,有 parent、child 指针 每创建一个池子,会在首部创建一个 哨兵 对象,作为标记 最外层池子的顶端会有一个 next 指针。当链表容量满了,就会在链表的顶端,并指向下一张表。
c、objc_autoreleasePoolPush: 把当前 next 位置置为 nil,即哨兵对象,然后 next 指针指向下一个可入栈位置, AutoreleasePool 的多层嵌套,即每次 objc_autoreleasePoolPush,实际上是不断地向栈中插入哨兵 对象。
d、objc_autoreleasePoolPop: 根据传入的哨兵对象找到对应位置。 给上次 push *** 作之后添加的对象依次发送 release 消息。 回退 next 指针到正确的位置。
RunTime简称运行时。OC就是运行时机制,其中最主要的是消息机制。OC的函数,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用.
objc_msgSend,只有对象才能发送消息,因此以objc开头.
对象根据方法编号SEL去映射表查找对应的方法实现
1,交换方法
2,动态添加方法(感觉没啥用,还不如直接分类)
3,给分类添加属性
4,字典转模型
5,快速归档解档
6,获取类的所有的属性和方法
当系统提供的控件不能满足我们的需求的时候,我们可以
1:通过继承系统控件,重写系统的方法,来扩充子类的行为。
2:当需要为系统类扩充别的属性或是方法的时候,与哪个类有关系,就为哪个类创建分类(不能在分类中重写系统方法,因为会把系统的功能给覆盖掉,而且分类中不能调用super)。
3:利用runtime修改系统的类,交换方法(对象 发送消息 调用的方法被交换了,声明不会交换,所以会写成声明和调用是一个方法,实际交换方法后,对象调用的是交换后的方法)
交换的实际上是交换的方法列表和IMP(方法实现的对应关系)原理图如下
交换前:
交换后:
通过为要修改的类添加一个分类来实现。例如交换系统的imageNamed方法。
1,添加分类(UIImage的分类Image)
1.1定义一个方法调用交换后的系统方法
1.2,在+ (void)load中交换方法(// 把类加载进内存的时候调用,只会调用一次)
开发使用场景:如果一个类方法非常多,加载类到内存的时候也比较耗费资源,需要给每个方法生成映射表,可以使用动态给某个类添加方法解决(通过performSelector调用,class_addMethod实现)。
经典面试题:有没有使用performSelector,其实主要想问你有没有动态添加过方法。
简单使用
分类不能直接添加属性的原因:
在分类里使用@property声明属性,只是将该属性添加到该类的属性列表,并声明了setter和getter方法,但是没有生成相应的成员变量,也没有实现setter和getter方法。所以说分类不能添加属性。使用@property的时候,系统会自动生成带“_”的成员变量和该变量的setter和getter方法。也就是说,属性相当于一个成员变量加getter和setter方法。
解决办法:在分类里使用@property声明属性后,即使实现了setter和getter方法,也仍然没有添加带“_”的成员变量,也就是说,在setter和getter方法里仍然不能直接访问以下划线开头的成员变量,因为在分类里用@property声明属性时系统并没有添加以“”开头的成员变量。此时要达到添加的目的可以 使用运行时的关联对象 。
原理:给一个类声明属性,其实本质就是给这个类添加关联
简单示例
思路:利用运行时,遍历模型中所有属性,根据模型的属性名,去字典中查找key,取出对应的值,给模型的属性赋值。
在Controller中实现具体的存取 *** 作
KVO和KVC可以利用这个。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)