006--iOS底层 - 类的结构(属性、成员变量、方法的探索)

引言

上一篇讲到了内存偏移的知识和操作,接下来内存偏移将在本文用到具体的示例。我们对对象的探究已经了解了对象的底层结构,isa的走向和对象的继承链。本文将还原探究类内部结构的过程。

类的探索

一、寻找objc_class

001-OC对象原理探究 - alloc这篇文章中,我们用到了objc源码环境调试。本文我们也在此基础上,探究类的结构。我们在对象的底层结构探索的时候,发现了类Class的底层为typedef struct objc_class *Class;也就是说,Class是一个结构体指针的别名。
具体寻找步骤:
1、objc源码调试环境工程,搜索Class {,在结果中我们得到runtime.h中的class结构:

image.png

2、这似乎就是我们要找的Class底层,但是看到#if !__OBJC2__以及OBJC2_UNAVAILABLE,才知道,整个结构体struct objc_class并不适用于objc2中(本文调试的环境的是objc4_818_2
3、那么我们怎么找到正确的Class呢?请看框起来的注释/* UseClassinstead ofstruct objc_class **/
源码如下:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;

在此源码中,我们还得到一个信息:OC中id类型的底层竟然是typedef struct objc_object *id;,这就是为什么我们在定义id类型的变量时,不加*号的原因。
探索源码真的能学到很多!!!
4、搜索框输入struct objc_class,其中objc_runtime-new.h中的结果就是我们想要的,结果如下:

image.png

二、bits

上图的objc_class内部可知bitsClass superclasscache_t cache之后。我们调试得到bits,需要上篇文章提到的内存偏移来得到。因此,我们需要知道偏移了多少字节,接下来我们开始探索Class superclasscache_t cache的内存大小。
1、superclassClass指针类型,因此superclass8字节
2、cache_t为结构体类型,其内部结构如下:(由于我们需要知道结构体内存的大小,只需要知道其成员变量的大小即可,cache_t内部的static和方法函数均不影响结构体内存大小,因此以下源码为简化后的cache_t):

struct cache_t {
private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;
#if __LP64__
            uint16_t                   _flags;
#endif
            uint16_t                   _occupied;
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
}

3、分析
a)、_bucketsAndMaybeMaskexplicit_atomic的泛型变量,因此实际大小为泛型的大小,即uintptr_t的大小,uintptr_t的源码为:typedef unsigned long uintptr_t;因此占8字节
b)、union为共用体,内存大小为最大的成员的大小。
1)struct中,_maybeMaskmask_t,源码为typedef uint32_t mask_t;4字节uint16_t大小为2字节。结构体最大占用内存4 + 2 + 2 = 8字节
2)_originalPreoptCachepreopt_cache_t *,结构体指针类型,我们知道指针类型的大小为8字节
3)其实换个角度来看,union中,我们只需要看_originalPreoptCache的大小即可知道union占用大小为8字节
4、cache_t所占内存大小为:16字节
到此为止,我们只需要或许到类的首地址后,将其平移isa:8 + superclass:8 + cache_t:16 = 32字节才能得到bits。简化后的objc_class源码如下:


struct objc_class : objc_object {
    Class ISA; //8字节
    Class superclass;// 8字节
    cache_t cache;             // 16字节
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
}

下面我们开始获取bits
1、QLPerson设计如下:

@interface QLPerson : NSObject{
    NSString *fullName;
}
@property (nonatomic,copy) NSString *nickName;
@property (nonatomic,assign) NSInteger age;
- (void)test1;
+ (void)test1;
@end

2、对QLPerson类进行lldb调试
3、p *$2->data()objc_class中的方法

class_rw_t *data() const {
   return bits.data();
}

image.png

但是似乎未能得到我们想要的东西。换种思路继续
4、我们继续objc_class向下翻找,治世之尊没有找到能看到类的属性和方法的关键词。后来看到bits后面的注释,我们要的东西是否在class_rw_t里。点进去终于看到了

const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
        }
    }

methods()properties()protocols()这正是我们日思夜想的东西嘛。lldb调试如下:

image.png
由图可知:我们的@property声明的属性,均存在property_list_t中,用上图的lldb调试可以找到属性的值。但是问题来了,我们的方法成员变量均未得到,它们放在哪儿呢?
补充:properties()返回类型为property_array_t,继承自list_array_tt,源码如下:

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};

说明:关于二维数组容器list_array_tt的知识请移步这篇文章
5、properties()探索结束,未能达到我们的目的,我们接着探索methods()

image.png

说明:我们用探索properties的方式来探索methods最终得到了该类的实例方法getter setter方法,达到部分目的,因为,我们的+(void)test2还未出现。
6、methods()探索结束我们仍未找到类方法成员变量ivar的存储位置,我们接着往下探索,在class_rw_t内找到一个ro()方法,源码如下:

const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
        }
        return v.get<const class_ro_t *>(&ro_or_rw_ext);
    }

class_ro_t内部部分源码为:

struct class_ro_t {
    void *baseMethodList;
    const ivar_list_t * ivars;

这个ivars操作如下:

image.png

到此为止,我们拿到了成员变量的存储位置已经搞清楚。

7、类方法探索,换种思路,实例方法也叫做对象方法类方法似乎与对象无关,那么它是否在元类里呢?按照这个思路,我们对QLPerson的元类进行上面的查找操作:

image.png

由图可得到类方法存储在元类中

总结

1、探索类的结构是一个漫长而复杂的过程,有些地方卡在那里,如果不转换思路,将进入死胡同。对类的探索,应该多借鉴前辈的肩膀,偶尔用上帝视角去解决遇到的难题。
2、存储位置:
类的首地址+0x20得到bits
bits->data()得到class_rw_t
a)获取类的属性(@property标记):bits中的class_rw_t中的properties()
b)获取成员变量(类大括号内的声明):bits中的class_rw_t中的ro()
c)获取实例方法(也叫对象方法-()):bits中的class_rw_t中的methods(),每一项需要加.big()来打印
d)获取类方法(+()):元类中的bits->class_rw_t->methods(),每一项需要加.big()来打印

3、以method_array_t为例,结构图如下:

image.png

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

推荐阅读更多精彩内容