MST

星途 面试题库

面试题:Objective-C 运行时机制深入剖析

深入解释 Objective-C 的运行时机制,包括类的结构、对象的本质、方法的调用过程等。举例说明如何利用运行时机制实现方法交换(Method Swizzling),以及这种技术在实际项目中的应用场景和可能带来的风险。如何通过运行时机制动态创建类和对象,并为其添加属性和方法?
38.8万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

Objective-C 的运行时机制

  1. 类的结构
    • 在 Objective - C 中,类是一个 objc_class 结构体。这个结构体包含了类的元数据,如类名、父类指针、类的实例变量列表、方法列表、协议列表等。例如,一个简单的 Person 类,它的 objc_class 结构体就会包含 Person 类名、指向其父类(如 NSObject)的指针,以及 Person 类定义的实例变量(如 nameage 等)和方法(如 -(void)sayHello)相关信息。
    • 类的方法列表存储的是 objc_method 结构体,每个 objc_method 结构体包含了方法名、方法的实现(函数指针)以及方法的类型编码等信息。
  2. 对象的本质
    • 对象本质上是一个指向 objc_object 结构体的指针。objc_object 结构体只有一个成员变量 isa 指针,这个指针指向对象所属的类。例如,创建一个 Person 类的实例 personperson 就是一个指向 objc_object 结构体的指针,其 isa 指针指向 Person 类的 objc_class 结构体。通过 isa 指针,对象可以找到它所属类的方法列表等元数据,从而调用相应的方法。
  3. 方法的调用过程
    • 当向一个对象发送消息(调用方法)时,首先会根据对象的 isa 指针找到对应的类。
    • 在类的方法列表中查找是否有与消息对应的方法,如果找到,就直接调用该方法的实现。
    • 如果在本类中没有找到,就沿着继承链向父类查找,直到找到对应的方法或者到达根类 NSObject。如果最终都没有找到,就会进入动态方法解析阶段。
    • 在动态方法解析阶段,运行时系统会给程序一次机会,通过 + (BOOL)resolveInstanceMethod:(SEL)sel(实例方法)或 + (BOOL)resolveClassMethod:(SEL)sel(类方法)方法来动态添加方法实现。如果动态方法解析没有处理,就会进入备用接收者阶段,尝试寻找其他能够处理该消息的对象。如果还是没有找到,最后就会进入消息转发阶段,程序可以通过 forwardingTargetForSelector: 等方法来处理未识别的消息,否则程序会抛出 unrecognized selector sent to instance 异常。

方法交换(Method Swizzling)

  1. 实现方法
    • 可以使用 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
  1. 应用场景
    • 日志记录:可以在系统自带的方法(如 UIViewControllerviewDidLoad)中插入日志记录代码,通过方法交换,在原方法执行前后添加日志输出,方便调试和分析程序运行流程。
    • 性能优化:在一些频繁调用的系统方法(如 NSArrayobjectAtIndex:)中,通过方法交换添加性能监测代码,统计方法调用的耗时等,从而找出性能瓶颈。
  2. 可能带来的风险
    • 稳定性问题:如果交换的方法在不同的库或框架中有依赖关系,可能会导致不可预见的错误。例如,某个库依赖于 UIViewControllerviewDidLoad 方法的原始实现,而方法交换改变了其行为,可能会使该库功能异常。
    • 代码维护困难:方法交换会改变原有方法的行为,当项目规模较大时,可能会使代码逻辑变得复杂,难以理解和维护。特别是当多个地方进行方法交换且相互影响时,调试和排查问题会变得非常困难。

动态创建类和对象,并添加属性和方法

  1. 动态创建类
    • 使用 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;
}
  1. 添加属性
    • 使用 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;
}
  1. 添加方法
    • 使用 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 的运行时机制动态创建类和对象,并为其添加属性和方法。