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))
指令。其执行过程实际上是一个查找和调用的过程,具体流程如下:
- 根据
receiver
对象的isa
指针获取其对应的类。 - 在类的
cache
中查找message
方法,如果找不到,再到methodLists
中查找。 - 如果在类中找不到,再向其父类(通过
super_class
指针)中查找。 - 一旦找到
message
方法,就执行其实现的IMP
。
这种基于消息传递的机制使得 Objective-C 特别灵活,也支持后续的动态特性如方法交换和消息转发等。
消息发送与消息转发机制
self 与 super
self
和 super
都表示当前对象,但 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
和 _cmd
。self
表示方法的调用对象,而 _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:
方法来寻找可以转发消息的对象。如果返回非 nil
和 self
的对象,消息将会转发到返回的对象。
- (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 代码的关键。