面试题答案
一键面试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
),这些信息在整个动态绑定过程中至关重要,运行时系统依据这些信息来进行方法的动态查找、解析和转发等操作。