关于ios runtime理解

什么是runtime

说到runtime,根据字面意思就是运行期间。
但我觉得首先应该说一下oc到底是个什么东西。首先,它是一门编程语言;其次,它是基于C语言,在其基础上面增加了面向对象的特性。最后重点的是,oc使用的是“消息结构”,而并非“函数调用”。
关键性的区别就是:基于消息结构的的语言,运行时所执行的代码是由运行环境来决定的,而不是实在编译期间决定的,这一点就衍生出了runtime机制。
简单来说runtime就是在运行的期间去选择到底是去使用哪一个方法,而不是在编译期间决定死了。打个通俗的比方,一个人从同一个起点到同一个目的地,如果乘公交车的话,线路是死的,公交只会按照既定的公交线路来走。如果是自己开车的话,可以随便选择自己随性所欲的路线,最终到达目的地就ok了。乘公交的方式,就好比编译期间决定;自己开车的方式就好比运行期间决定。

预备知识

oc中对象的定义

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

oc中id对象的定义

struct objc_object {
    Class isa ;
} *id;

oc中Class的定义

// 类
struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;  //指向metaClass(元类)

#if !__OBJC2__
    Class super_class                       OBJC2_UNAVAILABLE;  // 父类
    const char *name                        OBJC2_UNAVAILABLE;  // 类名
    long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0 => 序列化支持
    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;  // 方法缓存;优化methodLists查找
    struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议链表
#endif
} OBJC2_UNAVAILABLE;

typedef struct objc_class *Class;

关于元类(metaClass):
class结构体中存放的是类的元数据(metadata),其结构体中的首个变量也是isa指针,很显然说明了Class本身也是一个OC对象。
同时调用类方法就是向该类发送消息,会在该方法的方法列表中寻找此方法。

函数调用原型

void objc_msgSend(id self, SEL cmd, ...)

上面函数调用原型是一个参数可变的函数,可以接受两个或者两个以上的参数。
第一个参数代表接收者,第二个参数代表SEL类型,后面的参数就是消息中的附带的参数,按照原顺序依次在后。

下面正式开始

1.理解objc_msgSend的作用

知道了上面的信息后,第一步要明白oc调用机制,也就是上面所说的消息调用机制,至于调用原型,参考上面的函数调用原型。
oc调用对象的方法就是如此,用oc的术语来说,叫做“传递消息”。所以在整个oc开发中,这一点一定要铭记于心。
比如我们在oc中调用方法是这样写的

[someObj doSomeThing:parameter];

但编译器在看到此条消息后,会将其转化成标准的C语言函数调用。如下:

objc_msgSend(someObj, @selector(doSomeThing:), parameter);

当oc开始调用方法的时候,objc_msgSend方法会根据后面的参数来匹配具体的方法。匹配的时候,回去该接收者所属类中的方法列表(methodLists)中去查找。如果查找到了,则跳转到方法中去实现。如果找不到的话,就进行消息转发操作(后面会讲消息转发)。
如果每次调用都来实现类似的操作的话,那么性能肯定会浪费。所以,objc_msgSend会将匹配的结果缓存起来,下次会从缓存里面取。

2.消息转发机制

上面讲了消息的传递机制,但是如果碰到无法解读的消息之后呢?此时oc就会开启消息转发。
先看看转发的几个方法:
1.当前类处理

+ (Bool)resolveInstanceMethod:(SEL)selector
+ (Bool)resolveClassMethod:(SEL)selector

2.转发到其它类来处理

- (id)forwardingTargetForSelector:(SEL)selector

3.转发到其它类来处理

- (void)forwardingInvocation:(NSInvocation*)invocation

关于消息转发的话,总体上就是三步。
首先由resolveInstanceMethod来处理,如果成功则继续处理找到的方法。如果返回false,则继续向下转发。
接下来是第二次机会,由forwardingTargetForSelector:来处理,同样的,成功则返回找到的方法。如果返回为nil的话,则继续向下转发。
还无法处理,就到forwardingInvocation:。到了这一步只能启用完整的消息转发了。会首先创建一个NSInvacation对象,将那条未处理的相关消息(sel,target,参数)全部封装到里面。触发了NSInvacation对象时,就由oc的消息派发系统来调用forwardingInvocation:。当发现某个调用操作不应该由此类来处理的时候,那么就调用超类的同名方法。这样的话继承体系中的每个类都有机会处理该调用请求,直到NSObject。如果最后调用了NSObject的方法,那么该方法会调用“doesNotRecognizeSelector:”来抛出异常信息,结果显示此方法未能得到处理。

