面试题答案
一键面试Objective-C 的运行时机制
- 类的结构
- 在 Objective - C 中,类是一个
objc_class
结构体。这个结构体包含了类的元数据,如类名、父类指针、类的实例变量列表、方法列表、协议列表等。例如,一个简单的Person
类,它的objc_class
结构体就会包含Person
类名、指向其父类(如NSObject
)的指针,以及Person
类定义的实例变量(如name
、age
等)和方法(如-(void)sayHello
)相关信息。 - 类的方法列表存储的是
objc_method
结构体,每个objc_method
结构体包含了方法名、方法的实现(函数指针)以及方法的类型编码等信息。
- 在 Objective - C 中,类是一个
- 对象的本质
- 对象本质上是一个指向
objc_object
结构体的指针。objc_object
结构体只有一个成员变量isa
指针,这个指针指向对象所属的类。例如,创建一个Person
类的实例person
,person
就是一个指向objc_object
结构体的指针,其isa
指针指向Person
类的objc_class
结构体。通过isa
指针,对象可以找到它所属类的方法列表等元数据,从而调用相应的方法。
- 对象本质上是一个指向
- 方法的调用过程
- 当向一个对象发送消息(调用方法)时,首先会根据对象的
isa
指针找到对应的类。 - 在类的方法列表中查找是否有与消息对应的方法,如果找到,就直接调用该方法的实现。
- 如果在本类中没有找到,就沿着继承链向父类查找,直到找到对应的方法或者到达根类
NSObject
。如果最终都没有找到,就会进入动态方法解析阶段。 - 在动态方法解析阶段,运行时系统会给程序一次机会,通过
+ (BOOL)resolveInstanceMethod:(SEL)sel
(实例方法)或+ (BOOL)resolveClassMethod:(SEL)sel
(类方法)方法来动态添加方法实现。如果动态方法解析没有处理,就会进入备用接收者阶段,尝试寻找其他能够处理该消息的对象。如果还是没有找到,最后就会进入消息转发阶段,程序可以通过forwardingTargetForSelector:
等方法来处理未识别的消息,否则程序会抛出unrecognized selector sent to instance
异常。
- 当向一个对象发送消息(调用方法)时,首先会根据对象的
方法交换(Method Swizzling)
- 实现方法
- 可以使用
class_getInstanceMethod
函数获取类的实例方法,使用method_exchangeImplementations
函数交换两个方法的实现。例如:
- 可以使用
#import <objc/runtime.h>
@implementation MyClass
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(originalMethod);
SEL swizzledSelector = @selector(swizzledMethod);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
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 called");
}
- (void)swizzledMethod {
NSLog(@"Swizzled method called");
[self swizzledMethod];
}
@end
- 应用场景
- 日志记录:可以在系统自带的方法(如
UIViewController
的viewDidLoad
)中插入日志记录代码,通过方法交换,在原方法执行前后添加日志输出,方便调试和分析程序运行流程。 - 性能优化:在一些频繁调用的系统方法(如
NSArray
的objectAtIndex:
)中,通过方法交换添加性能监测代码,统计方法调用的耗时等,从而找出性能瓶颈。
- 日志记录:可以在系统自带的方法(如
- 可能带来的风险
- 稳定性问题:如果交换的方法在不同的库或框架中有依赖关系,可能会导致不可预见的错误。例如,某个库依赖于
UIViewController
的viewDidLoad
方法的原始实现,而方法交换改变了其行为,可能会使该库功能异常。 - 代码维护困难:方法交换会改变原有方法的行为,当项目规模较大时,可能会使代码逻辑变得复杂,难以理解和维护。特别是当多个地方进行方法交换且相互影响时,调试和排查问题会变得非常困难。
- 稳定性问题:如果交换的方法在不同的库或框架中有依赖关系,可能会导致不可预见的错误。例如,某个库依赖于
动态创建类和对象,并添加属性和方法
- 动态创建类
- 使用
objc_allocateClassPair
函数可以动态创建一个类。例如:
- 使用
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建一个新类
Class newClass = objc_allocateClassPair([NSObject class], "MyDynamicClass", 0);
if (newClass) {
// 注册类
objc_registerClassPair(newClass);
}
}
return 0;
}
- 添加属性
- 使用
class_addIvar
函数可以为动态创建的类添加实例变量(属性)。例如:
- 使用
#import <objc/runtime.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
Class newClass = objc_allocateClassPair([NSObject class], "MyDynamicClass", 0);
if (newClass) {
// 添加一个名为 "name" 的属性,类型为 char *
class_addIvar(newClass, "name", sizeof(char *), log2(sizeof(char *)), @encode(char *));
objc_registerClassPair(newClass);
}
}
return 0;
}
- 添加方法
- 使用
class_addMethod
函数可以为动态创建的类添加方法。例如:
- 使用
#import <objc/runtime.h>
void sayHello(id self, SEL _cmd) {
NSLog(@"Hello from dynamic method");
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
Class newClass = objc_allocateClassPair([NSObject class], "MyDynamicClass", 0);
if (newClass) {
class_addIvar(newClass, "name", sizeof(char *), log2(sizeof(char *)), @encode(char *));
// 添加一个名为 "sayHello" 的方法
class_addMethod(newClass, @selector(sayHello), (IMP)sayHello, "v@:");
objc_registerClassPair(newClass);
id instance = [[newClass alloc] init];
[instance performSelector:@selector(sayHello)];
}
}
return 0;
}
通过上述方式,可以利用 Objective - C 的运行时机制动态创建类和对象,并为其添加属性和方法。