iOS Runtime

214 阅读18分钟

1 Runtime 简介

Runtime 是指在 iOS 操作系统上执行代码时所涉及的运行时环境。

Objective-C Runtime 是一个运行时库,提供了一系列的 C 语言 API,用于实现对象的创建、消息传递、方法调用、内存管理等重要功能。

Swift Runtime 提供了 Swift 语言的类型系统、内存管理、异常处理等基础功能。与 Objective-C Runtime 相比,Swift Runtime 的设计更加现代化,支持泛型、协议扩展、错误处理等 Swift语言特性。

静态语言(如 C语言)编译器在编译时就确定了函数调用的地址和参数。静态函数调用方式在一定程度上会比 Objective-C 的动态消息传递方式更高效,因为不需要在运行时动态查找方法实现,而是在编译时就确定了调用关系。

2 Runtime 机制

2.1 编译时

Objective-C 中使用 [object method] 这种方式调用方法,在编译后最终都会被转换成 objc_msgSend 函数的调用。

实例对象的调用方法

objc_msgSend(instenceClass, @selector(methodName));

类对象的调用方法

objc_msgSend(objc_getClass("CLASS"), @selector(methodName));

2.2 运行时

当向一个对象发送消息时,Runtime 会进行 消息发送 和 消息转发。

消息发送:当向一个对象发送消息时,Runtime 会根据对象所属类的方法列表查找对应的方法实现,如果找到则执行该方法,如果找不到则触发消息转发机制。

  1. 首先,通过 object 的 isa 指针查找它的类对象 objc_class

  2. 在类对象 objc_class 的方法缓存 objc_cache 中查找方法选择器 SEL

  3. 若不存在,则会在方法列表 objc_method_list 中查找;

  4. 如果还是没找到,则会沿着继承链向上到父类 super_class 中查找;

  5. 一旦找到方法选择器 SEL ,就去执行它的方法实现 IMP

  6. 仍然没找到的话,就会调用 _objc_msgForward(id, SEL, ...) 进行消息转发。

消息转发:当 Runtime 在类的方法列表中找不到对应的方法实现时,会触发消息转发机制。通过动态方法解析、备用接收者和完整消息转发来处理消息,如果消息转发失败会导致程序崩溃。

消息转发机制分为三个步骤:

1. 动态方法解析:在对象接收到无法处理的消息后,首先会尝试动态添加方法实现。Runtime 会调用 +resolveInstanceMethod:+resolveClassMethod: 动态地为类添加方法的实现。如果方法解析成功,Runtime 重新启动消息发送。

2. 备用接收者:如果方法解析失败,Runtime 会调用 -forwardingTargetForSelector: ,在这个方法中返回一个备用接收者对象,消息将被转发给该对象进行处理。

3. 完整消息转发:如果备用接收者未能处理消息,Runtime 会调用 -forwardInvocation: 方法,将原始的未知消息封装成 NSInvocation 对象,手动处理消息。

2.3 消息转发示例

调用方法的方式是使用 [实例 方法名] 或 [实例 方法名:参数]

私有方法调用,可使用 NSObject 的 performSelector 方法,只支持调用最多两个入参,并且入参类型和返回类型为 id 的方法。若入参的个数多于两个,可以使用 NSInvocation 来调用方法。

1. NSInvocation 调用示例

1. 假设有如下方法:

- (NSString *)getName:(NSString *)name andAge:(NSInteger)age {
    return @"test";
}

2. 获取该方法的签名

NSMethodSignature *signature = [self methodSignatureForSelector:@selector(getName:andAge:)];

断点后,可看到有一个参数 _typeString 值为 @32@0:8@16q24

  • @32 方法的返回类型 NSString *
  • @0和:8 方法自带的参数 self 和 _cmd
  • @16 自己设置的参数(NSString *)name
  • q24 自己设置的参数 (NSInteger)age

3. NSInvocation 具体调用

SEL sel = @selector(getName:andAge:);
// 通过 NSObject 的实例方法来获取方法签名
NSMethodSignature *signature = [self methodSignatureForSelector:sel];
// 通过 NSObject 的类方法来获取方法签名
NSMethodSignature *signature = [self.class instanceMethodSignatureForSelector:sel];
// 通过方法类型字符串来获取方法签名
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"@32@0:8@16q24"];
// 通过方法类型字符串来获取方法签名
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:"@@:@q"];

NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
// 防止参数被释放
[invocation retainArguments];
// 设置方法调用者
invocation.target = self;
// 设置方法选择器,这里的方法名一定要与方法签名类中的方法一致
invocation.selector = sel;
// 设置第2个参数的值 第0、1参数被方法的 self 与 _cmd 占用
NSString *name = @"xiaomi";
[invocation setArgument:&name atIndex:2];
// 设置第3个参数的值
NSInteger age = 20;
[invocation setArgument:&age atIndex:3];
// 调用方法
[invocation invoke];

// 获取方法的返回值,该方法需要在 invoke 之后调用,否则是 nil
NSString *returnValue;
[invocation getReturnValue:&returnValue];
// 获取第2个参数值
NSString *argument2;
[invocation getArgument:&argument2 atIndex:2];
// 获取第3个参数值
NSInteger argument3;
[invocation getArgument:&argument3 atIndex:3];

2. 消息转发示例

调用未知方法 [[MyClass new] unknownMethod]; 触发消息转发机制,具体处理如下:

1. 动态方法解析示例

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface MyClass : NSObject

@end

@implementation MyClass

// 动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(unknownMethod)) {
        IMP imp = class_getMethodImplementation(self, @selector(dynamicMethod));        
        Method method = class_getInstanceMethod(self, @selector(dynamicMethod));        
        const char *type = method_getTypeEncoding(method);
        BOOL didAddMethod = class_addMethod(self, sel, imp, type);        
        return didAddMethod;
    }

    return [super resolveInstanceMethod:sel];
}

- (void)dynamicMethod {
    NSLog(@"Dynamic method is called!");
}

@end

2. 快速转发-备用接收者重定向示例

#import <Foundation/Foundation.h>

@interface BackupHandler : NSObject

- (void)unknownMethod;

@end

@implementation BackupHandler

- (void)unknownMethod {    
    NSLog(@"Message handled by backup handler");
}

@end

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface MyClass : NSObject

@end

@implementation MyClass

// 动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO; // 返回NO,进入下一步转发
}

// 备用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(unknownMethod)) {
        if ([[BackupHandler new] respondsToSelector:aSelector]) {
            return [BackupHandler new];
        }
    }

    return [super forwardingTargetForSelector:aSelector];
}

3. 慢速转发-消息重定向示例

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface MyClass : NSObject

@end

@implementation MyClass

// 动态方法解析
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO; // 返回NO,进入下一步转发
}

// 备用接收者
- (id)forwardingTargetForSelector:(SEL)aSelector {
    return nil; // 返回nil,进入下一步转发
}

// 方法签名,获取方法的参数和返回值类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(unknownMethod)) {
        // 自定义方法的签名,进入 forwardInvocation:
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }

    return [super methodSignatureForSelector:aSelector];
}

// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL aSelector = anInvocation.selector;    
    if ([self respondsToSelector:aSelector]) {
        [anInvocation invokeWithTarget:self];
    } else if ([[BackupHandler new] respondsToSelector:aSelector]) {
        [anInvocation invokeWithTarget:[BackupHandler new]];
    } else {
        // 无法处理消息,崩溃
        [self doesNotRecognizeSelector:aSelector];    
    }
}

@end

2.4 数据结构

objc_object: 表示实例对象结构体,是所有对象的基础结构,它定义了对象的通用属性和行为。

struct objc_object {
    Class isa; // 指向类的指针,用于动态派发方法
};

objc_class: 表示类的结构体,用于描述类的属性和行为

struct objc_class {
    Class isa; // 指向元类的指针
    Class super_class; // 指向父类的指针
    const char *name; // 类名
    long version; // 类的版本信息
    long info; // 类的附加信息
    long instance_size; // 实例对象的大小
    struct objc_ivar_list *ivars; // 实例变量列表
    struct objc_method_list **methodLists; // 方法列表数组
    struct objc_cache *cache; // 方法缓存
    struct objc_protocol_list *protocols; // 协议列表
};

SEL: 表示方法选择器的结构体,包含方法名和类型编码。

// 定义消息发送相关的结构体
typedef struct objc_selector *SEL;

struct objc_selector {
    char *name; // 方法名
    char *types; // 类型编码
};

IMP: 方法实现的函数指针类型,用于指向具体的方法实现。

typedef id (*IMP)(id, SEL, ...);

objc_method_list: 方法列表结构体,包含一系列方法定义。

struct objc_method_list {
    struct objc_method_list *next; // 指向下一个方法列表的指针
    int count; // 方法数量
    struct objc_method method_list[1]; // 实际方法数组
};

