Objective-C 的 Runtime 运行时(二)

iOS Dec 14, 2015

5 月份的时候写过一篇Objective-C 的 runtime 运行时(一)

这次主要是说消息机制的。

上次我们说

runtime.age = 19;

转换成 objc_msgSend 就是

objc_msgSend((id)runtime, sel_registerName("setAge:"), (NSInteger)19);

大概是这么回事 id objc_msgSend(id self, SEL op, ...)

self 是第一个参数,后面一个 SEL,self 回头再讲,今天先研究下 SEL,他的消息到底发给谁。

#import "Cat.h"

@implementation Cat

- (instancetype)init {
    
    if (self = [super init]) {
        
        NSLog(@"%@", [self className]);
        NSLog(@"%@", [super className]);
    }
    return self;
}
@end

那两句 NSLog,转成运行时代码就是

objc_msgSend((id)self, sel_registerName("class"))));
objc_msgSendSuper((__rw_objc_super){self, class_getSuperclass(objc_getClass("Cat"))}, sel_registerName("class"))));

看不懂是吧,我也看不懂,散了吧散了吧。

开个玩笑

objc_msgSendSuper 大概是这么回事 id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

objc_super 就是图上的 __rw_objc_super 就是一个结构体 (__rw_objc_super){self, class_getSuperclass(objc_getClass("Cat"))} 返回一个 __rw_objc_super 类型的结构体,然后剩下的就和 objc_msgSend 一样了。

其实这块我还真不懂,莫非说是 objc_msgSendSuper 是给 super 类发消息?这里咱们留一个疑问,objc_msgSendSuper 和 objc_msgSend 到底给谁发消息。来做个测试。

网上流行一道题,问

- (instancetype)init {
    
    if (self = [super init]) {
        
        NSLog(@"%@", [self className]);
        NSLog(@"%@", [super className]);
    }
    return self;
}

打印结果是什么,正确答案是都是当前类的名字

2015-12-13 23:41:30.721 runtime[7825:857680] Cat
2015-12-13 23:41:30.723 runtime[7825:857680] Cat

如果在当前类复写这个方法

#import "Cat.h"

@implementation Cat

- (instancetype)init {
    
    if (self = [super init]) {
        
        NSLog(@"%@", [self className]);
        NSLog(@"%@", [super className]);
    }
    return self;
}

- (NSString *)className {
    
    return @"Cat";
}

@end

正确答案是

2015-12-13 23:45:11.731 runtime[7913:866259] Cat
2015-12-13 23:45:11.732 runtime[7913:866259] Cat

What happened?

第一个 Cat,还能理解,肯定是当前实例调用自己的方法,然后就打印 Cat 咯,第二个什么鬼,莫非是 objc_msgSendSuper 真的会给当前实例发消息?

非也

如果方法这么改

- (NSString *)className {
    
    return @"Cattttt";
}

聪明的同学已经猜到了答案

2015-12-13 23:47:32.255 runtime[7933:870835] Cattttt
2015-12-13 23:47:32.256 runtime[7933:870835] Cat

聪明的同学可能已经大概知道 objc_msgSend 是个什么鬼了,发送消息,就是发送给该发送给的对象,如果自己有这个方法,那就调用自己的,如果自己没有,就往上级查找去。此处为什么还是 Cat 呢,咱们留个疑问

如果我们在 Animal 类中也写一个方法,经过上次的教训,我已经不敢写真实的类名了

- (NSString *)className {

    return @"Animalllll";
}

结果是

2015-12-13 23:50:05.171 runtime[7952:875359] Cattttt
2015-12-13 23:50:05.172 runtime[7952:875359] Animalllll

来解决刚才的疑问,既然 [super className] 是优先调用自己类的 className 方法,那为毛他总是显示结果是 Cat,super 肯定是 Animal 啊。

其实这个问题我就不清楚了,我说真的,不过根据猜测,来看 __rw_objc_super 这个结构体

struct __rw_objc_super { 
	struct objc_object *object; 
	struct objc_object *superClass; 
	__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {} 
};

他第一个成员 object,我猜这货八成就是上面 id objc_msgSend(id self, SEL op, ...) 里面的 self,不过仅限猜测,superClass 可能只是一个标志符之类的东西,只是说,从这个类开始查找方法,并不实际起作用。其实这个猜测的前半部分是错的,咱们后面讲

为了验证这个说法,我把 runtime 的源码搞来看了一看,原来 objc_super 结构体是长这个样子的

/// Specifies the superclass of an instance. 
struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained id receiver;

    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained Class class;
#else
    __unsafe_unretained Class super_class;
#endif
    /* super_class is the first class to search */
};
#endif

receiver 是被指定的一个实例,super_class 是被指定的一个 接受消息 的实例。

那消息是什么,消息不就是 objc_msgSend((id)runtime, sel_registerName("setAge:"), (NSInteger)19); 里面的 sel_registerName("setAge:"), (NSInteger)19)

那说白了,就是从传入的这个类开始寻找方法,实际被指定的实例,还是当前类,也就是说 objc_msgSendSuper((__rw_objc_super){self, class_getSuperclass(objc_getClass("Cat"))}, sel_registerName("class")))); 只是给 __rw_objc_super 发消息,从这里开始找 class 方法,实际作为 receiver 的,是当前的类 self,也就是 Cat

刚才那个猜测,现在清楚了,id objc_msgSend(id self, SEL op, ...) 里面的 self 也没什么卵用,并不代表一个实例,只是从这个类开始找方法。虽然他和被指定的实例是一个东西。

Tags

Jie Li

🚘 On-road / 📉 US Stock / 💻 Full Stack Developer / 🎓 Grad Student / ®️ ENTJ

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.