运行时机制Runtime

Runtime是什么?

Runtime是oc语言实现动态的核心

  • 将尽可能多的决策从编译时和链接时推迟到运行时
  • 运行时系统充当着Object-C语言的操作系统,它使语言能够工作

Runtime用来干什么?用在哪些地方?

用来干什么 基本作用

  • 在程序运行过程中,动态的创建类,动态添加、修改这个类的属性和方法;
  • 遍历一个类中所有的成员变量、属性、以及所有方法
  • 消息传递、转发

用在哪些地方 Runtime的典型事例

  • 给系统分类添加属性、方法
  • 方法交换
  • 获取对象的属性、私有属性
  • 字典转换模型
  • KVC、KVO
  • 归档(编码、解码)
  • 类的自我检测

Runtime的消息发送

其中有个方法objc_msgSend就是消息发送的方法,在Object-C中所有调用的方法,都是向这个类、或者发送消息

  • Objc-msgSend所做的事情

1.找到方法的实现,由于通过单独的类以不同方式创建相同的方法,因此这个方法的实现的确定取决于接收消息的类对象,也即是说多个实例类对戏那个可以创建同样的方法,每个实例对象中的该方法都是独立存在的。
2.调用该方法实现,将接收消息类指针,以及该方法的参数传递给这个类
3.最后将过程的返回值作为自己的返回值传递

Objc-msgSend的发送过程

1.消息发送给对象时,消息传递函数遵循对象的isa指针指向类结构的指针,在该结构中它查询结构体变量methodLists中的方法SEL(方法选择器)
2.如在isa指向的类结构中找不到SEL(方法选择器),Objc_msgSend会跟随指向Supercalss(父类)指针并再次尝试查找该SEL
3.如连续失败直到NSObject类,它的superclass也就是它自己本身
一旦找到SEL,该函数就会调用methodLists的方法并将接收对象的指针传给它

上述过程就是Runtime的是实现方式,在面向对象的编程属于中,方法动态的绑定到消息。

加速消息发送

有的时候在一个类会有继承关系,Objective-C中大部分对象都是继承于NSObject、自己自定义类,在这种继承体系当中有很多的方法,这些方法有可能不会用到,在向类发送消息的时候,去methodLists中查找无疑会拖慢程序的运行速度,所以Apple在开发的时候加入了cache的概念,也就是缓存
在每个类中都会有一个单独的缓存,它可以包含继承过来的方法SEL以及自己定义的SEL,在搜索methodLists之前,消息传递程序会检查接受者对象的告诉缓存cache,如果找到,就不会在去搜索庞大的methodLists列表,一旦在缓存当中存在你需要的SEL,这样以后也就比函数调用稍微慢一点
理论上cache缓存的是一些会再次调用的SEL,当写的程序预热足够时间,那么所有发送过的SEL都会在cache中找到

cache会动态增长,容纳新的消息,知道程序中所有调用的SEL运行一遍为止
原理时:好比是 通常小圈子找人总比大圈子找人要快

总结

消息机制就是向接收者发送消息,并带有参数,根据接收者对象的数据结构,找到相关发放实现,最后达到这个消息的目的

objc_msgSend是Runtime的核心,Objective-C中调用对象方法就是消息传递。

objc_msgSend并不是直接调用方法实现(IMP)而是发送消息,让类的结构体去动态查到方法实现,所以在为查找到方法实现之前我们可以动态的去修改这个方法的实现

Runtime的消息转发

引言

iOS的消息转发机制,在我们开发中有时候忘记实现某个声明的方法,从而在运行过程中调用该方法出现崩溃,

当然这类问题是可以解决的,在当前对象或者父类对象中添加对象的方法实现,再重新运行,调用该方法就能解决这个问题

又或者在我们运行的时候动态的去添加接收者中未知方法实现

解决方案

第一种方式

遇到这种情况通常第一个想法就是在该对象或继承树中的实现文件中添加该方法并实现,这种形式,就是你必须要去实现方法,需要开发者主动去写代码。

第二种方式

1.动态方法解析

+(BOOL)resolveInstanceMethod:(SEL)sel //实例方法解析
+(BOOL)resolveClassMethod:(SEL)sel// 类方法解析

当运用消息转发运行时,根据调用的方法类型调用这两个方法其中一个,返回值BOOL类型,告诉系统该消息是否被处理,YES处理 NO 未处理

  • resolveInstanceMethod实例方法调用
  • resolveClassMethod类方法调用

这样的作用是,当接受者接受到的消息方法并没有找到的情况下,系统会调用该函数,给予这个对象一次动态添加该消息方法实现的机会,如果该对象动态的添加了这个方法的实现,就返回YES,告诉系统这个消息我已经处理完毕。再次运行该方法

注意:
当这个对象在实现了resolveInstanceMethod,resolveClassMethod两个方法,并没有对该对象消息进行处理,那么该方法会被调用两次:

一次是没有找到该方法需要对象解析处理;第二次是告诉系统我处理完成需要再次调用该方法但实际上并没有处理完成,所以会调用第二次该方法崩溃

