MST

星途 面试题库

面试题:Objective-C消息传递机制与动态绑定

阐述Objective-C消息传递过程中,运行时系统是如何根据方括号表达式进行动态方法解析、备用接收者查找和完整的动态方法解析流程的?结合示例代码说明在动态绑定过程中方括号表达式所起的作用。
34.2万 热度难度
编程语言Objective-C

知识考点

AI 面试

面试题答案

一键面试

1. 动态方法解析

在Objective-C中,当向一个对象发送一条它当前类的方法列表中不存在的消息时,运行时系统会启动动态方法解析流程。

  • 动态方法解析流程
    • 运行时系统首先调用+ (BOOL)resolveInstanceMethod:(SEL)sel(对于实例方法)或+ (BOOL)resolveClassMethod:(SEL)sel(对于类方法)。如果类实现了这个方法,并且在其中为该SEL动态添加了方法实现,那么动态方法解析就成功了,消息可以正常发送。
    • 示例代码:
#import <Foundation/Foundation.h>

@interface Person : NSObject
- (void)run;
@end

@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(run)) {
        class_addMethod(self, sel, (IMP)runFunction, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void runFunction(id self, SEL _cmd) {
    NSLog(@"Running...");
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        [p run];
    }
    return 0;
}

在上述代码中,Person类开始并没有-run方法的实现,但是通过resolveInstanceMethod:方法动态添加了runFunction作为-run方法的实现,从而使[p run]能够正常执行。

2. 备用接收者查找

如果动态方法解析没有成功,运行时系统会进入备用接收者查找阶段。

  • 备用接收者查找流程
    • 运行时系统会调用- (id)forwardingTargetForSelector:(SEL)aSelector方法。如果该方法返回一个非nil对象,并且这个对象能够响应aSelector消息,那么这个对象就成为备用接收者,消息会被发送给它。
    • 示例代码:
#import <Foundation/Foundation.h>

@interface Helper : NSObject
- (void)run;
@end

@implementation Helper
- (void)run {
    NSLog(@"Helper is running...");
}
@end

@interface Person : NSObject
@property (nonatomic, strong) Helper *helper;
@end

@implementation Person
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(run)) {
        return self.helper;
    }
    return nil;
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        p.helper = [[Helper alloc] init];
        [p run];
    }
    return 0;
}

在这个例子中,Person类没有-run方法,但是通过forwardingTargetForSelector:返回了helper对象,而helper对象有-run方法的实现,所以[p run]能够通过备用接收者机制执行。

3. 完整的动态方法解析流程

如果备用接收者查找也失败,运行时系统会进入完整的动态方法解析流程。

  • 完整动态方法解析流程
    • 运行时系统会创建一个NSInvocation对象,该对象封装了消息的所有信息,包括目标对象、选择器和参数等。然后运行时系统调用- (void)forwardInvocation:(NSInvocation *)anInvocation方法。在这个方法中,开发者可以手动处理这个NSInvocation对象,例如将消息转发给其他对象,或者手动执行一些操作。如果没有实现forwardInvocation:方法,运行时系统会抛出unrecognized selector sent to instance异常。
    • 示例代码:
#import <Foundation/Foundation.h>

@interface Helper : NSObject
- (void)run;
@end

@implementation Helper
- (void)run {
    NSLog(@"Helper is running...");
}
@end

@interface Person : NSObject
@property (nonatomic, strong) Helper *helper;
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    SEL sel = anInvocation.selector;
    if ([self.helper respondsToSelector:sel]) {
        [anInvocation invokeWithTarget:self.helper];
    } else {
        [super forwardInvocation:anInvocation];
    }
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *sig = [super methodSignatureForSelector:aSelector];
    if (!sig) {
        sig = [self.helper methodSignatureForSelector:aSelector];
    }
    return sig;
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        p.helper = [[Helper alloc] init];
        [p run];
    }
    return 0;
}

在这个代码中,Person类通过forwardInvocation:方法将-run消息转发给了helper对象,实现了完整的动态方法解析。

4. 方括号表达式在动态绑定过程中的作用

方括号表达式[receiver selector]是Objective-C中向对象发送消息的语法。在动态绑定过程中:

  • 启动动态绑定流程:当运行时系统遇到[receiver selector]表达式时,如果receiver当前类的方法列表中没有找到selector对应的方法实现,就会启动上述的动态方法解析、备用接收者查找和完整的动态方法解析等流程。
  • 携带消息信息:方括号表达式明确了消息的接收者(receiver)和要发送的选择器(selector),这些信息在整个动态绑定过程中至关重要,运行时系统依据这些信息来进行方法的动态查找、解析和转发等操作。