标签:
这一章,我们就要开始讨论Runtime中最有意思的一部分:消息处理机制。我们将详细讨论消息的发送及消息的转发。typedef struct objc_selector *SEL;objc_selector结构体的详细定义没有在<objc/runtime.h>头文件中找到。方法的selector用于表示运行时方法的名字。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。如下代码所示:
SEL sel1 = @selector(method1); NSLog(@"sel : %p", sel1);上面的输出为:
2014-10-30 18:40:07.518 RuntimeTest[52734:466626] sel : 0x100002d72两个类之间,不管它们是父类与子类的关系,还是之间没有这种关系,只要方法名相同,那么方法的SEL就是一样的。每一个方法都对应着一个SEL。所以在Objective-C同一个类(及类的继承体系)中,不能存在2个同名的方法,即使参数类型不同也不行。相同的方法只能对应一个SEL。这也就导致Objective-C在处理相同方法名且参数个数相同但类型不同的方法方面的能力很差。如在某个类中定义以下两个方法:
- (void)setWidth:(int)width; - (void)setWidth:(double)width;这样的定义被认为是一种编译错误,所以我们不能像C++, C#那样。而是需要像下面这样来声明:
-(void)setWidthIntValue:(int)width; -(void)setWidthDoubleValue:(double)width;当然,不同的类可以拥有相同的selector,这个没有问题。不同类的实例对象执行相同的selector时,会在各自的方法列表中去根据selector去寻找自己对应的IMP。
id (*IMP)(id, SEL, ...)这个函数使用当前CPU架构实现的标准的C调用约定。第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针),第二个参数是方法选择器(selector),接下来是方法的实际参数列表。
typedef struct objc_method *Method; struct objc_method { SEL method_name OBJC2_UNAVAILABLE; // 方法名 char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; // 方法实现 }我们可以看到该结构体中包含一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,我们便可以找到对应的IMP,从而调用方法的实现代码。具体操作流程我们将在下面讨论。
struct objc_method_description { SEL name; char *types; };
// 调用指定方法的实现 id method_invoke ( id receiver, Method m, ... ); // 调用返回一个数据结构的方法的实现 void method_invoke_stret ( id receiver, Method m, ... ); // 获取方法名 SEL method_getName ( Method m ); // 返回方法的实现 IMP method_getImplementation ( Method m ); // 获取描述方法参数和返回值类型的字符串 const char * method_getTypeEncoding ( Method m ); // 获取方法的返回值类型的字符串 char * method_copyReturnType ( Method m ); // 获取方法的指定位置参数的类型字符串 char * method_copyArgumentType ( Method m, unsigned int index ); // 通过引用返回方法的返回值类型字符串 void method_getReturnType ( Method m, char *dst, size_t dst_len ); // 返回方法的参数的个数 unsigned int method_getNumberOfArguments ( Method m ); // 通过引用返回方法指定位置参数的类型字符串 void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len ); // 返回指定方法的方法描述结构体 struct objc_method_description * method_getDescription ( Method m ); // 设置方法的实现 IMP method_setImplementation ( Method m, IMP imp ); // 交换两个方法的实现 void method_exchangeImplementations ( Method m1, Method m2 );
// 返回给定选择器指定的方法的名称 const char * sel_getName ( SEL sel ); // 在Objective-C Runtime系统中注册一个方法,将方法名映射到一个选择器,并返回这个选择器 SEL sel_registerName ( const char *str ); // 在Objective-C Runtime系统中注册一个方法 SEL sel_getUid ( const char *str ); // 比较两个选择器 BOOL sel_isEqual ( SEL lhs, SEL rhs );sel_registerName函数:在我们将一个方法添加到类定义时,我们必须在Objective-C Runtime系统中注册一个方法名以获取方法的选择器。
objc_msgSend(receiver, selector)如果消息中还有其它参数,则该方法的形式如下所示:
objc_msgSend(receiver, selector, arg1, arg2, ...)这个函数完成了动态绑定的所有事情:
- strange { id target = getTheReceiver(); SEL method = getTheMethod(); if ( target == self || method == _cmd ) return nil; return [target performSelector:method]; }当然,这两个参数我们用得比较多的是self,_cmd在实际中用得比较少。
void (*setter)(id, SEL, BOOL); int i; setter = (void (*)(id, SEL, BOOL))[target methodForSelector:@selector(setFilled:)]; for ( i = 0 ; i < 1000 ; i++ ) setter(targetList[i], @selector(setFilled:), YES);这里需要注意的就是函数指针的前两个参数必须是id和SEL。
if ([self respondsToSelector:@selector(method)]) { [self performSelector:@selector(method)]; }不过,我们这边想讨论下不使用respondsToSelector:判断的情况。这才是我们这一节的重点。
-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940 *** Terminating app due to uncaught exception 'NSInvalidArgumentException' , reason: '-[SUTRuntimeMethod method]: unrecognized selector sent to instance 0x100111940'这段异常信息实际上是由NSObject的”doesNotRecognizeSelector”方法抛出的。不过,我们可以采取一些措施,让我们的程序执行特定的逻辑,而避免程序的崩溃。
void functionForMethod1(id self, SEL _cmd) { NSLog(@ "%@, %p" , self, _cmd); } + (BOOL)resolveInstanceMethod:(SEL)sel { NSString *selectorString = NSStringFromSelector(sel); if ([selectorString isEqualToString:@ "method1" ]) { class_addMethod(self. class , @selector(method1), (IMP)functionForMethod1, "@:" ); } return [ super resolveInstanceMethod:sel]; }不过这种方案更多的是为了实现@dynamic属性。
- (id)forwardingTargetForSelector:(SEL)aSelector如果一个对象实现了这个方法,并返回一个非nil的结果,则这个对象会作为消息的新接收者,且消息会被分发到这个对象。当然这个对象不能是self自身,否则就是出现无限循环。当然,如果我们没有指定相应的对象来处理aSelector,则应该调用父类的实现来返回结果。
@interface SUTRuntimeMethodHelper : NSObject - (void)method2; @end @implementation SUTRuntimeMethodHelper - (void)method2 { NSLog(@"%@, %p", self, _cmd); } @end #pragma mark - @interface SUTRuntimeMethod () { SUTRuntimeMethodHelper *_helper; } @end @implementation SUTRuntimeMethod + (instancetype)object { return [[self alloc] init]; } - (instancetype)init { self = [super init]; if (self != nil) { _helper = [[SUTRuntimeMethodHelper alloc] init]; } return self; } - (void)test { [self performSelector:@selector(method2)]; } - (id)forwardingTargetForSelector:(SEL)aSelector { NSLog(@"forwardingTargetForSelector"); NSString *selectorString = NSStringFromSelector(aSelector); // 将消息转发给_helper来处理 if ([selectorString isEqualToString:@"method2"]) { return _helper; } return [super forwardingTargetForSelector:aSelector]; } @end这一步合适于我们只想将消息转发到另一个能处理该消息的对象上。但这一步无法对消息进行处理,如操作消息的参数和返回值。
- (void)forwardInvocation:(NSInvocation *)anInvocation运行时系统会在这一步给消息接收者最后一次机会将消息转发给其它对象。对象会创建一个表示消息的NSInvocation对象,把与尚未处理的消息有关的全部细节都封装在anInvocation中,包括selector,目标(target)和参数。我们可以在forwardInvocation方法中选择将消息转发给其它对象。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
消息转发机制使用从这个方法中获取的信息来创建NSInvocation对象。因此我们必须重写这个方法,为给定的selector提供一个合适的方法签名。完整的示例如下所示:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *signature = [super methodSignatureForSelector:aSelector]; if (!signature) { if ([SUTRuntimeMethodHelper instancesRespondToSelector:aSelector]) { signature = [SUTRuntimeMethodHelper instanceMethodSignatureForSelector:aSelector]; } } return signature; } - (void)forwardInvocation:(NSInvocation *)anInvocation { if ([SUTRuntimeMethodHelper instancesRespondToSelector:anInvocation.selector]) { [anInvocation invokeWithTarget:_helper]; } }NSObject的forwardInvocation:方法实现只是简单调用了doesNotRecognizeSelector:方法,它不会转发任何消息。这样,如果不在以上所述的三个步骤中处理未知消息,则会引发一个异常。
不过消息转发虽然类似于继承,但NSObject的一些方法还是能区分两者。如respondsToSelector:和isKindOfClass:只能用于继承体系,而不能用于转发链。便如果我们想让这种消息转发看起来像是继承,则可以重写这些方法,如以下代码所示:
(BOOL)respondsToSelector:(SEL)aSelector { if ( [super respondsToSelector:aSelector] ) return YES; else { /* Here, test whether the aSelector message can * * be forwarded to another object and whether that * * object can respond to it. Return YES if it can. */ } return NO; }
标签:
原文地址:http://blog.csdn.net/shimazhuge/article/details/51431410