Runtime¶
面试点¶
数据结构,类对象与元类对象,消息传递,方法缓存,消息转发, Method-Swizzling,动态添加方法,动态方法解析
数据结构¶
1. objc_object¶
objc_object
是 Objective-C 中所有对象的基础结构体。它包含一个 isa 指针,该指针指向对象的类。
struct objc_object {
Class isa;
};
2. objc_class¶
objc_class
是描述类的结构体。它本质上也是一个 objc_object
,因此包含 isa 指针。一个类的 isa 指针指向它的元类(metaclass)。
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; // 协议列表
};
3. isa 指针¶
isa 指针是每个 objc_object
和 objc_class
都包含的一个指针。它是 runtime 系统中用于实现动态类型和消息传递机制的关键。
- 对于实例对象,isa 指针指向它的类(objc_class)。
- 对于类对象,isa 指针指向它的元类(metaclass)。
- 元类的 isa 指针最终指向 NSObject 的元类,形成一个闭环。
isa 指针的作用包括:
- 用于确定对象的类型。
- 用于查找对象的方法实现。
isa_t
是一个在 Objective-C 运行时中用来表示 isa 指针的结构体。它包含了指向对象的类的指针以及一些标志位,用于存储对象的附加信息。在现代的 Objective-C 运行时中,Apple 引入了“优化的 isa”技术,这使得 isa 指针不仅仅是一个简单的指针,而是一个多用途的结构,包含多个字段。
指针类型的 isa 和非指针类型的 isa
- 指针类型的 isa:传统的 isa 指针是一个简单的指针,直接指向对象的类。
- 非指针类型的 isa:优化的 isa 是一个包含多个字段的位域(bitfield), 这些字段可以包含指向类的指针以及其他标志位和信息。
优化的 isa 位域
在优化的 isa 实现中,isa_t 包含多个字段,例如:
- 类指针(class pointer):指向对象所属的类。
- 指针标志位(pointer bit):指示 isa 是否为指针类型。
- 弱引用计数(weak reference count):管理对象的弱引用计数。
- 额外标志位(extra flags):存储附加信息,如对象是否已被标记为自由等。
4. cache_t¶
cache_t
是用于方法缓存的结构体。它在类的结构体中起到了加速方法查找的作用。当一个方法被调用时,runtime 系统首先会在这个缓存中查找,以减少方法查找的开销。
struct cache_t {
struct bucket_t *_buckets;
mask_t _mask;
mask_t _occupied;
};
- _buckets:缓存桶数组,存储方法的缓存条目。
- _mask:用于计算索引的掩码。
- _occupied:已使用的缓存条目的数量。
- 用于快速查找方法执行函数
- 是可增量扩展的哈西表结构
- 是局部性原理的最佳应用
5. class_data_bits_t¶
class_data_bits_t
是一个位域,包含类的只读和读写数据的指针。在 Apple 的运行时实现中,这个类型用于高效存储和访问类的数据。
- class_data_bits_t主要是对class_rw_t的封装
- class_rw_t代表了类相关的读写信息、对class_ro_t的封装
- class_ro_t代表了类相关的只读信息
6. class_rw_t¶
class_rw_t
是类的读写数据的结构体,包含类的动态信息,如方法列表、属性列表等。这些数据在运行时可以被修改。
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro; // 指向只读数据
method_array_t methods; // 方法列表
property_array_t properties; // 属性列表
protocol_array_t protocols; // 协议列表
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
};
7. class_ro_t¶
class_ro_t
是类的只读数据的结构体,包含类的静态信息,如类名、实例大小、成员变量列表等。这些数据在运行时不可修改。
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
const uint8_t * ivarLayout;
const char *name; // 类名
const method_list_t *baseMethods; // 基本方法列表
const protocol_list_t *baseProtocols; // 基本协议列表
const ivar_list_t *ivars; // 成员变量列表
const uint8_t *weakIvarLayout;
const property_list_t *properties; // 属性列表
};
8. method_t¶
method_t
是一个描述方法的结构体,包含方法的选择器(selector)、方法实现(IMP)和方法类型编码(type encoding)。
struct method_t {
SEL name; // 方法选择器
const char *types; // 方法类型编码
IMP imp; // 方法实现
};
9. 结构和关系¶
这些结构体和类型在 Objective-C 的类和方法管理中起着关键作用。它们的关系和作用可以通过下面的示例代码来理解:
// 类对象结构示例
struct objc_class {
Class isa;
Class super_class;
cache_t cache; // 方法缓存
class_data_bits_t bits; // 类数据位域
};
// 类数据位域展开后的数据
struct class_data_t {
class_ro_t *ro; // 只读数据
class_rw_t *rw; // 读写数据
};
// 读写数据包含的方法列表
struct class_rw_t {
method_array_t methods; // 方法列表
// 其他字段省略
};
// 方法列表包含的方法
struct method_list_t {
uint32_t count;
method_t first;
};
- cache_t:用于方法缓存,加速方法查找。
- class_data_bits_t:包含类的只读和读写数据指针的位域。
- class_rw_t:类的读写数据,包含方法列表、属性列表等,可以在运行时修改。
- class_ro_t:类的只读数据,包含类名、实例大小、成员变量列表等,运行时不可修改。
- method_t:表示方法的结构体,包含方法选择器、类型编码和实现指针。
对象,类对象,元类对象¶
- 类对象存储实例方法列表等信息
- 元类对象存储类方法列表等信息
+----------------------+
| Root Metaclass |
+----------------------+
| +classMethod |
isa +-------------------------------+ isa
+----> | isa指向自身 |
| +-------------------------------+
|
+----v----------------+
| Metaclass |
+---------------------+
| +classMethod |
+---------------------+
isa +-------------------------------+ isa
+----> | isa指向Root Metaclass |
| +-------------------------------+
|
+----v----------------+
| Class |
+---------------------+
| -instanceMethod |
+---------------------+
isa +-------------------------------+ isa
+----> | isa指向Metaclass |
+-------------------------------+
|
+--------------v--------------+
| Instance Object |
+-----------------------------+
| isa指向Class |
+-----------------------------+
类对象和元类对象有什么区别和联系?
实例对象通过 isa 指针找到类对象,类对象存储着实例对象的方法列表等信息,以及类对象可以通过 isa 指针指向元类对象,从而可以访问到类方法的相关信息。 类对象和元类对象都是 objc_class 结构,他们都继承 objc_object, 所以都包含 isa 指针。
元类的 isa 指针都指向根元类对象,根元类对象的 isa 指向自身。根元类对象的 superclass 指针指向 根类对象。
这句话来自视频,保留意见,似乎是错误的。 如果调用类方法,在根元类中找不到的时候,他就会调用根类的同名实例方法。
面试题:
self class
和 super class
的解释
self class
[self class]
调用的是当前实例的 class
方法。这意味着它会返回当前对象所属的类。
- 实例对象:当你在一个实例方法中调用
[self class]
时,它会返回当前实例对象的类,即实例对象的isa
指针所指向的类。 - 类对象:如果你调用
[Phone class]
,这将返回Phone
类对象。
super class`
[super class]
也是调用当前实例的 class
方法,但用 super
的语法提示编译器从父类中查找方法实现。不过,class
方法是继承自 NSObject
的,所以无论是通过 self
还是 super
调用,它们最终调用的是 NSObject
的 class
方法。
深入理解类对象和元类
- 类对象:
[Phone class]
返回Phone
类对象。 - 元类对象:每个类对象的
isa
指针指向其对应的元类对象。
class
方法的查找过程
[self class]
和[super class]
最终都会调用NSObject
的class
方法。class
方法会返回调用者的类,即当前对象的isa
指针所指向的类。
代码示例和执行流程
#import "Mobile.h"
@interface Phone : Mobile
@end
@implementation Phone
- (id)init {
self = [super init];
if (self) {
NSLog(@"%@", NSStringFromClass([self class])); // Line A
NSLog(@"%@", NSStringFromClass([super class])); // Line B
}
return self;
}
@end
- Line A:
NSStringFromClass([self class])
调用时,[self class]
返回当前实例的类,即Phone
。 - Line B:
NSStringFromClass([super class])
调用时,虽然语法使用super
,但依旧返回当前实例的类,即Phone
。
实际执行过程
-
创建实例:
objective-c Phone *phone = [[Phone alloc] init];
-
[self class]
调用:- 调用
Phone
实例的class
方法。 - 通过
isa
指针查找到Phone
类对象。 - 返回
Phone
类对象。
- 调用
-
[super class]
调用:- 调用
Phone
实例的class
方法。 super
语法会查找父类的class
方法,但返回值仍然是当前对象的类。- 结果与
[self class]
相同,返回Phone
类对象。
- 调用
总结
[self class]
返回的是当前实例的类,即Phone
。[super class]
实际上也是返回当前实例的类,即Phone
,因为super
只是改变了方法查找路径,并没有改变实际调用者的isa
指针。
不论使用 [self class]
还是 [super class]
,最终都返回当前实例的类名,因为 class
方法定义在 NSObject
中,而所有类最终都继承自 NSObject
。 class
方法返回的就是调用者的 isa
指针指向的类。
这样一来,无论是通过 self
还是 super
调用 class
方法,都返回当前对象所属的类。
消息传递¶
void objc_msgSend(void /*id self, SEL op, ...*/)
[self class] <---> objc_msgSend(self, @selector(class))
void objc_msgSendSuper(void/*struct objc_super *super, SEL op, SEL op ...*/)
struct objc_super {
/// Specifies an instance of a class
__unsafe_unretained id receiver; ==> 当前对象 self
}
[super class] <--> objc_msgSendSuper(super, @selector(class))
方法缓存,缓存查找¶
1. 当前类中查找¶
- 对于已排序好的列表,采用二分查找算法查找方法对应执行函数。
- 对于没有排序的列表,采用一般遍历查找方法对应执行函数。
2. 父类逐级查找¶
消息转发¶
1. 实例转发流程¶
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface RuntimeObject : NSObject
- (void)test;
@end
NS_ASSUME_NONNULL_END
#import "RuntimeObject.h"
@implementation RuntimeObject
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(test)) {
NSLog(@"resolveInstanceMethod:");
return NO;
} else {
return [super resolveInstanceMethod:sel];
}
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSLog(@"forwardingTargetForSelector:");
return nil;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSLog(@"methodSignatureForSelector:");
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
} else {
return [super methodSignatureForSelector:aSelector];
}
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@"forwardInvocation:");
}
@end
//resolveInstanceMethod:
//forwardingTargetForSelector:
//methodSignatureForSelector:
//forwardInvocation:
Method-Swizzling¶
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface RuntimeObject : NSObject
- (void)test;
- (void)otherTest;
@end
NS_ASSUME_NONNULL_END
#import "RuntimeObject.h"
#import <objc/runtime.h>
@implementation RuntimeObject
+ (void)load {
Method test = class_getInstanceMethod(self, @selector(test));
Method exchangeTest = class_getInstanceMethod(self, @selector(otherTest));
method_exchangeImplementations(test, exchangeTest);
}
- (void)test {
NSLog(@"test");
}
- (void)otherTest {
NSLog(@"otherTest");
[self otherTest];
}
@end
// otherTest
// test
动态添加方法¶
在 Objective-C 中,动态添加方法是一种强大的功能,允许你在运行时向类中添加方法。这通常在以下几种情况下非常有用:
- 动态扩展类的功能。
- 在运行时处理未知选择器。
- 实现消息转发机制。
1. 动态添加方法的步骤¶
- 定义方法的实现:实现一个 C 函数,作为动态添加方法的实现。
- 动态添加方法:使用
class_addMethod
函数将该实现添加到类中。 - 处理未实现的方法:通过
resolveInstanceMethod
或resolveClassMethod
方法来处理未实现的方法。
2. 代码示例¶
以下是一个完整的示例,展示了如何动态添加一个实例方法和类方法。
定义类和方法实现¶
首先,定义一个类 MyClass
和一个动态添加的方法实现。
#import <objc/runtime.h>
#import <Foundation/Foundation.h>
@interface MyClass : NSObject
@end
@implementation MyClass
@end
// 动态添加的方法实现
void dynamicMethodIMP(id self, SEL _cmd) {
NSLog(@"Dynamic method -dynamicMethod called");
}
动态添加实例方法¶
在类的实现中,重写 +resolveInstanceMethod:
方法,以动态添加方法。
@implementation MyClass
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(dynamicMethod)) {
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end
动态添加类方法¶
类似地,重写 +resolveClassMethod:
方法,以动态添加类方法。
@implementation MyClass (DynamicClassMethod)
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(dynamicClassMethod)) {
Class metaClass = objc_getMetaClass(class_getName([self class]));
class_addMethod(metaClass, sel, (IMP)dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveClassMethod:sel];
}
@end
使用动态添加的方法¶
最后,在主函数中调用这些方法。
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyClass *myObject = [[MyClass alloc] init];
// 调用动态添加的实例方法
if ([myObject respondsToSelector:@selector(dynamicMethod)]) {
[myObject performSelector:@selector(dynamicMethod)];
}
// 调用动态添加的类方法
if ([MyClass respondsToSelector:@selector(dynamicClassMethod)]) {
[MyClass performSelector:@selector(dynamicClassMethod)];
}
}
return 0;
}
代码解释¶
-
动态添加实例方法:
+resolveInstanceMethod:
方法在实例方法查找失败时被调用。- 如果方法选择器是
@selector(dynamicMethod)
,则使用class_addMethod
动态添加该方法。 class_addMethod
的参数包括类、选择器、方法实现和类型编码。IMP
类型是指向方法实现的指针。"v@:"
是类型编码,表示返回类型为void
,参数为self
和_cmd
。
-
动态添加类方法:
+resolveClassMethod:
方法在类方法查找失败时被调用。- 使用
objc_getMetaClass
获取元类,然后使用class_addMethod
向元类添加方法。 - 元类的类型编码与实例方法相同。
运行结果¶
当运行上述代码时,会看到如下输出:
Dynamic method -dynamicMethod called
Dynamic method -dynamicMethod called
这表明动态添加的实例方法和类方法都被成功调用。
3. 总结¶
- 动态方法解析:通过重写
+resolveInstanceMethod:
和+resolveClassMethod:
方法,可以在运行时动态添加方法。 - 方法添加:使用
class_addMethod
将方法添加到类或元类中。 - 方法实现:动态添加的方法实现可以是一个 C 函数,指向
IMP
类型。
这种技术在许多高级场景中非常有用,例如为现有类添加功能、处理未知消息、实现消息转发等。
动态方法解析¶
@dynamic
- 动态运行时语言将函数决议推迟到运行时。
- 编译时语言在编译期进行函数决议。