iOS开发基础18-深入理解 Objective-C Runtime 机制

要深入理解 Objective-C 的 Runtime 机制,必须全面探讨其结构、功能和底层逻辑,这包括数据结构、消息发送流程、消息转发机制、技巧和高级功能等方面。

Objective-C 是一种基于 C 语言,加入了面向对象特性和消息传递机制的动态语言。其核心特性之一是运行时(Runtime)系统,它不仅负责方法调用,还支持动态类型检查、垃圾回收等操作。通过详细分析 Apple 开源的 Runtime 代码(objc4-646.tar),可以更全面地理解 Objective-C 的 Runtime 机制。

Objective-C Runtime 的核心数据结构

SEL - 方法选择器

SEL 是表示方法选择器的类型,实质上是映射到方法名的 C 字符串:

typedef struct objc_selector *SEL;

SEL 可以通过编译器命令 @selector() 或通过 Runtime 的 sel_registerName 函数获取:

SEL sel = @selector(methodName);
SEL sel = sel_registerName("methodName");

如果需要知道 SEL 对应的方法名,可以通过 NSString* NSStringFromSelector(SEL aSelector)SEL 转为字符串输出:

NSString *methodName = NSStringFromSelector(sel);
NSLog(@"%@", methodName);

id - 通用对象指针

id 是通用类型指针,可以表示任意对象:

typedef struct objc_object *id;

id 实际上是指向 objc_object 结构体的指针。objc_object 结构体包含一个 Class 类型的 isa 指针:

struct objc_object {
    Class isa;
};

isa 是一个指针,指向对象所属的类。通过 isa 指针可以找到类对象,进而获得类的方法列表和其他信息。

Class - 类对象

Class 表示对象所属的类,实质上是一个指向 objc_class 结构体的指针:

typedef struct objc_class *Class;

类本质上也是一种对象,它的结构如下:

struct objc_class {
    Class isa;                            // Meta-class 的指针
    Class superclass;                     // 父类
    cache_t cache;                        // 方法缓存
    class_data_bits_t bits;               // 指向类数据的指针
}

Class 的继承关系和 Meta-class

在 Objective-C 中,每个类都有一个关联的 Meta-class 来存储类方法。当你调用对象方法时,消息会通过实例的 isa 指针传递到类对象;当你调用类方法时,消息会通过类对象的 isa 指针传递到 Meta-class。

Meta-class 本身也是一个对象,它与普通类的关系如下:

  • 每个类的实例有一个 isa 指针,指向类对象。
  • 类对象有一个 isa 指针,指向 Meta-class 对象。
  • Meta-class 的 isa 指针指向 NSObject 的 Meta-class(Root Meta-class),形成一个闭环。
  • 类对象和 Meta-class 都有一个 superclass 指针,类对象指向其父类,而 Meta-class 的 superclass 指向父类的 Meta-class。

这种层次结构建立了类方法和实例方法的分层,实现了对象和类的统一。

Method - 方法结构

Method 表示类中的某个方法,定义如下:

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

IMP - 方法实现指针

IMP 是一个函数指针,指向方法的实现:

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

IMP 直接指向方法的实现代码,这样在调用方法时,可以绕过消息传递阶段,直接执行具体的函数实现。

Ivar - 实例变量结构

Ivar 表示类的实例变量,定义如下:

typedef struct objc_ivar *Ivar;

struct objc_ivar {
    char *ivar_name;     // 变量名
    char *ivar_type;     // 变量类型编码
    int ivar_offset;     // 变量偏移量
};

Cache - 方法缓存

Cache 用于缓存经常访问的方法,从而提高方法调用的性能。定义如下:

struct objc_cache {
    unsigned int mask;           // 掩码,缓存容量-1
    unsigned int occupied;       // 已占用的缓存条目数量
    Method buckets[1];           // 方法缓存桶,实际大小为 mask + 1
};

每次查找方法时,首先会在 cache 中查找,如果找到则直接调用,会显著提高方法调度的效率。

objc_msgSend - 核心函数

objc_msgSend 是消息发送的核心函数,用于实现 [receiver message] 的消息传递机制:

id objc_msgSend(id self, SEL op, ...);

在实际运行时,当调用 [receiver message] 时,编译器会将其转换为 objc_msgSend(receiver, @selector(message)) 指令。其执行过程实际上是一个查找和调用的过程,具体流程如下:

  1. 根据 receiver 对象的 isa 指针获取其对应的类。
  2. 在类的 cache 中查找 message 方法,如果找不到,再到 methodLists 中查找。
  3. 如果在类中找不到,再向其父类(通过 super_class 指针)中查找。
  4. 一旦找到 message 方法,就执行其实现的 IMP