objc_method: 方法结构体,包含方法选择器、方法类型编码和方法实现。

struct objc_method {
    SEL method_name; // 方法选择器
    char *method_types; // 方法类型编码字符串
    IMP method_imp; // 方法实现的函数指针
};

objc_cache: 方法缓存结构体,用于提高方法调用的性能。

struct objc_cache {
    unsigned int mask; // 掩码,用于快速计算方法索引
    unsigned int occupied; // 已占用的缓存槽数量
    Method buckets[1]; // 方法缓存槽数组
};

objc_protocol_list: 协议列表结构体,包含一系列协议定义。

// 定义消息转发相关的结构体
struct objc_protocol_list {
    struct objc_protocol_list *next; // 指向下一个协议列表的指针
    long count; // 协议数量
    Protocol *list[1]; // 实际协议数组
};

Message: 消息结构体,包含类和选择器,用于消息传递。

typedef struct {
    Class isa;
    SEL selector;
} *Message;

objc_msgSend_fp: objc_msgSend 函数指针类型,用于动态发送消息。

typedef id (*objc_msgSend_fp)(id self, SEL op, ...);

3 Runtime API

 3.1 objc_ 函数

用于 Runtime API 的核心功能,如创建类、注册方法、消息传递等。

  1. 创建与销毁
    • objc_constructInstance:创建并返回给定大小的实例(ARC中无效)。

    • objc_destructInstance:释放对象的实例变量,并在必要时调用析构函数(ARC中无效)。

  2. 协议操作
    • objc_allocateProtocol:创建一个新的协议。

    • objc_registerProtocol:注册一个由 objc_allocateProtocol 创建的协议。

    • objc_getProtocol:获取指定名称的协议。

    • objc_copyProtocolList:获取当前进程中已注册的协议列表。

  3. 类与元类操作
    • objc_allocateClassPair: 创建一个新的类和其元类。
    • objc_registerClassPair: 注册一个由objc_allocateClassPair创建的类。
    • objc_disposeClassPair: 注销一个类并释放其占用的资源。
    • objc_getClass: 通过类名获取已注册的类。
    • objc_getMetaClass: 获取类的元类。
    • objc_getClassList: 获取当前进程中已注册的类列表。
    • objc_lookUpClass: 在当前进程中查找类。
  4. 关联对象操作
    • objc_getAssociatedObject: 获取与对象关联的对象。
    • objc_setAssociatedObject: 设置与对象关联的对象。
    • objc_removeAssociatedObjects: 移除与对象关联的对象。

3.2 class_ 函数

用于操作类的函数,如获取类名、获取类的父类、检查类是否遵循某个协议等。

  1. 类创建与销毁
    • class_createInstance: 创建类的实例。
  2. 类成员操作
    • class_copyIvarList: 获取类的实例变量列表。
    • class_copyMethodList: 获取类的方法列表。
    • class_copyPropertyList: 获取类的属性列表。
    • class_copyProtocolList: 获取类遵循的协议列表。
    • class_addIvar: 添加实例变量到类。
    • class_addMethod: 添加方法到类。
    • class_addProperty: 添加属性到类。
    • class_addProtocol: 添加协议到类。
    • class_getInstanceVariable: 获取类的实例变量。
    • class_getClassVariable: 获取类的类变量。
    • class_setIvarLayout: 设置类的实例变量布局。
    • class_getWeakIvarLayout: 获取类的弱引用实例变量布局。
    • class_getInstanceMethod: 获取类的实例方法。
    • class_getClassMethod: 获取类的类方法。
    • class_getMethod: 获取类的方法。
    • class_getMethodImplementation: 获取方法的实现。
    • class_getProperty: 获取类的属性。
    • class_replaceMethod: 替换类的方法。
    • class_getIvarLayout: 获取类的实例变量布局。
    • class_setWeakIvarLayout: 设置类的弱引用实例变量布局。
    • class_setSuperclass: 设置类的父类。
  3. 类信息获取
    • class_getName: 获取类的名称。
    • class_getSuperclass: 获取类的父类。
    • class_getInstanceSize: 获取类的实例大小。
    • class_getVersion: 获取类的版本号。
    • class_setVersion: 设置类的版本号。
    • class_isMetaClass: 判断类是否为元类。
  4. 类行为判断
    • class_respondsToSelector: 判断类是否响应特定的选择器。
    • class_conformsToProtocol: 判断类是否遵循特定协议。

