iOS晋级知识汇总(二)runtime

Runtime

数据结构

  • objc_object
  • objc_class
  • isa指针
  • method_t

objc_object id == objc_object

  • isa_t
  • 关于isa操作相关
  • 弱引用相关
  • 关联对象相关
  • 内存管理相关

objc_class class == objc_class 继承于objc_object

Class superClass

cache_t cache

  • 快速查找方法执行函数
  • 可增量扩展的哈希表结构
  • 局部性原理的最佳应用
Cache_t.png

class_data_bits_t bits

- class\_data\_bits\_t主要是对class_rw_t的封装
- class\_rw\_t代表了类相关的读写信息、对class\_ro\_t的封装

class_rw_t

  • class_ro_t 代表了类相关的只读信息
  • protocols
  • properties
  • methods
class_rw_t.png

class_ro_t

Class_ro_t.png

isa指针

  • 指针型isa 的值代表Class的地址
  • 非指针型isa 的值的部分代表Class的地址
  • 产生这两种的初衷:我们在寻址实际上30~40位数就可以保证我们寻找到所有class的地址,多出来的位可以存储一些别的内容,来达到节省内存的目的
  • 指向问题:
    • 关于对象,其指向类对象
    • 关于类对象,其指向元类对象
    • 如果是实例方法从类对象查找,如果是类方法从元类对象查找
isa指针.png

method_t

method_t.png

const char * types

type Encodings技术

Type Encoding.png

数据结构总结

Runtime数据结构.png

对象、类对象、元类对象

  • 类对象存储实例方法列表等信息

  • 元类对象存储类方法类表等信息

  • 类对象和元类对象都是objc_class数据结构,由于继承了objc_object数据结构所以才有isa指针,

  • 所以才能实例对象找到类对象访问它的实例方法列表等信息,

  • 类对象通过isa可以找到元类对象访问类方法列表等信息

  • 元类对象的isa指针(包含根元类对象)都指向根元类对象

  • 根元类对象的superclass指向的根类对象

    • 当在跟类中的类方法没找到,会指向根类对象中去查找它的同名的实例方法

消息传递过程

实例方法消息传递过程

1、 调用了一个实例对象A对象的实例方法
2、 通过A实例对象的isa指针找到它的类对象,类对象中遍历方法列表去查找同名的方法实现,找到后Runtime发送消息调用
3、 如果没有找到就会根据superclass指针指向的父类对象,并查找其方法列表。。。直到根类对象中的方法列表,如果找到Runtime发送消息调用,如果到根类对象还没有找到就会返回nil

类方的消息传递过程

1、 调用一个类对象的类方法。
2、 通过类对象的isa指针找到元类对象,元类对象中遍历方法列表查找同名的类方法是心啊,找到后直接调用
3、如果没有找到就会通过superclass指针指向父元类对象,并查找其类方法列表。。。直到根元类对象中的类方法列表,如果找到直接调用,如果没有找到进入到消息转发
4、根元类对象如果没有找到会superclass会指向根类对象,并查找同名的实例方法,如果找到直接调用,如果没有找到返回nil

消息传递

消息传递.png
消息传递Supr.png
消息传递流程.png

1、缓存查找

例: 给定值是SEL 目标值是对应bucket_t的IMP。

  • 首先给定的函数选择器通过一个函数来映射出bucket_t在数组当中位置,这个过程是hash查找
  • 哈希查找实际上,通过给定的一个值比如说方法的选择器经过哈希函数的算法酸楚的值实际上就是给定值在对应数组当中所对应的索引位置。
    • f(key) = key & mask
    • 通过哈希查 解决查找效率的问题
    • 通过哈希查找,找到对应的bucket_t,就可以拿到IMP

2、当前类中查找

对于已排序好的列表,采用二分查找算法查找方法对应执行函数
对于没有排序的列表,采用一般遍历查找方法对应执行函数

3、父类逐级查找

父类逐级查找的过程.png

消息转发

消息转发.png

Method-Swizzling

MethodSwizzling.png
Method method1 = class_getInstanceMethod(self, @selector(test));
Method method2 = class_getInstanceMethod(self, @selector(Mytest));
method_exchangeImplementations(method1, method2);

动态添加方法

编译的时候没有这个方法,运行时有这个方法需要调用performSelector方法,在消息转发中的第一次处理函数resolveInstanceMethod动态添加DoThings:Num:方法

