iOS 底层原理 iOS 进阶之路

OC底层原理二十:OC底层面试题

2020-10-27  本文已影响0人  markhetao

OC底层原理 学习大纲

目录:

  1. 关联对象AssociationsManager是否唯一
  2. 分类方法覆盖本类方法吗?
  3. 所有分类方法优先本类吗?
  4. Runtime是什么
  5. 方法的本质,SEL是什么?IMP是什么?两者之间关系是什么?
  6. 编译后能否添加实例变量
  7. 能否向运行时创建添加实例变量?
  8. [self class][super class]区别原理分析
  9. runtime如何实现weak,为什么可以自动置为nil?
  10. runtime Associate方法关联对象,是否需要在dealloc中释放?

准备工作:


1. 关联对象AssociationsManager是否唯一

1. 运行验证:
移除锁,这样可以同时存在2个manager了。

image.png
  • 加入测试代码,创建2个manager,都调用get(),发现2个读取的associations相同地址
  • 证明AssociationsHashMap在内存中是独一份的,而manager只是外层包装,可以创建多个。
    image.png

2. 分类方法覆盖本类方法吗?

详细的attachLists绑定流程: OC底层原理十八:类的加载(中) SEL & 分类的加载 中 4.4 attachLists 绑定数据


3. 所有分类方法优先本类吗?

1. +load之外的方法,分类方法都会插入本类方法前面

2. +load方法的顺序为何是本类前面

代码验证:

  • 创建HTPerson类、HTStudent类、HTPeroson(CatA)分类、HTPeroson(CatB)分类,都实现+load方法:
+(void)load { NSLog(@"%s", __func__); }

打印顺序如下:

image.png
  • 打印了所有本类+load方法,顺序调用分类+load

源码分析

  • 我们知道所有+load方法,都是在app启动过程中,dyld库调用objc库_objc_init中,load_images函数内进行的实现 (不熟悉的请看 【OC底层原理 学习大纲】 第5部分:dyld加载 & 类的加载

  • 打开objc4源码,搜索load_images

    image.png
  • 找到prepare_load_methods准备load函数和call_load_methods调用load函数两部分:

2.1 准备load函数

  • 进入prepare_load_methods:

    image.png
  • 这里分别记录本类分类load函数,先进入schedule_class_load:

    image.png
  • 这里有递归执行,添加操作是在add_class_to_loadable_list中:

    image.png
  • 这里进行了读取检查扩容存储操作。我们检查getLoadMethod读取:

    image.png
  • getLoadMethod中从ro中读取了函数列表,寻找并返回load函数的Imp
    ------ 所有本类load的函数列表已准备完毕 ------

  • 回到上面add_category_to_loadable_list,检查分类的load列表的准备:

    image.png
  • 检查分类的读取_category_getLoadMethod

    image.png
    这里也从分类类方法列表中找到load函数并返回了imp
    ------ 所有分类load的函数列表已准备完毕 ------

2.2 调用load函数

  • 进入call_load_methods:

    image.png
    这里的do-while循环内部,调用本类load函数时,使用了while循环。 所以load的调用顺序是:
    先调用所有本类+load方法,所有本类都没load方法时,才调用分类的load方法
  • 所以我们打印的结果,不仅HTPerson本类CatACatB分类前面调用+load方法HTStudent本类也在他们前面调用

image.png

4. Runtime是什么


5. 方法的本质,SEL是什么?IMP是什么?两者之间关系是什么?

  1. 快速查找(objc_msgSend)~ cache_t缓存信息中读取(汇编
  2. 慢速查找(lookUpImpOrForward)~ 递归自己/父类,从methodlist中查找
  3. 查找不到消息时: 动态方法解析 ~ resolveInstanceMethod
  4. 消息快速转发 ~ forwardTargetForSelector (更换实现对象)
  5. 消息慢速转发 ~ methodSignatureForSelector & forwardInvocation (自定义实现,或飘走不管了~)

SELIMP的关系


6. 编译后能否添加实例变量


7. 能否向运行时创建添加实例变量?


8. [self class][super class]区别原理分析

实际运行时,[super class]在汇编层执行的是objc_msgSendSuper2,直接从superclass父类开始搜索,节约了一轮查找资源

测试代码:

@interface HTPerson : NSObject
@end
@implementation HTPerson
- (instancetype)init {
   if (self = [super init]) {
       NSLog(@"%@ %@", [self class], [super class]);
   }
   return self;
}
@end

int main(int argc, const char * argv[]) {
   @autoreleasepool {
       HTPerson * person = [[HTPerson alloc] init];
   }
   return 0;
}
  • 打印结果: 都是HTPerson
image.png
  • 1. 查看class实现

    image.png
  • 进入object_getClass,发现是getIsa()读取的:

    image.png
  • 进入getIsa,其实就是从指针优化isa中,进行位运算取出类cls

    image.png

想知道cls是什么,我们必须确定消息接收对象是谁,是谁的isa

  • clang生成cpp编译文件(clang -rewrite-objc main.m -o main.cpp),打开main.cpp文件:

    • 搜索HTPerson_init,找到实例化方法的编译结果
      image.png
  • 可以看到[self class],是使用objc_msgSend发送的消息,第1个参数是self,第2个参数是class方法编号,执行对象是self,测试代码中selfHTPerson实例
    所以读取到的是selfisa指向的类HTPerson类[self class]打印结果为HTPerson

  • [super class]编译后,是使用objc_msgSendSuper发送消息,第1个参数是__rw_objc_super结构体,第2个参数是class方法编号。
    执行对象第1个参数,所以我们在当前文件中搜索__rw_objc_super:

    image.png

其中objectselfsuperClass(id)class_getSuperclass(objc_getClass("HTPerson"))

  • 我们在objc4源码中搜索objc_msgSendSuper(,查看其格式:

    image.png
  • 发现第1个参数是struct objc_super结构,我们搜索struct objc_super:

    image.png
  • 在这里,可以看到第1个参数是receive接收者,第二个是super_class父类。

到这,我们就已经完整的弄清楚了[super class]实际调用对象__rw_objc_super结构中的第1个入参self,所以调用class方法时,传入的是selfselfisa指向的类HTPerson类

  • 所以[super class]打印结果为HTPerson

补充:

  1. super虽然可以直接调用方法,但是它并不是对象只是语法关键字。真正的调用者,是当前对象

  2. 虽然clang编译中我们看到[super class]被编译为发送objc_msgSendSuper消息,但实际断点检查汇编流程时,发现它callq调用的是objc_msgSendSuper2:

    image.png
  • 搜索objc_msgSendSuper2objc_super 说明了就是当前搜索类,并不是父类

    image.png
  • 汇编源码:直接调用CacheLookup,从superclass中的cache快速查找方法

    image.png

9. runtime如何实现weak,为什么可以自动置为nil?

  1. 通过SideTable找到我们的weak_table
  2. weak_table 根据referent 找到或者创建 weak_entry_t
  3. 然后append_referrer(entry, referrer)将我的新弱引用的对象加进去entry
  4. 最后weak_entry_insertentry加入到我们的weak_table
image.png

10. runtime Associate方法关联对象,是否需要在dealloc中释放?

指针优化的isa中的has_assoc记录了是否有关联属性,在析构函数触发时,会检查是否有关联属性主动释放

image.png
  • 查看hasAssociatedObjects
    image.png
上一篇下一篇

猜你喜欢

热点阅读