3.3 object_ 函数

用于操作实例对象,如获取和设置对象的类、复制对象、管理对象的关联对象等。

  1. 实例对象创建与销毁
    • object_copy: 复制一个对象(ARC中无效)。
    • object_dispose: 释放一个对象占用的内存(ARC中无效)。
  2. 实例对象操作
    • object_setInstanceVariable: 设置对象的实例变量(ARC中无效,使用object_setIvar)。
    • object_getInstanceVariable: 获取对象的实例变量(ARC中无效,使用object_getIvar)。
    • object_setIvar: 设置对象的实例变量或对象的属性。
    • object_getIvar: 获取对象的实例变量或对象的属性。
    • object_setIvarWithStrongDefault: 设置对象的实例变量,并采用默认的强引用方式。
    • object_getIndexedIvars: 获取对象的索引实例变量。
    • object_setField: 设置对象的字段。
    • object_getField: 获取对象的字段。
  3. 实例对象信息获取与设置
    • object_getClassName: 获取对象的类名。
    • object_setClass: 修改对象的类。
    • object_getClass: 获取对象的类。
    • object_isClass: 判断对象是否为类。

3.4 ivar_ 函数

用于操作实例变量,包括获取实例变量的列表、类型、偏移量等。

  1. 实例变量信息获取
    • ivar_getName: 获取实例变量的名称。
    • ivar_getTypeEncoding: 获取实例变量的类型编码。
    • ivar_getOffset: 获取实例变量的偏移量。

3.5 method_ 函数

用于操作方法,如获取类的方法列表、获取方法的选择器、动态添加方法等。

  1. 方法属性获取
    • method_getName: 获取方法的名称。
    • method_getImplementation: 获取方法的实现。
    • method_getTypeEncoding: 获取方法的类型编码。
    • method_getDescription:获取方法的描述。
  2. 方法参数信息获取
    • method_getNumberOfArguments: 获取方法的参数数量。
    • method_getArgumentType: 获取方法的参数类型。
    • method_copyArgumentType: 复制方法的参数类型。
    • method_getReturnType: 获取方法的返回类型。
    • method_copyReturnType: 复制方法的返回类型。
  3. 方法行为修改
    • method_exchangeImplementations: 交换两个方法的实现。
    • method_setImplementation:设置方法的实现。

3.6 property_ 函数

用于操作属性,包括获取类的属性列表、属性的特性、属性的名称等。

  1. 属性信息获取
    • property_getName: 获取属性的名称。
    • property_getAttributes: 获取属性的特性列表。
  2. 属性特性信息获取
    • property_copyAttributeList: 复制属性的特性列表。
    • property_copyAttributeValue: 复制属性的特定特性的值。

3.7 protocol_ 函数

用于操作协议,如获取已注册的协议列表、获取协议的名称、检查类是否实现了某个协议等。

  1. 协议信息获取
    • protocol_getName: 获取协议的名称。
  2. 协议方法管理
    • protocol_getMethodDescription: 获取协议方法的描述。
    • protocol_copyMethodDescriptionList: 复制协议方法的描述列表。
  3. 协议属性管理
    • protocol_copyPropertyList: 复制协议的属性列表。

    • protocol_addProperty:向协议中添加属性。

    • protocol_getProperty:获取协议中的属性。

  4. 协议列表管理
    • protocol_copyProtocolList: 复制协议遵循的协议列表。
    • protocol_conformsToProtocol: 判断一个协议是否遵循另一个协议。
  5. 协议操作
    • protocol_addProtocol:向协议中添加另一个协议。
    • protocol_addMethodDescription:向协议中添加方法描述。
    • protocol_isEqual:判断两个协议是否相等。

3.8 sel_ 函数

用于操作方法选择器,包括创建方法选择器、将方法选择器转换为字符串等。

  1. 选择器创建
    • sel_registerName: 根据字符串注册一个选择器。
  2. 选择器信息获取
    • sel_getName: 获取选择器的名称。
    • sel_getUid: 获取选择器。
  3. 选择器操作
    • sel_isEqual: 判断两个选择器是否相等。

3.9 imp_ 函数

用于操作方法的实现,如获取方法的实现、设置方法的实现等。

  1. 函数实现
    • imp_implementationWithBlock: 使用Block创建一个函数实现。
    • imp_getBlock: 获取函数实现中的Block。
    • imp_removeBlock: 移除函数实现中的Block。

3.10 cache_ 函数

