iOS类底层结构之LLDB分析

前言

在我们分析OC类底层原理时,经常会涉及对象、isa、类之间的关系链,以及类的继承链,例如:
1、对象isa指向类,类的isa指向元类,元类isa指向根元类,根元类isa指向根元类自己。
2、类继承自元类,元类继承自根元类,根元类继承自NSObject等。
具体的见官方的一张经典的isa,superclass图。


isa流程图.png

一、类结构

参考Objc源码objc4,类结构如下:

struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    Class ISA;  //继承自objc_object
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    //.....  省略后面代码
}

结构分析:
Class ISA; 是继承自objc_object的成员变量
Class superclass; 父类
cache_t cache; 缓存方法,指针的结构体
class_data_bits_t bits;存储类相关的数据信息结构体,这个正是这篇文章来分析的。

LLDB获取class_data_bits_t数据

要想从对象内存结构中获取某个成员的内存数据,就需要涉及内存偏移,需要内存偏移就需要知道涉及的成员类型占用内存的大小,如下:
Class ISA;这是一个指针,自然占用的大小为8个byte;
Class superclass; 这也是一个指针,也是占用8个byte;
cache_t cache;从源码分析,这个结构体占用16个byte,期待后续篇幅,在此略过;

那么我们就知道要想从类中得到class_data_bits_t,那么内存需要偏移32个字节,类定义如下:

@interface WJPerson : NSObject {
    NSString *hobby;
}
// isa
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;

- (void)instanceMethod;

+ (void)classMethod;

@end

断点在[WJPerson alloc]处,如下:


171624782181_.pic.jpg

在调试区域使用LLDB x/4gx命令查看WJPerson类内存数据如下:

(lldb) x/4gx WJPerson.class
0x1000083b8: 0x0000000100008390 0x000000010036a140
0x1000083c8: 0x00000001003623b0 0x0000802800000000
(lldb) p/x 0x1000083b8 + 0x20  //0x20: 偏移32字节
(long) $1 = 0x00000001000083d8
(lldb) p (class_data_bits_t *)0x00000001000083d8 //强制转换
(class_data_bits_t *) $2 = 0x00000001000083d8

p/x 0x1000083b8 + 0x20: 偏移32字节获取到class_data_bits_t内存,

二、获取属性properties列表

分析思路:class_data_bits_t -> class_rw_t -> properties()
获取class_rw_t

(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000101044940
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000504
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

从class_class_t中获取properties,class_class_t -> properties() -> list -> ptr

(lldb) p $4->properties()
(const property_array_t) $5 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x00000001000082e8
      }
      arrayAndFlag = 4295000808
    }
  }
}
  Fix-it applied, fixed expression was: 
    $4.properties()
(lldb) p $5.list
(const RawPtr<property_list_t>) $6 = {
  ptr = 0x00000001000082e8
}
(lldb) p $6.ptr
(property_list_t *const) $7 = 0x00000001000082e8

ptr -> property_list_t -> get(n) 获取到具体的属性名

(lldb) p *$7
(property_list_t) $8 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 2)
}
(lldb) p $8.get(0)
(property_t) $9 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
(lldb) p $8.get(1)
(property_t) $10 = (name = "nickName", attributes = "T@\"NSString\",C,N,V_nickName")
(lldb) 

由此获取到属性name,nickName

三、获取成员变量ivars列表

分析思路:class_data_bits_t -> class_rw_t -> class_ro_t -> ivars

获取class_ro_t

