Runtime

5/19/2021

Objective-C 语言是一门动态语言。它把一些决策从编译阶段、链接阶段推迟到运行时阶段

  • 静态语言:在编译阶段就已确定所有变量的数据类型,同时也确定要调用的函数,以及函数的实现。常见的静态语言,如:C/C++、Java、C# 等。
  • 动态语言:程序在运行时可以改变其结构。也就是说在运行时检查变量数据类型,同时在运行时才会根据函数名查找要调用的具体函数。如 Objective-C。

# isa

  • 在arm64架构之前,isa就是一个普通的指针,存储着Class、Meta-Class对象的内存地址
  • 从arm64架构开始,对isa进行了优化,变成了一个联合体(union)结构,还使用位域来存储更多的信息
struct objc_object {
private:
    isa_t isa; // 8 bytes
public:
...
}

union isa_t {
    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

ISA_BITFIELD的定义如下

# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19
#   define RC_ONE   (1ULL<<45)
#   define RC_HALF  (1ULL<<18)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

联合体 isa_t 涉及到一个位域的概念.

因为部分数据在存储时候并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。正是基于这种考虑,C语言又提供了一种叫做位域的数据结构。

这里 ISA_BITFIELD 位域成员通过跟 bits 相与来取对应的值。
在 ISA_BITFIELD 中定义的参数的含义

ISA_BITFIELD

# 类的结构

# objc_class

Objective-C 类是由 Class 类型来表示的
typedef struct objc_class *Class;可以看到Class是一个objc_class的指针

struct objc_class: objc_object {
  // Class isa; 继承自objc_object
  Class superclass;
  cache_t cache;  // formerly cache pointer and vtable
  class_data_bits_t bits; // 用户获取具体类信息
  class_rw_t *data() const {
    return bits.data();
  }
} 
1
2
3
4
5
6
7
8
9

objc_class 继承自objc_object ,主要是继承了一个isa

# class_rw_t 结构体

通过 objc_class 的 bits & FAST_DATA_MASK 获取到 class_rw_t 结构体信息

class_rw_t* data() const {
    return (class_rw_t *)(bits & FAST_DATA_MASK);
}
1
2
3
struct class_rw_t {
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif
    // explicit_atomic 是为了安全操作
    explicit_atomic<uintptr_t> ro_or_rw_ext;
    Class firstSubclass;
    Class nextSiblingClass;
    ...
}

struct class_rw_ext_t { // class_rw_t 扩展
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    char *demangledName;
    uint32_t version;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

class_rw_t 里面的 methods、properties、protocols 是二维数组,是可读可写的,包含了类的初始内容、分类的内容.

class_rw_t

# class_ro_t 结构体

上述的 class_rw_ext_t 中 class_ro_t 结构体信息:

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
    ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

class_ro_t 里面的 baseMethodList、baseProtocols、ivars、baseProperties 是一维数组,是只读的,存储了当前类在编译器就已经确定了都属性、方法及遵循的协议

class_ro_t

# method_t 结构体

method_t 是对方法、函数的封装

using MethodListIMP = IMP;
struct method_t {
    SEL name; // 方法、函数名
    const char *types; // 编码 (返回值类型、参数类型)
    MethodListIMP imp; // 指向方法、函数的指针(函数地址)
    ...
};
1
2
3
4
5
6
7

# imp属性

method_t 结构体中的 imp 代表函数的具体实现,底层源码中的定义如下:

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif
1
2
3
4
5

# SEL 属性

SEL 代表方法、函数名,一般叫做选择器,底层结构跟 char * 类似

typedef struct objc_selector *SEL;
1

获取方法:

  • 可以通过 @selector() 和 sel_registerName() 获得
  • 可以通过 sel_getName() 和 NSStringFromSelector() 转成字符串
  • 不同类中相同名字的方法,所对应的方法选择器是相同的

# types属性

这个 types 值表示什么呢?iOS 中提供了一个叫做 @encode 的指令,可以将具体的类型表示成字符串编码

Code Meaning Code Meaning
c A char * A character string (char *)
i An int @ An object (whether statically typed or typed id)
s A short # A class object (Class)
l A longl is treated as a 32-bit quantity on 64-bit programs. : A method selector (SEL)
q A long long [array type] An array
C An unsigned char {name=type...} A structure
I An unsigned int (name=type...) A union
S An unsigned short bnum A bit field of num bits
L An unsigned long ^type A pointer to type
Q An unsigned long long ? An unknown type (among other things, this code is used for function pointer)
f A float d A double
B A C++ bool or a C99 _Bool v A void

# 方法缓存

Class 内部结构中有个方法缓存(cache_t),调用了方法之后会缓存在里面,他用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度

cache_t

这里的散列表的原理就是,通过 @selector(methodName) & _mask 获得一个索引值,通过这个索引就能很快在 buckets 中拿到对应的 bucket_t(key, _imp)

  • 存放:如果生成的索引在 buckets 下已经存在 data 。那么他会把 index - 1,减到零了还没有空闲位置,它会从数组最大值开始继续往前找位置,直到有位置
  • 获取:在拿到 bucket_t 后,会比较一下 key 与 @selector(methodName) 是否对应,如果不对应,那就回按照存放的那样方式一个一个找。如果存满了,buckets 就会走扩容

这就是空间换时间

# 其他

# id是什么,id和void *区别

