iOS 类的结构分析

一、什么是类

字面上看,类即Class。

typedef struct objc_class *Class
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 *data() { 
        return bits.data();
    }
    ......
}
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
}
struct objc_object {
private:
    isa_t isa;
public:
    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
    // getIsa() allows this to be a tagged pointer object
    Class getIsa();
    ......
}

由Objective-C objc-runtime-new.h里的代码可以知道Class是一个结构体,该结构体继承自objc_object,objec_object也就是我们所说的对象。那么我们可以得出结论:类是一个结构体,也是一个对象。这也从侧面印证了万物皆对象。

二、类的结构

类的主要结构如下:


image

显而易见:

  • 第一个元素是一个隐藏元素(来自于父类),该属性即为isa,其指向元类;
  • 第二个元素是Classsuperclass,指向其父类;
  • 第三个元素是cache_t cache,类的相关缓存;
  • 第四个元素是class_data_bits_t bits,返回类存储的相关信息;
  • 同样我们也可以从class_rw_t *data()方法返回这些信息。

现在我们分析一下objc_clas的结构所占的内存:

  • Class isa 是一个指针,占用8字节;
  • Class superclass 同样是一个指针,占用8字节;
  • cache_t 是一个结构体,其中第一个元素是一个指针占8字节,第二、三个元素都是int32各占4字节,总共占16字节
LGPerson *person = [LGPerson alloc];
Class pClass     = object_getClass(person);

通过在在lldb中使用x/4gx 打印的地址:

0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da260 0x0000000000000000

其中0x001d800100002389就是isa的地址,因为我们已经通过上面的计算得到了前三个元素所占用的内存,根据内存偏移,我们可以得到class_data_bits_t bits的地址为0x1000023b0 + 8 + 8 + 16 = 0x1000023b0,接着分析:

image

image

image

通过以上在llbd的分析,我们可以看到我们给类定义的属性存在的位置如下:

class_rw_t -> (class_ro_t *)ro -> (property_list_t)baseProperties

我们自定义的成员变量,系统给属性生成的变量(_开头命名)存储的位置如下:

class_rw_t -> (class_ro_t *)ro -> (ivar_list_t)ivars

系统给属性生成的getter、setter方法和我们自定义的实例方法存储的位置如下:

class_rw_t -> (class_ro_t *)ro -> (method_list_t)baseMethodList

我们自定义的类方法存储在元类里。

1、isa

isa是一个名为isa_t的联合体,主要属性为Class cls和 uintptr_t bits。是实例对象、类、元类的桥梁。我么可以通过(Class)(isa.bits & ISA_MASK)获取到当前的类。

union isa_t {
    isa_t() { }
    isa_t(uintptr_t value) : bits(value) { }
    Class cls;
    uintptr_t bits;
#if defined(ISA_BITFIELD)
    struct {
        ISA_BITFIELD;  // defined in isa.h
    };
#endif
};
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
}

arm64下isa的内存结构如下:

#   define ISA_MASK        0x0000000ffffffff8ULL
#   define ISA_MAGIC_MASK  0x000003f000000001ULL
#   define ISA_MAGIC_VALUE 0x000001a000000001ULL
#   define ISA_BITFIELD                                                      \
      uintptr_t nonpointer        : 1;                                       \
      uintptr_t has_assoc         : 1;                                       \
      uintptr_t has_cxx_dtor      : 1;                                       \
      uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
      uintptr_t magic             : 6;                                       \
      uintptr_t weakly_referenced : 1;                                       \
      uintptr_t deallocating      : 1;                                       \
      uintptr_t has_sidetable_rc  : 1;                                       \
      uintptr_t extra_rc          : 19

x86_64下:

#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                                        \
      uintptr_t nonpointer        : 1;                                         \
      uintptr_t has_assoc         : 1;                                         \
      uintptr_t has_cxx_dtor      : 1;                                         \
      uintptr_t shiftcls          : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
      uintptr_t magic             : 6;                                         \
      uintptr_t weakly_referenced : 1;                                         \
      uintptr_t deallocating      : 1;                                         \
      uintptr_t has_sidetable_rc  : 1;                                         \
      uintptr_t extra_rc          : 8

