OC中类结构分析

想要分析OC中类的结构,我们可以通过clang命令得到底层的实现:
clang -rewrite-objc main.m -o main.cpp
然后在源码中我们可以看到Class的实现:
typedef struct objc_class *Class;
可以得出,Class真正类型时objc_class。

接下来我们就可以研究objc_class的结构了

struct objc_class : objc_object {
    // Class ISA; // 8
    Class superclass; // 8
    cache_t cache;    // 16 不是8         // 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();
    }
    //......其他的省略
}

objec_class继承自objc_object。OC中objc_object是一些对象的基类。ISA指针也是继承于objc_object。
OC中的NSObject其实和底层的objc_object是对应的。
我们可以看到有个bits有个data()函数,可以猜测类中的方法和属性可能存在于bits中。

然后我们开始探索bits中的内容:

首先,我们可以打印出类对象的地址,因为objc_class是struct,所以我们可以通过地址的平移得到属性bits。
属性占用内存情况:
ISA:8字节;
superclass:8字节;
cache_t的结构如下:

struct cache_t {
    struct bucket_t *_buckets; // 8
    mask_t _mask;  // 4
    mask_t _occupied; // 4

8+4+4=16
cache:16字节;

所以可以得出bits相对比objc_object首地址的偏移为32字节。
(lldb) x/4gx pClass
0x1000023b0: 0x001d800100002389 0x0000000100b37140
0x1000023c0: 0x00000001003da260 0x0000000000000000
(lldb) p 0x1000023d0 //首地址+32
(long) $2 = 4294976464
(lldb) p (class_data_bits_t *)0x1000023d0 //因为bits的类型是class_data_bits_t,这里需要强转一下
(class_data_bits_t *) $3 = 0x00000001000023d0
(lldb) p $3->data()
(class_rw_t *) $6 = 0x0000000100f39980
(lldb) p *$6
//这里我们得到了bits中data()的所有内容,通过探索,可以得出class的属性和方法列表都在ro中
(class_rw_t) $7 = {
  flags = 2148139008
  version = 0
  ro = 0x0000000100002308
  methods = {
    list_array_tt<method_t, method_list_t> = {
       = {
        list = 0x0000000100002240
        arrayAndFlag = 4294976064
      }
    }
  }
  properties = {
    list_array_tt<property_t, property_list_t> = {
       = {
        list = 0x00000001000022f0
        arrayAndFlag = 4294976240
      }
    }
  }
  protocols = {
    list_array_tt<unsigned long, protocol_list_t> = {
       = {
        list = 0x0000000000000000
        arrayAndFlag = 0
      }
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
  demangledName = 0x0000000000000000
}

接下里我们打印ro

(lldb) p (class_ro_t *)$7.ro
(class_ro_t *) $11 = 0x0000000100002308
(lldb) p *$11
(class_ro_t) $12 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100001f89 "\x02"
  name = 0x0000000100001f80 "LGPerson"
  baseMethodList = 0x0000000100002240
  baseProtocols = 0x0000000000000000
  ivars = 0x00000001000022a8
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000022f0
}

可以看到ro中含有baseMethodList、baseProperties 、ivars等方法和属性的列表。
先来打印方法列表

(lldb) p $12.baseMethodList
(method_list_t *) $13 = 0x0000000100002240
(lldb) p *$13
(method_list_t) $14 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    count = 4//存在4个方法
    first = {
      name = "sayHello"
      types = 0x0000000100001f8b "v16@0:8"
      imp = 0x0000000100001b90 (LGTest`-[LGPerson sayHello] at LGPerson.m:13)
    }
  }
}
(lldb) p $14.get(1)
(method_t) $15 = {
  name = ".cxx_destruct"
  types = 0x0000000100001f8b "v16@0:8"
  imp = 0x0000000100001c60 (LGTest`-[LGPerson .cxx_destruct] at LGPerson.m:11)
}
(lldb) p $14.get(2)
(method_t) $16 = {
  name = "nickName"
  types = 0x0000000100001f93 "@16@0:8"
  imp = 0x0000000100001bf0 (LGTest`-[LGPerson nickName] at LGPerson.h:17)
}
(lldb) p $14.get(3)
(method_t) $17 = {
  name = "setNickName:"
  types = 0x0000000100001f9b "v24@0:8@16"
  imp = 0x0000000100001c20 (LGTest`-[LGPerson setNickName:] at LGPerson.h:17)
}

再来打印属性列表

(lldb) p $12.baseProperties
(property_list_t *) $18 = 0x00000001000022f0
(lldb) p *$18
(property_list_t) $19 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1 //只有一个属性
    first = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
  }
}

