004-ivars

isa_的指向

上一篇我们了解了isa_t的结构, isa是类之中的一个成员. 现在我们继续往下搞一下. 先看下面两幅图我使用的是x86的mask:


01.png
02.png
0x0000000100004348 LGPerson -> 0x0000000100004320 LGPerson-> 0x0000000100353190 NSObject
0x00000001003531e0 NSObject -> 0x0000000100353190 NSObject
两个不同的实例, 最终isa都指向了同一个地址的NSObject的地址, 而且NSObject继续mask之后都是本身的地址. 

很奇怪, 一个实例对象的isa指向了地址不同的自己又指向了一个NSObject的东西, 一个NSObject的实例对象也是指向了两个不同地址的自己. 但是最终都指向了一个地方, 这到底是什么鬼东西???

Class class1 = [LGPerson class];
Class class2 = [LGPerson alloc].class;
Class class3 = object_getClass([LGPerson alloc]);
Class class4 = [LGPerson alloc].class;

log:
0x100004380
0x100004380
0x100004380
0x100004380

我们发现不管是实例的class, 还是类的class都是指向同一个地址, 有两个LGPerson我们也不清楚. 我们去macho的符号表, 然后就看到了下图:

03.png

一个_OBJC_METACLASS$_LGPerson的东西, 一个MetaClass的东西, 我们自己创建的都是Class, 并没有创建MetaClass, 所以只能是系统或者编译器在我们创建Class的时候帮我们生成的一个类别:

上面的整个过程其实可以猜想出:

普通对象:
实例isa -> 类 isa ->元类isa -> NSObject isa(自己指向自己)
NSObject对象:
实例isa -> 类 isa ->元类isa(自己指向自己)

最终的目的地都是同一地址

接下来就是官方文档的一副流程图:


isa流程图.png

superclass的走向

由上图可知, superclass的继承链, 我们可以用代码验证一下:

void testSuperClass() {
    LGTeacher *t = [LGTeacher alloc];
    LGPerson  *p = [LGPerson alloc];
    NSLog(@"%@-%@",t,p);
    
    NSLog(@"%@",class_getSuperclass(LGTeacher.class));
    NSLog(@"%@",class_getSuperclass(LGPerson.class));
    NSLog(@"%@",class_getSuperclass(NSObject.class));
}

2021-06-20 09:09:08.740327+0800 ObjcBuild[78576:3928152] <LGTeacher: 0x101d047e0>-<LGPerson: 0x101d04770>
2021-06-20 09:09:08.740447+0800 ObjcBuild[78576:3928152] LGPerson
2021-06-20 09:09:08.740583+0800 ObjcBuild[78576:3928152] NSObject
2021-06-20 09:09:08.740719+0800 ObjcBuild[78576:3928152] (null)

上述代码我们可以得知类对象的继承关系, 最终NSObject类是没有父类的.
接下来我们继续验证一下元类的superClass走向:

先确认继承关系 LGTeacher -> LGPerson -> NSObject

    NSObject *obj = [NSObject alloc];
    Class class1 = object_getClass(obj);
    Class classMeta = object_getClass(class1);
    Class classRootMeta = object_getClass(classMeta);
    Class classRootRootMeta = object_getClass(classRootMeta);
    
    NSLog(@"\n%p 实例对象\n%p 类\n%p 元类\n%p 根元类\n%p 根根元类",obj,class1,classMeta,classRootMeta,classRootRootMeta);


    Class tMetaClass = object_getClass(LGTeacher.class);
    Class tsuperClass = class_getSuperclass(tMetaClass);
    NSLog(@"LGTeacher的父类->%@ - %p",tsuperClass,tsuperClass);

    Class pMetaClass = object_getClass(LGPerson.class);
    Class psuperClass = class_getSuperclass(pMetaClass);
    NSLog(@"LGPerson的父类->%@ - %p",psuperClass,psuperClass);
        
    Class nsuperClass = class_getSuperclass(NSObject.class);
    NSLog(@"NSObject的父类%@ - %p",nsuperClass,nsuperClass);
    
    Class rnsuperClass = class_getSuperclass(classMeta);
    NSLog(@"NSObject根源类的父类%@ - %p",rnsuperClass,rnsuperClass);

0x101042830 实例对象
0x1003571e0 类
0x100357190 元类
0x100357190 根元类
0x100357190 根根元类

LGTeacher的父类->LGPerson - 0x100004448
LGPerson的父类->NSObject - 0x100357190
NSObject的父类(null) - 0x0
NSObject根源类的父类NSObject - 0x1003571e0

整个demo的验证, 最大的亮点还是最后的根源类的地址, 和isa的指向不一样的, 根源类最后的superClass是指向了NSObject类的地址;
和isa流程图中的superClass的指向一致.

类的本质objc_class

之前的文章中我们写过: Class, id是什么

Class 就是 objc_class 结构体指针, 定义为objc_class *
id 就是 objc_object 结构体指针objc_object *

我们看一下objc_class在源码中对应的实际结构:

首先我在源码中搜索如下图:
04.png

上图所示, 这显然不是我要的, 然后我根据指引注释搜索了*Class如下图:
05.png

但是我在点击objc_class的时候却发现点击不进去, 然后我直接搜索了定义


06.png

在objc-runtime-new中找到了, objc_class新的结构, 代码如下:
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;
    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();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
  ...
}

我把比较重要的部分先挑出来, 之前我们说isa_t里面的位域的哪些位置代表了什么, 之前的对齐算法 , 在objc_class里面都有提供对应的方法. 用到什么看什么, 这里不多提了. 继续往下看:
我把上述代码每个结构都点击进去看了一遍, 在class_rw_t结构中发现了methods() 的方法, 还有一个就是调用data()方法实际是bits.data(); 代如下

