iOS 类原理探索:类的结构分析

OC 类原理探索 系列文章

  1. OC 类原理探索:类的结构分析
  2. OC 类原理探索:类结构分析补充
  3. OC 类原理探索:属性的底层原理

前言

上一篇 OC 对象原理探索(三):对象的本质 & isa,介绍了isa的结构,关联到了类,这篇文章主要对类的结构进行分析。

一、isa 分析到元类

先通过lldb调试进行一波探索:

image.png

如上图:通过isa & isa的掩码,最终得到了SSLPerson,但是类中的isa & isa的掩码也得到了SSLPerson这是为什么呢?

是不是内存中存了很多个SSLPerson类对象呢,下面我们来验证一下:

Class class1 = [SSLPerson class];
Class class2 = [SSLPerson alloc].class;
Class class3 = object_getClass([SSLPerson alloc]);
Class class4 = [SSLPerson alloc].class;
NSLog(@"\n%p-\n%p-\n%p-\n%p-",class1,class2,class3,class4);

打印结果:
0x1000081f0-
0x1000081f0-
0x1000081f0-
0x1000081f0-

打印的地址都是相同的,可以判断SSLPerson类对象在内存中只有一个。

我们用MachOView打开本项目的MachO文件 ->Symbol Table->*Symbols-> 搜索框输入class -> 向下滑:

106931623940431_.pic_hd.jpg
  • __OBJC_CLASS_RO_$_SSLPerson是我们创建的SSLPerson类。
  • __OBJC_METACLASS_RO_$_SSLPerson并不是我们创建的,它是系统创建的SSLPersonMETACLASS元类),类对象的isa指针就指向了元类,那么元类的isa指针又指向哪儿里呢。

二、isa 走位图和继承链

1. isa 的走位链

SSLPerson走位打印情况:

// SSLPerson 实例对象
SSLPerson *person = [SSLPerson alloc];
// SSLPerson 类对象
Class class = object_getClass(person);
// SSLPerson 元类
Class metaClass = object_getClass(class);
// SSLPerson 根元类
Class rootMetaClass = object_getClass(metaClass);
// SSLPerson 根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);

NSLog(@"\n实例对象:%p \n类:%p \n元类:%p \n根元类:%p \n根根元类:%p",person,class,metaClass,rootMetaClass,rootRootMetaClass);

打印结果:
实例对象:0x101044470 
类:0x1000081f0 
元类:0x1000081c8 
根元类:0x7fff80843fe0 
根根元类:0x7fff80843fe0

看打印结果,根元类根根元类的地址都是0x7fff80843fe0。可以得出SSLPersonisa指向关系:实例isa -> 类 isa -> 元类 isa -> 根元类 isa -> 根元类

NSObject走位打印情况:

// NSObject 实例对象
NSObject *object = [NSObject alloc];
// NSObject 类
Class class = object_getClass(object);
// NSObject 元类
Class metaClass = object_getClass(class);
// NSObject 根元类
Class rootMetaClass = object_getClass(metaClass);
// NSObject 根根元类
Class rootRootMetaClass = object_getClass(rootMetaClass);
    
NSLog(@"\n实例对象:%p \n类:%p \n元类:%p \n根元类:%p \n根根元类:%p ",object,class,metaClass,rootMetaClass,rootRootMetaClass);

打印结果:
实例对象:0x100562780 
类:0x7fff80844008 
元类:0x7fff80843fe0 
根元类:0x7fff80843fe0 
根根元类:0x7fff80843fe0 

看打印结果,元类根元类根根元类的地址都是0x7fff80843fe0。可以得出NSObjectisa指向关系:实例isa -> 类 isa -> 根元类 isa -> 根元类。可以发现NSObject跟普通类有所不同,isa指针直接指向了根元类

根据上面的分析可以得到isa的走位图:

image.png

2. 继承链

看下面的打印结果:

// SSLStudent 元类
Class tMetaClass = object_getClass(SSLStudent.class);
// SSLStudent 元类的父类
Class tSuperClass = class_getSuperclass(tMetaClass);
NSLog(@"SSLStudent 元类:%p",tMetaClass);
NSLog(@"SSLStudent 元类的父类:%p",tSuperClass);
    
// LGPerson 元类
Class pMetaClass = object_getClass(SSLPerson.class);
// LGPerson 元类的父类
Class pSuperClass = class_getSuperclass(pMetaClass);
NSLog(@"SSLPerson 元类:%p",pMetaClass);
NSLog(@"SSLPerson 元类的父类:%p",pSuperClass);
    
// NSObject 元类
Class nMetaClass = object_getClass(NSObject.class);
// NSObject 元类的父类
Class nSuperClass = class_getSuperclass(nMetaClass);
NSLog(@"NSObject 元类:%p",nMetaClass);
NSLog(@"NSObject 元类的父类:%p",nSuperClass);

// NSObject 父类
Class superClass = class_getSuperclass(NSObject.class);
NSLog(@"NSObject :%p",NSObject.class);
NSLog(@"NSObject 父类:%p",superClass);

打印结果:
SSLStudent 元类:0x100008178
SSLStudent 元类的父类:0x1000081c8
SSLPerson 元类:0x1000081c8
SSLPerson 元类的父类:0x7fff80843fe0
NSObject 元类:0x7fff80843fe0
NSObject 元类的父类:0x7fff80844008
NSObject :0x7fff80844008
NSObject 父类:0x0
  • SSLStudent 元类的父类SSLPerson 元类的地址都是0x1000081c8
  • SSLPerson 元类的父类NSObject 元类的地址都是0x7fff80843fe0
  • NSObject 元类的父类NSObject的地址都是0x7fff80844008
  • NSObject 父类是空。

