iOS底层高级面试题整理一(I)
iOS底层高级面试题整理【OC本质、KVC、KVO、Categroy、Block】(I)
1. 查看一个OC对象占用了多少内存
系统分配了16个字节给NSObject对象(通过malloc_size函数获得) 但NSObject对象内部只使用了8个字节的空间(64bit环境下,可以通过class_getInstanceSize函数获得)
在64位CPU中,NSObject 对象占用大小为16字节,其中8字节为指针大小,8字节为实例对象结构体所占大小。 在32位CPU中,NSObject 对象占用大小为8字节,其中4字节为指针大小,4字节为实例对象结构体所占大小。
1 | |
2. 常用的LLDB指令
1 | |
3. OC对象
OC对象 可以分为3种:
1). instance对象 (实例对象) 2). class对象 (类对象) 3). meta-class对象 (元类对象)
3.1 instance对象 (实例对象)
instance对象就是通过类alloc出来的对象,每次调用alloc都会产生新的instance对象
1 | |

实例对象的内存分布
TIP:
- isa 的数据结构
1 | |
- isa的底层是
isa_t,isa_t的结构是联合体+位域。 - 联合体的大小取决于内部最大的元素的大小,所以
isa的大小为8字节。 - 联合体内部的的元素在内存中的互相覆盖的,所以
cls,bits是不会同时存在的。
在联合体内又增加了位域的结构来使isa包罗万象。 8字节即为64个二进制位。每个二进制位都定义了存储的内容,即为位域。
1 | |
nonpointer:表示是否对 isa 指针开启指针优化(0:纯isa指针,1:不⽌是类对象地址,isa 中包含了类信息、对象的引⽤计数等)has_assoc:关联对象标志位(0没有,1存在)has_cxx_dtor:该对象是否有 C++ 或者 Objc 的析构器,如果有析构函数,则需要做析构逻辑,如果没有,则可以更快的释放对象shiftcls: 存储类指针的值。开启指针优化的情况下,在arm64架构下有33位用来存储类指针magic:用于调试器判断当前对象是真的对象还是没有初始化的空间weakly_referenced:对象是否被指向或者曾经指向⼀个 ARC 的弱变量,没有弱引⽤的对象可以更快释放。deallocating:标志对象是否正在释放内存has_sidetable_rc:当对象引⽤技术⼤于 10 时,则需要借⽤该变量存储进位extra_rc:当表示该对象的引⽤计数值,实际上是引⽤计数值减 1,例如,如果对象的引⽤计数为 10,那么extra_rc为 9。 如果引⽤计数⼤于 10,则需要使⽤has_sidetable_rc。
- TaggedPointer
当对象为指针为
TaggedPointer类型时,指针的值不是地址了,而是真正的值,直接优化了存储,提升了获取速度。
- ①
TaggedPointer的特点专门用来存储小对象,例如NSNumber和部分NSString指针不在存储地址,而是直接存储对象的值。 - ② 所以,它不是一个对象,而是一个伪装成对象的普通变量。
- ③ 内存也不在堆,而是在栈,由系统管理,不需要malloc和free在内存读取上有着3倍的效率,创建时比以前快106倍。(少了malloc流程,获取时直接从地址提取值)
3.2 Class对象 (类对象)
我们平时说的类,其实也是对象,称为类对象, 每个类在内存中有且只有一个class对象
class对象在内存中存储的信息主要包括 isa指针 superclass指针 类的属性信息(@property)、类的对象方法信息(instance method) 类的协议信息(protocol)、类的成员变量信息(ivar) ……

类对象的内存分布
3.3 meta-class对象 (元类对象)
每个类在内存中有且只有一个meta-class对象
meta-class对象和class对象的内存结构是一样的,但是用途不一样,在内存中存储的信息主要包括 isa指针 superclass指针 类的类方法信息(class method) ……
1 | |

元类对象的内存分布
3.4 三者之间的关系


instance的isa指向class 当调用对象方法时,通过instance的isa找到class,最后找到对象方法的实现进行调用
class的isa指向meta-class 当调用类方法时,通过class的isa找到meta-class,最后找到类方法的实现进行调用

对象的内存分布

isa 和 superClass 总结
1 | |
总结
- OC对象本质是结构体,都有 isa 指针
- 实例对象:内存只存储 isa 和 成员变量的值
- 类对象:superclass 指针,对象方法列表、属性信息等都是存放在类对象中的
- meta-class对象:是一种特殊的类对象,内存结构相同
- 通过 isa 和 superclass 指针,把对象都串联起来了
4. KVO实现原理
KVO(Key-Value Observing)是一套事件观察 & 通知机制,开发者可以使用它来监测对象属性的变化并做出响应。
延展 KVO和NSNotificatioCenter都是 iOS 观察者模式的一种实现,两者的区别在于: 相对于被观察者和观察者之间的关系,
KVO是一对一的,NSNotificatioCenter是一对多的KVO对被监听对象无侵入性,不需要修改其内部代码即可实现监听
4.1 基本实现:
KVO使用三部曲:
- 注册观察者
1 | |
- 实现回调
1 | |
- 移除观察者
1 | |
苹果官方推荐的方式是——在init的时候进行addObserver,在dealloc时removeObserver,这样可以保证add和remove是成对出现的,这是一种比较理想的使用方式
4.2 底层实现原理

KVO底层实现图解

NSKVONotifying_XXX 的内部结构及方法调用顺序
利用RuntimeAPI动态生成一个子类
NSKVONotifying_XXX,并且让instance对象的isa指向这个全新的子类NSKVONotifying_XXX当修改对象的属性时,会在子类
NSKVONotifying_XXX调用Foundation的_NSSetXXXValueAndNotify函数在
_NSSetXXXValueAndNotify函数中依次调用: 1)、willChangeValueForKey2)、父类原来的setter3)、didChangeValueForKey,didChangeValueForKey:内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath: ofObject: change: context:)
扩展
NSKVONotifying_Person 重写了 class 方法,直接
return class _getSuperClass(object_getClass(self))是为了隐藏 NSKVONotifying_Person 不被外界看到Foundation 框架中很多例如 _NSSetBoolValueAndNotify、_NSSetCharValueAndNotify、_NSSetFloatValueAndNotify、_NSSetLongValueAndNotify 等类簇。可以通过命令查询
1nm Foundation grep ValueAndNotify
5. KVC实现原理
KVC(key-Value coding) 键值编码,指iOS开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。不需要调用明确的存取方法,这样就可以在运行时动态访问和修改对象的属性,而不是在编译时确定。

KVC底层实现图解

KVC底层设值流程
1 | |
NSKeyValueCoding类别中的其他方法
1 | |
问答延展:
1). 如何手动触发KVO? 手动调用
willChangeValueForKey:和didChangeValueForKey:2). 直接修改成员变量会触发KVO么? 不会触发KVO 3).通过KVC修改属性会触发KVO么? 会触发KVO
部分来源参考: 稀土掘金作者:iSwifter 链接:https://juejin.cn/post/6844903920322494478