Objective-C 中的对象 & isa &

作者: CoderHG | 来源:发表于2018-04-28 00:22 被阅读129次

    说到对象,什么是对象?

    由于文章的连贯性、强烈建议先看看之前的文章:Objective-C 中类的数据结构Objective-C 中实例所占内存的大小

    在面向对象编程中,有两个重要的概念:对象。在实际的内存中 又是以什么样的形式存在的呢?万物皆屌丝,不对、是万物皆对象。

    一、对象

    在 OC 中什么是对象?有很长一段时间坚信认为 +alloc 出来的才是对象。如果说平时这么说还行,一旦放到面试的时候这样来回答、那恐怕就不行,因为这个答案是不全的。主要分为以下三种:

    • 1、instance 对象,也称实例对象。
    • 2、class 对象,也称类对象。
    • 3、meta-class 对象,也称元类对象。

    没错,就是这三个对象。突然感觉哪里不对劲,不是说好的 block 也是一种特殊的对象么?对,也没有错,但是今天暂时不讨论这个 block 对象。
    接下来、将会讨论这些对象中都包含了什么样的信息,这些信息都是如何关联起来的,成员变量是存在哪里的,类方法与实例方法是存在哪里的,如何找到 superclass 的。。。。。关于这些问题,将会一一的梳理一遍。

    1.1 instance 对象

    1.1.1 什么是 instance 对象

    总而言之就是通过 +alloc 之后的的都是 instance 对象。但是这里还需要强调一点的是: instance 对象并不仅仅是 NSObject 的对象,还有一个代理类 NSProxy,也是能创建 instance 对象。

    关于 NSProxy,大家应该高度重视,当面试官问你在 OC 中有什么代理类的时候,别说不知道什么代理类,就知道代理协议(delegate)。如果你说不知道,那有一点可以很肯定,YYKit 那么优秀的框架都不去学习一下,对于大厂来说,恐怕会遭到鄙视的。具体的可以参考 YYKit 中的 YYWeakProxy。看完 YYWeakProxy 之后,你还会学到另一个技能:如何处理 OC 中定时器的循环引用。这两个问题在面试中,含金量都不低。在接下来的介绍中,不再提及 NSProxy 类。

    代码中是如何获取一个对象的:

    // 创建一个对象
    NSObject* obj = [NSObject alloc];
    

    1.1.2 instance 对象中的信息

    instance 对象中都包含什么呢?通过前面的两篇文章 Objective-C 中类的数据结构Objective-C 中实例所占内存的大小得知,仅有成员变量,没有其它的,其中他们的成员变量是有继承关系的。比如 Person 类继承于 NSObject,那么 Person 的 instance 对象中就会继承 NSObject 中的所有成员变量。

    1.2 class 对象

    1.2.1 获取 class 对象

    class 对象是怎么被创建的?毕竟在开发过程中也没有见过通过 +alloc 的方式创建之。但是知道怎么去获取一个 class 对象,见下面的代码:

    {
        // 创建对象
        ClsObject* cObj = [[ClsObject alloc] init];
        
        // 获取 class 的 所有方法
        [self fetchClassWiothCObj:cObj];
    }
    
    // 获取 class 的 所有方法
    - (void)fetchClassWiothCObj:(ClsObject*)cObj {
        Class obcCls1 = [cObj class];
        Class obcCls2 = [ClsObject class];
        Class obcCls3 = object_getClass(cObj);
        
        NSLog(@"%@, %@, %@", NSStringFromClass(obcCls1), NSStringFromClass(obcCls2), NSStringFromClass(obcCls3));
        // 打印结果: ClsObject, ClsObject, ClsObject
        
        NSLog(@"%p, %p, %p", obcCls1, obcCls2, obcCls3);
        // 打印结果: 0x10f52dd30, 0x10f52dd30, 0x10f52dd30
    }
    

    以上代码中的 ClsObject 是一个直接继承于 NSObject 的 Class。

    没错,不管是通过什么方法获取的 class 对象都是一样的,包括地址。说明在一个项目中一个 Class 仅有一个对象。但是上面的三种获取 class 对象的方式有什么不一样呢?
    第一种与第二种是通过方法获取的,直接获取的是当前 instance 的 Class,但是第三种方式不一样,这种方式是获取当前 instance 的 isa 的值。

    可以这样做一个实验,给上面的 cObj 做一个 KVO 监听,我们再看一下打印结果,会发现打印的结果变成了这样的:

    // 打印结果:ClsObject, ClsObject, NSKVONotifying_ClsObject
    // 打印结果:0x102e5ee10, 0x102e5ee10, 0x60000011a820
    

    是的,第三个值变了,变成了 NSKVONotifying_ClsObject。同时还发现,所有同一个 Class 的 instance 注册的 KVO 的 NSKVONotifying_ 的 class 对象的值也是一样的。

    1.2.2 class 对象中的信息

    • 1、isa
    • 2、superclass
    • 3、属性 property
    • 4、instance 方法
    • 5、协议 protocal
    • 6、成员变量,这里的成员变量信息并不是一个 instance 中成员变量的值,而是指在这个 Class 中有哪些成员变量,是 NSSting 的,还是 int 类型的。
    • 7、其它
      。。。。。。。

    1.3 meta-class 对象

    1.3.1 获取 meta-class 对象

    同理在开发中是不会手动去 +alloc 一个元类对象,可以通过 object_getClass 函数获取 class 对象的 isa 类获取之。代码如下:

    // 获取元类对象
    - (void)metaClass:(ClsObject*)cObj {
        // 获取一个对象的 isa
        Class obcISA = object_getClass(cObj);
        // 获取元类对象
        Class metaClass = object_getClass(obcISA);
        NSLog(@"%p, %@", metaClass, NSStringFromClass(metaClass));
    }
    

    会发现,元类还是当前的 Class,但是是另一个对象地址。
    其次,不管是 class 对象还是元类对象,其类型都是 Class,说明在内存结构上是一致的。但是其包含的信息含义是不一样,其用途也不一样。

    1.3.2 meta-class 对象中的信息

    • 1、isa
    • 2、superclass
    • 3、类方法信息
    • 4、其它

    1.4 对象总结

    • 1、总共有三种对象:instance 对象、class 对象与 meta-class 对象
    • 2、成员变量的值都存于 instance 对象中。
    • 3、属性、instance (实例)方法、协议 protocol、成员变量都存于 class 对象中。
    • 4、类方法都存于 meta-class 对象中。
    对象的信息分布

    二、关于 isa

    以上的三种对象是如何关联起来的呢?是通过 isa 关联的:

    instance 对象的 isa 的值是 class 对象,class 对象的 isa 的值是 meta-class对象。

    尽然实例方法是存在 class 对象中,那么当给一个 instance 对象发送消息的时候,是如何找到具体的方法实现的呢?

    当调用实例方法的时候, 通过 instance 对象中的 isa 找到 class,找到对应的实例方法的实现。

    同理,类方法的调用也是一样:

    当调用类方法的时候,通过 class 对象的 isa 指针找到 meta-class,并找到对应的方法实现。

    不管是调用 Class 方法还是对象方法都是消息发送,这里有一个面试题是这样问的:OC 中的消息发送的本质是什么?在之前我是这样的回答的:通过 SEL 去找对应的 IMP 实现,首先是从当前 Class(meta-class) 寻找,如果一旦找不到就会到父类寻找,当所有的都没有找到那么会启动消息转发机制,一旦找到了、那么会将当前的 SEL 与 IMP 缓存起来方便下一次查询。之前一直以为这样的回答够完美的了,但是现在看来需要再加一点专业术语会更加的完善。消息转发的本质是通过 isa 查找对应的 IMP 实现。然后加上之前的回答即可。
    为什么要强调这一点呢?难道在 OC 中还有不需要 isa 直接发送消息的??是的、有一个方法被调用就没有通过 isa 的查询,那就是 +load 方法。在很久之前也一直有一个疑问:为什么在分类中重写了 +load 方法之后,原生 Class 的+load 方法还能被调用。原来是因为 +load 方法的调用逻辑是在 dyld 加载阶段,一旦检测到当前的 Class 或者其分类重写了 +load 直接通过 IMP 地址进行调用。所以这种情况就不会出现原生 Class 的 IMP 后移从而导致没有机会被调用的情况。

    三、关于 superclass

    superclass 指针 是相对于 class 对象meta-class 对象 来说的。这个指针有什么作用呢?
    定义两个 Class:Person 继承于 NSObject,Student 继承于 Person。现在有一个场景,通过 Student 的 instance 对象调用 Person 中实现的实例方法,具体的调用过程如下:

    通过 Student 类的 instance 对象 的 isa 找到对应 Student 类的 class 对象,但是没有找到相关的实现,系统会继续到 superclass 中找,于是会到 Person 类的 class 对象 中找到具体的实现,并调用。

    类方法的调用,也是一样。

    四、 isa 与 superclass

    美图欣赏,以上所说的都是为了能看懂这张图片:


    class.png

    由图可知:

    1、isa

    • 1、instance 的 isa 指向 class
    • 2、class 的 isa 指向 meta-class
    • 3、meta-class 的 isa 指向基类的 meta-class

    2、superclass

    • 1、class 的 superclass 指向父类的 class,如果没有父类,superclass 为 nil
    • 2、neta-class 的 superclass 指向父类的 meta-class,基类的 meta-class 的 super 指向基类的 class

    3、 方法调用轨迹

    instance 对象: isa 找到class,方法如果不存在,就通过 superclass找父类。
    class 对象: isa 找到meta-class,方法如果不存在,就通过 superclass 找父类。

    谢谢!

    相关文章

      网友评论

        本文标题:Objective-C 中的对象 & isa &

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