2、superclass

superclass指向类的父类,是继承的桥梁。

3、cache_t cache

struct cache_t {
    struct bucket_t *_buckets;
    mask_t _mask;
    mask_t _occupied;
};
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits

下一节分析

4、class_data_bits_t bits

class_data_bits_t是一个结构体,大致结构如下:

struct class_data_bits_t {
    uintptr_t bits;
private:
    bool getBit(uintptr_t bit) {
        return bits & bit;
    }
public:
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    ......
};

其包含一个名为class_rw_t的结构体指针,该结构体指针指向了存储了类的method、property、protocol等相关信息的内存:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;
    const class_ro_t *ro;
    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    Class firstSubclass;
    Class nextSiblingClass;
    char *demangledName;
#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif
    void setFlags(uint32_t set) {
        OSAtomicOr32Barrier(set, &flags);
    }
    void clearFlags(uint32_t clear) {
        OSAtomicXor32Barrier(clear, &flags);
    }
    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear) {
        assert((set & clear) == 0);
        uint32_t oldf, newf;
        do {
            oldf = flags;
            newf = (oldf | set) & ~clear;
        } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
    }
};

在class_ro_t这个结构体中

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif
    const uint8_t * ivarLayout;
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;
    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

tips:

类、元类创建的时机:编译期

tips:

当我们计算结构体大小的时候,不计算函数,只计算属性,函数存储的位置不一样。

tips:内存偏移

LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [LGPerson alloc];

NSLog(@"--p1--%@----%p", p1, &p1);
NSLog(@"--p2--%@----%p", p2, &p2);

得到结果如下:
--p1--<LGPerson: 0x10108a090>----0x7ffeefbff508
--p2--<LGPerson: 0x10108e080>----0x7ffeefbff500

我们可以看出p1、p2的内存地址 和 指针地址都是不相同的。

int c[4] = {1, 2, 3, 4};
int *d = c;
NSLog(@"%p--%p--%p--%p", &c, &c[0], &c[1], &c[2]);
NSLog(@"%p--%p--%p",d,d+1,d+2);
0x7ffeefbff480--0x7ffeefbff480--0x7ffeefbff484--0x7ffeefbff488    
0x7ffeefbff480--0x7ffeefbff484--0x7ffeefbff488

for (int i = 0; i<4; i++) {
    int value1 = c[i];
    int value2 = *(d+i);
    NSLog(@"value1=%d==value=%d",value1, value2);
}
value1=1==value=1
value1=2==value=2
value1=3==value=3
value1=4==value=4

由打印的结果可以看出来 &c[0], &c[1],&c[2]的地址是递增的,间隔一个数组元素所占的内存大小;而由d,d+1,d+2可以看出来,地址指针加1就相当于内存加一个元素的大小。我们可以利用内存的偏移来反向得到内存地址中所存储的对象。

tips:内存占用大小

image
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • 1.类的结构定义 我们在main.m文件中写一段简单的代码: 然后,我们打开终端cd到当前main.m的上层文件夹...
    Jeffery_zc阅读 378评论 0 2
  • 一、类的探究 我们从上一篇文章iOS中的isa分析对象的时候已经过渡到类了,提到了 对象,类,元类,根元类等概念,...
    Fred丶Lee阅读 1,179评论 0 4
  • 内存偏移 以数组为例: 打印结果: 由上面结果可知:1.由&a与&a[0]的打印结果相同可知,数组的首地址存着数组...
    e521阅读 674评论 2 1
  • 关于我的仓库 这篇文章是我为面试准备的iOS基础知识学习中的一篇 我将准备面试中找到的所有学习资料,写的Demo,...
    太阳骑士索拉尔阅读 1,179评论 0 4
  • 家,今天母亲来了。剪着短短的头发有些像我。我温柔如羽的长发落得如短发。 记着在品味轩的日子。忆苦思甜也...
    溪境阅读 429评论 0 0