iOS RunTime之一:基础 - 数据结构

作者: SvenLearn | 来源:发表于2016-05-15 00:33 被阅读836次

    参考:
    Objective-C Runtime Programming Guide
    深入Objective-C的动态特性
    Objective-C Runtime 运行时之一六:类与对象拾遗
    刨根问底Objective-C Runtime 系列
    Objective-C Runtime 1小时入门教程
    Objective-C 与 Runtime
    Objective-C Runtime源码传送门


    一个剖析runtime的利器:

    $ clang -rewrite-objc 源文件名(比如test.m)
    

    引子

    Runtime到底是什么?

    Objective-C,顾名思义,和C++一样都是在C的基础上加入面向对象的特性扩充而成的程序设计语言,但二者实现的机制差异很大(C++是基于静态编译时类型,而Objective-C是基于动态运行时类型)。关键就在于OC的runtime, runtime是一个用C和汇编编写的动态库,它将OC和C紧密关联并提供动态特性(动态类型、动态绑定函数实现、动态加载资源),这个系统主要做两件事 :
    1、封装C语言的结构体和函数,让开发者在运行时创建、检查或者修改类、对象和方法等等。
      ◈ Class ( objc_class * ) => objc_objectid ( objc_object * )、objc_super
      ◈ Ivarobjc_property_t ( objc_property_attribute_t )
      ◈ objc_method_description ( 即SEL + types ) + IMP + => Method ( objc_method * )
      ◈ ……
      ◈ Objective-C Runtime Reference
    2、传递消息,找出方法的最终执行代码。
      ◈ 静态类型编程语言(比如C++)的函数调用和OC的方法调用(消息发送)的区别在于前者在编译期就确定了(函数的地址),而后者是运行时动态确定(代价是性能下降,objc_class中的objc_cache就是用来补偿这种性能下降的);
      ◈ 类层次体系查找 ( isa+objc_method_list ) + 消息转发 ( 动态解析 => 备用接收者 => ( 签名+打包+完整转发 )

    动态加载NSBundle类提供了许多面向对象的便捷接口用于动态加载;比如Retina设备自动加载@2x的图片。

    应用场景举例

    1、遍历对象的成员变量/属性
    比如:自动实现<NSCoping>、<NSMutableCopying>、<NSCoding>协议(参考 Mantle 的源码实现)

    2、动态添加/修改属性或成员变量,动态添加/修改/替换方法

    3、动态创建类/对象/协议等等
    比如:Model与Json Dictionary的相互转换(还涉及对象成员变量/属性的遍历),参考 MJExtension 的实现

    4、方法拦截调用
    比如:拦截imageNamed:、viewDidLoad、viewWillAppear:(统计页面展现次数等)、alloc。

    5、为分类添加的属性提供动态的存取方法实现


    传送门:RunTime源码

    1. 基础数据类型

    #类、对象、id以及协议

    关注Class定义和isa、super_class、cache和version字段

    // 类
    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;
    
    // 对象
    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    
    // id
    typedef struct objc_object *id;
    
    // 协议
    typedef struct objc_object Protocol;
    
    #分类
    typedef struct objc_category *Category;
    
    #元类(Meta Class)

    类自身也是一个对象(元类的对象),调用类方法其实就是向类对象发送消息;区别:
     ◈ 向一个对象发送消息时,runtime会在对象所属类的方法列表中查找方法;
     ◈ 向一个类发送消息时,会在这个类的meta-class的方法列表中查找方法。

    #成员变量、属性
    • 成员变量与属性的联系 - 属性声明的本质是根据指定规则生成对应的成员变量及其存取方法
    • 成员变量名:默认是_xxx;当然你也可以用语法糖@synthesize自行指定
    • 存取方法:包括原子性(锁机制)、读写权限、内存管理策略、存取方法名设定
    • 编码
    • 纯类型编码 - c、i、s、l、q、C、I、S、L、Q或f、d、B、V、*、@、#、:、[]、{}、()、b、^、?
    ^{example=@*i}  //example类型表示包含一个id对象、一个char *、一个int的结构体的指针
    ^^{example} //一个example指针的指针
    
    • 方法类型编码
    对 - (BOOL)danceWith:(NSString *)people at:(char)place on:(int)count;
    调用const char * method_getTypeEncoding ( Method m );
    => 返回值(BOOL) + 隐藏参数self(id) + 隐藏参数_cmd(SEL) + 第一个参数(NSString *) + 第二个参数(char) + 第三个参数(int)
    => ("B"+32) + ("@"+0) + (":"+8) + ("@"+16) + ("c"+24) + ("i"+28)
    => "B32@0:8@16c24i28"
    
    • 属性类型编码 - T<纯类型编码>、V<s>、R、C、&、N、G<s>、S<s>、D、W、P、t<s>
    对 @property (nonatomic, readonly, retain) NSString* vName;
    调用const char * property_getAttributes ( objc_property_t property );
    => 属性编码 + nonatomic + readonly + retain + 属性对应的成员变量
    => ("T" + "@\"NSString\"") + ("N" + "") + ("R" + "") + ("&" + "") + ("V" + "_vName")
    => "T@\"NSString\",R,&,N,V_vName"
    
    • 基础数据类型
    // 成员变量 - objc_ivar的结构在iOS 9.0已经被隐藏,其封装了成员变量的名字和类型。
    typedef struct objc_ivar *Ivar;
    
    // 属性
    typedef struct objc_property *objc_property_t;
     
    // 属性的特性 - 注意:特性V对应的是成员变量名(可被@synthesize指定或自动生成加前缀‘_’),不是属性名
    typedef struct {
      const char *name;           // 特性名 - 必选:T、V 可选:R、C、&、N、G、S、D、W、P、t
      const char *value;          // 特性值 - 只有name为TVGSt时value不为nil,且T用纯类型编码表示
    } objc_property_attribute_t;
    
    #方法
    • SEL - 本质上是一个根据方法名hash化了的KEY值,它的存在只是为了加快方法的查询速度。
    • IMP - IMP实际上是一个函数指针,指向方法实现的首地址。
    // 方法选择器
    typedef struct objc_selector *SEL;
    
    // 方法实现 - 第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针)
    id (*IMP)(id, SEL, ...)
    
    // 方法 - 将SEL和对应的实现IMP关联起来
    struct objc_method {
        SEL method_name                     OBJC2_UNAVAILABLE;  // 方法名
        char *method_types                  OBJC2_UNAVAILABLE;  // 返回值和参数类型编码; 可用+signatureWithObjCTypes:封装成方法签名
        IMP method_imp                      OBJC2_UNAVAILABLE;  // 方法实现
    }  
    
    typedef struct objc_method *Method;
    
    #图形化
    OC对象的内存布局.png OC的类继承体系结构

    附:健壮的实例变量
    由于超类实例变量的变更(增删改)导致OC对象的内存布局变化,将由系统通过Ivar对象(封装了基地址和偏移量)管理,我们需要做的仅仅是通过名字直接获取或通过Ivar间接获取实例变量的值,复杂的事就交给系统吧。


    自动偏移

    2. 操作函数

    注:增改类的方法放在系列二中。

    #类
    // 获取所有已注册类 - 前6个函数的搜索范围都是已注册到运行时系统的类(objc_registerClassPair)。
    int objc_getClassList ( Class *buffer, int bufferCount );
    Class * objc_copyClassList ( unsigned int *outCount );
    
    ------------------------------------------------  // 功能类似,区别在于未找到Class的情况:
    Class objc_getClass ( const char *name )       // 会调class handler callback???,并再次检查Class是否注册
    Class objc_lookUpClass ( const char *name )    // 直接返回nil
    Class objc_getRequiredClass ( const char *name ) // 会kill进程,程序crash
    
    Class objc_getMetaClass ( const char *name ) 
    Class class_getSuperclass ( Class cls );      // cls不需注册
    
    -------------------------------------------
    const char * class_getName ( Class cls );     // cls不需注册
    size_t class_getInstanceSize ( Class cls );    // cls不需注册
    
    BOOL class_isMetaClass( Class cls );        // 不注册就没有元类
    
    #分类

    Runtime并没有在 <objc/runtime.h> 头文件中提供针对分类的操作函数。因为分类中的信息最终会被合并到对应类中(也即对应的objc_class中),所以我们可以通过针对objc_class的操作函数来获取分类的信息。

    #协议

    注意:从未被任何类实现的协议不会被自动注册到运行时系统,当然你也可以objc_registerProtocol手动注册

    // 获取已注册的协议
    Protocol * objc_getProtocol ( const char *name );  
    Protocol ** objc_copyProtocolList ( unsigned int *outCount ); 
    
    -------------------------------------------
    /* 属性 */
    objc_property_t protocol_getProperty ( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty );
    objc_property_t * protocol_copyPropertyList ( Protocol *proto, unsigned int *outCount );
    
    // 方法 - 方法描述包含了方法名(比如"init")、返回值和参数的类型编码信息(比如"@16@0:8")
    struct objc_method_description protocol_getMethodDescription ( Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod );
    struct objc_method_description * protocol_copyMethodDescriptionList ( Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount );
    
    /* 与其他协议的关系 */
    Protocol ** protocol_copyProtocolList ( Protocol *proto, unsigned int *outCount );
    BOOL protocol_conformsToProtocol ( Protocol *proto, Protocol *other );
    
    -------------------------------------------
    const char * protocol_getName ( Protocol *p );
    BOOL protocol_isEqual ( Protocol *proto, Protocol *other );
    
    -------------------------------------------
    BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
    Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
    
    #对象
    /* 根据成员变量的类型(OC对象/其他)用不同的方法获取其信息*/
    id object_getIvar ( id obj, Ivar ivar );
    Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
    void * object_getIndexedIvars ( id obj ); // 给定实例obj的extra bytes的指针,详见上文图形化
    
    /* 针对对象的类进行操作 */
    const char * object_getClassName ( id obj );
    Class object_getClass ( id obj );
    
    #版本
    int class_getVersion ( Class cls );
    void class_setVersion ( Class cls, int version );
    
    #成员变量
    // 获取信息
    const char * ivar_getName ( Ivar v );
    const char * ivar_getTypeEncoding ( Ivar v ); // 比如"@\"NSString\""
    ptrdiff_t ivar_getOffset ( Ivar v );
    
    // 获取
    Ivar class_getInstanceVariable ( Class cls, const char *name );   // 实例成员变量
    Ivar class_getClassVariable ( Class cls, const char *name );     // 类变量,如何用OC代码声明一个类变量???
    Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );  // 实例/类的成员变量列表
    
    #属性 - 属性可以replace,纯成员变量不行。
    const char * property_getName ( objc_property_t property ); // 返回属性名(区别于成员变量名)
    const char * property_getAttributes ( objc_property_t property ); 
    char * property_copyAttributeValue ( objc_property_t property, const char *attributeName ); 
    objc_property_attribute_t * property_copyAttributeList ( objc_property_t property, unsigned int *outCount ); 
    
    // 获取
    objc_property_t class_getProperty ( Class cls, const char *name );
    objc_property_t protocol_getProperty( Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty )
    => objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
    => objc_property_t * protocol_copyPropertyList( Protocol *proto, unsigned int *outCount );
    
    // 属性列表
    objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
    

    #SEL
    SEL sel_registerName ( const char *str ); // 也可使用更方便的语法糖 @selector(…)
    SEL sel_getUid ( const char *str );     // 实现等价于sel_registerName
    
    const char * sel_getName ( SEL sel );
    
    BOOL sel_isEqual ( SEL lhs, SEL rhs );   // 只要方法名相同就返回YES
    
    #IMP
    // 获取实现 - 注意:涵盖了消息转发机制!!!
    IMP method_getImplementation ( Method m );
    IMP class_getMethodImplementation ( Class cls, SEL name );
    IMP class_getMethodImplementation_stret ( Class cls, SEL name );
    
    // 设置、交换实现
    => IMP method_setImplementation ( Method m, IMP imp );
    => void method_exchangeImplementations ( Method m1, Method m2 );
    
    // 使用方便的块语法
    => IMP imp_implementationWithBlock(id block)
    => id imp_getBlock( IMP anImp) 
    => BOOL imp_removeBlock( IMP anImp) 
    
    #Method

    注:要获取类方法列表,用class_copyMethodList(object_getClass(cls), &count)实现。

    // 查
    Method class_getInstanceMethod ( Class cls, SEL name );         // 搜索父类
    Method class_getClassMethod ( Class cls, SEL name );           // 搜索父类
    Method * class_copyMethodList ( Class cls, unsigned int *outCount ); // 不搜索父类, 只返回实例方法
    
    SEL method_getName ( Method m );
    unsigned int method_getNumberOfArguments ( Method m );
    struct objc_method_description * method_getDescription ( Method m );
    
    // 获取参数或/和返回值的类型编码,
    const char * method_getTypeEncoding ( Method m );           // 比如"B32@0:8@16c24i28"
    char * method_copyArgumentType ( Method m, unsigned int index );  // 0->"@", 1->":", 2->"c", 3->"i"
    void method_getArgumentType ( Method m, unsigned int index, char *dst, size_t dst_len );
    char * method_copyReturnType ( Method m );               // "B"
    void method_getReturnType ( Method m, char *dst, size_t dst_len );
    
    --------------------------------------------------------------------------------
    // 方法调用
    => id method_invoke ( id receiver, Method m, ... );
    => void method_invoke_stret ( id receiver, Method m, ... ); // 调用返回一个数据结构的方法的实现
    
    #关联对象

    => 解决了category不能添加成员变量()的问题 => 引申:为指定对象动态添加功能的解决方案

    注意
    1、在category中可以声明属性,但是不会自动生成成员变量和对应的存取方法;
    2、关联对象并不会自动生成属性或成员变量,通常做法是声明一个属性,并利用关联对象动态实现其存取方法,使其看起来就像一个正常的属性一样。

    // 5种内存管理策略: OBJC_ASSOCIATION_[ASSIGN/RETAIN/RETAIN_NONATOMIC/COPY/COPY_NONATOMIC]
    // 也可以通过设置value为nil来移除关联
    => void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) 
    
    => id objc_getAssociatedObject(id object, void *key) 
    
    => void objc_removeAssociatedObjects ( id object );
    

    相关文章

      网友评论

        本文标题:iOS RunTime之一:基础 - 数据结构

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