Objective-C的runtime system

作者: FindCrt | 来源:发表于2015-09-05 00:24 被阅读1240次

    刚学OC的时候,不知道runtime这个东西,也不知道动态是什么概念。后来知道了runtime,但是看了一眼,什么class结构体、isa指针,不是说是“运行时”吗,这些跟运行什么关系?根本无法理解runtime是个什么东西,就是大脑里完全无法建立概念。

    学OpenGL,一开始接触到“管线”这个词,看到说绘制管线,脑子里想到的是一根根的线、电路之类的,和图形绘制什么关系?后来理解,其实是“流水线”,绘制图形的过程像一个工厂流水线一样,流程基本是固定,但我们可以通过脚本对流程的一个个环节做处理。

    同样,runtime的理解,我觉得不要受“运行时”这个翻译影响,也不要管“runtime”这个单词(至少一开始不要管)。首先定性,runtime是一个库、一个系统。runtime这个名字,只是这个系统要干的事,它的目的。

    文档里介绍runtime的第一句话:

    The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.

    OC想要把决定从编译期推迟到运行期,就是尽可能的动态。这是OC的特性,而runtime就是用来让OC具有这样的性质,OC是建立在runtime之上的,而runtime之下是C。

    我的理解是,C本身不是动态的,也不适合面向对象的编程,那我想要有一个动态的语言,怎么办?我来弄一个库,它里面有一些数据结构,有一些方法,可以在C的基础之上构建一个新的语言,让我可以享受想要的这些性质。所以runtime是一个中间层,连接着C和OC。

    所以看了runtime的代码后,会了解类、对象等等本质上是啥,了解OC里做的处理实际是干了什么,至少是一定程度的本质。

    类,实际是一个结构体,它里面有变量保存父类,有保存方法列表、变量列表等等;对象也是一个结构体,只不过有个变量指向它的类结构体。而调用方法,实际是调用消息发送。

    通过runtime里的方法,可以获取类的属性、方法,可以添加方法,甚至可以更改对象的类。一个对象的类是什么,就是它的isa变量存的class类型的变量的值,那把值改掉就好了。

    runtime的存在,是用来支撑OC的运行和它的特性的,而不是用来帮助我们写iOS程序的,至少这不是它该有的意义。比如修改一个对象的类,可以做到,这种事有必要吗?还有,可以修改一个method的函数实现(IMP),让你代码里写的是调用A方法,但实际执行的是B函数。这样不就违背了原本面向对象的编程了?类A的对象A1,最后运行起来类B的方法,这样不是瞎搞?

    但又觉得如果编程用不到,那么OC这么搞有什么意义?或许我该想想动态的目的,为什么要尽力在运行时做决定?这一切都是为了这个目的而设计的吧!

    //9.13更,runtime库的函数们

    画了张图

    以类为核心,类可以构建生成对象;而类本身具有方法、成员变量和属性;然后,我们可以通过类别和协议给类在cocoa层面添加属性和变量。由这些行为,把主要的类型关联起来,即类、方法、成员变量、属性、类别、协议。

    1.类和对象的关联

    类的结构原型:

    typedef struct objc_class *Class;
    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY; //指向元类metaClass
    
    #if !__OBJC2__
        Class super_class     //父类                                  
        const char *name      //名字                            
        long version                                          
        long info                                              
        long instance_size                                     
        struct objc_ivar_list *ivars      //变量列表                   
        struct objc_method_list **methodLists       //方法列表           
        struct objc_cache *cache       //方法调用缓存                          
        struct objc_protocol_list *protocols        //协议列表            
    #endif
    
    } OBJC2_UNAVAILABLE;
    

    对象原型:

    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    

    对象的isa 指向它自身的类,也就是由这个isa把类和对象关联起来。
    而类本身也有isa变量,这个是指向metaClass的,对于一个类,实例方法存放自身的方法列表里,类方法存放在metadClass的方法列表里;成员变量也是一样。

    类和它的父类是通过superclass关联起来,这样类-父类-对象整个的都关联起来了。

    2.方法调用机制:
    对于[A methodB:xxx],是怎么一个过程?
    方法调用实际是给对象发送消息,

    [A methodB:xxx]
    

    就是,会把对象作为第一个参数,方法名构建SEL作为第二个参数,如有更多参数放在后面。

    objc_msgSend(A, @selector(methodB:))
    

    A是对象,可以由A取到它的类,然后取到方法列表,匹配方法,找到了调用,找不到到父类中继续找;用代码过程类似这样:

     NSObject *A = [[NSObject alloc]init]; //调用方法的对象
        SEL selector ; //调用的方法名构建的SEL
        
        Class classA = object_getClass(A);//获取对象的类
        IMP findFunc = NULL;
        while (classA) {
            
            IMP func = class_getMethodImplementation(classA, selector);
            if (func) {
                findFunc = func;
                break;
            }
            
            /*
             //或者,获取method再获取IMP;method有实例方法和类方法之分,分成两个函数分别获取
             Method m = class_getInstanceMethod(classA, selector);
             if (m) {
             findFunc = method_getImplementation(m);
             break;
             }
             m = class_getClassMethod(classA, selector);
             if (m) {
             findFunc = method_getImplementation(m);
             break;
             }
             */
            classA = class_getSuperclass(classA); //获取父类
        }
    

    然后,为什么要获取IMP?
    首先IMP是method的一部分,

    struct objc_method {
        SEL method_name                                          OBJC2_UNAVAILABLE;
        char *method_types                                       OBJC2_UNAVAILABLE;
        IMP method_imp                                           OBJC2_UNAVAILABLE;
    }                                                            OBJC2_UNAVAILABLE;
    

    然后IMP本身:

    // A pointer to the function of a method implementation. 
    #if !OBJC_OLD_DISPATCH_PROTOTYPES
    typedef void (*IMP)(void /* id, SEL, ... */ ); 
    #else
    typedef id (*IMP)(id, SEL, ...); 
    #endif
    

    这个宏没研究,运行试了下,是下面一种定义。
    也就是IMP本质是一个函数指针,而且,第一个参数是调用这个方法的对象,第二个参数是这个方法的SEL(objc_method中的method_name,也是消息发送里的selector).
    使用IMP类似这样:

    typedef  id(*myFunc)(id object,SEL selector,...);
        myFunc func3 = (id (*)(id,SEL,...))findFunc;
        func3(A,selector);
    

    所以对于一个方法objc_method,本身包含3部分,一个方法名,一个参数类型列表,和一个实际的函数实现。

    所以如果把一个方法的IMP更改了,表面上,你还是在调用[A methodB:xxx],但实际执行的代码变成了另一个。在某些特殊情况下会用到吧,比如看不了别人源码时,把执行过程替换成自己的......

    3.获取、修改属性和变量
    对于有多个的成员,像属性、变量、协议,都会提供两个方法,一个是根据名字获取特定的,一个是返回所有的列表,如:

    objc_property_t pro = class_getProperty(classA, "xxx");
       unsigned int count; //count用来返回个数
       objc_property_t* proList = class_copyPropertyList(classA, &count);
    

    修改添加:

    objc_property_attribute_t attri;
            unsigned int count;
            class_addProperty(classA, "name", &attri, count);
            class_replaceProperty(classA, "name",&attri, count);
    

    objc_property_attribute_t是个结构体,包含name,value两个变量;
    找个类测试了一下:

    //类Book的一个属性:
    @property (nonatomic,copy) NSString* name;
    
    objc_property_t p = class_getProperty([Book class], "name");
        unsigned int count;
        objc_property_attribute_t *attri = property_copyAttributeList(p, &count);
        
        for (int i = 0; i<count; i++) {
            printf("%s = %s\n",attri[i].name,attri[i].value);
        }
    

    输出结果为:

    T = @"NSString"
    C = 
    N = 
    V = _name
    

    开头必须为T,值是属性的名字,"@"在Type Encodings里代表对象,如果是int那类型就是“i”; 结尾必须是V,值为属性的变量名(属性名为name,实际生成变量名是_name);其他的在文档的Declared Properties有详细说明。

    相关文章

      网友评论

      • f3969479e601:最近也在了解一些runtime,但是有些吃力吧,不知道他动态添加属性是为什么?还需要深层的了解
      • 公爵海恩庭斯:如果 C 不是动态的,void* 是干嘛的?
        FindCrt:@公爵海恩庭斯 个人理解,能力有限,凑合着看吧
      • 59442f354c98:所以你说的动态的目的只是支撑oc啊吗
        59442f354c98:@find_1991 恩 更改方法实现用到过 就不知道这种方式会不会引起什么问题
        FindCrt:@我的一天 动态是oc的性质,runtime支撑这个性质,最终这个性质存在还是为更好编程。个人理解
      • 546055050dc0:还是不太懂,一般面试时候会怎么提问?
        FindCrt:@EXALEX 我也不知道,我觉得,面试会问具体的问题。比如怎么动态替换类的方法、怎么动态添加属性。去研究下runtime头文件的代码吧

      本文标题:Objective-C的runtime system

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