消息转发的流程图如下

runtime使用

当我们了解了oc的消息机制后,肯定会疑惑,runtime有何用处?
下面会说明runtime使用的两个方法,但是一定要慎用,因为可能造成未知的错误。
1.method swizzling,俗称“黑魔法”。
2.关联对象(Associated Object)

1.method swizzling

正式因为oc的动态绑定,所以我们才可以使用method swizzling黑魔法。
类方法会将相应的sel名称映射到相关的方法实现上,这些方法均已函数指针的形式来表示,该指针叫做IMP。原型如下:

id (*IMP)(id, SEL, ...)

根据下面这张图,我们来看看映射对应关系:

这种映射关系是在运行期间选择的,所以我们可以通过一些方法来新增,交换其中映射关系,使本该调用imp1的lowercaseString方法从而实现调用了uppercaseString方法。调用后的映射关系图如下:

方法实现的获取函数如下:

Method class_getInstanceMethod(Class aClass, SEL aSelector)

方法实现的交换函数如下:

void method_exchangeImplementations(Method m1, Method m2);

新增一个方法的函数如下:

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

通过上面3个方法我们就可以来实现运行期间方法的改变了。
代码示例,就直接上Mattt大神的代码了,如下:

#import "UIViewController+swizzling.h"
#import <objc/runtime.h>

@implementation UIViewController (swizzling)

//load方法会在类第一次加载的时候被调用
//调用的时间比较靠前,适合在这个方法里做方法交换
+ (void)load{
    //方法交换应该被保证,在程序中只会执行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        //获得viewController的生命周期方法的selector
        SEL systemSel = @selector(viewWillAppear:);
        //自己实现的将要被交换的方法的selector
        SEL swizzSel = @selector(swiz_viewWillAppear:);
        //两个方法的Method
        Method systemMethod = class_getInstanceMethod([self class], systemSel);
        Method swizzMethod = class_getInstanceMethod([self class], swizzSel);

        //首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
        BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
        if (isAdd) {
            //如果成功,说明类中不存在这个方法的实现
            //将被交换方法的实现替换到这个并不存在的实现
            class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
        }else{
            //否则,交换两个方法的实现
            method_exchangeImplementations(systemMethod, swizzMethod);
        }

    });
}

- (void)swiz_viewWillAppear:(BOOL)animated{
    //这时候调用自己,看起来像是死循环
    //但是其实自己的实现已经被替换了
    [self swiz_viewWillAppear:animated];
    NSLog(@"swizzle");
}

@end

在一个自己定义的viewController中重写viewWillAppear

- (void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    NSLog(@"viewWillAppear");
}

想看效果的自己跑起来试试。

2.关联对象(Associated Object)

一把我们都是直接在程序里面直接添加对象,但万一那段代码不是我们自己写的,或者我们接触不到具体代码怎么办,但是我们又想新增一个对象?这个时候,就可以使用关联对象了。

我们先来了解关联对象的基本知识。
1.对象关联类型

关联类型 等效的@property属性
OBJC_ASSOCIATION_ASSIGN assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC nonatomic,retain
OBJC_ASSOCIATION_COPY_NONATOMIC nonatomic,copy
OBJC_ASSOCIATION_RETAIN retain
OBJC_ASSOCIATION_COPY copy

2.用给定的键和策略为某对象设置关联对象值

//关联对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)

3.用给定的键从某对象中获取对应的关联对象值

//获取关联的对象
id objc_getAssociatedObject(id object, const void *key)

4.移除指定对象的全部关联对象

//移除关联的对象
void objc_removeAssociatedObjects(id object)

要注意的是,设置关联对象的时候,如果想让两个建匹配到同一个值,那么意味着两者的指针必须完全相同。简单来说,就是设置关联对象的建的时候,用静态全局变量做建就行了。

具体使用的示例参考别人写的这篇文章,讲得很清楚了。iOS runtime实战应用:关联对象

参考资料

《effective Objective-C 2.0》

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,670评论 0 9
  • 目录 Objective-C Runtime到底是什么 Objective-C的元素认知 Runtime详解 应用...
    Ryan___阅读 1,923评论 1 3
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,521评论 33 466
  • 参考链接: http://www.cnblogs.com/ioshe/p/5489086.html 简介 Runt...
    乐乐的简书阅读 2,125评论 0 9
  • 测试一把测试一把测试一把测试一把测试一把测试一把测试一把测试一把测试一把测试一把测试一把测试一把
    Zomll阅读 151评论 0 0