+ (BOOL)resolveInstanceMethod:(SEL)sel{

    NSLog(@"%@",NSStringFromSelector(sel));

    if(sel == @selector(DoThings:Num:)){
        class_addMethod([self class], sel, (IMP)MyMethodIMP, "v@:");
        return YES;
    }

    return  [super resolveInstanceMethod:sel];
}

void MyMethodIMP(id self,SEL _cmd,NSString * name,NSInteger num){
    
    NSLog(@"调用了消息转发的内容");
    NSLog(@"%@",NSStringFromSelector(_cmd));
    NSLog(@"%@",name);
    NSLog(@"%ld",(long)num);
}

动态方法解析

@dynamic

  • 动态运行时语言将函数决议推迟到运行时。
    • 当我们把一个属性标示为@dynamic的时候代表着不需要编译器在编译时,为我们生成属性的setter跟getter方法的具体实现,而在在具体运行时我们调用了setter、geter方法的时候,再去添加具体的实现。
    • 这种功能也只有动态运行时特性的语言支持
  • 编译时语言在编译期进行函数决议

面试题

nsobject有父类吗?

没有nsobject是根类

当在跟类中的类方法没找到,但是有同名的实例方法实现,那么会不会崩溃?会不会产生实际的调用?

由于根元类对象的superclass指针指向了根类对象,去查找实例同名的实例方法,如果找到就会执行同名的实例方法调用

下图中打印的是什么?

笔试题1.png
 [self class]会转化成     
 objc_msgSend(<#id  _Nullable self#>, <#SEL  _Nonnull op, ...#>)
 phone的实例对象,[self class],通过isa找到phone类对象,phone类对象是没有class的,通过superclass找到父类的。。。直到查找到根类nsobject,有calss的实现,打印出来的自然是phone
 
 [super class]会转化成
 objc_msgSendSuper(<#struct objc_super * _Nonnull super#>, <#SEL  _Nonnull op, ...#>)
 
 objc_msgSendSuper实际的接收者仍然是phone的这个实例对象,objc_msgSendSuper传递的含义,从phone的这个实例对象的父类开始查找,  直到查找到根类nsobject有class的实现,所以打印出来的结果还是phone
 

打印结果都是phone

对页面的进出添加进出信息

使用MethodSwizzling来交换viewDidLoadviewWillAppear,在替换代码中添加一些埋点操作

[obj foo]和objc_msgSend()函数之间有什么关系?

  • 向 obj对象发送一条foo的消息,[obj foo]在编译期处理以后就变成了objc_msgSend(obj,@selector(foo))
  • 由于没有参数,所以 objc_msgSend函数调用只有2个参数
  • 然后就开始了runtime的消息传递过程

runtime如何通过Selector找到对应的IMP地址的?

  • 查找当前实例所对应类对象的缓存,是否有selelctor对应imp实现如果缓存命中了,
    我们就把命中的缓存函数返回给调用方。
  • 如果缓存没有就去当前类的方法列表查找selector对应的imp实现
  • 当前类如果没有命中,就根据当前类的superclass逐级查找父类的方法列表。。。直到根类对象,直到查到selector方法的imp实现

能否向编译后的类中增加实例变量?

class_ro_t ro代表的是readonly

编译后的类是无法添加实例变量的。

能否向动态添加的类中增加实例变量?

可以的,动态添加的这个类过程当中只要在它调用注册方法之前去完成实例变量的添加就是可以实现的

消息传递机制

当我们在消息传递的过程中如何通过缓存的方法查找?

isa指针的含义?

  • 非指针类型 isa值的部分代表calss地址
  • 指针类型isa 代表class地址
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 分类 用分类干了什么事情? 声明私有方法 分解体积庞大类文件 把Framework的私有方法公开 特点 运行时决议...
    struggle3g阅读 583评论 0 0
  • 1.网络 1.网络七层协议有哪些? 物理层:主要功能:传输比特流;典型设备:集线器、中继器;典型协议标准和应用:V...
    _我和你一样阅读 8,884评论 1 38
  • 1.说说OC的消息机制? OC中的方法调用其实都是转成了objc_msgSend函数的调用,给receiver(方...
    齐玉婷阅读 3,379评论 0 0
  • Apple相关源码地址 在 https://opensource.apple.com/source/[https:...
    Jason1226阅读 2,958评论 0 1
  • 1.weak和assign区别 修饰变量类型的区别: weak 只可以修饰对象。如果修饰基本数据类型,编译器会报错...
    coderjon阅读 4,664评论 0 1