美文网首页iOS 底层原理 iOS 进阶之路
OC底层原理二十:OC底层面试题

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

作者: markhetao | 来源:发表于2020-10-27 21:03 被阅读0次

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是否唯一

  • AssociationsManager结构中,manager只是对外代言人,并不是唯一的,AssociationsHashMap哈希表才是唯一的。

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

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

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

  • 分类方法会调用attachLists,将分类方法插入了本类方法前面,全都存储起来。并不是覆盖本类方法。

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


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

  • 除了+load方法,其他分类方法都会优先本类
    调用所有本类+load方法,顺序调用分类+load

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是什么

  • CC++汇编实现的一套API,为OC语言加入了面向对象运行时的功能

  • 运行时(Runtime)是指数据类型确定,由编译器推迟到运行时
    (比如Extentioncategory的区别,extension编译期就确定了,但是懒加载category是在运行时动态加入的)


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

  • 方法本质: 发送消息
    消息有以下几个流程:
  1. 快速查找(objc_msgSend)~ cache_t缓存信息中读取(汇编
  2. 慢速查找(lookUpImpOrForward)~ 递归自己/父类,从methodlist中查找
  3. 查找不到消息时: 动态方法解析 ~ resolveInstanceMethod
  4. 消息快速转发 ~ forwardTargetForSelector (更换实现对象)
  5. 消息慢速转发 ~ methodSignatureForSelector & forwardInvocation (自定义实现,或飘走不管了~)
  • sel方法编号 ~ 在read_images期间,编译进入内存
    sel带地址字符串方法排序是依据SEL地址值进行的排序

  • imp函数实现指针,找imp就是找函数的过程

SELIMP的关系

  • 如同查字典,你想查的意思,首先在目录通过niu找到对应页数,在页数内有关于详细解释
    (sel就是niuimp就是页码,而imp指针指向的内容,就是详细的解释)

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

  • 不可以。 因为编译好的实例变量存放的位置在ro,一旦编译完成,内存结构就完全确定了,无法修改。

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

  • register注册前可以添加。但是调用运行时register注册后,就完成了内存的注入,内存结构确定了,无法修改

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

  • [self class]就是发送消息objc_msgSend,消息接受者是self,方法编号(SEL)是class

  • [super class]本质是objc_msgSendSuper,消息接受者还是self,方法编号是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中释放?

  • 不需要。因为设置关联对象时,会将isahas_assoc设置为true。当持有对象释放时,会调用c++析构函数,此时会将关联对象全部释放

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

image.png
  • 查看hasAssociatedObjects
    image.png
  • 流程:
    1: c++函数释放: object_cxxDestruct
    2: 移除关联属性 : _object_remove_assocations
    3: 将弱引用自动设置nil : weak_clear_no_lock(&table.weak_table, (id)this);
    4: 引用计数处理: table.refcnts.erase(this)
    5: 销毁对象: free(obj)

相关文章

网友评论

    本文标题:OC底层原理二十:OC底层面试题

    本文链接:https://www.haomeiwen.com/subject/bdatvktx.html