用于操作方法缓存,如获取类的方法缓存、清空方法缓存等。

  1. 方法缓存
    • cache_getImp: 获取方法缓存中的实现。
    • cache_setImp: 设置方法缓存中的实现。

3.11 message_ 函数

用于消息传递相关的函数,如向对象发送消息、消息转发等。

  1. 消息发送
    • message_send: 发送消息给对象。
    • message_sendSuper:发送消息给父类的实现。

4 开发问题

4.1 向 nil 对象发送消息将会发生什么?

向 nil 对象发送消息时,实际上并不会发生任何异常或错误,而是会返回一个默认值,具体取决于返回类型。

消息的返回类型:

  • 返回对象类型的消息,返回值将会是 nil
  • 返回基本数据类型的消息,返回值将会是 0 或者相应的默认值
  • 返回结构体的消息,返回值将会是一个空的结构体

4.2 对象的 isa 指针?

1. 对象关系:

  • 对象的 isa 指针 指向类对象
  • 类对象的 isa 指针 指向元类对象
  • 元类对象的 isa 指针 指向根元类(root class's metaclass)
  • 根元类的 isa 指针 指向自身
  • superclass 指针 指向当前类对象的父类的类对象

2. 对象结构:

  • 实例对象结构体中只包含一个 isa 指针,指向该实例对象所属的类
  • 类对象的结构体包含 isasuperclass、方法、属性、协议列表以及成员变量的描述等信息

3. 调用方法机制:

  • 所有对象调用方法的方式相同,方法的定义存储在类对象中,因为类对象只有一个,所以方法可以存储在类对象中,而不需要存在于每个实例对象中

4. isa 指针作用:

  • 确定对象的类: 通过 isa 指针,确定对象的类型,从而确定该类定义的方法和访问属性
  • 实现消息传递: 通过 isa 指针,确定对象的类型,然后在类中查找对应的方法实现进行调用
  • 支持继承和多态: isa 指针的存在支持了面向对象编程中的继承和多态特性。子类对象的 isa 指针指向其父类或者实现的协议,从而可以实现方法的重写和动态绑定

4.3 _objc_msgForward 函数作用,直接调用它将会发生什么?

_objc_msgForward 函数用于处理消息转发(Message Forwarding)。当 OC 对象接收到一个无法识别的消息时,Runtime 会调用 _objc_msgForward 函数来进行消息转发,以实现动态方法解析或消息转发到其他接收者。

直接调用 _objc_msgForward 函数通常会导致编译器错误或者运行时异常。正确的做法是,当需要实现自定义的消息转发逻辑时,可以通过重写 OC 对象的 forwardInvocation: 方法或者动态添加方法来实现消息转发,而不是直接调用 _objc_msgForward 函数。

4.4 class_rw_t 和 class_ro_t 的区别

class_ro_t 存储了当前类在编译期就已经确定的属性、方法以及遵循的协议,里面是没有分类的方法的。那些运行时添加的方法将会存储在运行时生成的 class_rw_t 中。 

 ro 即表示 read only,是无法进行修改的。

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;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

ObjC 类中的属性、方法还有遵循的协议等信息都保存在 class_rw_t 中。

rw 代表可读可写。 

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro; // 指向只读的结构体,存放类初始信息

    /*
     这三个都是二维数组,是可读可写的,包含了类的初始内容、分类的内容。
     methods中,存储 method_list_t ----> method_t
     二维数组,method_list_t --> method_t
     这三个二位数组中的数据有一部分是从 class_ro_t 中合并过来的。
     */
    method_array_t methods; // 方法列表(类对象存放对象方法,元类对象存放类方法)
    property_array_t properties; // 属性列表
    protocol_array_t protocols; //协议列表

    Class firstSubclass;
    Class nextSiblingClass;
    
    //...
}

两者区别在于:

class_ro_t 是在编译期间确定的;

class_rw_t 是在运行时确定的,Runtime 在调用的 realizeClass 方法时,会生成 class_rw_t 结构体,先将 class_ro_t 的内容拷贝过去,然后再将当前类的分类的属性、方法等拷贝到其中。

所以可以说 class_rw_t 是 class_ro_t 的超集,当然实际访问类的方法、属性等也都是访问的 class_rw_t 中的内容

4.5 method swizzling

1. 定义

方法交换,用于在运行时动态交换两个方法的实现。

2. 基本原理

通过修改方法的实现指针,将一个方法的实现替换为另一个方法的实现,从而达到动态改变方法行为的目的。

