runtime小序曲,从运行时多态看这股神秘力量

runtime一直很神秘,本文从面向对象的基本特性多态,引出runtime,并做了基本的解释和简单应用梳理。后续会深入runtime。
学习进度:

一、多态

多态(英语:polymorphism),是指计算机程序运行时,相同的消息可能会送给多个不同的类别之对象,而系统可依据对象所属类别,引发对应类别的方法,而有不同的行为。简单来说,所谓多态意指相同的消息给予不同的对象会引发不同的动作称之。
多态可分为静态多态和动态多态:

  • 静态多态,允许将不同的特殊行为和单个泛化记号相关联,由于这种关联处理于编译期而非运行期,因此被称为“静态”,比如函数重载。
  • 动态多态,也即运行时多态,对于C++来说,通过类继承机制和虚函数机制生效于运行期,可以优雅地处理异质对象集合,只要其共同的基类定义了虚函数的接口;对于OC来说,通过借鉴于smallTalk的消息传递机制实现动态多态。一般来说,动态多态才是真正的多态。

那么,接下来,分别看一下C++和OC对动态多态的代码实现。

二、C++、OC多态对比

1、C++动态多态实现

代码

#include<iostream>  
using namespace std;  
class Base  
{  
public:  
    virtual void f(float x)  
    {  
        cout<<"Base::f(float)"<< x <<endl;  
    }  
    void g(float x)  
    {  
        cout<<"Base::g(float)"<< x <<endl;  
    }
};  
class Derived : public Base  
{  
public:  
    virtual void f(float x)  
    {  
        cout<<"Derived::f(float)"<< x <<endl;
    }  
    void g(int x)  
    {  
        cout<<"Derived::g(int)"<< x <<endl;
    } 
};  
int main(void)  
{  
    Derived d;  
    Base *pb = &d;  
    Derived *pd = &d;  
    pb->f(3.14f);   // 输出:Derived::f(float) 3.14  
    pd->f(3.14f);   // 输出:Derived::f(float) 3.14  
    pb->g(3.14f);   // 输出:Base::g(float)  3.14  
    pd->g(3.14f);   // 输出:Derived::g(int) 3.14
    return 0;  
}  

分析
上述代码创建了Derived对象d,将Base类型指针pb和Derived类型指针pd指向d,可知pb和pd的编译时刻的类型分别是Base和Derivevd,而运行时刻类型均为Derived。看上述四个输出,pd的输出没有什么可说的,只是为了对比。重点是pb的f、g两个函数的输出,可见f为其运行时刻类型的输出结果,g为编译时刻的输出结果,而两者的区别即为f是虚函数。所以,可以得出结论,对于C++来说,虚函数完成了其动态多态的实现。

2、OC动态多态实现

代码

#import <Foundation/Foundation.h>
@interface Base: NSObject
@end
@implementation Base
- (void)f {
    NSLog(@"Base f");
}
@end
@interface Derived: Base
@end
@implementation Derived
- (void)f {
    NSLog(@"Derived f");
}
@end

int main(int argc, char *argv[]) {
    Derived *d = [[Derived alloc] init];
    Base *pb = d;
    Derived *pd = d;
    [pb f];     // 输出:Derived f
    [pd f];     // 输出:Derived f
    return 0;
}

分析
和C++类似,上述代码创建了Derived对象d,将Base类型指针pb和Derived类型指针pd指向d,可知pb和pd的编译时刻的类型分别是Base和Derived,而运行时刻类型均为Derived。从两个输出来看,可以发现,OC并没有使用虚函数,也没有虚函数的概念,但是OC自动完成了动态多态的实现。

疑问
是何种神秘力量帮我们完成了动态多态?请往下看。

三、运行时机制

C语言是一种静态语言,而OC是一个依托于C的语言。为了保持了C编译时的功能(如类型检查),并且增加灵活性,OC在C的基础上,借鉴了smalltalk的消息传递机制为其添加了运行时机制,这也就是runtime机制。
换种方式来说,runtime机制是一个由C和汇编编写的Library,而这个Library给C增加了面向对象的特性,从而形成了OC语言。

基于runtime,OC中对象的类型和对象所执行的方法都是在运行时阶段进行查找并确认的,这种机制被称为动态绑定。也就是上述的神秘力量。

