iOS底层之类的对象本质是什么和对象与类的关系(3)

908 阅读4分钟

前言

上一篇我们基本了解isa的存放着什么,和calloc是怎么分配内存的。最后我们遗留了几个问题,将会在本文逐一分析,这一章的内容,还是比较简单,来吧,一起看看吧。

对象与类的联系

对象isa

对象可以有多个,我们的类就只有1个。那对象和类是怎么联系的呢?

那我们就先创建一个对象,并一起看下对象的类。

Person *object = [Person alloc];

object_getClass(object);

我们点击进去object_getClass看下是什么?

Class object_getClass(id obj)
{

    if (obj) return obj->getIsa();

    else return Nil;

}

我们可以看到重点obj->getIsa();

看这意思基本是和isa是有关系的,那我们再进去看看

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];

    }

}

这里明显是return 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

}

有收获了,这里返回的是isa.bits & ISA_MASK

我们看下ISA_MASK是什么

#   define ISA_MASK        0x00007ffffffffff8ULL

这个是__x86_64_下的宏,由源码我们能得出一个结论:对象的isa指针 & ISA_MASK就是该对象的类,也可以叫做类对象。

isa & ISA_MASK

既然我们得出对象和类的联系了,那我们就试一下看下是不是真的是这样。

先创建一个对象

Person *p = [Person alloc];
NSLog(@"%p",p);

NSLog那里打一个断点。

然后我们使用LLDB指令输出一下。

(lldb) x/4xg p

0x100595940: 0x011d8001000080e9 0x0000000000000000

0x100595950: 0x00007fff7c5d3d2c 0x00000000a893fef3

(lldb) p/x Person.class

(Class) $1 = 0x00000001000080e8 Person

(lldb) p/x 0x011d8001000080e9 & 0x00007ffffffffff8ULL

(unsigned long long) $2 = 0x00000001000080e8

(lldb)

我们先打印下p对象,可以知道前面8个字节0x011d8001000080e9就是isa指针。然后& 0x00007ffffffffff8ULL,可以发现最后结果和Person.class是一样的。

由这个,我们可以试下创建多个Person对象,然后看下该对象&isa,是不是同样的Person地址,由这个也可以得出结论:对象是有多个,类只有1个。也就是说类的内存地址只有1份。

isa的走位分析

我们分析了对象的isa是指向类,但是类里面也有isa,那有指向什么呢?

我们直接上图吧。

image.png

我们先看虚线的,由最下面由左往右开始看起。

Subclass对象的isa指向Subclass类。

Subclass类的isa指向Subclass的元类。

Subclass元类的isa指向NSObject的元类。

NSObject的元类的isa指向还是它自己。

简单来说就是对象 -> 类 -> 元类 -> 根元类 -> 根元类

那我们顺便再看下实线,这是个关于类继承关系的,我们就看下灰色区域,由最下往上看。

Subclass类的父类是SuperClass类。

SuperClass类的父类是RootClass,也就是NSObject类。

NSObject类的父类为 nil

Subclass元类的父类是SuperClass元类。

SuperClass类的父类是RootClass元类,也就是NSObject元类。

NSObject元类的父类是 NSObject类。

有了这些东西,后面关于方法调用流程就会很清晰了,后续会开篇章讲到。

对象的本质

对象的本质是什么?

直接上源码吧。对象的本质就是结构体

struct objc_class : objc_object {

    // Class ISA;

    Class superclass;

    cache_t cache;             // formerly cache pointer and vtable

    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 

       return bits.data();

    }

    void setData(class_rw_t *newData) {

         bits.setData(newData);

    }
}    

我们可以看到,前面8个字节是isa指针,再8位就是存放superClass父类。这个我就不去验证了。

这里有个印象就行,后面也会有文章说到里面的内容。

clang分析

现在要通过clang来分析对象的属性。

我们先在main文件里面添加我们的类。

@interface Person : NSObject

@property (nonatomic, copy) NSString *name; 

@end

@implementation Person

@end

然后我们先把我们的main.m文件转为c++文件。

clang -rewrite-objc main.m -o main.cpp

我们就可以看到同目录输出了一个main.cpp文件。打开main.cpp文件。直接搜索int main

我们就可以看到上面关于Person的信息。

struct Person_IMPL {

struct NSObject_IMPL NSObject_IVARS;

NSString _name;

};

我们可以看到Person有_name的成员变量。

static NSString * _I_Person_name(Person * self, SEL _cmd) { return** (*(NSString )((char *)self + OBJC_IVAR_$_Person$_name)); }

static void _I_Person_setName_(Person * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct Person, _name), (id)name, 0, 1); }

这不就是name的set方法个get方法。

我们直接搜一下_I_Person_name

{{(struct objc_selector *)"name", "@16@0:8", (void *)_I_Person_name},

{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_Person_setName_}}

我们根据SEL来,找到IMP,就是函数的实现的指针,从而实现set和get方法。

这里有一点要说明一下如果我们是成员变量,那么是不会生成set和get方法的。

总结

  1. 对象的isa指针& ISA_MASK就是该类。
  2. 对象可以创建多个,类只有1个。
  3. 对象的本质是结构体。

下一章,我们更详细聊聊类的结构。