美文网首页iOS开发之深入理解runtimeRuntime源码iOS
iOS开发之runtime(2):浅析NSObject对象的Cl

iOS开发之runtime(2):浅析NSObject对象的Cl

作者: kyson老师 | 来源:发表于2018-11-27 21:40 被阅读109次

    本系列博客是本人的源码阅读笔记,如果有iOS开发者在看runtime的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论

    runtime logo

    前言

    NSObject对象是iOS开发者都很熟悉的对象,它几乎是所有对象的根类。在任何.m文件中输入以下代码:

    NSObject 
    

    点击NSObject跳转到其定义文件,发现如下声明:

    @interface NSObject <NSObject> {
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wobjc-interface-ivars"
        Class isa  OBJC_ISA_AVAILABILITY;
    #pragma clang diagnostic pop
    }
    

    其中#pragma clang diagnostic push用于去除警告,因此,我们能发现NSObject对象只有一个Class类型的成员变量:isa
    那么:

    • 什么是isa
    • 什么是Class类型,与class 方法有何区别

    这篇文章将要给大家揭晓该问题。

    我们知道任何一个类都有 class方法比如:

    [NSObject class];
    

    当然还有superclass方法:

    [NSObject superclass];
    

    更多和clas相关的方法列举如下:

    - (BOOL)isKindOfClass:(Class)aClass;
    - (BOOL)isMemberOfClass:(Class)aClass;
    

    这两个方法相信大家都不陌生,只不过这两个方法位于@property NSObject中,但NSObject中还是有其实现的。所以我们有理由相信,NSObject中的成员变量isa是有特殊含义的,点击改成员变量的类型Class我们可以看到其定义:

    typedef struct objc_class *Class;
    

    继续点击objc_class

    struct objc_class : objc_object {
    //这里省略成员变量以及方法...
    }
    

    再次点击objc_object

    struct objc_object {
    private:
        isa_t isa;
    //这里省略成员变量以及方法...
    }
    

    层次有点深,但大家只关注其结构即可:

    Class本质是一个结构体。

    关于结构体,大家应该都有所了解,这里再做个复习吧:
    C语言和C++都支持结构体,只是C++的结构体基本上和类没有区别。以下是摘自知乎某答主:

    结构体和类的区别
    本质上来说结构体与类是同一个东西,可是默认情况下基于可读性的原因还是加一些区分:
    结构体就只含数据成员和构造函数、析构函数,尽可能保持简单。
    类则包含更多的非构造、析构成员函数,概念更大,用来描述普遍意义上的对象类型。

    我们有理由相信:NSObject对象的各个方法,基本上是针对其结构体isa对象的操作。这里我们研究几个我们常用的方法:


    本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime


    isMemberOfClass:

    - (BOOL)isMemberOfClass:(Class)cls {
        return [self class] == cls;
    }
    

    很简单,判断一下,当前的class方法是否等于参数。
    因为selfNSObject对象,因此我们查看class方法:

    - (Class)class {
        return object_getClass(self);
    }
    

    点击object_getClass查看其定义:

    Class object_getClass(id obj)
    {
        if (obj) return obj->getIsa();
        else return Nil;
    }
    

    继续进入方法getIsa:

    inline Class 
    objc_object::getIsa() 
    {
        if (!isTaggedPointer()) return ISA();
    
        uintptr_t ptr = (uintptr_t)this;
        if (isExtTaggedPointer()) {
            uintptr_t slot = 
                (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
            return objc_tag_ext_classes[slot];
        } else {
            uintptr_t slot = 
                (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
            return objc_tag_classes[slot];
        }
    }
    

    可以发现,越牵扯越深,阅读有点困难了。但大家别着急,我们可以屏蔽
    if (!isTaggedPointer()) return ISA();
    以下的代码,关于什么是TaggedPointer,笔者会在后面的文章中分析给大家。因此上面的代码可以先简化成:

    inline Class 
    objc_object::getIsa() 
    {
        if (!isTaggedPointer()) return ISA();
    }
    

    继续研究ISA()方法:

    inline Class 
    objc_object::ISA() 
    {
        assert(!isTaggedPointer()); 
    #if SUPPORT_INDEXED_ISA
        if (isa.nonpointer) {
            uintptr_t slot = isa.indexcls;
            return classForIndex((unsigned)slot);
        }
        return (Class)isa.bits;
    #else
        return (Class)(isa.bits & ISA_MASK);
    #endif
    }
    

    同样去掉暂时不需要我们理解的部分,简化代码如下:

    inline Class 
    objc_object::ISA() 
    {
        return (Class)(isa.bits & ISA_MASK);
    }
    

    至此,我们可以看到class方法最终获取的即是:结构体objc_objectisa.bits & ISA_MASK的结果。
    那,大家的疑问也会随之而来:

    • inline 关键字作用,为何这里的几个方法实现都在.h文件中
    • 在方法:objc_object::ISA() 中双冒号的作用。
    • objc_object 中的isa又是什么
    • isa.bits & ISA_MASK 的含义

    inline关键字

    用来定义一个类的内联函数,引入它的主要原因是用它替代C中表达式形式的宏定义。

    也就是说,用inline关键字修饰的是内联函数,内联函数用于替代宏定义。取代宏定义的原因是:

    1. C中使用define这种形式宏定义的原因是因为,C语言是一个效率很高的语言,这种宏定义在形式及使用上像一个函数,但它使用预处理器实现,没有了参数压栈,代码生成等一系列的操作,因此,效率很高,这是它在C中被使用的一个主要原因。
    2. 这种宏定义在形式上类似于一个函数,但在使用它时,仅仅只是做预处理器符号表中的简单替换,因此它不能进行参数有效性的检测,也就不能享受C++编译器严格类型检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。
    3. 在C++中引入了类及类的访问控制,这样,如果一个操作或者说一个表达式涉及到类的保护成员或私有成员,你就不可能使用这种宏定义来实现(因为无法将this指针放在合适的位置)。
    4. inline 推出的目的,也正是为了取代这种表达式形式的宏定义,它消除了宏定义的缺点,同时又很好地继承了宏定义的优点。

    双冒号

    用于表示“域操作符”,例:声明了一个类A,类A里声明了一个成员函数void f(),但没有在类的声明里给出f的定义,那么在类外定义f时,就要写成void A::f(),表示这个f()函数是类A的成员函数。

    objc_object 中的isa

    之前我们已经写了objc_object的定义,可以知道,isa其实是一个isa_t的对象,那isa_t是什么呢,我们继续看一下它的实现:

    union isa_t 
    {
    //这里省略很多变量
    }
    

    可以知道,isa_t是个联合体,也就是说:objc_object 中的isa其实是个结构体

    isa.bits & ISA_MASK 含义

    上面我们知道,isa是个联合体,其内部的属性bits呢?

    union isa_t 
    {
       //省略部分方法和属性...
        uintptr_t bits;
    

    然后看uintptr_t实现:

    typedef unsigned long       uintptr_t;
    

    发现其是个unsigned long类型,而ISA_MASK的定义如下:

    # if __arm64__
    #   define ISA_MASK        0x0000000ffffffff8ULL
    # elif __x86_64__
    #   define ISA_MASK        0x00007ffffffffff8ULL
    # else
    #   error unknown architecture for packed isa
    # endif
    

    可知,其实ISA_MASK还是个数值类型。也就是说判断两个对象是否是同一个class其实是通过比对objc_object中的数值计算后得出的结果是否相等得出的。

    讲完了 isMemberOfClass方法,isKindOfClass方法这里就不多做介绍了,给出其源代码即可:

    isKindOfClass:

    其实现如下:

    + (BOOL)isKindOfClass:(Class)cls {
        for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
            if (tcls == cls) return YES;
        }
        return NO;
    }
    

    总结

    • Class类型本质是个结构体,该结构体中存储了该NSObject中的所有信息。
    • 比对两个类是否是同一个类,其实是判断Class中的某个数值运算的结果是否相等。

    本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime

    相关文章

      网友评论

        本文标题:iOS开发之runtime(2):浅析NSObject对象的Cl

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