Effective Objective-C 2.0: Item 12: Understand Message Forwarding

原创 2013年12月05日 22:47:41

这点很有意思,理清楚了Message Forward的过程(特别那个例子有意思),但是感觉 具体例子太少了~~ 难道是我没有理解透?

Item 12: Understand Message Forwarding

Item 11 explains why it’s important to understand the way messages are sent to objects. Item 12 explores why it’s important to understand what happens when a message is sent to an object that it doesn’t understand.

A class can understand only messages that it has been programmed to understand, through implementing methods. But it’s not a compile-time error to send a message to a class that it doesn’t understand, since methods can be added to classes at runtime so the compiler has no way of knowing whether a method implementation is going to exist. When it receives a method that it doesn’t understand, an object goes through message forwarding, a process designed to allow you as the developer to tell the message how to handle the unknown message.

Even if you are unaware of it, you will very likely already have encountered messages going through the message-forwarding pathways. Every time you’ve seen a message such as the following in the console, it’s because you’ve sent a message to an object that it doesn’t understand and the default NSObject implementation of the forwarding path has kicked in:

-[__NSCFNumber lowercaseString]: unrecognized selector sent to
instance 0x87
*** Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: '-[__NSCFNumber
lowercaseString]: unrecognized selector sent to instance 0x87'

This is an exception being thrown from NSObject’s method calleddoesNotRecognizeSelector: telling you that the receiver of the message is of type__NSCFNumber and that it doesn’t understand the selector lowercaseString. That’s not really surprising in this case, since NSNumber (__NSCFNumber is the internal class used in toll-free bridging—see Item 49—which is created when you allocate anNSNumber). In this case, the forwarding pathways ended in the application’s crashing, but you can hook into the forwarding pathways in your own classes to perform any desired logic instead of crashing.

The forwarding pathways are split into two avenues. The first gives the class to which the receiver belongs a chance to dynamically add a method for the unknown selector. This is called dynamic method resolution. The second avenue is the full forwarding mechanism. If the runtime has made it this far, it knows that there’s no longer a chance for the receiver itself to respond to the selector. So it asks the receiver to try to handle the invocation itself. It does this in two steps. First, it asks whether another object should receive the message instead. If there is, the runtime diverts the message, and everything proceeds as normal. If there is no replacement receiver, the full forwarding mechanism is put into effect, using the NSInvocation object to wrap up the full details about the message that is currently unhandled and gives the receiver one final chance to handle it.

Dynamic Method Resolution

The first method that’s called when a message is passed to an object that it doesn’t understand is a class method on the object’s class:

+ (BOOL)resolveInstanceMethod:(SEL)selector

This method takes the selector that was not found and returns a Boolean to indicate whether an instance method was added to the class that can now handle that selector. Thus, the class is given a second opportunity to add an implementation before proceeding with the rest of the forwarding mechanism. A similar method, called resolveClassMethod:, is called when the unimplemented method is a class method rather than an instance method.

Using this approach requires the implementation of the method to already be available, ready to plug in to the class dynamically. This method is often used to implement @dynamic properties (see Item 6), such as occurs in CoreData for accessing properties of NSManagedObjects, since the methods required to implement such properties can be known at compile time.

Such an implementation of resolveInstanceMethod: for use with @dynamicproperties might look like this:

id autoDictionaryGetter(id selfSEL _cmd);
void autoDictionarySetter(id selfSEL _cmd, id value);

