您的位置:首页 > 移动开发 > Objective-C

深入理解Objective-C的Runtime机制

2016-04-27 12:41 821 查看
摘要:Objective-C是基于C加入了面向对象特性和消息转发机制的动态语言,除编译器之外,还需用Runtime系统来动态创建类和对象,进行消息发送和转发。本文作者通过分析Apple开源的Runtime代码来深入理解OC的Runtime机制。

这时首先会构造出objc_super结构体,这个结构体第一个成员是self,第二个成员是(id)class_getSuperclass(objc_getClass("Son")),实际上该函数会输出Father。然后在Father类查找class方法,查找不到,最后在NSObject查到。此时,内部使用objc_msgSend(objc_super->receiver, @selector(class))去调用,与[self class]调用相同,所以结果还是Son。

隐藏参数self和_cmd

当[receiver message]调用方法时,系统会在运行时偷偷地动态传入两个隐藏参数self和_cmd,之所以称它们为隐藏参数,是因为在源代码中没有声明和定义这两个参数。至于对于self的描述,上面已经解释非常清楚了,下面我们重点讲解_cmd。

_cmd表示当前调用方法,其实它就是一个方法选择器SEL。一般用于判断方法名或在Associated Objects中唯一标识键名,后面在Associated Objects会讲到。

方法解析与消息转发

[receiver message]调用方法时,如果在message方法在receiver对象的类继承体系中没有找到方法,那怎么办?一般情况下,程序在运行时就会Crash掉,抛出unrecognized selector sent to…类似这样的异常信息。但在抛出异常之前,还有三次机会按以下顺序让你拯救程序。

Method Resolution
Fast Forwarding
Normal Forwarding



Message Forward from Google

Method Resolution

首先Objective-C在运行时调用+ resolveInstanceMethod:或+ resolveClassMethod:方法,让你添加方法的实现。如果你添加方法并返回YES,那系统在运行时就会重新启动一次消息发送的过程。

举一个简单例子,定义一个类Message,它主要定义一个方法sendMessage,下面就是它的设计与实现:

[cpp] view
plaincopy

@interface Message : NSObject

- (void)sendMessage:(NSString *)word;

@end

[cpp] view
plaincopy

@implementation Message

- (void)sendMessage:(NSString *)word

{

NSLog(@"normal way : send message = %@", word);

}

@end

如果我在viewDidLoad方法中创建Message对象并调用sendMessage方法:

[cpp] view
plaincopy

- (void)viewDidLoad {

[super viewDidLoad];

Message *message = [Message new];

[message sendMessage:@"Sam Lau"];

}

控制台会打印以下信息:

normal way : send message = Sam Lau


但现在我将原来sendMessage方法实现给注释掉,覆盖resolveInstanceMethod方法:

[cpp] view
plaincopy

#pragma mark - Method Resolution

/// override resolveInstanceMethod or resolveClassMethod for changing sendMessage method implementation

+ (BOOL)resolveInstanceMethod:(SEL)sel

{

if (sel == @selector(sendMessage:)) {

class_addMethod([self class], sel, imp_implementationWithBlock(^(id self, NSString *word) {

NSLog(@"method resolution way : send message = %@", word);

}), "v@*");

}

return YES;

}

控制台就会打印以下信息:

method resolution way : send message = Sam Lau


注意到上面代码有这样一个字符串"v@*,它表示方法的参数和返回值,详情请参考Type
Encodings。

如果resolveInstanceMethod方法返回NO,运行时就跳转到下一步:消息转发(Message Forwarding)。

Fast Forwarding

如果目标对象实现- forwardingTargetForSelector:方法,系统就会在运行时调用这个方法,只要这个方法返回的不是nil或self,也会重启消息发送的过程,把这消息转发给其他对象来处理。否则,就会继续Normal Fowarding。

继续上面Message类的例子,将sendMessage和resolveInstanceMethod方法注释掉,然后添加forwardingTargetForSelector方法的实现:

[cpp] view
plaincopy

#pragma mark - Fast Forwarding

- (id)forwardingTargetForSelector:(SEL)aSelector

{

if (aSelector == @selector(sendMessage:)) {

return [MessageForwarding new];

}

return nil;

}

此时还缺一个转发消息的类MessageForwarding,这个类的设计与实现如下:

[cpp] view
plaincopy

@interface MessageForwarding : NSObject

- (void)sendMessage:(NSString *)word;

@end

[cpp] view
plaincopy

@implementation MessageForwarding

- (void)sendMessage:(NSString *)word

{

NSLog(@"fast forwarding way : send message = %@", word);

}

@end

此时,控制台会打印以下信息:

fast forwarding way : send message = Sam Lau


这里叫Fast,是因为这一步不会创建NSInvocation对象,但Normal Forwarding会创建它,所以相对于更快点。

Normal Forwarding

如果没有使用Fast Forwarding来消息转发,最后只有使用Normal Forwarding来进行消息转发。它首先调用methodSignatureForSelector:方法来获取函数的参数和返回值,如果返回为nil,程序会Crash掉,并抛出unrecognized selector sent to instance异常信息。如果返回一个函数签名,系统就会创建一个NSInvocation对象并调用-forwardInvocation:方法。

继续前面的例子,将forwardingTargetForSelector方法注释掉,添加methodSignatureForSelector和forwardInvocation方法的实现:

[cpp] view
plaincopy

#pragma mark - Normal Forwarding

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

{

NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];

if (!methodSignature) {

methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];

}

return methodSignature;

}

- (void)forwardInvocation:(NSInvocation *)anInvocation

{

MessageForwarding *messageForwarding = [MessageForwarding new];

if ([messageForwarding respondsToSelector:anInvocation.selector]) {

[anInvocation invokeWithTarget:messageForwarding];

}

}

关于这个例子的示例代码请到Github下载。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: