类的结构分析

在本篇文章中,我们一起来探索类的结构

分析

runtime中我们可以通过object_getClass方法获取实例对象类信息

     LYPerson *person = [[LYPerson alloc] init];
     Class p = object_getClass(person)

我们来看下 Class的实现

typedef struct objc_class *Class;

struct objc_class : objc_object {
    // Class ISA; // 1
    Class superclass;   // 2
    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() const { //3
        return bits.data();
    }
}
  • 1,继承自 objc_object默认都会有 isa指针。
  • 2,superclass:其父类信息。
  • 3, class_rw_t: 类的具体分析。

从 对objc_class整体的探索我们可以看到,类对象的第一部分由 isa指针组成。我们先来分析isa指向的问题

isa指向

在之前的文章 isa结构分析 一文中,我们分析了 isa指针的结构组成。如果对其结构不熟悉,可以进行查看。
我们先初始化一个实例对象person, person继承于 NSObject:

     LYPerson *person = [[LYPerson alloc] init];

1,我们先查看下实例对象person内存分布

(lldb) x/4gx person // 1 打印类的内存分布
0x10061eca0: 0x001d8001000021b9 0x0000000000000000
0x10061ecb0: 0x646e6946534e5b2d 0x466e726574746150

2,通过ISA_MASK获取类信息

(lldb)  p/x 0x00007ffffffffff8ULL & 0x001d8001000021b9 // 获取类信息
(unsigned long long) $2 = 0x00000001000021b8

(lldb) po 0x00000001000021b8 // 打印类信息
LYPerson

⚠️我们可以看到 实例对象person的isa指针指向 LYPerson

3,我们直接查看LYPerson.class的内存分布来进行验证

(lldb) x 0x00000001000021b8 // 查看类信息的内存分布
0x1000021b8: e0 21 00 00 01 00 00 00 40 11 3f 00 01 00 00 00  .!......@.?.....
0x1000021c8: 20 f4 61 00 01 00 00 00 03 00 00 00 10 80 01 00   .a.............

(lldb) x LYPerson.class //直接查看 `LYPerson.class`的内存分布
0x1000021b8: e0 21 00 00 01 00 00 00 40 11 3f 00 01 00 00 00  .!......@.?.....
0x1000021c8: 20 f4 61 00 01 00 00 00 03 00 00 00 10 80 01 00   .a.............

我们可以看出 它们两个的内存空间是一样的,这就说明了实例对象的isa指向 类对象(Class)
4,我们继续查看LYPerson类对象的内存分布

(lldb)  x/4gx LYPerson.class // 1,获取 Person的类信息
0x1000021b8: 0x00000001000021e0 0x00000001003f1140
0x1000021c8: 0x00000001014261b0 0x000680100000000f

我们可以看出 类对象的isa指向另一个类
5,通过ISA_MASK得到类对象的指向

(lldb) p/x 0x00000001000021e0 & 0x00007ffffffffff8ULL // 2,与上ISA_Mask得到类信息,
// 得到类对象的isa,就得到类对象的指向元类
(unsigned long long) $12 = 0x00000001000021e0

⚠️ 这样我们就看出 类对象的isa指向元类
6,查看 元类的内存分布,我们可以发现,元类也是一个类,也有isa

(lldb) x/4gx 0x00000001000021e0 // 3,查看元类的内存分布
0x1000021e0: 0x00000001003f10f0 0x00000001003f10f0
0x1000021f0: 0x000000010061f460 0x0005e03100000007

紧接着我们看下元类对象指向哪个类

(lldb) p/x 0x00000001003f10f0 & 0x00007ffffffffff8ULL // 查看 元类的指向
(unsigned long long) $13 = 0x00000001003f10f0

(lldb) po 0x00000001003f10f0 // 查看 元类 isa指向的类信息值
NSObject

⚠️ 原来,person对象的元类isa指向 NSObject

7,我们接着查看NSObject内存分布

(lldb) x/4gx 0x00000001003f10f0 // 查看 NSObjec 的内存分布
0x1003f10f0: 0x00000001003f10f0 0x00000001003f1140
0x1003f1100: 0x0000000101406a80 0x0004e03100000007

⚠️ NSObjecisa指向NSObjec本身

小结
  • 实例对象isa 指向 类对象类对象isa指向 元类对象元类对象isa指向 NSObject, NSObjectisa指向NSObject本身。
  • isa代表一种归属关系,实例对象归属类对象,类对象归属元类对象元类对象的归属是 NSObjec, 元类定义创建都是由编译器自动创建的

类的继承关系

我们新建一个LYTeacher类,LYPerson类 继承 于 LYTeacher

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LYPerson *person = [[LYPerson alloc] init];
        LYTeacher *teacher = [[LYTeacher alloc] init];
        NSObject *obj = [[NSObject alloc] init];
        NSLog(@"%@", person.superclass);
        NSLog(@"%@", teacher.superclass);
        NSLog(@"%@", obj.superclass);
    }
    return 0;
}

其输出结果如下