OC中与运行时机制进行交互的方面大致以下三种:
1、Objective-C源代码
大部分情况下你就只管写你的Objc代码就行,runtime 系统自动在幕后辛勤劳作着。比如二中的下述代码:

[pb f]; 

实质是(clang转成cpp后):

((void (*)(id, SEL))(void *)objc_msgSend)((id)pb, sel_registerName("f"));

2、NSObject的方法
Cocoa 中大多数类都继承于NSObject类,也就自然继承了它的方法。有的NSObject中的方法起到了抽象接口的作用,比如description方法需要你重载它并为你定义的类提供描述内容。NSObject还有些方法能在运行时获得类的信息,并检查一些特性,比如class返回对象的类;isKindOfClass:和isMemberOfClass:则检查对象是否在指定的类继承体系中;respondsToSelector:检查对象能否响应指定的消息;conformsToProtocol:检查对象是否实现了指定协议类的方法;methodForSelector:则返回指定方法实现的地址。

3、Runtime的函数
Runtime 系统是一个由一系列函数和数据结构组成,具有公共接口的动态共享库。头文件存放于/usr/include/objc目录下。许多函数允许你用纯C代码来重复实现 Objc 中同样的功能。虽然有一些方法构成了NSObject类的基础,但是你在写 Objc 代码时一般不会直接用到这些函数的,除非是写一些 Objc 与其他语言的桥接或是底层的debug工作。在Objective-C Runtime Reference中有对 Runtime 函数的详细文档。

四、静态语言和动态语言对比

上文提过,C是一门静态语言,而runtime为C增加了面向对象的特性,并且runtime的消息传递方式给了这门语言一种动态的特性。下面就看看静态和动态的区别,以及我们如何利用。

对C来说

//编译前:
#include < stdio.h >
int main(int argc, const char **argv[])
{
        printf("Hello World!");
        return 0;
}

//编译后
.text
 .align 4,0x90
 .globl _main
_main:
Leh_func_begin1:
 pushq %rbp
Llabel1:
 movq %rsp, %rbp
Llabel2:
 subq $16, %rsp
Llabel3:
 movq %rsi, %rax
 movl %edi, %ecx
 movl %ecx, -8(%rbp)
 movq %rax, -16(%rbp)
 xorb %al, %al
 leaq LC(%rip), %rcx
 movq %rcx, %rdi
 call _printf
 movl $0, -4(%rbp)
 movl -4(%rbp), %eax
 addq $16, %rsp
 popq %rbp
 ret
Leh_func_end1:
 .cstring
LC:
 .asciz "Hello World!"

这里不用关注一堆汇编代码,只需要看最后一句,可得知,在编译时刻,已经确定了输出结果。

对OC而言

//编译前
[pb f];

//编译后
((void (*)(id, SEL))(void *)objc_msgSend)((id)pb, sel_registerName("f"));

在编译后,完全不知道会输出什么,只知道会给对象pb,发送消息f,即把一部分操作推到了运行时。

当然,OC并不是一个动态语言,只是给C添加了动态特性。

而我们所谓的runtime应用,也就是利用的这样一个延迟,即在运行时做一些事情,下面简单介绍下应用。

五、runtime应用

谈了半天,也不知道runtime有什么鬼用途。在三中我就提到了,runtime为我们提供了运行时刻操纵代码的能力,接下来,稍微说下runtime的常见应用,不过多解释,看看就好,会在后续的篇目中进行深入。

  • Swizzling,替换两个方法的实现,可以做一些埋点、容错等工作。
  • 关联属性,在运行时刻为某个class增加属性。
  • Json映射,通过runtime的方法将Json映射到model。

runtime功能很强大,这里只是几个最常见的用法,如果想了解更多内容,请继续关注我。

六、文献

1、https://zh.wikipedia.org/wiki/%E5%A4%9A%E5%9E%8B_(%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%A7%91%E5%AD%A6)
2、http://yulingtianxia.com/blog/2014/11/05/objective-c-runtime/

本文仅仅从一个角度引出runtime,后续会继续用最通俗的大白话进行runtime的讲解。

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

推荐阅读更多精彩内容