2.后备接收者对象

-(id)forwardingTargetForSelector:(SEL)aSelector

在消息转发第一次方法解析中没有处理方法,并告诉系统本对象无法处理,需另寻办法,那么系统会给予另外一个办法,就是让别的对象B来处理该问题,如果对象B能够处理该消息,那么该消息转发结束。

将未知SEL作为参数传入,寻找另外对象处理,如果可以处理,返回该对象

3.以其他形式实现该消息方法

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

-(void)forwardInvocation:(NSInvocation *)anInvocation

当我们在前面两个步骤都没有处理该未知SEL时,就会到第三个步骤,上述两个方法是最后寻找IML的机会

  • 将未知SEL作为参数传入methodSignatureForSelector,在该方法中处理该消息,一旦能够处理,返回方法签名(自由的修改方法签名,apple签名),让后续forwardInvocation来进行处理

  • forwardInvocation中我们可以做很多的操作,这个方法比forwardingTargetForSelector更灵活

  • 也可以做到像forwardingTargetForSelector一样的结果,不同的是一个是让别的对象去处理,后者是直接切换调用目标,也就是该方法的Target

  • 我们也可以修改该方法的SEL,就是重新替换一个新的SEL

  • ....
    4.直到最后未处理,抛出异常

(void)doesNotRecognizeSelector:(SEL)aSelector

END

附:Runtime常用方法

Runtime 方法交换

+ (void)load{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method originalMethod = class_getInstanceMethod([self class], @selector(originalMethod));
        Method swizzlingMethod = class_getInstanceMethod([self class], @selector(swizzlingMethod));
        BOOL isAdded = class_addMethod([self class], method_getName(originalMethod), method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
//添加不存在的方法,返回Yes;
//        Method originalMethodTest = class_getInstanceMethod([self class], @selector(originalMethodTest));
//        BOOL isAddedTest = class_addMethod([self class], method_getName(originalMethodTest), method_getImplementation(originalMethodTest), method_getTypeEncoding(originalMethod));
        if (isAdded)
        {
            NSLog(@"方法在原类中不存在,已经添加成功,用下面的方法替换其实现");
            class_replaceMethod([self class], method_getName(originalMethod), method_getImplementation(swizzlingMethod), method_getTypeEncoding(swizzlingMethod));
        }
        else
        {
            NSLog(@"方法在原类中存在,用下面的方法交换其实现方法");
            method_exchangeImplementations(originalMethod, swizzlingMethod);
        }
        
        NSLog(@"end");
    });
    
}
- (void)originalMethod{
    NSLog(@"原类中的方法");
}


- (void)swizzlingMethod{
    NSLog(@"替换过的方法");
}

Runtime 动态添加属性方法

在Objective-C中,category分类默认只能添加方法,不能添加属性。根本原因在于声明了@property后,category并不会自动生成set和get方法。如果有需要在category中添加属性,可以利用runtime的特性实现。
动态添加属性

//新建一个NSObject的category类,并添加一个customString属性
@interface NSObject (Category)
@property(nonatomic,copy)NSString *customString;
@end

//在.m文件中实现set、get方法,此时添加属性代码便完成了,就是如此简单
#import "NSObject+Category.h"
#import <objc/message.h>
- (void)setCustomString:(NSString *)customString {
    objc_setAssociatedObject(self, &customStringKey, customString, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)customString {
    return  objc_getAssociatedObject(self, &customStringKey);
}

//测试一下,如果打印出1111,就代表添加属性成a国
- (void)viewDidLoad {
    [super viewDidLoad];
     ///动态添加属性
    NSObject *objct = [[NSObject alloc] init];
    objct.customString = @"1111";
    NSLog(@"%@",objct.customString);    
}

动态添加方法

///例如我们有一个people类,people类中没有任何属性和方法,
//我们为之添加一个名为sing的方法
- (void)viewDidLoad {
    [super viewDidLoad];
    People *people = [[People alloc] init];
    //添加方法
    class_addMethod([People class], @selector(sing), class_getMethodImplementation([self class], @selector(peopleSing)), "v@:");
    //people调用刚添加的方法    
    [people performSelector:@selector(sing)];
}

- (void)peopleSing
{
    NSLog(@"在唱歌");
}

END

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

推荐阅读更多精彩内容

  • 夜莺2517阅读 127,709评论 1 9
  • 版本:ios 1.2.1 亮点: 1.app角标可以实时更新天气温度或选择空气质量,建议处女座就不要选了,不然老想...
    我就是沉沉阅读 6,876评论 1 6
  • 我是黑夜里大雨纷飞的人啊 1 “又到一年六月,有人笑有人哭,有人欢乐有人忧愁,有人惊喜有人失落,有的觉得收获满满有...
    陌忘宇阅读 8,520评论 28 53
  • 兔子虽然是枚小硕 但学校的硕士四人寝不够 就被分到了博士楼里 两人一间 在学校的最西边 靠山 兔子的室友身体不好 ...
    待业的兔子阅读 2,583评论 2 9