2020-09-13 14:19:49.968665+0800 001[74326:2492359] LYTeacher
2020-09-13 14:19:49.969259+0800 001[74326:2492359] NSObject
2020-09-13 14:19:49.969316+0800 001[74326:2492359] (null)

我们可以看到 实例对象person父类LYTeacher类对象实例对象teacher父类NSObject类对象实例对象obj父类nil
⚠️OC对象的继承关系源于类,实例对象是没有继承关系的

综上所述,isa的指向类对象的继承可概括为下图

isa流程图.png

class_rw_t

属性的存储

在文章的开头部分,我们分析了 objc_class的整体结构,包含8字节的isa8字节的superclass16字节的cache。接下来我们分析propertymethod是存在哪里的?
1,我们给LYPerson类新增一个name ,height属性和 -(void)sayHello方法

@interface LYPerson : NSObject
{
    NSInteger height;
}
@property(nonatomic, copy) NSString *name;
@property(nonatomic, copy) NSString *nickName;

-(void)sayHello;
+(void)say666;

@end

@implementation LYPerson
-(void)sayHello{
    NSLog(@"hello");
}

+(void)say666 {
    NSLog(@"6666");
}
@end

2,新建LYPerson类,并打印其类对象内存地址

(lldb) p/x LYPerson.class
(Class) $0 = 0x0000000100002338 LYPerson

3,class_data_bits_t bits属性在结构体内偏移32位置存放(0~7: isa8~15superclass16 ~ 31: cache),我们通过首地址 + 偏移值得到 bits的值。

(lldb) p/x 0x0000000100002338 + 0x20 // 1
(long) $2 = 0x0000000100002358

(lldb) p (class_data_bits_t *)$2 // 2
(class_data_bits_t *) $3 = 0x0000000100002358

(lldb) p *$3 // 3
(class_data_bits_t) $4 = (bits = 4301859572)
  • 1,计算 class_data_bits_t bits的实际地址值。
  • 2,输出 bits
  • 3,读取内存值。

4,获取 class_rw_t *data()的值

(lldb) p $4.data()
(class_rw_t *) $6 = 0x0000000100692af0

(lldb) p *$6
(class_rw_t) $7 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = 4294975792
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

5,从 class_rw_t 中获取 properties

(lldb) p $7.properties()
(const property_array_t) $8 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x0000000100002278
      arrayAndFlag = 4294976120
    }
  }
}

6,获取list数组

(lldb) p $8.list
(property_list_t *const) $9 = 0x0000000100002278

7,读取 list数组里面的值

(lldb) p *$9
(property_list_t) $10 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 2 // 1
    first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
  }
}
  • 1,count = 2:表示一共有2个属性。

8,我们依次获取属性值

(lldb) p $10.get(0) // 1
(property_t) $11 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $10.get(1) // 2
(property_t) $12 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
(lldb) p $10.get(2) //3
Assertion failed: (i < count), function get, file /Users/ritamashin/Desktop/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
  • 1,2, :依次读取第一个属性和第二个属性。
  • 3,当读取第3个属性时,数组越界报错,但我们在类里面定义了 2个属性和1个成员变量成员变量去哪里了???
成员变量的存储

我们对源码进行分析,在class_ro_t中有const ivar_list_t * ivars,我们通过 class_rw_t找到 class_ro_t,然后对 ivars 进行查看。

(lldb) p $7.ro() \\ 1
(const class_ro_t *) $26 = 0x0000000100002130
  • 1,$7 是 class_rw_t结构体,调用ro()方法,得到 class_ro_t对象。
(lldb) p $26->ivars  \\1
(const ivar_list_t *const) $28 = 0x0000000100002210
(lldb) p $28 
(const ivar_list_t *const) $28 = 0x0000000100002210
(lldb) p *$28 \\2
(const ivar_list_t) $29 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 3
    first = {
      offset = 0x00000001000022a8
      name = 0x0000000100000f2d "height"
      type = 0x0000000100000f87 "q"
      alignment_raw = 3
      size = 8
    }
  }
}
  • 得到ivars的值,得到ivars_list对象。
(lldb) p $29.get(0)
(ivar_t) $30 = {
  offset = 0x00000001000022a8
  name = 0x0000000100000f2d "height"
  type = 0x0000000100000f87 "q"
  alignment_raw = 3
  size = 8
}
(lldb) p $29.get(1)
(ivar_t) $31 = {
  offset = 0x00000001000022b0
  name = 0x0000000100000f34 "_name"
  type = 0x0000000100000f89 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $29.get(2)
(ivar_t) $32 = {
  offset = 0x00000001000022b8
  name = 0x0000000100000f3a "_nickName"
  type = 0x0000000100000f89 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $29.get(3)
Assertion failed: (i < count), function get, file /Users/ritamashin/Desktop/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
  • 依次读取 ivar_list的值,其值依次为height,_name,_nickName
小计

由此我们可以得出,属性存放在 properties里面,成员变量@property属性生成的成员变量都存放在 ivars里面。

Methods

实例方法 (instance method)

9,获取 该类的方法列表

(lldb) p $7.methods()
(const method_array_t) $13 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002178
      arrayAndFlag = 4294975864
    }
  }
}