(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000504
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

(lldb) p $4.ro()
(const class_ro_t *) $11 = 0x00000001000081b8
(lldb) p * $11
(const class_ro_t) $12 = {
  flags = 0
  instanceStart = 8
  instanceSize = 32
  reserved = 0
   = {
    ivarLayout = 0x0000000000000000
    nonMetaclass = nil
  }
  name = {
    std::__1::atomic<const char *> = "WJPerson" {
      Value = 0x0000000100003ea6 "WJPerson"
    }
  }
  baseMethodList = 0x0000000100008200
  baseProtocols = nil
  ivars = 0x0000000100008280
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000082e8
  _swiftMetadataInitializer_NEVER_USE = {}
}

class_ro_t -> ivars

(lldb) p $12.ivars
(const ivar_list_t *const) $13 = 0x0000000100008280
(lldb) p *$13
(const ivar_list_t) $14 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 3)
}
(lldb) p $14.list
error: <user expression 16>:1:5: no member named 'list' in 'ivar_list_t'
$14.list
~~~ ^
(lldb) p $14.get(0)
(ivar_t) $15 = {
  offset = 0x0000000100008328
  name = 0x0000000100003f3e "hobby"
  type = 0x0000000100003f87 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $14.get(1)
(ivar_t) $16 = {
  offset = 0x0000000100008330
  name = 0x0000000100003f44 "_name"
  type = 0x0000000100003f87 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $14.get(2)
(ivar_t) $17 = {
  offset = 0x0000000100008338
  name = 0x0000000100003f4a "_nickName"
  type = 0x0000000100003f87 "@\"NSString\""
  alignment_raw = 3
  size = 8
}

获取成员变量总结

1.获取到成员变量hobby
2.另外发现属性name和nickName会生成一个对应的成员变量_name和_nickName;

四、获取方法methods列表

分析思路:class_data_bits_t -> class_rw_t -> methods()

4.1 获取实例方法列表

(lldb) p *$3
(class_rw_t) $18 = {
  flags = 2148007936
  witness = 0
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000504
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
(lldb) p $18.methods()
(const method_array_t) $19 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008200
      }
      arrayAndFlag = 4295000576
    }
  }
}
(lldb) p $19.list
(const method_list_t_authed_ptr<method_list_t>) $20 = {
  ptr = 0x0000000100008200
}
(lldb) p $20.ptr
(method_list_t *const) $21 = 0x0000000100008200
(lldb) p *$21
(method_list_t) $22 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 5)
}

从上得知有5个方法,那么接下来找出下每个方法,

