oc类的探索

前言

在前面关于alloc探索的过程中,我们看到了alloc的流程,发现了类对象内存对齐的原则和isa的结构,对象的探索暂时告一段落,那么类究竟是什么样的呢,下面让我们一起瞅一瞅😁。代码比较多,需较多耐性

首先提出以下几个问题
1.什么是类,类的继承关系是什么
2.类的变量存储位置
3.类的方法、方法、协议、扩展存储位置

一、类的探索

首先先创建一个类MHPerson

@interface MHPerson : NSObject {
    NSString *hobby;
    int height;
}

@property(nonatomic, strong) NSString *name;
@property(nonatomic, copy) NSString *peat;
@property(nonatomic, assign) int *age;

+(void)eat;
-(void)sayHello;
@end

然后这样

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

输出结果如下:
MHPerson-0x1000085d8 
MHPerson-0x1000085d8 
MHPerson-0x1000085d8 
MHPerson-0x1000085d8

竟然是一样的,那么用object_getClass打印一下,历史总是惊人的相似,却拥有者显著的差别(由于太长只打印两个):

NSLog(@"\n %@-%p \n %@-%p \n %@-%p \n %@-%p",object_getClass(class1),object_getClass(class1),object_getClass(class2),object_getClass(class2));
结果:
 MHPerson-0x1000085b0 
 MHPerson-0x1000085b0 

有些懵,为什么他们都是MHPerson 地址却不一样咧?来个断点打印

(lldb) x/4gx 0x1000085d8
0x1000085d8: 0x00000001000085b0 0x000000010036a140
0x1000085e8: 0x0000000101237be0 0x000280380000000f

(lldb) x/4gx 0x1000085b0
0x1000085b0: 0x000000010036a0f0 0x000000010036a0f0
0x1000085c0: 0x0000000100743e00 0x0003e03100000007

(lldb) po 0x000000010036a0f0
NSObject

我去,好像发现了一丢丢规律
0x1000085d8 (MHPerson) 指向了0x1000085b0(MHPerson),
而后者指向了0x000000010036a0f0(NSObject)
等等!!!

0x1000085b0: 0x000000010036a0f0 0x000000010036a0f0

这两个地址竟然是一样的,我的天啊!顺着刚刚的思路再往下面打印:
(lldb) x/4gx 0x000000010036a0f0
0x10036a0f0: 0x000000010036a0f0 0x000000010036a140
0x10036a100: 0x000000010132a960 0x0002e0310000000f
(lldb) po 0x000000010036a140
NSObject

所以是
MHPerson(0x1000085d8) -> MHPerson(0x1000085b0) -> NSObject(0x000000010036a0f0) -> NSObject(0x000000010036a140)

为什么是这样我们先留个悬念哈!

二、类的探究

准备工作
首先先把之前大神cooci编译的oc源码下下来;
再把刚刚的类MHPerson放进来。

开始探究
首先,我们的主角(MHPerson)是继承自NSObject的,所以,让我们看看主角的爸爸长成什么样子

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

然后就找了一下 @interface Class ,出人意料的是,并没有找到,然后 发现了OBJC_ISA_AVAILABILITY,全局搜一下:
记住要考的

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

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 

objc_class不适用objc2 ,所以搜一下objc_object,芜湖! 好多! 经过仔细寻找 发现了一个

struct objc_class : objc_object {
	。。。省略部分代码。。。
    // 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
    。。。。。省略n多代码。。。。

看到了superclass、ISA ,于是我确定了就是他,NSObject的具体构成,经过之前x/4gx 0x1000085d8 ; x/4gx 0x1000085b0 x/4gx ;0x000000010036a0f0;打印 发现 第一个地址是ISA,
第二个应该对应superclass,芜湖!superclass,所以这里
↓需要用的哦
MHPerson(0x1000085d8) -> MHPerson(0x1000085b0) -> NSObject(0x000000010036a0f0) -> NSObject(0x000000010036a140)
↑需要用的哦
MHPerson(0x1000085b0)竟然是MHPerson(0x1000085d8) 的爸爸,不可思议!
明明是同样的名字 ,就地址不一样,再留一个悬念罒ω罒

上面咱们说道第一个地址是ISA,第二个应该对应superclass,所以这两个分别占8个字节,cache根据后面注释:formerly cache pointer and vtable,是之前的虚值函数和指针缓存,那占用空间大小是多少呢? 点进去,发现:

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;
    };
   。。。。。省略n多代码。。。。

一个联合体 一个uintptr_t ,根据类对象的内存这篇文章,联合体的大小为preopt_cache_t *的大小,是8字节 用sizeof(uintptr_t)输出也为8字节,所以cache占用16字节,

芜湖!前三个确定了,那第四个bits 注释是class_rw_t * plus custom rr/alloc flags 找到了 custom、alloc,都是想要的,哼哼!那么根据内存偏移 0x1000085d8偏移32个字节,就是+0x20
= 0x1000085f8,知道了bits的地址,类型是class_data_bits_t *,打印:

(lldb) p (class_data_bits_t *)0x1000085f8
(class_data_bits_t *) $3 = 0x00000001000085f8

点进去,经过筛选,发现有我们这次需要用的代码如下所示:

struct class_data_bits_t {
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;
       。。。。。省略n多代码。。。。
     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;
    }

    // Get the class's ro data, even in the presence of concurrent realization.
    // fixme this isn't really safe without a compiler barrier at least
    // and probably a memory barrier when realizeClass changes the data field
    const class_ro_t *safe_ro() const {
        class_rw_t *maybe_rw = data();
        if (maybe_rw->flags & RW_REALIZED) {
            // maybe_rw is rw
            return maybe_rw->ro();
        } else {
            // maybe_rw is actually ro
            return (class_ro_t *)maybe_rw;
        }
    }
       。。。。。省略n多代码。。。。

感觉要找到了,先看看data(class_rw_t *)类型的,打印一下:

(lldb) p $3->data()
(class_rw_t *) $7 = 0x00000001007484e0
(lldb) p *$7
(class_rw_t) $8 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000248
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
(lldb) 

好像没有想要的,但是按照我对data的理解,里面肯定是数据啊,所以老规矩,点class_rw_t进去:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;

private:
    using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;

    const ro_or_rw_ext_t get_ro_or_rwe() const {
        return ro_or_rw_ext_t{ro_or_rw_ext};
    }

    void set_ro_or_rwe(const class_ro_t *ro) {
        ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
    }

    void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
        // the release barrier is so that the class_rw_ext_t::ro initialization
        // is visible to lockless readers
        rwe->ro = ro;
        ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
    }

    class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);

public:
    void setFlags(uint32_t set)
    {
        __c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
    }

    void clearFlags(uint32_t clear) 
    {
        __c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
    }

    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear) 
    {
        ASSERT((set & clear) == 0);

        uint32_t oldf, newf;
        do {
            oldf = flags;
            newf = (oldf | set) & ~clear;
        } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
    }

    class_rw_ext_t *ext() const {
        return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
    }

    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
        } else {
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
        }
    }

    class_rw_ext_t *deepCopy(const class_ro_t *ro) {
        return extAlloc(ro, true);
    }

    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);
    }

    void set_ro(const class_ro_t *ro) {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
        } else {
            set_ro_or_rwe(ro);
        }
    }

    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};
        }
    }
};

超长的一段代码,发现了几个常见的字段 properties、protocols、methods,还有一个ro, 重新运行一下

(lldb) x/6gx 0x1000085d8
0x1000085d8: 0x00000001000085b0 0x000000010036a140
0x1000085e8: 0x000000010134d0f0 0x000280380000000f
0x1000085f8: 0x000000010134c774 0x0000000100008628
(lldb) p (class_data_bits_t *)0x1000085f8
(class_data_bits_t *) $0 = 0x00000001000085f8
(lldb) p $0->data()
(class_rw_t *) $1 = 0x000000010134c770
(lldb) p *$1
(class_rw_t) $2 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000248
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}
(lldb) 

然后到我们刚刚到的地方 class_rw_t $2,先看看properties里面有什么:

(lldb) p  $2.properties()
(const property_array_t) $5 = {
  list_array_tt<property_t, property_list_t, RawPtr> = {
     = {
      list = {
        ptr = 0x0000000100008258
      }
      arrayAndFlag = 4295000664
    }
  }
}
(lldb) p $5.list.ptr
(property_list_t *const) $6 = 0x0000000100008258
(lldb) p *$6
(property_list_t) $7 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 3)
}
(lldb) 

发现最后面是一个数组,有三个元素,count = 3

(lldb) p $7.get(0)
(property_t) $9 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")
(lldb) p $7.get(1)
(property_t) $10 = (name = "peat", attributes = "T@\"NSString\",C,N,V_peat")
(lldb) p $7.get(2)
(property_t) $11 = (name = "age", attributes = "T^i,N,V_age")
(lldb)

分别对应三个属性,芜湖,属性找到了,接下来就是methods,一共七个:

(lldb) p $2.methods()
(const method_array_t) $12 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008100
      }
      arrayAndFlag = 4295000320
    }
  }
}
(lldb) p $12.list.ptr
(method_list_t *const) $13 = 0x0000000100008100
(lldb) p *$13
(method_list_t) $14 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 7)
}
(lldb) p $14.get(0)
(method_t) $15 = {}

空的 ? 点进去看一看,

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;
    };
      。。。。。省略n多代码。。。。

有个big的结构体,打印一下看一哈:

(lldb) p $14.get(0).big()
(method_t::big) $16 = {
  name = "peat"
  types = 0x0000000100003f69 "@16@0:8"
  imp = 0x0000000100003ab0 (KCObjcBuild`-[MHPerson peat])
}
peat的getter方法

全部打印 分别是三个属性的getter/setter方法和sayHello方法,但是没有类方法方法eat和成员变量 hobby和height
所以可以推断出protocols应该和代理之类的东西,那剩下的ro是什么呢?打印一下看看:

lldb) p $2.ro()
(const class_ro_t *) $24 = 0x00000001000080b8
(lldb) p *$24
(const class_ro_t) $25 = {
  flags = 0
  instanceStart = 8
  instanceSize = 48
  reserved = 0
   = {
    ivarLayout = 0x0000000000000000
    nonMetaclass = nil
  }
  name = {
    std::__1::atomic<const char *> = "MHPerson" {
      Value = 0x0000000100003f34 "MHPerson"
    }
  }
  baseMethodList = 0x0000000100008100
  baseProtocols = nil
  ivars = 0x00000001000081b0
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x0000000100008258
  _swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $25.weakIvarLayout
(const uint8_t *const) $26 = 0x0000000000000000
(lldb) p $25.baseProperties
(property_list_t *const) $27 = 0x0000000100008258
(lldb) p * $27
(property_list_t) $28 = {
  entsize_list_tt<property_t, property_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 16, count = 3)
}
(lldb) 

突然发现太多了,又重新运行,执行到$4=ro

(lldb) p/x $4.ivars
(const ivar_list_t *const) $6 = 0x00000001000081b0
(lldb) p *$6
(const ivar_list_t) $7 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0, PointerModifierNop> = (entsizeAndFlags = 32, count = 5)
}
(lldb) p $7.get(0)
(ivar_t) $8 = {
  offset = 0x0000000100008570
  name = 0x0000000100003e37 "hobby"
  type = 0x0000000100003f58 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
(lldb) p $7.get(1)
(ivar_t) $9 = {
  offset = 0x0000000100008578
  name = 0x0000000100003e3d "height"
  type = 0x0000000100003f64 "i"
  alignment_raw = 2
  size = 4
}
(lldb) 

找到了成员变量 hobby和height ,还有三个属性正好五个,原来存在这里,
那类方法在哪里呢,这要找到上面,

`MHPerson(0x1000085d8) -> MHPerson(0x1000085b0) -> NSObject(0x000000010036a0f0) -> NSObject(0x000000010036a140)`

既然他这里找不到, 去他爸爸(0x1000085b0)那里看一下,用同样的方法,重新运行,嘻嘻:

(lldb) x/6gx 0x1000085b0
0x1000085b0: 0x000000010036a0f0 0x000000010036a0f0
0x1000085c0: 0x0000000101240a70 0x0003e03100000007
0x1000085d0: 0x0000000101389f34 0x00000001000085b0
(lldb) p/x (class_data_bits_t *)0x1000085d0
(class_data_bits_t *) $0 = 0x00000001000085d0
(lldb) p $0->data()
(class_rw_t *) $1 = 0x0000000101389f30
(lldb) p *$1
(class_rw_t) $2 = {
  flags = 2684878849
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4314106801
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff8034eeb0
}
(lldb) p $2.methods()
(const method_array_t) $3 = {
  list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
     = {
      list = {
        ptr = 0x0000000100008098
      }
      arrayAndFlag = 4295000216
    }
  }
}
(lldb) p $3.list.ptr
(method_list_t *const) $4 = 0x0000000100008098
(lldb) p *$4
(method_list_t) $5 = {
  entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $5.get(0).big()
(method_t::big) $6 = {
  name = "eat"
  types = 0x0000000100003f50 "v16@0:8"
  imp = 0x0000000100003a40 (KCObjcBuild`+[MHPerson eat])
}
(lldb) 

找到了!

小记:
①NSObject是objec_class继承自objc_object;里面有四个成员
Class ISA;
Class superclass; //父类
cache_t cache; // 历史属性指针缓存
class_data_bits_t bits; // 数据信息

②数据信息存在 (class_rw_t *) bits.data()
然后bits 里面有 (method_array_t)mehtods 实例方法 属性的getter、setter方法
(property_array_t)properties 里面是属性列表
(class_ro_t *)ro 里面的
ivars 里面存的是成员变量和属性
baseProperties 是实例方法属性的getter、setter方法
③在父类里面有相同的结构
但是只在mehtods里面有类方法 其他相同地方是nil

所以这个类的爸爸(0x1000085b0)究竟是什么?
找找资料发现了这个
ISA流程图
meta? 元? 元类?
所以是类方法在元类里面?

总结

继承顺序是 子类->父类->根类
指针顺序是 子类 -> 子元类 -> 父元类->根元类
又偷偷打印了一下NSObject的父类和元类 一个是nil、一个是自己但是地址不一样
所以NSObject 在oc中是祖宗级别的存在,但是在源码(c++)里面是
class -> objc_class -> objc_object

所以:

1.类是结构体
2.类的集成关系 子类->父类->根类
  元类集成关系 子类 -> 子元类 -> 父元类->根元类
3.类方法存在于类对应的元类中
4.属性变量在于 类的bits->data() -> properties()中
  方法存在于 bits->data() -> methods() 中
    成员变量存在于 bits->data() -> ro()->ivars() 中(也有属性)

以上是目前对类的探索,浅薄之见,望大家指教

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值