struct class_data_bits_t {
...省略
public:

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    void setData(class_rw_t *newData)
    {
        ASSERT(!data()  ||  (newData->flags & (RW_REALIZING | RW_FUTURE)));
        // Set during realization or construction only. No locking needed.
        // Use a store-release fence because there may be concurrent
        // readers of data and data's contents.
        uintptr_t newBits = (bits & ~FAST_DATA_MASK) | (uintptr_t)newData;
        atomic_thread_fence(memory_order_release);
        bits = newBits;
    }
...省略
}




struct class_rw_t {
...省略
    Class firstSubclass;
    Class nextSiblingClass;
...省略 

    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};
        }
    }
//在此我们就找到了属性, 方法等列表
}

nextSiblingClass下一个兄弟类, 只要看到Sibling这个单词其实就可以想象常用的数据结构到只有红黑树用到了兄弟节点, 我们可以猜测其实类的底层是以树状结构联系在一起的.

接下来我们怎么查看, 找到这个methodList呢?
在结构体中, 我们想要访问对应的数据, 就是拿到结构体指针然后根据偏移量找到对应的成员. 所以我们只要知道ISA(父类的), superclass和cache的大小, 然后就可以找到bits的地址.

struct objc_class : objc_object {
...
    // Class ISA; 父类的成员, Class就是objc_class *, 是一个指针, 8字节
    Class superclass; //和上面一样8字节
    cache_t cache;             // 未知
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
  ...
}

上述代码可知, 我们只需要找到cache_t的大小 , 就可以根据首地址计算出bits的偏移量, 从而找到bits的结构, cache_t如下:

struct cache_t {
private:
//uintptr_t  typedef unsigned long
    explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    union {
        struct {
            explicit_atomic<mask_t>    _maybeMask; //uint32_t
#if __LP64__
            uint16_t                   _flags; //uint16_t
#endif
            uint16_t                   _occupied; //uint16_t
        };
        explicit_atomic<preopt_cache_t *> _originalPreoptCache;
    };
...
其余的部分, 全都是方法和staic静态变量, 还有const标注的常量, 内存并不在结构体之中, 最终简化的代码如上述

上述代码可知是一个cache_t是一个结构体, 内部的成员是一个

  • explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
    • 首先explicit_atomic点击进入发现是一个泛型设计, 根据传入的数据进行返回的, 所以看uintptr_t就行, uintptr_t是typedef unsigned long无符号长整型8字节 ;
  • union联合体
    • 联合体内部也一样, 直接看preopt_cache_t *这个就是preopt_cache_t的结构体指针8字节, 也可以直接查联合体中的mask_t(4), _flags(2),_occupied(2)也是8字节.

所以上述原因可知, cache_t是一个16字节大小的结构体.
所以可以得到bits的偏移量为8+8+16 = 32 = 0x20;

下面用看一下调试过程:


07.png

我们根据上述可以找到结构体中对应的methods, properties协议等, 但是继续往下却不知道怎么找了. 我们看上述代码可以知道property_array_t的一个结构体, 点击进去看一下, list的实例成员:

class method_array_t : 
    public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
{
///省略
};


class property_array_t : 
    public list_array_tt<property_t, property_list_t, RawPtr>
{
    typedef list_array_tt<property_t, property_list_t, RawPtr> Super;

 public:
    property_array_t() : Super() { }
    property_array_t(property_list_t *l) : Super(l) { }
};

继续看一下list_array_tt结构体
class list_array_tt {
    struct array_t {
        uint32_t count;
        Ptr<List> lists[0];

        static size_t byteSize(uint32_t count) {
            return sizeof(array_t) + count*sizeof(lists[0]);
        }
        size_t byteSize() {
            return byteSize(count);
        }
    };
//////省略
public:
发现了一个list, 我们可以输出一下试试. 打印出来是property_list_t数组, 把数组的ptr取出. 
    union {
        Ptr<List> list;
        uintptr_t arrayAndFlag;
    };
//////省略
}

然后根据list取出数组首地址, 找到数组.
但是找到了数组, 试了很多方法只能打印出数组的首地址, 数据并不知道怎么打印出来:

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};
property_list_t是空实现, 我们全局搜索继承的entsize_list_tt结构

template <typename Element, typename List, uint32_t FlagMask, typename PointerModifier = PointerModifierNop>
struct entsize_list_tt {
    省略大部分代码
    Element& getOrEnd(uint32_t i) const { 
        ASSERT(i <= count);
        return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
    }

//在这里可以找到一个getOrEnd和get方法, 传入下标返回Element元素.
    Element& get(uint32_t i) const { 
        ASSERT(i < count);
        return getOrEnd(i);
    }

    size_t byteSize() const {
        return byteSize(entsize(), count);
    }
    
    static size_t byteSize(uint32_t entsize, uint32_t count) {
        return sizeof(entsize_list_tt) + count*entsize;
    }

};

所以最终就查找到了元素的存储, 并且打印出来


08.png

然后我继续查看了一下method的方法:


09.png

可以打印出list, 但是缺看不到方法名??? 于是我又找到了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;
    };
.....
}

所以直接把big输出出来就好了, 下图


10.png

总结

遗留了两个问题

  • 自己定义的成员变量并没有在属性列表中
  • 自己定义的类方法, 并没有在类的方法列表中

篇幅太长, 下篇继续.

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

推荐阅读更多精彩内容