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

logo

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

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

前言

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

NSObject 

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

@interface 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

广告

我的首款个人开发的APP壁纸宝贝上线了,欢迎大家下载。

壁纸宝贝

你可能感兴趣的:(iOS开发之runtime(2):浅析NSObject对象的Class)