OC底层原理二十:OC底层面试题
目录:
-
关联对象AssociationsManager是否唯一? -
分类方法会覆盖本类方法吗? - 所有
分类方法都优先于本类吗? -
Runtime是什么 - 方法的本质,
SEL是什么?IMP是什么?两者之间关系是什么? -
编译后的类能否添加实例变量? - 能否向
运行时创建的类添加实例变量? -
[self class]和[super class]区别和原理分析 -
runtime如何实现weak,为什么可以自动置为nil? -
runtime Associate方法关联的对象,是否需要在dealloc中释放?
准备工作:
- 可编译的
objc4-781源码: https://www.jianshu.com/p/45dc31d91000
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之外的方法,分类方法都会插入本类方法前面:
- 这个
attachLists绑定流程: OC底层原理十八:类的加载(中) SEL & 分类的加载 中 4.4 attachLists 绑定数据,讲解得非常清楚。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本类在CatA和CatB分类前面调用+load方法,HTStudent本类也在他们前面调用image.png
4. Runtime是什么
-
由
C、C++和汇编实现的一套API,为OC语言加入了面向对象、运行时的功能 -
运行时(Runtime)是指数据类型的确定,由编译器推迟到运行时
(比如Extention和category的区别,extension是编译期就确定了,但是懒加载的category是在运行时动态加入的)
5. 方法的本质,SEL是什么?IMP是什么?两者之间关系是什么?
-
方法的本质: 发送消息。
消息有以下几个流程:
- 快速查找(
objc_msgSend)~cache_t缓存信息中读取(汇编) - 慢速查找(
lookUpImpOrForward)~递归自己/父类,从methodlist中查找 - 查找不到消息时:
动态方法解析~resolveInstanceMethod - 消息快速转发 ~
forwardTargetForSelector(更换实现对象) - 消息慢速转发 ~
methodSignatureForSelector&forwardInvocation(自定义实现,或飘走不管了~)
-
sel是方法编号~ 在read_images期间,编译进入内存。
(sel是带地址的字符串,方法排序是依据SEL地址值进行的排序) -
imp是函数实现的指针,找imp就是找函数的过程
SEL和IMP的关系
- 如同查字典,你想查
牛的意思,首先在目录通过niu找到对应的页数,在页数内有关于牛的详细解释。
(sel就是niu,imp就是页码,而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; }
- 打印结果:
都是HTPersonimage.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,测试代码中self是HTPerson实例。
所以读取到的是self的isa指向的类是HTPerson类。[self class]打印结果为HTPerson而
[super class]编译后,是使用objc_msgSendSuper发送消息,第1个参数是__rw_objc_super结构体,第2个参数是class方法编号。
执行对象是第1个参数,所以我们在当前文件中搜索__rw_objc_super:
image.png
其中
object是self,superClass是(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方法时,传入的是self,self的isa指向的类是HTPerson类。
- 所以
[super class]打印结果为HTPerson。
补充:
super虽然可以直接调用方法,但是它并不是对象,只是语法关键字。真正的调用者,是当前对象。虽然
clang编译中我们看到[super class]被编译为发送objc_msgSendSuper消息,但实际断点检查汇编流程时,发现它callq调用的是objc_msgSendSuper2:
image.png
搜索
objc_msgSendSuper2,objc_super说明了就是当前搜索类,并不是父类
image.png
汇编源码:直接调用CacheLookup,从superclass中的cache快速查找方法
image.png
9. runtime如何实现weak,为什么可以自动置为nil?
- 通过
SideTable找到我们的weak_table -
weak_table根据referent找到或者创建weak_entry_t - 然后
append_referrer(entry, referrer)将我的新弱引用的对象加进去entry - 最后
weak_entry_insert把entry加入到我们的weak_table
image.png
10. runtime Associate方法关联的对象,是否需要在dealloc中释放?
- 不需要。因为设置
关联对象时,会将isa的has_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)
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png
image.png