+ (BOOL)resolveInstanceMethod:(SEL)selector {
    NSString *selectorString = NSStringFromSelector(selector);
    if ( /* selector is from a @dynamic property */ ) {
        if ([selectorString hasPrefix:@"set"]) {
        } else {
        return YES;
    return [super resolveInstanceMethod:selector];

The selector is obtained as a string and then checked to see whether it looks like a setter. If it is prefixed with set, it is assumed to be a setter; otherwise, it is assumed to be a getter. In each case, a method is added to the class for the given selector pointing to an implementation defined as a pure C function. In these C functions there would be code to manipulate whatever data structure was being used by the class to store the properties’ data. For example, in the case of CoreData, these methods would talk to the database back end to retrieve or update values accordingly.

Replacement Receiver

The second attempt to handle an unknown selector is to ask the receiver whether a replacement receiver is available to handle the message instead. The method that handles this is:

- (id)forwardingTargetForSelector:(SEL)selector

The unknown selector is passed in, and the receiver is expected to return the object to act as its replacement or nil if no replacement can be found. This method can be used to provide some of the benefits of multiple inheritance by combining its use with composition. An object could own a range of other objects internally that it returns in this method for selectors that they handle, making it look as though it is itself handling them.

Note that there is no way to manipulate the message using this part of the forwarding path. If the message needs to be altered before sending to the replacement receiver, the full forwarding mechanism must be used.

Full Forwarding Mechanism

If the forwarding algorithm has come this far, the only thing left to do is to apply the full forwarding mechanism. This starts by creating an NSInvocation object to wrap up all the details about the message that is left unhandled. This object contains the selector, target, and arguments. An NSInvocation object can be invoked, which causes the message-dispatch system to whir into action and dispatch the message to its target.

The method that gets called to attempt forwarding this way is:

- (void)forwardInvocation:(NSInvocation*)invocation

A simple implementation would change the target of the invocation and invoke it. This would be equivalent to using the replacement receiver method, so such a simple implementation is rarely used. A more useful implementation would be to change the message in some way before invoking it, such as appending another argument or changing the selector.

An implementation of this method should always call its superclass’s implementation for invocations it doesn’t handle. This means that once all superclasses in the hierarchy have been given a chance to handle the invocation, NSObject’s implementation will be called. This invokes doesNotRecognizeSelector: to raise the unhandled selector exception.

The Full Picture

The process through which forwarding is handled can be described by a flow diagram like that shown in Figure 2.2.


Figure 2.2 Message forwarding

At each step, the receiver is given a chance to handle the message. Each step is more expensive than the one before it. The best scenario is that the method is resolved at the first step, since the method that was resolved will end up being cached by the runtime such that forwarding does not have to kick in at all next time the same selector is invoked on an instance of the same class. At the second step, forwarding a message to another receiver is simply an optimization of the third step for the case in which a replacement receiver can be found. In that case, the only thing that needs to be changed about the invocation is the target, which is much simpler to do than the final step, in which a complete NSInvocation needs to be created and handled.

Full Example of Dynamic Method Resolution

To illustrate how forwarding can be useful, the following example shows the use of dynamic method resolution to provide @dynamic properties. Consider an object that allows you to store any object in it, much like a dictionary, but provides access through properties. The idea of the class will be that you can add a property definition and declare it @dynamic, and the class will magically handle storing and retrieving that value. That would be pretty fantastic, right?

The interface for the class will be like this:

#import <Foundation/Foundation.h>

@interface EOCAutoDictionary : NSObject
@property (nonatomic, strong) NSString *string;
@property (nonatomic, strong) NSNumber *number;
@property (nonatomic, strong) NSDate *date;
@property (nonatomic, strong) id opaqueObject;

It doesn’t particularly matter for this example what the properties are. In fact, I’ve shown a variety of types just to illustrate the power of this feature. Internally, the values for each property will be held in a dictionary, so the start of the implementation of this class would look like the following, including declaring the properties as @dynamic such that instance variables and accessors are not automatically created for them:

#import "EOCAutoDictionary.h"
#import <objc/runtime.h>

@interface EOCAutoDictionary ()
@property (nonatomic, strong) NSMutableDictionary *backingStore;

@implementation EOCAutoDictionary

@dynamic string, number, date, opaqueObject;

- (id)init {
    if ((self = [super init])) {
        _backingStore = [NSMutableDictionary new];
    return self;

Then comes the fun part: the resolveInstanceMethod: implementation:

+ (BOOL)resolveInstanceMethod:(SEL)selector {
    NSString *selectorString = NSStringFromSelector(selector);
    if ([selectorString hasPrefix:@"set"]) {
    } else {
    return YES;


The first time it encounters a call to a property on an instance of EOCAutoDictionarythe runtime will not find the corresponding selector, since they’re not implemented directly or synthesized. For example, if the opaqueObject property is written, the preceding method will be invoked with a selector of setOpaqueObject:. Similarly, if the property is read, it will be invoked with a selector of opaqueObject. The method detects the difference between set and get selectors by checking for a prefix of set. In each case, a method is added to the class for the given selector pointing to a function called either autoDictionarySetter or autoDictionaryGetter, as appropriate. This makes use of the runtime method class_addMethod, which adds a method dynamically to the class for the given selector, with the implementation given as a function pointer. The final parameter in this function is the type encoding of the implementation. The type encoding is made up from characters representing the return type, followed by the parameters that the function takes.

The getter function would then be implemented as follows:

id autoDictionaryGetter(id self, SEL _cmd) {
    // Get the backing store from the object
    EOCAutoDictionary *typedSelf = (EOCAutoDictionary*)self;
    NSMutableDictionary *backingStore = typedSelf.backingStore;

    // The key is simply the selector name
    NSString *key = NSStringFromSelector(_cmd);

    // Return the value
    return [backingStore objectForKey:key];

Finally, the setter function would be implemented like this:

void autoDictionarySetter(id self, SEL _cmd, id value) {
    // Get the backing store from the object
    EOCAutoDictionary *typedSelf = (EOCAutoDictionary*)self;
    NSMutableDictionary *backingStore = typedSelf.backingStore;

    /** The selector will be for example, "setOpaqueObject:".
     *  We need to remove the "set", ":" and lowercase the first
     *  letter of the remainder.
    NSString *selectorString = NSStringFromSelector(_cmd);
    NSMutableString *key = [selectorString mutableCopy];

    // Remove the ':' at the end
    [key deleteCharactersInRange:NSMakeRange(key.length - 11)];

    // Remove the 'set' prefix
    [key deleteCharactersInRange:NSMakeRange(03)];

    // Lowercase the first character
    NSString *lowercaseFirstChar =
        [[key substringToIndex:1lowercaseString];
    [key replaceCharactersInRange:NSMakeRange(01)

    if (value) {
        [backingStore setObject:value forKey:key];
    } else {
        [backingStore removeObjectForKey:key];

Using EOCAutoDictionary is then a simple matter of the following:

EOCAutoDictionary *dict = [EOCAutoDictionary new];
dict.date = [NSDate dateWithTimeIntervalSince1970:475372800];
NSLog(@"dict.date = %@", dict.date);
// Output: dict.date = 1985-01-24 00:00:00 +0000

The other properties on the dictionary could be accessed just like the date property, and new properties could be introduced by adding a @property definition and declaring it @dynamic. A similar approach is employed by CALayer, part of the CoreAnimation framework on iOS. This approach allows CALayer to be a key-value-coding-compliant container class, meaning that it can store a value against any key.CALayer uses this ability to allow the addition of custom animatable properties whereby the storage of the property values is handled directly by the base class, but the property definition can be added in a subclass.

Things to Remember

Image Message forwarding is the process that an object goes through when it is found to not respond to a selector.

Image Dynamic method resolution is used to add methods to a class at runtime as and when they are used.

Image Objects can declare that another object is to handle certain selectors that it doesn’t understand.

Image Full forwarding is invoked only when no previous way of handling a selector has been found.

编写高质量的iOS代码--Effective Objective-C 2.0 读书笔记

编写高质量的iOS代码--Effective Objective-C 2.0 读书笔记 这本书年初刷完,感觉不错,介绍了很多小点,都是平日不怎么关注的. 第1章 熟悉Objective-C...
  • uxyheaven
  • uxyheaven
  • 2014年12月26日 23:56
  • 5091

Effective Objective-C 2.0 读书笔记

第 1 章 熟悉 Objective-C 第 2 章 对象消息运行时 第 3 章 接口和 API 设计 第 4 章 协议与分类 第 5 章 内存管理 循环引用 普通的两个变量互相引用 Block 循环...
  • xsl_bj
  • xsl_bj
  • 2016年06月06日 12:27
  • 4798

Effective Objective-C 2.0: Item 37: Understand Blocks

Effective Objective-C 2.0 介绍Blocks的文章,有些论点比较新颖,觉得不错,值得一看...
  • chuanyituoku
  • chuanyituoku
  • 2013年12月11日 21:55
  • 1579

Effective Objective-C 2.0: Item 6: Understand Properties

前面的runtime没看懂- -; 后面的atomicity 和 thread-safe 说明, 很好很强大 ^ ^ Item 6: Understand Properties Pro...
  • chuanyituoku
  • chuanyituoku
  • 2013年12月02日 23:55
  • 938

《Effective Objective-C 2.0编写高质量iOS与OS X代码的52个有效方法》读书笔记(下)

1.为避免在不经意间使用了无效对象,一般在release之后会清空指针,=nil; 2.通常利用弱引用或者“手动”解除引用的方式破坏循环引用。 3.ARC下,规定以alloc、new、copy、mut...
  • zhaochen_009
  • zhaochen_009
  • 2017年04月06日 10:44
  • 354

Effective Objective-C 2.0 编写高质量iOS与OS X代码 objc_msgSend 的作用

1. 消息由接收者、选择子及参数构成。给某对象“发送消息”(invoke a message)也就相当于在该对象上“调用方法”(call a method); 2. 发给某对象的全部消息都由...
  • mad2man
  • mad2man
  • 2014年03月27日 16:15
  • 2946

阅读《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》总结

第1条:了解Objective-C语言的起源 Objective-C为C语言添加了面向对象特性,是其超集。Objective-C使用动态绑定的消息结构,也就是说,在运行时才会检查对象类型。接收一...
  • caojengineer
  • caojengineer
  • 2015年06月07日 22:52
  • 1236

Effective Objective-C 2.0: Item 21: Understand the Objective-C Error Model

Item 21: Understand the Objective-C Error Model Many modern languages, including Objective-C, hav...
  • chuanyituoku
  • chuanyituoku
  • 2013年12月07日 19:05
  • 734

《Effective Objective-C 2.0 编写高质量iOS与OS X代码的52个有效方法》--读书笔记

个人觉得这本书很不错!里面有很多很实用但以前没有太注意的地方,所以想纪录下来,当作自己的读书笔记吧。 熟悉Objective-C 1)Objective-C语言使用“消息结构”而不是“函数调用”。O...
  • weimeng809
  • weimeng809
  • 2016年01月11日 00:18
  • 607

《Effective Objective-C 2.0编写高质量iOS与OS X代码的52个有效方法》读书笔记(上)

1.OC对象所占内存总是分配在"堆"中,而绝不会分配在"栈"中,不能再栈中分配OC对象。"栈"中对象借助栈帧进行维护,"堆"中对象的管理借助引用计数机制. -(NSMutableArray *)te...
  • zhaochen_009
  • zhaochen_009
  • 2017年04月05日 10:10
  • 311
您举报文章:Effective Objective-C 2.0: Item 12: Understand Message Forwarding