runtime原理分析

runtime是oc中一个比较底层的纯c语言库,包含了很多底层c语言的api

runtime其实和我们编程密切相关,平时编写的oc代码中,在程序运行时,其实最终都是转成runtime的c语言代码


可以这么理解,oc的底层实际上是在调用一个个c函数,如何找到这些c函数的工作,就是由runtime去负责了


要理解runtime,首先要理解类的结构


类的本质是什么? 实际上就是一个类似结构体的玩意,那么类里面有什么内容呢,以下一张图就能够很清晰的理解


一个Person类的内部构造

isa就是“is a”,对于所有继承了NSObject的类其对象也都有一个isa指针。这个isa指针指向关于这个对象所属的类的定义。​

任何直接或间接继承了NSObject的类,它的实例对象(instacne objec)中都有一个isa指针,指向它的类对象(class object)。这个类对象(class object)中存储了关于这个实例对象(instace object)所属的类的定义的一切:包括变量,方法,遵守的协议等等

以下这图可以很清楚的表述上面的关系

Class isa and superclass relationship from Google

简单来说,一个类的对象的isa指针会指向这个类,这个类也有一个isa指针会指向这个类的元祖类(meta class)。

一个类以及其元祖类(meta class) 都会有一个super class 的指针指向其父类,一直到根类(NSObject),NSObject的元祖类的super class 会指回到NSObject类,至于NSObject是没有super class 的,其super class 指针会指向nil

至于类和元祖类是什么关系呢? 首先,他们都是对象,根本上都是objc_class对象,因此也有类对象和元祖类对象的一说。区别在于,类对象中包含了这个类的实例变量,实例方法的定义,而元祖类对象中包含类的方法的定义。


搞清楚对象和类的这层关系之后,要理解起来就轻松多了,你可以理解成isa 和 super class 就是一个门牌号或者路标,runtime就是根据这些路标去找到相应的静态方法和变量的。

-------------------------------------- 华丽的分割线   ----------------------------------

搞清楚指针之后,之后就看重点了

ivars(成员变量列表)

        这个属性 objc_ivar_list 结构体类型,其实就是一个链表,存储多个objc_ivar,而objc_ivar又是一个结构体,用来存储类的单个成员变量的信息

         简单粗暴一点理解,ivar 就是成员变量

objc_ivar里面又有什么内容呢?见下图


objc_ivar结构


methodlists(方法列表)

      同ivars一样理解,实际上这两个结构体几乎是一样的,也是一个objc_method_list 的结构体, 也是一个链表,存储多个objc_method,  而objc_method 又是一个结构体,用来存储类的某个方法信息

我们也可以来一张图来展示objc_method的风采


objc_method

看到SEL估计都不陌生了,一个方法的SEL 其实是一个C的字符串,并且会在OC的runtime中注册这个selector,这个操作一般是在加载到内存的时候就完成了这一步注册操作,即在+ load 的方法中

实际上我们平时调用方法,都不是一步到位,实际上是通过selector找到该方法,然后再找到这个方法的实现(IMP),IMP本质上就是一个函数指针,指向c函数

举个栗子方便理解:

[ a  sayHello]  ---> 在a对象中通过其isa指针找到其类对象--->找到methodList ---> 根据"sayHello"这个selector找到对应的方法 ---> 找到其对应的IMP指针  --->  c函数

以上仅仅是方便理解而已,因为实际上,上面调用方法的环节还要加入缓存池的操作(cache)


Cache(方法缓存池)

          二话不说,先上图


objc_cache

           Cache其实就是一个存储Method的链表,主要是为了优化方法调用的性能。当调用一个方法的时候,会先去缓存池找,如果没找到,再去methodLists查找


------------------------------------------又是华丽的分割线-----------------------------


消息发送

 当调用一个方法时[a sayHello],其实是被编译器转化为

objc_msgSend ( id self, SEL op, ... )

1.根据a实例对象的isa找到其对应的类对象

2.优先在类对象中的cache查找sayHello方法,如果找不到,再到methodLists查找

3.如果找不到,通过super_class 指针向父类查找,如果找不到,找父类的父类,一直到NSObject

4.一旦找到了,则执行IMP的实现


容错机制

要是找不到怎么办?一开始我也以为会抛出异常,崩溃,实际上在崩溃之前还有三次容错机制进行处理,按照以下顺序:

     Method Resolution

     Fast Forwarding

     Normal Forwarding

上图最直接:


容错处理的最后三步


Method Resolution

首先Objective-C在运行时调用+ resolveInstanceMethod:或+ resolveClassMethod:方法,让你添加方法的实现。如果你添加方法并返回YES,那系统在运行时就会重新启动一次消息发送的过程。

