美文网首页iOS开发实录技术iOS 开发
runtime小序曲,从运行时多态看这股神秘力量

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

作者: 天口三水羊 | 来源:发表于2016-12-22 18:29 被阅读2411次

    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的讲解。

    相关文章

      网友评论

      本文标题:runtime小序曲,从运行时多态看这股神秘力量

      本文链接:https://www.haomeiwen.com/subject/sfhhvttx.html