iOS 底层系列 - OC 对象的本质

1,215 阅读3分钟

一、前言

此篇文章主要介绍的底层原理系列 - OC 对象的本质,对于 OC 对象我们大多数人都了解,但是对于底层和很多原理相对就了解很少.

二、问题

1. struct objc_class的结构

2. id 和 instanceType 有什么区别?

相同点:

  • instancetype 和 id 都是万能指针,指向对象。

不同点:

  • id 在编译的时候不能判断对象的真实类型,instancetype 在编译的时候可以判断对象的真实类型。id 可以用来定义变量,可以作为返回值类型,可以作为形参类型;instancetype 只能作为返回值类型。

3. isKindOfClass | isMemberOfClass

  • isKindOfClass : 判断的是该对象是否是该类以及该类子类的实例。
  • isMemberOfClass : 判断的是该对象是否是该类的实例。

4. 一个NSObject对象占用多少内存?

因为寻址能力,32/8 =4 64/8 =8 cpu位数(32位数4字节,64位数8字节) 系统分配了 16个字节给NSObject 对象,这是系统内部定死的,有对象至少分配16 bytes 的大小。(通过malloc_size函数获得)

5. 对象的isa指针指向哪里?

instance对象的isa指向class对象class对象的isa指向meta-class对象meta-class对象的isa指向基类的meta-class对象

6. OC的类信息存放在哪里?

对象方法、属性、成员变量、协议信息,存放在class对象中类方法,存放在meta-class对象中成员变量的具体值,存放在instance对象

7. == 和 isEqual 和 isEqualToString 的区别

isEqualToString :

  • 比较两个字符串的内容.

== :

  • 比较两个对象的指针,若是字符串 对比的是首地址。

isEqual:

  • 默认情况下是比较两个对象的内存地址.系统自带的类( NSString, NSArray 等)重写了这个方法,改变了这个方法的判断规则。

(一般改为比较两个对象的内容,不是内存地址)

8. 什么时候需要重写 isEqual方法 和 hash方法

isEqual :

  • 当你的子类之间需要判断相等时,重写isEqual:方法

hash 方法:

  • 如果你写的子类还需要添加到集合类型(NSDictionary,NSSet等)中去,hash方法也需要重写。

三、对象的本质

1. Objective-C 代码的底层

2. 将Objective-C代码转换为C\C++代码

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp

NSObject 对象底层实现

Student 对象底层实现

3. 对象的分类

instance(实例对象)

class(类对象)

meta-class(元类对象)

4. isa 指针

通过 isa 指针可以找到类对象,元类对象 位运算找到具体的类对象,元类对象

5. 方法的调用

6. isa superclass 总结

四、Dealloc 的实现

Dealloc 的实现机制是内容管理部分的重点,把这个知识点弄明白,对于全方位的理解内存管理的只是很有 必要。

1. Dealloc 调用流程

首先调用 _objc_rootDealloc()接下来调用 rootDealloc()这时候会判断是否可以被释放,判断的依据主要有 5 个,判断是否有以上五种情况

NONPointer_ISA
weakly_reference
has_assoc
has_cxx_dtor
has_sidetable_rc

4-1. 如果有以上五中任意一种,将会调用 object_dispose()方法,做下一步的处理。 4-2. 如果没有之前五种情况的任意一种,则可以执行释放操作,C 函数的 free()。

执行完毕。

2. object_dispose() 调用流程。

直接调用 objc_destructInstance()。 之后调用 C 函数的 free()。

3. objc_destructInstance() 调用流程

先判断 hasCxxDtor,如果有 C++ 的相关内容,要调用 object_cxxDestruct() ,销毁 C++ 相关的内容。 再判断 hasAssocitatedObjects,如果有的话,要调用 object_remove_associations(), 销毁关联对象的一系列操作。 然后调用 clearDeallocating()。 执行完毕。

4. clearDeallocating() 调用流程。

先执行 sideTable_clearDellocating()。再执行 weak_clear_no_lock,在这一步骤中,会将指向该对象的弱引用指针置为 nil。接下来执行 table.refcnts.eraser(),从引用计数表中擦除该对象的引用计数。至此为止,Dealloc 的执行流程结束。