Method in Objective-C : Message Passing

By | May 16, 2018

Method in Objective-C : Message Passing

What is Message Passing?

At previous post I mentioned Method Swizzling, that refers some knowledge about message passing. So this post I decide to talk about it.

We know invoking an instance method called Message Passing in Objective-C. For example:

NSMutableArray *array = [NSMutableArray array];
[array addObject:@"hello world!"];

array is message receiver, addObject calls as selector that I mentioned at last post. message consists of selector and parameter.

Message passing adopt dynamic binding to decide to invoke which method. Dynamic binding means that the compiler doesn’t know which method implementation will be selected; instead the method implementation is looked up at runtime when the message is sent. So we write some code:

id num = @123;
//output 123
NSLog(@"%@", num);
//crash, error: [__NSCFNumber appendString:]: unrecognized selector sent to instance 0x8c28
[num appendString:@"Hello World"];

During compiling above code doesn’t have any problems, because id type can point all kinds of instance. NSString has a method appendString: ; compiler doesn’t sure which type instance num is, and add a new method to NSNumber is available in project running, thus it wouldn’t show error when compiler found appendString: method declaration. But in project running can’t find appendString: in NSNumber, program would be error. So this is a disadvantage of message passing, compiler can’t check method undefined.

You can learn more information about Message at Apple development website.

objc_msgSend()

Runtime provides a C language method for sending message, it’s objc_msgSend(receiver, selector, arg1, arg2, …). The first parameter is receiver that is method’s invoker, and second parameter is selector that is like method’s name, there are several arguments behind it. If you want to get C language function from your Objective-C method, there is a cool command:

clang -rewrite-objc main.m

this command can help you convert objc file extended .m to .cpp file.

So we have this code:

@interface Macbook : NSObject

@property (nonatomic, copy) NSString *type;
@property (nonatomic, assign) CGFloat price;

- (void)showPriceTag;

@end

@implementation Macbook

- (void)showPriceTag
{
    NSLog(@"This laptop type is %@, and price is $%.2f",self.type, self.price);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Macbook *macbookpro = [[Macbook alloc] init];
        macbookpro.type = @"Macbook Pro Retina";
        macbookpro.price = 199.9;
        [macbookpro showPriceTag];
    }
    return 0;
}

When I used clang command to convert code, I got a file called main.cpp in same folder, and I found these definitions:

#ifndef _REWRITER_typedef_Macbook
#define _REWRITER_typedef_Macbook
typedef struct objc_object Macbook;
typedef struct {} _objc_exc_Macbook;
#endif

extern "C" unsigned long OBJC_IVAR_$_Macbook$_type;
extern "C" unsigned long OBJC_IVAR_$_Macbook$_price;
struct Macbook_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    NSString *_type;
    CGFloat _price;
};


// @property (nonatomic, copy) NSString *type;
// @property (nonatomic, assign) CGFloat price;

// - (void)showPriceTag;

/* @end */


// @implementation Macbook


static void _I_Macbook_showPriceTag(Macbook * self, SEL _cmd) {
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_9t_z6zhv5ys04q0bby1zkgnm14h0000gn_T_main_a83434_mi_0,((NSString *(*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("type")), ((CGFloat (*)(id, SEL))(void *)objc_msgSend_fpret)((id)self, sel_registerName("price")));
}


static NSString * _I_Macbook_type(Macbook * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_Macbook$_type)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);

static void _I_Macbook_setType_(Macbook * self, SEL _cmd, NSString *type) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Macbook, _type), (id)type, 0, 1); }

static CGFloat _I_Macbook_price(Macbook * self, SEL _cmd) { return (*(CGFloat *)((char *)self + OBJC_IVAR_$_Macbook$_price)); }
static void _I_Macbook_setPrice_(Macbook * self, SEL _cmd, CGFloat price) { (*(CGFloat *)((char *)self + OBJC_IVAR_$_Macbook$_price)) = price; }
// @end

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        Macbook *macbookpro = ((Macbook *(*)(id, SEL))(void *)objc_msgSend)((id)((Macbook *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Macbook"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)macbookpro, sel_registerName("setType:"), (NSString *)&__NSConstantStringImpl__var_folders_9t_z6zhv5ys04q0bby1zkgnm14h0000gn_T_main_a83434_mi_1);
        ((void (*)(id, SEL, CGFloat))(void *)objc_msgSend)((id)macbookpro, sel_registerName("setPrice:"), 199.90000000000001);
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)macbookpro, sel_registerName("showPriceTag"));
    }
    return 0;
}

