NSMethodSignature和NSInvocation

问题

在OC的消息转发流程中的最后一步保障是methodSignatureForSelector和forwardInvocation的组合;这两个方法必须同时实现才可以执行,否则消息被直接发派到unrecognizedSelector中,crash。

这两个函数中涉及到的两个类,NSInvocation和NSMethodSignature;

对于NSInvocation,在使用NSOperationQueue时大部分情况下直接使用blockOperation和InvocationOperation两个系统写好的子类来实现多线程,Invocation直接通过给定的target和selector就可以知道把哪个对象的那个函数放到队列中去,所以可以粗略把他看成是对target和selector的封装,执行他的invoke函数就可以找到target对应的selector方法。

NSMethodSignature这个类比较陌生,翻译为”方法签名“,查阅了好多讲OC消息分派的博客中,都是直接给出了两个函数类似的实现方法,看了好久也看不出来这个类到底是什么意思:

有的这么一来:[NSMethodSignature signatureWithObjCTypes:"v@:"]

有的这么一来:NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];

还有的更随意的:return [NSMethodSignature signatureWithObjCTypes:"[email protected]:"]; (https://www.aliyun.com/jiaocheng/369199.html)

最后这位大哥真滴秀;第一印象还以为这个签名就是随便一个c语言的字符串,只要保证唯一的一个就行了,结果误导了我大半天。

解答

apple官方的解释是这样的:Use an NSMethodSignature object to forward messages that the receiving object does not respond to—most notably in the case of distributed objects. You typically create an NSMethodSignature object using the NSObject methodSignatureForSelector: instance method (in macOS 10.5 and later you can also use signatureWithObjCTypes:). It is then used to create an NSInvocation object, which is passed as the argument to a forwardInvocation: message to send the invocation on to whatever other object can handle the message. In the default case, NSObject invokes doesNotRecognizeSelector:, which raises an exception. For distributed objects, the NSInvocation object is encoded using the information in the NSMethodSignature object and sent to the real object represented by the receiver of the message.

谷歌翻译后:使用NSMethodSignature对象转发接收对象不响应的消息 - 最明显的是在分布式对象的情况下。 您通常使用NSObject methodSignatureForSelector:实例方法创建一个NSMethodSignature对象(在macOS 10.5及更高版本中,您还可以使用signatureWithObjCTypes :)。 然后用它创建一个NSInvocation对象,该对象作为参数传递给forwardInvocation:消息,以将调用发送到其他任何可以处理该消息的对象。 在默认情况下,NSObject会调用doesNotRecognizeSelector:,这会引发异常。 对于分布式对象,NSInvocation对象使用NSMethodSignature对象中的信息进行编码并发送到由消息接收者表示的实际对象。

理解:

+ (NSMethodSignature *)signatureWithObjCTypes:(const char *)types;

types:An array of characters containing the type encodings for the method arguments.

包含方法参数类型编码的字符数组。

上面的第一种和第三种方法就是这样创建的签名,”v@:“,意思就是返回值是v,也就是void,:右侧代表的是参数列表,这个没有参数;如果返回值和参数都是id类型的就这样写,"@@:@"第一个@是返回值,最后一个@是参数;如果是int型就”i@:i“,参数和返回值都是int型(果然还是没有理解最后那位大神是什么心态”[email protected]:“,这玩意能识别出来什么)

具体的型号编码见apple官方文档(https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100)

第二种创建方式就是使用selector创建,创建的时候不会关心selector里面具体是怎么实现的,只是获取到返回值和参数列表,比如说有如下两个函数:

- (NSString *)modalMethod: (NSString *);

- (NSString *)realMethod: (NSString *);

使用者中方式创建签名:NSMethodSignature *signature = [[self class] methodSignatureForSelector:@selector(modalMethod)];

使用这个签名创建一个Invocation:NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];

再使用这个Invocation来调用realMethod函数也是可以的:[invocation setTarget:self];[invocation setSelector:@selector(realMethod:)];[invocation invoke];

只要是函数的格式与signature中规定的一致就可以调用.

所以,函数签名就像是一个方法格式的规定模板,传入一个selector,他就会从里面提取出来这个方法的格式,Invocation初始化的时候参照这个签名的来初始化;而不是一个”唯一的标识作用的字符串“。参考Invocation的用法,发现setArgument和getReturnValue传参数的时候都是使用void* 指针传递参数的,因此需要为Invocation指定一个格式,取错类型会导致崩溃;假设实现的函数接收一个NSString *类型的参数,[anInvocation setArgument:&aString atIndex:2];(前两位是消息默认参数);然而初始化使用的签名是”v@:i“,invocation在发消息时会把第aString地址处的数据读成一个int而不是id,报错;改为”v@:@“后就可以正常运行。

Invocation是执行者,MethodSignature是执行者执行的时候需要参考的模板和手册,必须保持和MethodSignature一致才行。

结束

你可能感兴趣的:(NSMethodSignature和NSInvocation)