iOS 底层知识

总结一些零碎底层知识点

NSObject 对象内存占用分析

strut 8个字节
int 4 个字节
指针 8 个字节

NSObject 系统分配 16 个字节,但 ivar 只占用 8 个字节,剩余 8 个字节空余。
结构体在内存中占用,是最大成员所占空间的倍数。
底层内存分配,分块分配按 16 的倍数

创建一个实例对象,至少需要多少内存?

1
2
#import <objc/runtime.h>
class_getInstanceSize([NSObject class])

创建一个实例对象,实际分配了多少内存?

1
2
#import <malloc/malloc.h>
malloc_size((_bridge const void *)obj);

OC 对象有三种

  • 实例对象 instance
    NSObject *obj = [[NSObject alloc] init]
    存储 isa 指针、其它成员变量

  • 类对象 class
    Class *c = object_getClass(obj)
    Class *c = [NSObject class]
    存储 isa 指针,superclass,属性信息(@property),对象方法,协议(protocol),成员变量(ivar)
    每个类在内存中有且只有一个类对象信息。

  • 元类对象 meta-class
    Class *meta_class = object_getClass(c)
    Class *meta_class = objec_getClass([NSObject class])
    存储 isa 指针,superclass,类方法列表
    每个元类对象在内存只有一个。

isa 与 superclass 指向

isa 和 superclass


KVO 实现

  • 利用 runtime 动态生成监听对象的派生类(NSKVONotifying_XXX),一个全新子类,使 instance 的 isa 指向该子类
  • 当修改 instance 对象属性时,会调用该子类重写的属性的 set 方法
  • set 方法会调用 Foundation_NSSetXXXValueAndNotify方法
  • willChangeValueForKey:
  • 父类原来的set方法
  • didChangeValueForKey: 内部触发监听器的监听方法observeValueForKeyPath:ofObject:change:content:

手动调用 willChangeValueForKey:didChangeValueForKey: 可以手动触发 KVO

NSKVONotifying_XXX 内部结构

  • isa 指向派生类自己的 metaClass
  • superClass 指向原来的类
  • - (void)setXXX: 重写的方法,内部调用_NSSetXXXValueAndNotify
  • - (Class)class 重写的方法,返回原来的类,目的为了隐藏派生类,隐藏 KVO 内部实现
  • - (void)dealloc
  • _isKVOA

KVC

调用setValue: forKey: setValue: forKeyPath: 可以触发 KVO

setValue: forKey: 实现原理

setValue: forKey:

valueForKey:实现原理

valueForKey:

load 方法

在 runtime 加载类、分类时调用,只调用一次
根据方法的地址,直接调用

  1. 首先调用所有类的 load 方法
    按编译顺序调用(先编译,先调用)
    调用子类的 load 方法之前会先调用父类的 load 方法

  2. 再调用分类的 load 方法
    按编译顺序调用(先编译,先调用)

分类加载方法

prepare_load_methods

initialize 方法

在第一次接收到消息时调用
使用objc_msgsend调用
没有实现就不会调用

  1. 先调用父类,再调用自己
  2. 如果分类实现了initialize方法,会覆盖掉父类的实现(因为消息发送机制,分类->子类->父类)
  3. 子类没有实现,会带调用父类的,可能会有多次调用,但只会初始化一次(消息发送机制)

关联对象

category 的底层结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;

method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}

property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);

protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};

由结构可知,分类无法添加成员变量,但是可以通过关联对象间接实现。

  • 添加关联对象
1
void objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,id _Nullable value, objc_AssociationPolicy policy)
  • 获取关联对象
1
id _Nullable objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
  • 移除所有关联对象
1
void objc_removeAssociatedObjects(id _Nonnull object)

关联对象的原理

四个核心对象

  • AssociationsManager
  • AssociationsHashMap
  • ObjectAssociationMap
  • ObjcAssociation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;

public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }

AssociationsHashMap &get() {
return _mapStorage.get();
}

static void init() {
_mapStorage.init();
}
};
1
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
1
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
1
2
3
4
class ObjcAssociation {
uintptr_t _policy;
id _value;
}

关联对象实现的四个核心类关系

  1. 关联对象不是存储在被关联对象的内存中。
  2. 关联对象存储在全局统一的AssociationsManager中。
  3. 设置关联对象为 nil,相当于移除AssociationMap中的所属项。
  4. objc_removeAssociatedObjects 相当于移除AssociationsHashMap的所属项目。

Runloop