因此可以得到类的继承链:

image.png

3.官方走位继承图

isa流程图.png

三、源码分析类的结构

我们打开源码(objc4-818版本)搜索struct objc_class查看类结构。

1. 废弃的类结构

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

在网上我们也会经常看到这段代码,但是我们可以看到OBJC2_UNAVAILABLE,说明这个版本在2.0中已经废弃了,我们就不用再看这个版本。

2. 源码类结构

现在用的类结构版本在objc-runtime-new.h中,定义如下:

struct objc_class : objc_object {
    xxx...
    // Class ISA;
    Class superclass;
    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 {
        return bits.data();
    }
    xxx...
}

可以看到类是一个objc_class类型的结构体,继承于objc_object,我们之前文章已经了解到objc_object结构体中有个isa_t类型的isa成员变量。

objc_class中的成员变量:

  • isa:隐藏变量,继承于objc_object
  • superclass:类指针,指向父类;
  • cache:缓存;
  • bits:内存数据。

四、内存偏移(知识补充)

// 基本类型
int a = 10;
// 对象类型
SSLPerson *person = [SSLPerson alloc]; 
NSLog(@"%p -- %d",&a,a);
NSLog(@"%p -- %@",&person,person);

打印结果:
0x7ffeefbff3ac -- 10
0x7ffeefbff3a0 -- <SSLPerson: 0x100728a20>

a指针指向10的地址,person指针指向[SSLPerson alloc]开辟的内存地址,同时&person指针又指向了person指针的地址。

如图:

image.png
// 数组指针
int c[4] = {1,2,3,4};
int *d   = c;
NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
NSLog(@"%p - %p - %p",d,d+1,d+2);

for (int i = 0; i<4; i++) {
    int value =  *(d+i);
    NSLog(@"%d",value);
}

打印结果:
0x7ffeefbff3a0 - 0x7ffeefbff3a0 - 0x7ffeefbff3a4
0x7ffeefbff3a0 - 0x7ffeefbff3a4 - 0x7ffeefbff3a8
1
2
3
4
  • dcc[0]都指向了数组的首地址0x7ffeefbff3a0
  • 对地址进行加1操作就是地址偏移了sizeof(int)的大小,得到下一个元素的地址0x7ffeefbff3a40x7ffeefbff3a80x7ffeefbff3ac
  • 通过*(指针)操作得到地址所存储的值。
image.png

五、类结构的内存计算

1. 计算 bits 偏移大小

上面我们知道类结构中有isasuperclasscachebits四个成员变量。isa8个字节,superclass是结构体指针也占8个字节,下面来看下cache占多少个字节。

struct cache_t {

private:
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask; // 8
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask;// 4
#if __LP64__
            uint16_t                   _flags;  // 2
#endif
            uint16_t                   _occupied; // 2
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;// 指针 8
    };
}
  • cache_t的大小为_bucketsAndMaybeMaskunion的和。
  • 点进explicit_atomic<uintptr_t>中可以看到初始化和方法都是范型,所以_bucketsAndMaybeMask的大小就是<uintptr_t>的大小为8字节;
  • 也可得出union的大小为8字节,整个cache_t结构体的大小就是16字节。

所以得到,从类的首地址偏移到bits需要8 + 8 + 16 = 32个字节也就是0x20

2. 通过偏移获取 bits

lldb进行调试获取bitsclass_rw_t的值:

image.png

3. bts 中 class_rw_t 结构分析

接下来我们要探究class_rw_t方法属性的存储,打开源码,看一下class_rw_t中有些什么:

struct class_rw_t {
    xxx...
    
    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};
        }
    }
}
  • methods():获取方法函数,返回值为method_array_t类;
  • properties():获取属性,返回值为property_array_t类;

看下method_array_tproperty_array_t的定义:

class method_array_t : 
    public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
    xxx...
};

class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    xxx...
};

template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
    xxx...
}

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
struct property_t {
    const char *name;
    const char *attributes;
};

struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {
    xxx...
}
struct method_t {
    xxx...
    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };
    xxx...
}

template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
    xxx...
    Element& getOrEnd(uint32_t i) const { 
        ASSERT(i <= count);
        return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
    }
    Element& get(uint32_t i) const { 
        ASSERT(i < count);
        return getOrEnd(i);
    }
    xxx...
}
  • property_array_tmethod_array_t继承自list_array_tt
  • list_array_ttList对应property_list_tmethod_list_t
  • property_list_tmethod_list_t继承自entsize_list_tt
  • entsize_list_tt中有get()方法可以获取列表中元素;
  • property_t中有nameattributes成员变量;
  • method_t中有big结构体,结构体中有nametypesimp成员变量。

4. lldb 查看属性列表:property_list_t

image.png

properties()函数,最终得到了属性nameage,并没有获取到_hoby成员变量;

5. lldb 查看方法列表:method_list_t

image.png

methods()函数,最终得到了方法namesetNameagesetAgeeat,并没有获取到+ (void)run

6. lldb 查找类方法

类方法存储在元类中,lldb验证:

image.png

7. 成员变量结构分析

image.png
  • 最终找到ivar_list_t * ivars变量;
  • ivar_list_tmethod_list_tproperty_list_t一样都继承自entsize_list_tt

8. lldb 查看成员变量:ivar_list_t

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

推荐阅读更多精彩内容