Objective-C runtime机制(2)——消息机制

当我们用中括号[]调用OC函数的时候,实际上会进入消息发送和消息转发流程:

消息发送(Messaging),runtime系统会根据SEL查找对用的IMP,查找到,则调用函数指针进行方法调用;若查找不到,则进入动态消息解析和转发流程,如果动态解析和消息转发失败,则程序crash并记录日志。

在进入正题之前,我们先思考两个问题:

  1. 类实例可以调用类方法吗? 类可以调用实例方法吗? 为什么?

  2. 下面代码输出什么?

@interface Father : NSObject
@end

@implementation Father
@end

@interface Son : Father
- (void)showClass;
@end

@implementation Son
- (void)showClass {
    NSLog(@"self class = %@, super class = %@", [self class], [super class]);
}

...
Son *son = [Son new];
[son showClass];    // 这里输出什么?
...

消息相关数据结构


SEL

SEL被称之为消息选择器,它相当于一个key,在类的消息列表中,可以根据这个key,来查找到对应的消息实现。

在runtime中,SEL的定义是这样的:

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

它是一个不透明的定义,似乎苹果故意隐藏了它的实现。目前SEL仅是一个字符串。

这里要注意,即使消息的参数类型不同(注意,不是指参数数量不同)或方法所属的类也不同,但只要方法名相同,SEL也是一样的。所以,SEL单独并不能作为唯一的Key,必须结合消息发送的目标Class,才能找到最终的IMP

我们可以通过OC编译器命令@selector()或runtime函数sel_registerName,来获取一个SEL类型的方法选择器。

method_t

当需要发送消息的时候,runtime会在Class的方法列表中寻找方法的实现。在方法列表中方法是以结构体method_t存储的。

struct method_t {
    SEL name;
    const char *types;
    IMP imp;

    struct SortBySELAddress :
        public std::binary_function<const method_t&,
                                    const method_t&, bool>
    {
        bool operator() (const method_t& lhs,
                         const method_t& rhs)
        { return lhs.name < rhs.name; }
    };
};

可以看到method_t包含一个SEL作为key,同时有一个指向函数实现的指针IMPmethod_t还包含一个属性const char *types;
types是一个C字符串,用于表明方法的返回值和参数类型。一般是这种格式的:

v24@0:8@16

关于SEL type,可以参考Type Encodings

IMP

IMP实际是一个函数指针,用于实际的方法调用。在runtime中定义是这样的:

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
#endif

IMP是由编译器生成的,如果我们知道了IMP的地址,则可以绕过runtime消息发送的过程,直接调用函数实现。关于这一点,我们稍后会谈到。

在消息发送的过程中,runtime就是根据idSEL来唯一确定IMP并调用之的。

消息


当我们用[]向OC对象发送消息时,编译器会对应的代码修改为objc_msgSend, 其定义如下:

OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

其实,除了objc_msgSend,编译器还会根据实际情况,将消息发送改写为下面四个msgSend之一:
objc_msgSend
objc_msgSend_stret

objc_msgSendSuper
objc_msgSendSuper_stret

当我们将消息发送给super class的时候,编译器会将消息发送改写为**SendSuper的格式,如调用[super viewDidLoad],会被编译器改写为objc_msgSendSuper的形式。

objc_msgSendSuper的定义如下:

OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);

可以看到,调用super方法时,msgSendSuper的第一个参数不是id self,而是一个objc_super *objc_super定义如下:

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;
    /// Specifies the particular superclass of the instance to message. 
    __unsafe_unretained _Nonnull Class super_class;

};

objc_super 包含两个数据,receiver指调用super方法的对象,即子类对象,而super_class表示子类的Super Class。

这就说明了在消息过程中调用了 super方法和没有调用super方法,还是略有差异的。我们将会在下面讲解。

至于**msgSend中以_stret结尾的,表明方法返回值是一个结构体类型。

objc_msgSend 的内部,会依次执行:

  1. 检测selector是否是应该忽略的,比如在Mac OS X开发中,有了垃圾回收机制,就不会响应retainrelease这些函数。
  2. 判断当前receiver是否为nil,若为nil,则不做任何响应,即向nil发送消息,系统不会crash。
  3. 检查Class的method cache,若cache未命中,则进而查找Classmethod list
  4. 若在Classmethod list中未找到对应的IMP,则进行消息转发
  5. 若消息转发失败,程序crash

objc_msgSend

objc_msgSend 的伪代码实现如下:

id objc_msgSend(id self, S
  • 6
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值