什么时候会报unrecognized selector错误?
- 对象未实现该方法。
- 对象已经被释放。
iOS有哪些机制来避免走到这一步?
-
使用[id respondsToSelector:]进行判断。
2.Method resolution
objc运行时会调用+resolveInstanceMethod:或者 +resolveClassMethod:,让你有机会提供一个函数实现。如果你添加了函数,那运行时系统就会重新启动一次消息发送的过程,否则 ,运行时就会移到下一步,消息转发(Message Forwarding)。
返回Nil和self,去调用第三步methodSignatureForSelector和forwarInvocation;返回receiver,如果receiver有响应就直接处理,如果没有就去对应的对象内去调用第三步;调用子类的函数,子类没有进行这几个方法的重载,在父类处理时返回子类,会死循环。
3.Fast forwarding
如果目标对象实现了-forwardingTargetForSelector:,Runtime 这时就会调用这个方法,给你把这个消息转发给其他对象的机会。 只要这个方法返回的不是nil和self,整个消息发送的过程就会被重启,当然发送的对象会变成你返回的那个对象。否则,就会继续Normal Fowarding。 这里叫Fast,只是为了区别下一步的转发机制。因为这一步不会创建任何新的对象,但下一步转发会创建一个NSInvocation对象,所以相对更快点。
4.Normal forwarding
这一步是Runtime最后一次给你挽救的机会。首先它会发送-methodSignatureForSelector:消息获得函数的参数和返回值类型。如果-methodSignatureForSelector:返回nil,Runtime则会发出-doesNotRecognizeSelector:消息,程序这时也就挂掉了。如果返回了一个函数签名,Runtime就会创建一个NSInvocation对象并发送-forwardInvocation:消息给目标对象。
- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
文档翻译:
-(id)forwardingTargetForSelector:(SEL)aSelector
返回无法识别的消息应当首先被引导到的对象。
Parameters 一个接收者没有实现的方法的选择器。
Return Value 无法识别消息应该首先被导向到的对象。
Discussion
如果一个对象实现(或者继承)了这个方法,和返回一个非空(non-self)的结果,那么返回的这个对象被用作新的接收对象和消息分发到这个新对象。(明显地,如果你从这个方法返回self,代码将进入一个无限循环中注:调试测试发现不会死循环,而是直接崩溃)
如果你在一个non-root类实现这个方法,如果你的类对于这个被给的选择器没有什么东西返回,那么你应该返回调用super的实现的结果。
这方法给一个对象在调用花费更多的forwardInvocation:接管之前,重定向1个未知消息到它的一个机会。这是有用的当你简单的想重定向消息到另一个对象时和能比常规的转发快一个数量级。当转发的目标是在转发期间捕获NSInvocation或者篡改参数或者返回值时,它是没用的。
-(void)forwardInvocation:(NSInvocation *)anInvocation;
通过子类重载转发消息到其它对象。
Discussion
When an object is sent a message for which it has no corresponding method, the runtime system gives the receiver an opportunity to delegate the message to another receiver. It delegates the message by creating an NSInvocation object representing the message and sending the receiver a forwardInvocation: message containing this NSInvocation object as the argument. The receiver’s forwardInvocation: method can then choose to forward the message to another object. (If that object can’t respond to the message either, it too will be given a chance to forward it.)
The forwardInvocation: message thus allows an object to establish relationships with other objects that will, for certain messages, act on its behalf. The forwarding object is, in a sense, able to “inherit” some of the characteristics of the object it forwards the message to.
当一个对象被发送一个没有相应方法的消息时,运行时系统给接收对象一个委托这消息到另一个接收对象的机会。它通过创建1个NSInvocation对象表示这个消息和发送给接收对象1个包含这NSInvocation对象作为参数的forwardInvocation:消息来委托这个消息。这接收对象的forwardInvocation:方法能选择转发这个消息到另一个对象。(如果那对象也不能响应这个消息,它也将有机会转发它)
这forwardInvocation:消息因此允许1个对象和其它的对某一消息将起作用的对象建立关系。这转发对象在某种意义上能够"继承"转发消息到那个对象上的一些特性。
Important
To respond to methods that your object does not itself recognize, you must override methodSignatureForSelector: in addition to forwardInvocation:. The mechanism for forwarding messages uses information obtained from methodSignatureForSelector: to create the NSInvocation
object to be forwarded. Your overriding method must provide an appropriate method signature for the given selector, either by pre formulating one or by asking another object for one.
为了响应对象本身不能识别的方法,你必须除了forwardInvocation:外还要重写methodSignatureForSelector:。转发消息的原理是用从methodSignatureForSelector:获取的信息去创建一个NSInvocation对象来进行转发。重载的方法必须为被提供的slector提供一个一致的方法签名,或者通过预先制定一个或者通过请求另一个对象。
An implementation of the forwardInvocation: method has two tasks:
· To locate an object that can respond to the message encoded in anInvocation. This object need not be the same for all messages.
· To send the message to that object using anInvocation. ** anInvocation** will hold the result, and the runtime system will extract and deliver this result to the original sender.
In the simple case, in which an object forwards messages to just one destination (such as the hypothetical friend instance variable in the example below), a forwardInvocation: method could be as simple as this:
forwardInvocation:方法的实现有2个任务:
- 定位一个能响应编码在anInvocation中消息的对象,所有消息的对象不一定相同。
- 用anInvocation发送消息给对象。anInvocation将保存这个结果,运行时系统将提取和转发这个结果给原始的发送者。
在这个简单的情况下,1个对象转发消息只有1个目的地址(像在厦门的例子中假想的friend实例),1个forwardInvocation:方法能是这样简单:
Listing 1
- (void)forwardInvocation:(NSInvocation *)invocation{
SEL aSelector = [invocation selector];
if ([friend respondsToSelector:aSelector])
[invocation invokeWithTarget:friend];
else
[super forwardInvocation:invocation];
}
The message that’s forwarded must have a fixed number of arguments; variable numbers of arguments (in the style of printf()
) are not supported.
The return value of the forwarded message is returned to the original sender. All types of return values can be delivered to the sender: id
types, structures, double-precision floating-point numbers.
Implementations of the forwardInvocation:
method can do more than just forward messages. forwardInvocation:
can, for example, be used to consolidate code that responds to a variety of different messages, thus avoiding the necessity of having to write a separate method for each selector. A forwardInvocation:
method might also involve several other objects in the response to a given message, rather than forward it to just one.
NSObject’s implementation of forwardInvocation:
simply invokes the doesNotRecognizeSelector:
method; it doesn’t forward any messages. Thus, if you choose not to implement forwardInvocation:
, sending unrecognized messages to objects will raise exceptions.
消息转发必须有固定数量的参数,不支持可变数量的参数(像printf()这种格式的)。
转发消息的返回值被原始的发送者返回。所有返回值的类型都能被发送给发送者:id类型,结构体,双精度浮点数。
forwardInvocation:方法的实现能比只转发消息做更多的事情。例如,forwardInvocation:能被用来合并代码来响应多种不同消息,从而避免为每个选择器写一个独立的方法。forwardInvocation方法也能在响应被给的消息时调用几个其它的对象,而不是只转发给1个。
NSObject的forwardInvocation:的实现简单调用doesNotRecognizeSelector:方法,它不转发任何消息。因此,如果你选择不实现forwardInvocation:,发送1个不能识别的消息到对象将抛出异常。
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
Parameters
一个标识返回实现地址方法的选择器。当接收对象是一个实例时,选择器应当标识1个实例方法;当接收对象是一个类时,它应当是个类方法。
Return Value
返回一个包含由给定的选择器标识的方法描述符的NSMethodSignature对象,如果方法不能找到返回nil。
这个方法被用在协议的实现里。这方法也被用在创建1个NSInvocation对象的情况下,比如在转发消息期间。如果你的对象维护一个委托或者是能处理1个没有直接实现的消息,你应该重写这个方法来返回一个一致的方法签名。
+(BOOL)resolveInstanceMethod:(SEL)sel;
Parameters
name
The name of a selector to resolve.
Return Value
Dynamically provides an implementation for a given selector for an instance method.
YES if the method was found and added to the receiver, otherwise NO.
Discussion
This method and resolveClassMethod:
allow you to dynamically provide an implementation for a given selector.
An Objective-C method is simply a C function that take at least two arguments—self and _cmd. Using the class_addMethod
function, you can add a function to a class as a method. Given the following function:
Listing 1
void dynamicMethodIMP(id self, SEL _cmd){
// implementation ....
}
you can use resolveInstanceMethod: to dynamically add it to a class as a method (called resolveThisMethodDynamically) like this:
Listing 2
+ (BOOL) resolveInstanceMethod:(SEL)aSEL{
if (aSEL == @selector(resolveThisMethodDynamically)) {
class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, "v@:");
return YES;
}
return [super resolveInstanceMethod:aSel];
}
Special Considerations
This method is called before the Objective-C forwarding mechanism is invoked. If respondsToSelector:
or instancesRespondToSelector:
is invoked, the dynamic method resolver is given the opportunity to provide an IMP
for the given selector first.