美文网首页iOS底层基础知识Runtime
iOS之Runtime(一)彻底搞清类和对象

iOS之Runtime(一)彻底搞清类和对象

作者: 我阿郑 | 来源:发表于2018-12-05 22:43 被阅读66次

    Objective-C是基于动态运行时类型。C++程序编译时就直接编译成了可令机器读懂的机器语言;用Objective-C编写的程序不能直接编译成机器语言,而是在程序运行时期通过Runtime把程序转为机器语言。Runtime是OC不可缺少的重要一部分。

    OC就基于运行时来工作。运行时环境可处理弱类型、函数存在检查工作,会检测注册列表里是否存在对应的函数,类型是否正确,最后确定正确的函数地址,在进行保存寄存器状态,压栈、函数调用等实际操作,确保了OC的灵活性。

    理解 实例对象(instance)-》类对象(class object)-》元类对象(meta class) 的概念

    我们创建的一个实例对象 底层编译出来其实就是一个结构体(struct objc_object), objc_object这个结构体里有一个isa指针指向了它的类对象 结构体 objc_class,类对象里也有一个isa指针指向Meta Class 元类对象,其实元类对象也是一个objc_class的结构体。因此,元类对象也有一个isa指针,那么它的isa又是指向什么呢?

    为了不让这种结构无限延伸下去,OC的设计者让所有的元类对象isa指针都统一指向基类的元类对象(如果是从NSObject中继承而来的那么基类的元类对象就是指的NSObject的元类对象

    可直接理解为继承自NSObject的类,它们的类对象isa指针都统一指向NSObject的元类对象,而NSObject的元类对象的isa指针是指向它自己

    1 先看看对应的结构体代码

    // 实例对象的结构体
    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    
    // 类对象的结构体
    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    
        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                     
    }
    
    // id 类型
    typedef struct objc_object *id;
    // Class 类型
    typedef struct objc_class *Class;
    
    

    在上述objc_class结构体定义中,主要关注以下几个字段:

    • isa:在OC中对象的isa指针指向类,类的isa指针指向元类。
    • super_class: isa用于自省确定所属类,super_class确定继承关系 。所以super_class指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class指向nil
    • cache: 如果每次消息来时,都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。
    • version: 使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可以让我们识别出不同类定义版本中实例变量布局的改变。

    2 通过下面的图来再次说一下实例对象、类对象、元类对象的关系

    ![img2.png](https://img.haomeiwen.com/i2470124/b316d5979d7e97bd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

    说明: 虚线箭头代表isa指针(表示指向);实线箭头代表super_class指针(表示super_class指针的指向)

    • instance实例对象isa指针指向class类对象,类对象的isa指针都指向了meta元类对象,所有的元类对象(不论是Subclass的元类对象、还是Superclass的元类对象)都统一指向了Root基类的元类对象,而Root基类的元类对象,它isa指针指向了它自己。

    • 类对象super_class指针指向关系:Subclass类对象-》Superclass类对象-》Rootclass类对象-》nil (即 NSObject的类对象的super_class指针指向了nil)

    • 元类对象super_class指针指向关系:Subclass元类对象-》Superclass元类对象-》Rootclass类对象-》Rootclass类对象 (即 NSObject的元类对象的super_class指针指向了NSObject的类对象)

    如果上的解释没明白,看下面的解释

    img2.png

    D3继承D2,D2继承D1,D1最终继承NSObject。

    • D3实例对象有一个isa指针指向D3的类对象

    • D2实例对象有一个isa指针指向D2的类对象

    • D1实例对象有一个isa指针指向D1的类对象


    • D3的类对象有一个super_class指针指向D2的类对象

    • D2的类对象有一个super_class指针指向D1的类对象

    • D1的类对象有一个super_class指针指向NSObject的类对象

    • NSObject的类对象有一个super_class指针指向nil


    • D3的类对象有一个isa指针指向D3的元类对象

    • D2的类对象有一个isa指针指向D2的元类对象

    • D1的类对象有一个isa指针指向D1的元类对象

    • NSObject的类对象有一个isa指针指向NSObject的元类对象


    • D3、D2、D1元类对象isa指针都统一指向NSObject的元类对象

    • NSObject元类对象isa指针指向了自身

    • NSObject元类对象super_class指针指向了NSObject的类对象


    那么如果我们想获取isa指针的指向对象呢?

    • object_getClass: 获取isa指针指向的对象
    • class_isMetaClass:用于判断Class对象是否为元类
    
        Person *p = [[Person alloc] init];
        
        Class p_isa = object_getClass(p); // 获取p对象isa指针指向的类对象
        Class pclass_isa = object_getClass(p_isa); // 获取类对象isa指针指向的元类对象
        Class pmetaclass_isa = object_getClass(pclass_isa);// 获取元类对象isa指针指向的NSObject的元类对象
    
        // class_isMetaClass 判断是否是元类对象
        BOOL b1 = class_isMetaClass(p_isa);
        BOOL b2 = class_isMetaClass(pclass_isa);
        BOOL b3 = class_isMetaClass(pmetaclass_isa);
        
        NSLog(@"---%@---%zd",p_isa,b1);
        NSLog(@"---%@---%zd",pclass_isa,b2);
        NSLog(@"---%@---%zd",pmetaclass_isa,b3);
        
        // 打印结果
        ---Person---0
        ---Person---1
        ---NSObject---1
    
    

    通过代码可以看出

    • 一个实例对象通过object_getClass方法获取的Class就是实例对象的isa指针指向的类对象

    • 类对象通过object_getClass方法获取的Class就是类对象的isa指针指向的元类对象

    • 实例对象的Class 是 类对象

    • 类对象的Class 是 元类对象

    获取 superclass

    
    @interface Person : TestObj
    
    @end
    
    #############
    
        Person *p = [[Person alloc] init];
        
        Class p_isa = object_getClass(p); // 获取p对象isa指针指向的类对象
        Class pclass_isa = object_getClass(p_isa); // 获取类对象isa指针指向的元类对象
        Class pmetaclass_isa = object_getClass(pclass_isa);// 获取元类对象isa指针指向的NSObject的元类对象
        
        Class p_superclass = class_getSuperclass(p_isa); // 获取p的类对象的superclass
        Class p_superclass_s = class_getSuperclass(p_superclass); // 获取p的类对象的superclass 的superclass
        Class p_superclass_s_s = class_getSuperclass(p_superclass_s); // 获取p的类对象的superclass 的superclass 的superclass
        
        NSLog(@"---%@---%@",p_isa,p_superclass);
        NSLog(@"---%@---%@",pclass_isa,p_superclass_s);
        NSLog(@"---%@---%@",pmetaclass_isa,p_superclass_s_s);
        
        //打印结果
         ---Person---TestObj
         ---Person---NSObject
         --NSObject---(null)
    
    

    神经病院objc runtime入院考试

    (1) 下面的代码输出什么?

    @implementation Son : Father
    - (id)init {
        self = [super init];
        if (self) {
            NSLog(@"%@", NSStringFromClass([self class]));
            NSLog(@"%@", NSStringFromClass([super class]));
        }
        return self;
    }
    @end
    
    

    (2) 下面代码的结果?

    BOOL res1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
    BOOL res2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
    BOOL res3 = [(id)[Sark class] isKindOfClass:[Sark class]];
    BOOL res4 = [(id)[Sark class] isMemberOfClass:[Sark class]];
    
    // 上面的题换个写法
    
        Class c1 = [NSObject class];
        Class s1 = [Sark class];
    
        BOOL res1 = [c1 isKindOfClass:[NSObject class]];
        BOOL res2 = [c1 isMemberOfClass:[NSObject class]];
        BOOL res3 = [s1 isKindOfClass:[Sark class]];
        BOOL res4 = [s1 isMemberOfClass:[Sark class]];
        
        
        // 因为 NSObject meta class –super -> NSObject class 
        NSObject的元类对象的super_class指向了NSObject class
        相当于c1 是 NSObject class 子类的实例 所以第一个为真
        所以第二个c1是不是NSObject class 当前类的实例就为假了
        同样Sark的元类对象的super_class 最终指向了 NSObject class 所以s1 并不是 Sark 或者其子类的实例 ,所以 第三和第四都为假
    
    
    

    (3) 下面的代码会?Compile Error / Runtime Crash / NSLog…?

    @interface NSObject (Sark)
    + (void)foo;
    @end
    @implementation NSObject (Sark)
    - (void)foo {
        NSLog(@"IMP: -[NSObject (Sark) foo]");
    }
    @end
    

    // 测试代码

    [NSObject foo];
    [[NSObject new] foo];
    

    (4) 下面的代码会?Compile Error / Runtime Crash / NSLog…?

    @interface Sark : NSObject
    @property (nonatomic, copy) NSString *name;
    @end
    @implementation Sark
    - (void)speak {
        NSLog(@"my name's %@", self.name);
    }
    @end
    
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        id cls = [Sark class];
        void *obj = &cls;
        [(__bridge id)obj speak];
    }
    @end
    

    答案

    -(BOOL) isKindOfClass: classObj判断是否是这个类或者这个类的子类的实例
    
    -(BOOL) isMemberOfClass: classObj 判断是否是这个类的实例
    
    • (1) Son / Son 因为super为编译器标示符,向super发送的消息被编译成objc_msgSendSuper,但仍以self作为reveiver

    • (2) YES / NO / NO / NO <NSObject>协议有一套类方法的隐藏实现,所以编译运行正常;由于NSObject meta class的父类为NSObject class,所以只有第一句为YES

    • (3) 编译运行正常,两行代码都执行-foo。 [NSObject foo]方法查找路线为 NSObject meta class –super-> NSObject class,和第二题知识点很相似。

    • (4)编译运行正常,输出ViewController中的self对象。 编译运行正常,调用了-speak方法,由于

    id cls = [Sark class];
    void *obj = &cls;
    obj已经满足了构成一个objc对象的全部要求(首地址指向ClassObject),遂能够正常走消息机制;
    
    

    由于这个人造的对象在栈上,而取self.name的操作本质上是self指针在内存向高位地址偏移(32位下一个指针是4字节),按viewDidLoad执行时各个变量入栈顺序从高到底为(self, _cmd, self.class, self, obj)(前两个是方法隐含入参,随后两个为super调用的两个压栈参数),遂栈低地址的obj+4取到了self。

    类与对象相关的操作函数

    runtime提供了大量的函数来操作类与对象。

    • 类的操作方法大部分是以class为前缀
    • 对象的操作方法大部分是以objc或object_为前缀

    1 类相关操作函数

    • 类名(name)

        // 获取类的类名
        const char * class_getName ( Class cls );
        
        如果传入的cls为Nil,则返回一个空字符串。
      
    • 父类(super_class) 和是否为元类(meta-class)

       // 获取类的父类
       Class class_getSuperclass ( Class cls );
       
       当cls为Nil或者cls为根类时,返回Nil。
       不过通常我们可以使用NSObject类的superclass方法来达到同样的目的
      
    • 是否为元类(meta-class)

       // 判断给定的Class是否是一个元类
       BOOL class_isMetaClass ( Class cls );
       
       如果是cls是元类,则返回YES;
       如果否或者传入的cls为Nil,则返回NO。
      
    • 实例变量大小(instance_size)

        // 获取实例大小
        size_t class_getInstanceSize ( Class cls );
      
    • 成员变量(ivars)及属性

        // 获取类中指定名称实例成员变量的信息
        Ivar class_getInstanceVariable ( Class cls, const char *name );
      
        // 获取类成员变量的信息
        Ivar class_getClassVariable ( Class cls, const char *name );
      
        // 添加成员变量
        BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
      
        // 获取整个成员变量列表
        Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
        
        // 获取指定的属性
        objc_property_t class_getProperty ( Class cls, const char *name );
      
        // 获取属性列表
        objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
      
        // 为类添加属性
        BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
      
        // 替换类的属性
        void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
      
    • 方法(methodLists)

        // 添加方法
        BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
      
        // 获取实例方法
        Method class_getInstanceMethod ( Class cls, SEL name );
      
        // 获取类方法
        Method class_getClassMethod ( Class cls, SEL name );
      
        // 获取所有方法的数组
        Method * class_copyMethodList ( Class cls, unsigned int *outCount );
      
        // 替代方法的实现
        IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
      
        // 返回方法的具体实现
        IMP class_getMethodImplementation ( Class cls, SEL name );
        IMP class_getMethodImplementation_stret ( Class cls, SEL name );
      
        // 类实例是否响应指定的selector
        BOOL class_respondsToSelector ( Class cls, SEL sel );
      
    • 协议(objc_protocol_list)

        // 添加协议
        BOOL class_addProtocol ( Class cls, Protocol *protocol );
      
        // 返回类是否实现指定的协议
        BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
      
        // 返回类实现的协议列表
        Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
      

    2 实例相关的操作函数

    • 对实例对象进行操作的函数

        // 返回指定对象的一份拷贝
        id object_copy ( id obj, size_t size );
      
        // 释放指定对象占用的内存
        id object_dispose ( id obj );
        
        // 举例子
        NSObject *a = [[NSObject alloc] init];
        id newB = object_copy(a, class_getInstanceSize(MyClass.class));
        object_setClass(newB, MyClass.class);
        object_dispose(a);
      
    • 操作对象的实例变量

        // 修改类实例的实例变量的值
        Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
      
        // 获取对象实例变量的值
        Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
      
        // 返回指向给定对象分配的任何额外字节的指针
        void * object_getIndexedIvars ( id obj );
      
        // 返回对象中实例变量的值
        id object_getIvar ( id obj, Ivar ivar );
      
        // 设置对象中实例变量的值
        void object_setIvar ( id obj, Ivar ivar, id value );
      
    • 操作对象的类

        // 返回给定对象的类名
        const char * object_getClassName ( id obj );
      
        // 返回对象的类
        Class object_getClass ( id obj );
      
        // 设置对象的类
        Class object_setClass ( id obj, Class cls );
      

    三、什么是‘关联对象’?

    所谓“关联对象” 说白了也就是一个对象。 就是把一个类的实例(也叫对象)通过一个**唯一的key**动态绑定到另外一个对象上的过程叫做关联对象。

    举例说明:

    我们知道一个类的实例(对象)可以作为另外一个类的属性出现。比如有一个Dog类Person类,而Person下有一个属性是dog。

    @interface Person ()
    
    @property (nonatomic, strong) Dog *dog;
    
    @end
    

    通俗的理解就是: 可以给一个对象关联许多其它对象,关联的这些对象通过“key”来区分。

    怎么进行关联那?

    答: 需要用到 Runtime(运行时)里提供的两个方法。

    • objc_setAssociatedObject
    • objc_getAssociatedObject
    /* Returns the value associated with a given object for a given key.
    此方法根据给定的键(key)从某对象(object)中获取相应的关联对象值 */
    
    OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key);
    
     /* Sets an associated value for a given object using a given key and association policy.
     此方法用给定的键(key)和策略(policy)给某对象(object)设置关联对象值(value) */
     
    OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    
    

    参数说明:

    参数 说明
    id object 哪个对象设置关联对象 (比如这里的 Person类的实例对象)
    const void *key 指定关联对象唯一的key(之后通过该key来 获取)
    id value 指定关联的对象 (比如dog)
    objc_AssociationPolicy 指定内存管理策略(枚举)

    以下几种内存管理策略:

    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
        OBJC_ASSOCIATION_ASSIGN = 0,
        OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
        OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
        OBJC_ASSOCIATION_RETAIN = 01401,
        OBJC_ASSOCIATION_COPY = 01403
    };
    

    说明:

    如果想要解除关联对象,我们不需要去主动调用runtime提供的removeAssociated函数,可以使用setAssociatedObject置为nil来解除关联对象。

    参考文档:

    Runtime官方翻译

    Objective-C Runtime 1小时入门教程

    神经病院objc runtime入院考试

    相关文章

      网友评论

        本文标题:iOS之Runtime(一)彻底搞清类和对象

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