再来看成员变量

(lldb) p $12.ivars
(const ivar_list_t *) $20 = 0x00000001000022a8
(lldb) p *$20
(const ivar_list_t) $21 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x0000000100002378
      name = 0x0000000100001e64 "hobby"
      type = 0x0000000100001fa6 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}
(lldb) p $21.get(1)
(ivar_t) $22 = {
  offset = 0x0000000100002380
  name = 0x0000000100001e6a "_nickName"
  type = 0x0000000100001fa6 "@\"NSString\""
  alignment_raw = 3
  size = 8
}

最后我们可以得出,类的常用属性的结构基本如下:


image.png

注意:实例方法存储在类的methodList, 而类方法存储在对应元类的methodList中。

相关面试题

问题:问下面的代码打印的结果是什么

        BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];
        BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];
        BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];
        BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];
        NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);

        BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];
        BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];
        BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];
        BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];
        NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

正确的结果应该是:
re1: 1
re2: 0
re3: 0
re4: 0

re5: 1
re6: 1
re7: 1
re8: 1

re5、6、7、8很容易理解,是调用的对象方法。

下面我们分析一下re1:
[NSObject class] 调用类方法isKindOfClass,我们跟踪源码进去看看:

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

此处的self是NSObject类对象,然后调用object_getClass((id)self)后得到的tcls为NSObject的元类,而此时传进来的cls为NSObject类,所以第一次循环的结果 tcls不等于cls。然后继续循环判断,去取tcls->superClass。因为NSObject的元类是根元类,根元类的父类是NSObject(注:此处考察的就是这个知识点)。所以tcls成为了NSObject,然后和cls对比,正好相等,返回YES。

那么为什么re2返回为NO呢?我们来看下isMemberOfClass:的源码:

+ (BOOL)isMemberOfClass:(Class)cls {
    // 元类 VS 类
    return object_getClass((id)self) == cls;
}

此时是直接将self(NSObject类)的元类和cls(NSObject类)进行对比,直接返回NO。
同样的方法可以分析re3和re4,都是返回NO。

最后补充一下OC对象isa和superClass的流程图:


isa流程图.png

问题:
LGPerson的定义如下:


image.png

请问下面代码打印的结果是什么?

    LGPerson *person = [LGPerson alloc];
    Class pClass     = object_getClass(person);
    const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);

    IMP imp1 = class_getClassMethod(pClass, @selector(sayHello));
    IMP imp2 = class_getClassMethod(metaClass, @selector(sayHello));

    IMP imp3 = class_getClassMethod(pClass, @selector(sayHappy));
    IMP imp4 = class_getClassMethod(metaClass, @selector(sayHappy));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);

答案是:0x0-0x0-0x1000022a0-0x1000022a0
对于imp1和imp2我们都知道,sayHello是实例方案,所以获取到的都是空。而sayHappy是类方法,类方法是存在于元类中的实例方法,imp3理所当然是可以获取到的。但是imp4为什么也能获取到呢?下面我们可以跟踪class_getClassMethod:源码进去看看:

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

可以看出class_getClassMethod方法是通过获取到元类,然后取元类中的实例方法就是类方法。然后我们跟踪getMeta方法

Class getMeta() {
    if (isMetaClass()) return (Class)this;
    else return this->ISA();
}

到此可以明白了,因为我们传进来的是元类,然后直接返回自己。和传递进来类是一样的。

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

推荐阅读更多精彩内容