10,获取 method list

(lldb) p $13.list
(method_list_t *const) $14 = 0x0000000100002178
(lldb) p *$14
(method_list_t) $15 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 6
    first = {
      name = "sayHello"
      types = 0x0000000100000f7f "v16@0:8"
      imp = 0x0000000100000d10 (KCObjc`-[LYPerson sayHello])
    }
  }
}

11,依次获取method list里面的值

(lldb) p $15.get(0)
(method_t) $16 = { 
  name = "sayHello" // 1
  types = 0x0000000100000f7f "v16@0:8"
  imp = 0x0000000100000d10 (KCObjc`-[LYPerson sayHello])
}
(lldb) p $15.get(1) 
(method_t) $17 = { 
  name = ".cxx_destruct" // 2
  types = 0x0000000100000f7f "v16@0:8"
  imp = 0x0000000100000d40 (KCObjc`-[LYPerson .cxx_destruct])
}
(lldb) p $15.get(2) 
(method_t) $18 = {
  name = "name" // 3
  types = 0x0000000100000f95 "@16@0:8"
  imp = 0x0000000100000d80 (KCObjc`-[LYPerson name])
}
(lldb) p $15.get(3)
(method_t) $19 = {
  name = "setName:" // 4
  types = 0x0000000100000f9d "v24@0:8@16"
  imp = 0x0000000100000db0 (KCObjc`-[LYPerson setName:])
}
(lldb) p $15.get(4)
(method_t) $20 = {
  name = "setNickName:" //5
  types = 0x0000000100000f9d "v24@0:8@16"
  imp = 0x0000000100000e10 (KCObjc`-[LYPerson setNickName:])
}
(lldb) p $15.get(5)
(method_t) $21 = {
  name = "nickName" //6
  types = 0x0000000100000f95 "@16@0:8"
  imp = 0x0000000100000de0 (KCObjc`-[LYPerson nickName])
}
(lldb) p $15.get(6) // 7
Assertion failed: (i < count), function get, file /Users/ritamashin/Desktop/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
  • 1,实例方法 sayHello
  • 2,类的析构方法。
  • 3,name属性的get方法。
  • 4,name属性的set方法。
  • 5,nickNameset方法。
  • 6,nickNameget方法。
  • 7,当我们试图查找 类方法 say666时,造成了数组越界报错了。

我们在 method_list里面只找到了对象的实例方法,那类方法存在哪里呢?

在文章的第一部分我们提到 isa代表一种归属关系,实例对象归属类对象,类对象归属元类对象元类对象的归属是 NSObject。类对象里面存放了实例方法,那我们试着在 元类对象里面去查找类方法

类方法(class method)
(lldb) p/x LYPerson.class \\ 1
(Class) $0 = 0x0000000100002338 LYPerson
(lldb) x/4gx 0x0000000100002338 \\2
0x100002338: 0x0000000100002310 0x00000001003f0140
0x100002348: 0x00000001003ea450 0x0000802c00000000
  • 1,输出 LYPerson类对象的首地址。
  • 2,查看 LYPerson类对象内存分布,得到LYPerson类对象的元类地址 0x0000000100002310
(lldb) p/x 0x0000000100002310 + 0x20 // 1
(long) $1 = 0x0000000100002330
(lldb) p (class_data_bits_t *)$1 
(class_data_bits_t *) $2 = 0x0000000100002330
  • 1,获取 class_data_bits_t的首地址
(lldb) p $2-> data() \\1
(class_rw_t *) $3 = 0x00000001010180f0
(lldb) p $3.methods() \\2
(const method_array_t) $4 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100002110
      arrayAndFlag = 4294975760
    }
  }
}
  Fix-it applied, fixed expression was: 
    $3->methods()
  • 1,得到 class_rw_t对象。
  • 2,获得其 methods
(lldb) p $4.list \\1
(method_list_t *const) $5 = 0x0000000100002110
(lldb) p *$5 
(method_list_t) $6 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 1
    first = {
      name = "say666"
      types = 0x0000000100000f7f "v16@0:8"
      imp = 0x0000000100000ce0 (KCObjc`+[LYPerson say666])
    }
  }
}
  • 1,得到 method_list_t对象
(lldb) p $6.get(0)
(method_t) $7 = {
  name = "say666"
  types = 0x0000000100000f7f "v16@0:8"
  imp = 0x0000000100000ce0 (KCObjc`+[LYPerson say666])
}
(lldb) p $6.get(1)
Assertion failed: (i < count), function get, file /Users/ritamashin/Desktop/可编译objc源码/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
  • 依次获取method_list里面的值,我们可以看到 类方法存在于元类对象的method_list里面。

小计:在这里我们从class_rw_t中找到了,LYPerson类对象里面的方法列表属性列表成员变量ivars。从此可见,类对象包含了isa指针superClass类对象,属性列表方法列表实例方法存在类对象的method_list里面,类方法存在元类的method_list里面

总结:

我们在这篇文章中,我们探讨了 isa指针的指向对象之间的继承关系类对象的组成结构

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