3. 实现方式

method_exchangeImplementations: 交换两个方法的实现

class_replaceMethod: 替换方法的实现

method_setImplementation: 直接设置某个方法的实现

4. 注意事项

唯一性:在 +load 方法中,不在 +initialize 方法中改变方法实现。这是因为 +initialize 可能会被子类所继承并重新执行,而 load 并不会被继承。

原子性:使用 dispatch_once 来执行方法交换,这样可以保证只运行一次。

5. 示例

#import <objc/runtime.h>

@implementation MyClass

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // case 1: 替换实例方法 
        Class class = [self class];
        // case 2: 替换类方法 
        Class class = object_getClass([self class]); 
        // 源方法的 SEL 和 Method
        SEL originalSelector = @selector(originalMethod);
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        
        // 交换方法的 SEL 和 Method
        SEL swizzledSelector = @selector(swizzledMethod);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        // 尝试给类添加新方法,如果已存在同名方法则替换
        BOOL didAddMethod = class_addMethod(class,
                                            originalSelector,
                                            method_getImplementation(swizzledMethod),
                                            method_getTypeEncoding(swizzledMethod));
        
        // 如果方法已存在,则直接交换两个方法的实现
        if (didAddMethod) {
            class_replaceMethod(class,
                                swizzledSelector,
                                method_getImplementation(originalMethod),
                                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

- (void)originalMethod {
    NSLog(@"Original method implementation");
}

- (void)swizzledMethod {
    NSLog(@"Swizzled method implementation");
}

@end 

4.6 weak 底层实现原理?知道 SideTable 吗?

实现原理

  1. 初始化时:Runtime 会调用 objc_initWeak 函数,初始化一个新的 weak 指针指向对象的地址。
  2. 添加引用时:objc_initWeak 函数会调用 objc_storeWeak() 函数,其作用是更新指针指向,创建对应的弱引用表。
  3. 释放时:调用 clearDeallocating 函数。clearDeallocating 函数首先根据对象地址获取所有 weak 指针地址的数 组,然后遍历这个数组把其中的数据设为 nil,再把这个 entry 从 weak 表中删除,最后清理对象的记录。

SideTable 结构体是负责管理类的引用计数表和 weak 表。 

struct SideTable {
    spinlock_t slock;
    RefcountMap_t refcnts;
    weak_table_t weak_table;
}; 

SideTable 是用于存储 NSMutableDictionaryNSMutableSetNSCache 等可变集合类的底层数据结构。它的定义在 objc-runtime-new.h 中,是 OC Runtime 中的私有结构体。

  • spinlock_t slock:用于保护 SideTable 的读写操作的自旋锁。自旋锁是一种基本的同步机制,它在尝试获取锁时会一直循环(自旋)直到获得锁为止,适用于对资源的访问时间很短的情况。

  • RefcountMap_t refcnts:是一个 objc_object 到引用计数值的映射表。在 OC 的引用计数管理中,每个对象都有一个引用计数,用于追踪对象的生命周期。RefcountMap_t 是一个哈希表,用于存储对象的引用计数,以及一些额外的标志信息,例如是否是弱引用。

  • weak_table_t weak_table:是一个用于存储弱引用的哈希表。在 OC 中,如果一个对象被多个弱引用指向,这些弱引用的地址会被存储在 weak_table 中。当对象释放时,SideTableweak_table 中与该对象相关的所有弱引用都会被置为 nil,以避免野指针问题。

Weak 表是一个 hash(哈希)表,Key 是 weak 所指对象的地址,Value 是 weak 指针的地址(所指对象指针的地址)数组。

当 weak 引用指向的对象被释放时,又是如何去处理 weak 指针的呢?

当释放对象时,其基本流程如下:

  1. 调用 objc_release 
  2. 因为对象的引用计数为 0,所以执行 dealloc 
  3. 在 dealloc 中,调用了_objc_rootDealloc 函数 
  4. 在_objc_rootDealloc 中,调用了 object_dispose 函数 
  5. 调用 objc_destructInstance 
  6. 最后调用 objc_clear_deallocating  

objc_clear_deallocating 调用流程

  1. 从 weak 表中获取废弃对象的地址为键值的记录 
  2. 将包含在记录中的所有附有 weak 修饰符变量的地址,赋值为 nil 
  3. 将 weak 表中该记录删除 
  4. 从引用计数表中删除废弃对象的地址为键值的记录