  • id: typedef struct objc_object *id;我们创建的一个对象或一个实例,其实就是一个objc_object结构体指针,而我们常用的id也是这个结构体指针。
  • 区别
    • void* 是c语言中的泛型指针,指代任意的指针类型。当返回值是一个地址或者指针的时候,返回值可以用void*表示,也可以用此类型来定义任意类型的指针变量表示“对带有无类型/未知内容的某些随机块内存的引用”,只是一个指针,无法编辑指向地址上的内容。
    • id是OC语音中的泛型指针,指代任意对象类型的指针。当返回值是一个对象指针的时候,返回值类型都可以用id表示,也可以用此类型来定义任意类型的对象指针变量。表示“对未知类的一些随机Objective-C对象的引用”

# 什么是元类?为什么要这么设计?

元类,和类对象一样,也是一个对象。它存储了一个类的所有类方法。
元类使用基类的元类作为他们的类,而所有类的基类都是NSObject(大多数),所以大多数元类使用NSObject的元类作为它的类。
基类的元类就是它自己的类(isa指向自己)

objc-class

# 消息机制

首先编译器将方法调用编译为objc_msgSend(obj, @selector(methodName));

  • obj 消息接受者
  • @selector(methodName) 消息名称

objc_msgSend 的执行流程可以分为 3 大阶段

  • 消息发送
  • 找不到消息发送方法,就会进入动态方法解析,允许开发者动态创建新方法
  • 如果动态方法解析没有做任何操作,这时候开始进入消息转发

如果三个阶段都没找到合适的方法调用,就会报错: unrecognized selector sent to instance

# 消息发送

  1. 通过obj的isa指针找到obj对应的class对象
  2. 在class对象的cache中通过SEL查找对应的函数method
  3. 若cache中找到,直接通过method中的函数指针跳转到对应函数执行。若找不到,去class_rw_t中的methods找
  4. 若methods中找到,则将method加入到cache中,方便下次查找,并通过method中的函数指针跳转到对应函数执行。若找不到,则去superClass中查找
  5. 若superClass中找到,则将method加入到cache中,方便下次查找,并通过method中的函数指针跳转到对应函数执行。若找不到,依次通过superClass查找,最后都找不到则进入动态解析流程。

# 动态方法解析

  1. 动态方法解析:向当前类发送resolveInstanceMethod:或resolveClassMehtod: 消息,检查是否动态向该类添加了方法。
  2. 动态解析后,会重新走“消息发送”的流程,从receiverClass的cache中查找方法这一步开始执行

# 消息转发

  1. 快速消息转发:检查该类是否实现了forwardingTargetForSelector:方法,若实现了,则调这个方法。若该方法返回非nil且非self,则向该返回对象重新进行发送消息
  2. 标准消息转发:runtime发送methodSignatureForSelector:消息获得selector对应的方法签名。若返回值非空,则通过forwardInvocation:转发消息。若返回为空,则向当前对象发送doesNotRecognizeSelector:消息,程序崩溃退出。

# Runtime应用

# 字典转模型

  • 利用 Runtime 遍历所有的属性或者成员变量
  • 利用 KVC 设值
- (instancetype)initWithDict:(NSDictionary *)dict {
    self = [super init];
    if (self) {
        unsigned int count = 0;
        objc_property_t *propertys = class_copyPropertyList([self class], &count);
        for (int i = 0; i < count; i++) {
            objc_property_t property = propertys[i];
            //通过 property_getName 函数获得属性的名称
            const char *name = property_getName(property);
            NSString *nameStr = [[NSString alloc] initWithUTF8String:name];
            id value = [dict objectForKey:nameStr];
            [self setValue:value forKey:nameStr];
        }
        free(propertys);
    }
    return self;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 替换方法实现

替换方法实现常用的两个方法:

// 方法替换
IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
// 方法交换
void method_exchangeImplementations(Method m1, Method m2) 
1
2
3
4

# 对象自动归档

用 Runtime 提供的函数遍历 Model 自身所有属性,并对属性进行 encode 和 decode 操作

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super init]) {
        unsigned int outCount;
        Ivar * ivars = class_copyIvarList([self class], &outCount);
        for (int i = 0; i < outCount; i ++) {
            Ivar ivar = ivars[i];
            NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
            [self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
        }
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    unsigned int outCount;
    Ivar * ivars = class_copyIvarList([self class], &outCount);
    for (int i = 0; i < outCount; i ++) {
        Ivar ivar = ivars[i];
        NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
        [aCoder encodeObject:[self valueForKey:key] forKey:key];
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 分类添加属性

category里面无法添加实例变量,但是我们很多时候需要在category中添加和对象关联的值,这个时候就需要用到关联对象来实现了。

- (void)setAddName:(NSString *)addName {
    objc_setAssociatedObject(self, @selector(addName), addName, OBJC_ASSOCIATION_COPY);
}

- (NSString *)addName {
    return objc_getAssociatedObject(self, @selector(addName));
}
1
2
3
4
5
6
7
Last Updated: 10/25/2024, 6:55:06 AM