The properties generate getter and setter method, so they are methods as well. We can see objc_msgSend like this:

((void (*)(id, SEL, CGFloat))(void *)objc_msgSend)((id)macbookpro, sel_registerName("setPrice:"), 199.90000000000001);
  • 1.register method name
  • 2.send message with an argument 199.90000000000001 to reciever macbookpro by objc_msgSend

You should learn runtime how to implement a invoking of Object-Oriented to pass message in objc_msgSend. objc_msgSend can select appropriate method to invoke according to reciever. How does it work? We need to learn structure of Class in Runtime.

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

    Class super_class                                        
    const char *name                                         
    long version                                             
    long info                                                
    long instance_size                                       
    struct objc_ivar_list *ivars                             
    struct objc_method_list **methodLists                    
    struct objc_cache *cache                                 
    struct objc_protocol_list *protocols                     
}
/* Use `Class` instead of `struct objc_class *` */

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

You can look for Runtime open source code in here.

The struct objc_class* contains a member variable struct objc_method_list **methodLists, and then found out definition of struct objc_method_list:

static struct /*_method_list_t*/ {
    unsigned int entsize;  // sizeof(struct _objc_method)
    unsigned int method_count;
    struct _objc_method method_list[5];
} _OBJC_$_INSTANCE_METHODS_Macbook __attribute__ ((used, section ("__DATA,__objc_const"))) = {
    sizeof(_objc_method),
    5,
    {{(struct objc_selector *)"showPriceTag", "v16@0:8", (void *)_I_Macbook_showPriceTag},
    {(struct objc_selector *)"type", "@16@0:8", (void *)_I_Macbook_type},
    {(struct objc_selector *)"setType:", "v24@0:8@16", (void *)_I_Macbook_setType_},
    {(struct objc_selector *)"price", "d16@0:8", (void *)_I_Macbook_price},
    {(struct objc_selector *)"setPrice:", "v24@0:8d16", (void *)_I_Macbook_setPrice_}}
};

struct _objc_method {
    struct objc_selector * _cmd;
    const char *method_type;
    void  *_imp;
};

The struct objc_method_list contains three memeber variable: method size, method count and the most important method list. Element of the method list is struct _objc_method, the struct consists of selector, method type and implementation pointer.

That can explain how does objc_msgSend work, runtime searches this struct objc_method_list in class of receiver, so that it can mach message’s reciever and selector. If method is found out, the _imp in struct _objc_method will be called; else runtime will go to super_class pointer by inheritance tree, find out method, jump. If the method is not find out, when reach the root of inheritance tree (normally is NSObject), a method of NSObject called doesNotRecognizeSelector: will be invoked. That is the error unrecognized selector which you often come across.

Message Forwarding

Actually there are three chances process message before doesNotRecognizeSelector: invoked. That is message forwarding. NSObject provides four method to implement message forwarding:

// 1
+(BOOL)resolveInstanceMethod:(SEL)sel{
    // A chance to add the instance method and return YES. It will then try sending the message again.
}

// 2
- (id)forwardingTargetForSelector:(SEL)aSelector{
    // Return an object that can handle the selector.
}

// 3
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    // You need to implement this for the creation of an NSInvocation.
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    // Invoke the selector on a target of your choice.
    [invocation invokeWithTarget:target];
}

This is the flow of message forwarding:

  • First of all, program will send a request to reciever’s class, if it don’t find out relevant method by inheritance tree. +resolveInstanceMethod: tries to add a method dynamically.
  • Then Runtime will ask the reciever whether there is another instance can process this unknow selector in -forwardingTargetForSelector:, when current instance can’t add method. If it can return a backup reciever, the message will be processed, else if return nil, program will go to the next step.
  • The thrid chance is forwarding a invocation. At this time, the detail of all unknow message will encapsulate as a NSInvocation object, and invoke -forwardInvocation: method. This is the last chance, if NSObject can’t process the message, doesNotRecognizeSelector: will throw exception.

You can forward a method to another target. That’s very useful if you’re trying to bridge between different frameworks. This is what happens when you call a method that’s not implemented.

You can learn more information about Message Forwarding in Apple documents