本篇文章分析使用的 runtime 为 objc4-756.2
NSObject 是什么?
对于 objc 来说,一个对象可以想象成为一个结构体。至少他们在内存布局时是这样的。接下来我们来证实一下。首先我们来看下NSObject
头文件的定义。
1 | typedef struct objc_class *Class; |
可以看到定义为内部有一个类型为objc_class
的结构体指针isa
。然后新建一个工程,在main.m
中写下如下代码
1 | int main(int argc, const char * argv[]) { |
通过clang
将其编译成为cpp
文件。
1 | clang -rewrite-objc main.m -o main.cpp |
打开main.cpp
可以看到这样一个结构体
1 | struct NSObject_IMPL { |
有没有发现和NSObject
头文件的定义很像。其实这个就是NSObject
的结构。下面用一个例子来证明。
1 | struct Person_IMPL { |
代码过程为创建了一个Person
类。将其成员变量赋值。将创建好的Person
对象桥接成为Person_IMPL
结构体。通过访问结构体成员输出log
来看。对象就是结构体。
NSObject 是如何创建的
相信大家大家一定都使用过alloc
。用来给一个对象分配内存空间。那么它内部到底做了什么呢?参考Runtime
源码中NSObject.mm
这个文件。
1 | + (id)alloc { |
通过查看源码可以发现alloc
调用了_objc_rootAlloc
。而_objc_rootAlloc
调用了callAlloc
。接下来我们来分析callAlloc
做了什么。
1 | // Call [cls alloc] or [cls allocWithZone:nil], with appropriate |
callAlloc
第二个参数checkNil
入参为false
,因此此函数不会返回nil
。后续的创建就不一步步跟了,想要了解的同学可以自己查看源码。总之。最后他们都会回到一个函数
1 | static __attribute__((always_inline)) |
初始化isa
解析
1 | inline void |
初始化的过程就是对isa_t结构体初始化的过程。
1 | # if __arm64__ |
Init 做了什么事情?
1 | - (id)init { |
可以看到,对于NSObject
来说init
没有做任何事情。通过函数名可以看出alloc
是用于分配内存。而init
是用于初始化。而NSObject
不需要额外的初始化操作。所以就直接返回了自己。
对象是如何销毁的
1 | - (void)dealloc { |
最终会调用到这里
1 | inline void |
isTaggedPointer可以看这篇文章科普,这里就不详细介绍了 深入理解Tagged Pointer
indexed是代表是否开启isa指针优化。weakly_referenced代表对象被指向或者曾经指向一个 ARC 的弱变量。has_assoc代表对象含有或者曾经含有关联引用。has_cxx_dtor之前提到过了,是析构器。has_sidetable_rc判断该对象的引用计数是否过大。
1 | id |
cxx
表示该对象是否有析构函数,如果没有析构器就会快速释放内存。前面有说道过。assoc
标识是否有关联属性(分类新增成员变量),
object_cxxDestruct
实现和原理比较复杂。可以看 ARC下dealloc过程及.cxx_destruct的探究 这篇文章说的非常棒。总结一下。结论是
- ARC下对象的成员变量于编译器插入的.cxx_desctruct方法自动释放
- ARC下[super dealloc]方法也由编译器自动插入
- 所谓编译器插入代码过程需要进一步了解,还不清楚其运作方式
- clang的CodeGen也值得深入研究一下
_object_remove_assocations
后面会在runtime
的文章中介绍。主要适用于解除关联属性的。
接下来说下clearDeallocating
1 | inline void |
主要有两个函数就不一一贴上来了。功能是相同的。最终都会调用weak_clear_no_lock
。都是拿到弱引用表。进行擦除操作。
1 | weak_clear_no_lock(weak_table_t *weak_table, id referent_id) |