Runtime简单使用二

通常,我们以[object message]这种方式向一个类或者对象发送一个未定义消息时,在Xcode编译的时候就会报错。但是,如果出现以下情况时就会进入方法转发流程:
1.我们使用[object message]向一个对象发送了只有message定义没有实现的消息。
2.使用[object performSelector:@selector(message)]向一个未定义(或者未实现)message的对象发送消息时就会进入方法转发流程。

总之就是object无法响应message消息。一般如果出现无法响应message消息,系统就会崩溃并报出unrecognized selector sent to instance错误。但是如何不让系统直接崩溃了?那么就进入我们下面讨论的消息转发流程。

首先让我们来了解几个概览:

一.类和元类(Meta Class)

1.类

Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针,让我们来看一下它的结构

typedef struct objc_class *Class;

查看objc/runtime.hobjc_class结构体的定义如下

struct objc_class {
    Class isa;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;
  • isa:在object-c中,一个类也是一个对象(类对象),这个类的isa指向它的元类。
  • ivars:存放当前类的成员变量列表。
  • methodLists:方法列表,存放当前类的所有方法。

2.元类(Meta Class)

其实一个类也是一个对象,当我们给一个类发送一个消息的时候,比如[NSArray array]的时候就是给NSArray类对象发送array消息。这个类的isa指针指向包含这个类的objc_class结构体。

类对象所属类型就叫做元类,它用来表述类对象本身所具备的元数据。具体的结构可以查看下图:

类、元类的关系

二.隐式参数

objc_msgSend方法中有两个隐式参数,这也是为什么我们可以在方法中调用self的原因。两个隐藏参数如下:

  • 接受消息的对象(self)
  • 方法选择器(_cmd指向的内容)

之所以说是隐藏的,是因为它们在定义方法的源代码中没有声明。它们是在编译期被插入实现代码的。

三.消息转发

1.动态方法解析

当我们实现了+resolveInstanceMethod或者+resolveClassMethod方法 ,如果对象或者类无法处理所接受的消息的时候就会首先进入这一步。

类的创建和调用

创建一个类,并使用performSelector调用一个不存在的方法,并使用如下方法进入消息转发流程

// 拯救了一个实例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(myInstanceMethod)) {
        
        class_addMethod(self, sel, (IMP) funcInstanceM, "@:");
        
        return YES;
    }
    
    return [super resolveInstanceMethod:sel];
}

void funcInstanceM(id self, SEL _cmd) {
    NSLog(@"%@, %p", self, _cmd);
}

funcInstanceM函数包含必须添加的两个隐藏参数self_cmd

但是如果我们给MyClass发送一个无法响应的类方法的时候,怎么进行动态方法解析了?
我们可以使用+resolveClassMethod进行消息转发

[MyClass performSelector:@selector(myClassMethod)];

+ (BOOL)resolveClassMethod:(SEL)sel
{
    if (sel == @selector(myClassMethod)) {
        class_addMethod(object_getClass(self), sel, (IMP) funcClassM, "@:");
        
        return YES;
    }
    
    return [super resolveClassMethod:sel];
}

void funcClassM(id self, SEL _cmd) {
    NSLog(@"%@, %p", self, _cmd);
}

这里需要注意的是,我们使用object_getClass获取当前类的元类。

2.备用接受者

如果上述方法无法处理,那就使用- forwardingTargetForSelector将消息转发给能够处理的对象

// MyClass 定义
@interface MyClass : NSObject

- (void)method;

@end

@implementation MyClass

- (void)method{
    NSLog(@"%@, %p", self, _cmd);
}

@end

// MyRuntimeClass定义
@interface MyRuntimeClass : NSObject

@property (nonatomic, strong) MyClass *myClass;

- (void)test;

@end

@implementation MyRuntimeClass

- (instancetype)init
{
    if (self = [super init]) {
        self.myClass = [MyClass new];
    }
    
    return self;
}

- (void)test
{
    [self performSelector:@selector(method)];
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(method)) {
        return self.myClass;
    }
    
    return [super forwardingTargetForSelector:aSelector];
}

可以在外界调用MyRuntimeClasstest测试。
如果此方法返回nil或self,则会进入完整方法解析(forwardInvocation:);否则将向返回的对象重新发送消息。
可以使用+ forwardingTargetForSelector:返回一个类对象进行类方法的转发

3.完整方法转发

如果还没有使用上述方法处理,那么将会使用- forwardInvocation:进行最后的消息转发

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        if ([MyClass instancesRespondToSelector:aSelector]) {
            signature = [MyClass instanceMethodSignatureForSelector:aSelector];
        }
    }
    return signature;
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    // 返回一个BOOL值用来判断消息接受者实例能否相应给定的消息
    if ([MyClass instancesRespondToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:self.myClass];
    }
}

Runtime系统会向对象发送- methodSignatureForSelector:消息,并取到返回的方法签名用于生成NSInvocation对象。因此我们在使用- (void)forwardInvocation:进行消息转发时必须实现- methodSignatureForSelector:

如果上述方法你都没有处理,那么只有崩溃并报unrecognized selector sent to instance错误。

参考链接:
http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/
http://southpeak.github.io/2014/11/03/objective-c-runtime-3/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,692评论 0 9
  • 前言 runtime其实在我们日常开发过程中很少使用到,尤其是像我现在比较初级的程序猿就更用不到了。但是去面试很多...
    WolfTin阅读 620评论 0 2
  • 我们常常会听说 Objective-C 是一门动态语言,那么这个「动态」表现在哪呢?我想最主要的表现就是 Obje...
    Ethan_Struggle阅读 2,188评论 0 7
  • 文中的实验代码我放在了这个项目中。 以下内容是我通过整理[这篇博客] (http://yulingtianxia....
    茗涙阅读 918评论 0 6
  • 转载:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麦子阅读 729评论 0 2