(lldb) p $22.get(0).big()
(method_t::big) $23 = {
  name = "instanceMethod"
  types = 0x0000000100003f93 "v16@0:8"
  imp = 0x0000000100003cf0 (KCObjcBuild`-[WJPerson instanceMethod])
}
(lldb) p $22.get(1).big()
(method_t::big) $24 = {
  name = "name"
  types = 0x0000000100003f9b "@16@0:8"
  imp = 0x0000000100003d20 (KCObjcBuild`-[WJPerson name])
}
(lldb) p $22.get(2).big()
(method_t::big) $25 = {
  name = "setName:"
  types = 0x0000000100003fa3 "v24@0:8@16"
  imp = 0x0000000100003d50 (KCObjcBuild`-[WJPerson setName:])
}
(lldb) p $22.get(3).big()
(method_t::big) $26 = {
  name = "nickName"
  types = 0x0000000100003f9b "@16@0:8"
  imp = 0x0000000100003d80 (KCObjcBuild`-[WJPerson nickName])
}
(lldb) p $22.get(4).big()
(method_t::big) $27 = {
  name = "setNickName:"
  types = 0x0000000100003fa3 "v24@0:8@16"
  imp = 0x0000000100003db0 (KCObjcBuild`-[WJPerson setNickName:])
}

如上,获取到了5个方法。
另外,调用method_list_t数组中的方法.get(0),如下:

(lldb) p $22.get(0)
(method_t) $28 = {}

发现显示不出来方法相关的数据,这是因为get()方法输出property_t的description, 从property_t结构看有name,attributes,所以在获取属性、成员变量时会输出对应的名称,但是methods有自行实现property_t即method_t,

struct method_t {
    static const uint32_t smallMethodListFlag = 0x80000000;
    method_t(const method_t &other) = delete;
    // The representation of a "big" method. This is the traditional
    // representation of three pointers storing the selector, types
    // and implementation.
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };

private:
    bool isSmall() const {
        return ((uintptr_t)this & 1) == 1;
    }

    // The representation of a "small" method. This stores three
    // relative offsets to the name, types, and implementation.
    struct small {
        // The name field either refers to a selector (in the shared
        // cache) or a selref (everywhere else).
        RelativePointer<const void *> name;
        RelativePointer<const char *> types;
        RelativePointer<IMP> imp;

        bool inSharedCache() const {
            return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
                    objc::inSharedCache((uintptr_t)this));
        }
    };

    small &small() const {
        ASSERT(isSmall());
        return *(struct small *)((uintptr_t)this & ~(uintptr_t)1);
    }

    .....//省略n代码

    big &big() const {
        ASSERT(!isSmall());
        return *(struct big *)this;
    }

如上method_t定义,所以需要再调用big()方法根据description输出SEL name,另外,如果是M1电脑,需要使用method_list_t.get(n).big()获取。

获取方法名

4.1.1 非M1电脑获取方法名(旧款电脑)

method_list_t.get(n).big()

4.1.2 M1电脑获取方法名

method_list_t.get(n).small()

发现一个问题,我们的类方法怎么没有看到呢,大家都知道在OC层是有区分实例方法和类方法,哪里出了问题呢?既然类中没有找到,我们考虑下去其它地方找下,那我们尝试下去当前类关系最的元类里面找一下。

4.2 获取类方法列表

分析思路:获取WJPerson元类 -> class_rw_t -> methods()
获取WJPerson元类

(lldb) x/4gx WJPerson.class
0x1000083b8: 0x0000000100008390 0x000000010036a140
0x1000083c8: 0x00000001003623b0 0x0000802800000000
(lldb) p/x 0x0000000100008390 & 0x00007ffffffffff8 
//本地用的是mac 即ISA_MASK是x86_64的
//通过WJPerson的isa获取到WJPerson的元类
(long) $50 = 0x0000000100008390  //元类

按照前面讲到的类获取方法列表流程,如下:

(lldb) x/4gx 0x0000000100008390  //元类
0x100008390: 0x000000010036a0f0 0x000000010036a0f0
0x1000083a0: 0x0000000100762770 0x0002e03100000003
(lldb) p/x 0x100008390 + 0x20
(long) $51 = 0x00000001000083b0
(lldb) p (class_data_bits_t *)0x00000001000083b0
(class_data_bits_t *) $52 = 0x00000001000083b0
(lldb) p *$52
(class_data_bits_t) $53 = (bits = 4312044516)
(lldb) p $53->data()
(class_rw_t *) $54 = 0x00000001010493e0
  Fix-it applied, fixed expression was: 
    $53.data()
(lldb) p $53.data()
(class_rw_t *) $55 = 0x00000001010493e0
(lldb) p $55.methods()
(const method_array_t) $56 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008198
      }
      arrayAndFlag = 4295000472
    }
  }
}
  Fix-it applied, fixed expression was: 
    $55->methods()
(lldb) p $56.list
(const method_list_t_authed_ptr<method_list_t>) $57 = {
  ptr = 0x0000000100008198
}
(lldb) p $57.ptr
(method_list_t *const) $58 = 0x0000000100008198
(lldb) p *$58
(method_list_t) $59 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $59.get(0).bit()
error: <user expression 68>:1:12: no member named 'bit' in 'method_t'
$59.get(0).bit()
~~~~~~~~~~ ^
(lldb) p $59.get(0).big()
(method_t::big) $60 = {
  name = "classMethod"
  types = 0x0000000100003f93 "v16@0:8"
  imp = 0x0000000100003cc0 (KCObjcBuild`+[WJPerson classMethod])
}

得到元类中的方法列表中的classMethod,即是OC层WJPerson类方法+ (void)classMethod,由此可见OC层的类方法是存在元类中。

五、总结

1.在OC底层不存在类方法,所有方法都是实例方法,从另一方面也符合即万物皆对象。

2.类方法存储在元类中的方法列表中。

3.类结构中isa、superclass、chche、bits 成员变量,在对bits 探究过程中发现bits存储着大家熟悉的属性列表、成员变量列表、方法列表、协议列表等。

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

推荐阅读更多精彩内容