这种基于消息传递的机制使得 Objective-C 特别灵活,也支持后续的动态特性如方法交换和消息转发等。

消息发送与消息转发机制

self 与 super

selfsuper 都表示当前对象,但 super 是编译器标示符,用于告诉编译器从父类中查找方法,而不是当前类。例子:

@implementation Son : Father
- (id)init {
    self = [super init];
    if (self) {
        NSLog(@"%@", NSStringFromClass([self class]));    // Son
        NSLog(@"%@", NSStringFromClass([super class]));  // Son
    }
    return self;
}
@end

在这段代码中,[self class][super class] 都会打印 Son,这是因为 class 方法最终是在 NSObject 类中实现的,无论是调用 self 还是 super,最终都返回相同的结果。

隐藏参数 self 和 _cmd

在调用方法时,Objective-C 运行时动态传入两个隐藏参数 self_cmdself 表示方法的调用对象,而 _cmd 表示方法的选择器(SEL),这两个隐藏参数使得方法实现能够访问它们并动态处理。

例如:

- (void)exampleMethod {
    NSLog(@"self: %@", self);
    NSLog(@"_cmd: %@", NSStringFromSelector(_cmd));
}

方法解析与消息转发机制

当消息传递过程中无法找到对应的方法实现时,系统会通过一系列步骤进行方法解析与消息转发:

Method Resolution

当对象调用的方法无法找到时,系统首先会尝试通过 + resolveInstanceMethod:+ resolveClassMethod: 方法来处理。这两个方法允许我们在运行时动态地为该方法提供实现。

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(testMethod)) {
        class_addMethod([self class], sel, (IMP)dynamicTestMethodIMP, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

class_addMethod 是用于动态添加方法的函数。

Fast Forwarding

如果方法解析失败,运行时会调用对象的 - forwardingTargetForSelector: 方法来寻找可以转发消息的对象。如果返回非 nilself 的对象,消息将会转发到返回的对象。

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(testMethod)) {
        return alternateTarget;
    }
    return [super forwardingTargetForSelector:aSelector];
}

Normal Forwarding

如果 - forwardingTargetForSelector: 返回 nil,运行时会创建 NSInvocation 对象并调用 - methodSignatureForSelector:- forwardInvocation: 方法进行完整的消息转发:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(testMethod)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = [anInvocation selector];
    if ([alternateTarget respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:alternateTarget];
    } else {
        [super forwardInvocation:anInvocation];
    }
}

Associated Objects

Associated Objects 提供了一种将对象和值关联的机制,使得在已有类的 Category 中添加属性成为可能。它提供了三个 API:

void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);

objc_AssociationPolicy 是枚举类型,用于指定对象的内存管理策略:

typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN,
    OBJC_ASSOCIATION_RETAIN_NONATOMIC,
    OBJC_ASSOCIATION_COPY_NONATOMIC,
    OBJC_ASSOCIATION_RETAIN,
    OBJC_ASSOCIATION_COPY
};

Method Swizzling

Method Swizzling 是在运行时交换两个方法的实现。这个技术广泛应用于修改已有方法的行为、添加日志、统计等:

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod(self, @selector(originalMethod));
        Method swizzledMethod = class_getInstanceMethod(self, @selector(swizzledMethod));
        method_exchangeImplementations(originalMethod, swizzledMethod);
    });
}

- (void)swizzledMethod {
    // 调用原始方法
    [self swizzledMethod];
    // 添加新的行为
    NSLog(@"Swizzled!");
}

load 方法在类加载时自动调用,可以确保 Swizzling 只执行一次。

高级场景:面向切面编程(AOP)

面向切面编程(Aspect-Oriented Programming, AOP)使用 Method Swizzling 技术,可以实现横切关注点,如日志记录、性能监测等:

void swizzled_method(id self, SEL _cmd) {
    // 前置处理
    NSLog(@"Before [%@ %@]", self, NSStringFromSelector(_cmd));
    // 调用原方法实现
    ((void(*)(id, SEL))objc_msgSend)(self, @selector(swizzled_method));
    // 后置处理
    NSLog(@"After [%@ %@]", self, NSStringFromSelector(_cmd));
}

总结

通过深入分析 Objective-C 的 Runtime 数据结构、消息传递机制和高级功能(如方法 Swizzling 和消息转发),可以更好地理解和发挥 Objective-C 的动态特性。这不仅提高了代码的灵活性和可维护性,也为实现更复杂的功能提供了基础。对于开发者来说,理解这些底层机制是编写高效、优雅 iOS 代码的关键。

posted @ 2015-07-22 13:50  Mr.陳  阅读(296)  评论(0编辑  收藏  举报