Runtime tips

Runtime tips

Posted by FishYan on June 28, 2019

Runtime Tips

Tip1. OC的消息机制

  • 消息发送
    • runtime 通过selector查找IMP的过程
    • id objc_msgSend ( id self, SEL op, ... );
    • SEL:objc_object的结构体
  • 动态解析
  • 消息转发

objc_msgSend函数

  1. 检测这个 selector 是不是要忽略的。比如 Mac OS X 开发,有了垃圾回收就不理会 retain, release 这些函数了。
  2. 检测这个 target 是不是 nil 对象。ObjC 的特性是允许对一个 nil 对象执行任何一个方法不会 Crash,因为会被忽略掉。
  3. 如果上面两个都过了,那就开始查找这个类的 IMP,先从 cache 里面找,完了找得到就跳到对应的函数去执行。
  4. 如果 cache 找不到就找一下方法分发表。
  5. 如果分发表找不到就到超类的分发表去找,一直找,直到找到NSObject类为止。 如果还找不到就要开始进入动态方法解析了,后面会提到。

Tip2. runtime如何同个selector找到IMP地址

每个类对象中都有一个方发表,方法表中记录着方法名,参数,实现,通过方法名就可以查找到改方法。

参考 NSObject 上面的方法:

- (IMP)methodForSelector:(SEL)aSelector;
+ (IMP)instanceMethodForSelector:(SEL)aSelector;

Tip3. self & super

有一个 Son 类继承 Father 类,在Son类中 [self class] 和 [super class] 的区别?

[self class] -> Son

[super class] -> Son

两个输出结果一样, 原因:

self 是当前类的隐藏参数指向当前类这个毫无疑问

super 表面上和self是类似指向父类但实际并非如此super 实际上是一个 Magic Keyword它本身是一个编译标示符 self 是指向同一个消息接收者
不同点在于 super 会告诉编译器用super调用的方法要去父类里面查找而不是本类

runtime中表示

[self class]

id objc_msgSend(id self, SEL op, ...)
[super class]

id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

// 其中objc_super是一个结构体:

struct objc_super {
      __unsafe_unretained id receiver;
      __unsafe_unretained Class super_class;
};

receiver 是消息接受者

super_class 记录的是父类是什么

先看 [self class] 实现:

- (Class)class {
   return object_getClass(self);
}

当调用 [super class] 时,会转换为 objc_msgSendSuper 函数。

  • 第一步:先构建 objc_super结构体

    • 第一个参数为 self
    • 第二个参数,通过(id)class_getSuperclass(objc_getClass(“Son”))获取父类
  • 第二步:调用 -(Class)class 函数, 先在 Father 类中查找,没有 class 函数,再向父类 NSObject 中查找,有 class 函数,最后内部使用的是 ` objc_msgSend(objc_super->reciver, @selector(class)) 去调用,objc_super->reciver 即为 self ,此时与 [self class] 无异,所以输出结果为 Son`

Tip4. 使用runtime associate 创建的属性需要释放吗

ARC和MRC上都不需要手动释放

// 对象的内存销毁时间表

// 根据 WWDC 2011, Session 322 (36分22秒)中发布的内存销毁时间表

  1. 调用 -release :引用计数变为零
    • 对象正在被销毁,生命周期即将结束.
    • 不能再有新的 __weak 弱引用, 否则将指向 nil.
    • 调用 [self dealloc]
  2. 子类 调用 -dealloc
    • 继承关系中最底层的子类 在调用 -dealloc
    • 如果是 MRC 代码 则会手动释放实例变量们(iVars)
    • 继承关系中每一层的父类 都在调用 -dealloc
  3. NSObject 调 -dealloc
    • 只做一件事:调用 Objective-C runtime 中的 object_dispose() 方法
  4. 调用 object_dispose()
    • 为 C++ 的实例变量们(iVars)调用 destructors
    • 为 ARC 状态下的 实例变量们(iVars) 调用 -release
    • 解除所有使用 runtime Associate方法关联的对象
    • 解除所有 __weak 引用
    • 调用 free()

Tip5. OC实现多继承

  • 通过代理实现
  • 通过消息转发实现

Tip6. runtime如何实现weak

  • 初始化:objc_initWeak
  • 添加引用时:调用 objc_storeWeak 更新指针指向,解除原有对象绑定 weak_unregister_no_lock, 绑定新对象weak_register_no_lock
  • 释放:clearDeallocating

Tip7. Category 的实现原理

  • category 实际上是结构体,运行时决定
  • 可以添加类方法,方法,协议,属性。
  • 新添加的方法会在运行时倒序插入到原有的类最前面,
  • Category中有同名方法,最后编译的那个方法会生效。(swift中不允许同名扩展方法)
  • 作用:将一个类拆分为多个,是不相关的业务相互分离

Tip8. Category 是否能添加成员变量

Category 是不能添加成员变量的,因为在运行时实例内部的成员变量已经确定,这时再添加成员变量会破坏实例内部布局。

但是我们可以通过关联对象的方式去实现类似添加成员变量(与类本身的成员变量有本质区别),通过添加属性,自定义实现setter和getter方法

关联对象:通过objc_setAssociatedObject和objc_getAssociatedObject来关联对象,关联对象的值存在全局哈希表中,由AssociationsManager统一管理

Tip9. runtime 的具体应用

  • 利用关联对象给分类添加属性
  • 遍历类的所有属性,获取其私有属性
  • swizzing交换方法,为系统方法添加自有实现。
  • kvc字典转模型

Tip10. 怎么理解Objective-C是动态运行时语言。

  • 主要是将数据类型的确定由编译时,推迟到了运行时。这个问题其实浅涉及到两个概念,运行时和多态。

  • 简单来说, 运行时机制使我们直到运行时才去决定一个对象的类别,以及调用该类别对象指定方法。

  • 多态:不同对象以自己的方式响应相同的消息的能力叫做多态。

  • 意思就是假设生物类(life)都拥有一个相同的方法-eat;那人类属于生物,猪也属于生物,都继承了life后,实现各自的eat,但是调用是我们只需调用各自的eat方法。