举一个简单例子,定义一个类Message,它主要定义一个方法sendMessage,下面就是它的设计与实现:


@interface Message : NSObject

- (void)sendMessage:(NSString *)word;

@end


@implementation Message

- (void)sendMessage:(NSString *)word

{

NSLog(@"normal way : send message = %@", word);

}

@end

如果我在viewDidLoad方法中创建Message对象并调用sendMessage方法:


- (void)viewDidLoad {

[super viewDidLoad];

Message *message = [Messagenew];

[message sendMessage:@"Sam Lau"];

}

控制台会打印以下信息:

normal way : send message = Sam Lau

但现在我将原来sendMessage方法实现给注释掉,覆盖resolveInstanceMethod方法:


#pragma mark - Method Resolution

/// override resolveInstanceMethod or resolveClassMethod for changing sendMessage method implementation

+ (BOOL)resolveInstanceMethod:(SEL)sel

{

    if(sel == @selector(sendMessage:)) {

        class_addMethod([selfclass], sel, imp_implementationWithBlock(^(id self, NSString *word) {

        NSLog(@"method resolution way : send message = %@", word);

        }),"v@*");

    }

returnYES;

}

控制台就会打印以下信息:

method resolution way : send message = Sam Lau

注意到上面代码有这样一个字符串"v@*,它表示方法的参数和返回值,详情请参考Type Encodings

如果resolveInstanceMethod方法返回NO,运行时就跳转到下一步:消息转发(Message Forwarding)。

Fast Forwarding

如果目标对象实现- forwardingTargetForSelector:方法,系统就会在运行时调用这个方法,只要这个方法返回的不是nil或self,也会重启消息发送的过程,把这消息转发给其他对象来处理。否则,就会继续Normal Fowarding。

继续上面Message类的例子,将sendMessage和resolveInstanceMethod方法注释掉,然后添加forwardingTargetForSelector方法的实现:


#pragma mark - Fast Forwarding

- (id)forwardingTargetForSelector:(SEL)aSelector

{

    if(aSelector == @selector(sendMessage:)) {

    return[MessageForwardingnew];

    }

    return nil;

}

此时还缺一个转发消息的类MessageForwarding,这个类的设计与实现如下:


@interface MessageForwarding : NSObject

- (void)sendMessage:(NSString *)word;

@end


@implementation MessageForwarding

- (void)sendMessage:(NSString *)word

{

NSLog(@"fast forwarding way : send message = %@", word);

}

@end

此时,控制台会打印以下信息:

fast forwarding way : send message = Sam Lau

这里叫Fast,是因为这一步不会创建NSInvocation对象,但Normal Forwarding会创建它,所以相对于更快点。

Normal Forwarding

如果没有使用Fast Forwarding来消息转发,最后只有使用Normal Forwarding来进行消息转发。它首先调用methodSignatureForSelector:方法来获取函数的参数和返回值,如果返回为nil,程序会Crash掉,并抛出unrecognized selector sent to instance异常信息。如果返回一个函数签名,系统就会创建一个NSInvocation对象并调用-forwardInvocation:方法。

继续前面的例子,将forwardingTargetForSelector方法注释掉,添加methodSignatureForSelector和forwardInvocation方法的实现:


#pragma mark - Normal Forwarding

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

{

    NSMethodSignature *methodSignature = [super methodSignatureForSelector:aSelector];

    if(!methodSignature) {

        methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:*"];

    }

    return methodSignature;

}

- (void)forwardInvocation:(NSInvocation *)anInvocation

{

    MessageForwarding *messageForwarding = [MessageForwardingnew];

    if([messageForwarding respondsToSelector:anInvocation.selector]) {

        [anInvocation invokeWithTarget:messageForwarding];

    }

}


-----------------------------------华丽的分割线-------------------------------------------


结束语: runtime是个好东西,虽然本人平时日常开发很少用到,但是了解其原理以及底层结构,会让你对整个oc的运作有一个更好的理解


最后,本文章也是参考刘耀柱大神的文章写出来的,更多关于runtime的干货,在下面链接:


http://www.csdn.net/article/2015-07-06/2825133-objective-c-runtime/6

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,690评论 0 9
  • runtime 和 runloop 作为一个程序员进阶是必须的,也是非常重要的, 在面试过程中是经常会被问到的, ...
    made_China阅读 1,206评论 0 7
  • runtime 运行时语言,实现Object-C的C语言库,将OC转换成C进行编译的过渡者。 作为一门动态编程语言...
    夜雨聲煩_阅读 545评论 0 0
  • runtime 和 runloop 作为一个程序员进阶是必须的,也是非常重要的, 在面试过程中是经常会被问到的, ...
    SOI阅读 21,791评论 3 63
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,548评论 33 466