iOS 类的结构解析

一、源码静态解读

1、在objc最新版本objc-781,可以在objc-runtime-new.h找到objc_class的定义

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

    class_rw_t *data() const {
        return bits.data();
    }
 //下面都是方法代码省略......
}

在这段源码解读中可得

  • objc_class继承自objc_object,所以拥有属性有isa;还有其中自带的属性superclass;cache;bits
  • 我们知道 sizeof(isa)=8 sizeof(superclass)=8 但是cache是多少,需要我们接下来的分析;
  • 只要我们能算出前面三个属性的大小isa,superclass,cache,就能通过objc_class 首地址偏移得到指向bits的指针。

分析cache类cache_t

通过源码

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic _buckets;
    explicit_atomic _mask;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic _maskAndBuckets;
    mask_t _mask_unused;
    
    // How much the mask is shifted by.
    static constexpr uintptr_t maskShift = 48;
    
    // Additional bits after the mask which must be zero. msgSend
    // takes advantage of these additional bits to construct the value
    // `mask << 4` from `_maskAndBuckets` in a single instruction.
    static constexpr uintptr_t maskZeroBits = 4;
    
    // The largest mask value we can store.
    static constexpr uintptr_t maxMask = ((uintptr_t)1 << (64 - maskShift)) - 1;
    
    // The mask applied to `_maskAndBuckets` to retrieve the buckets pointer.
    static constexpr uintptr_t bucketsMask = ((uintptr_t)1 << (maskShift - maskZeroBits)) - 1;
    
    // Ensure we have enough bits for the buckets pointer.
    static_assert(bucketsMask >= MACH_VM_MAX_ADDRESS, "Bucket field doesn't have enough bits for arbitrary pointers.");
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    // _maskAndBuckets stores the mask shift in the low 4 bits, and
    // the buckets pointer in the remainder of the value. The mask
    // shift is the value where (0xffff >> shift) produces the correct
    // mask. This is equal to 16 - log2(cache_size).
    explicit_atomic _maskAndBuckets;
    mask_t _mask_unused;

    static constexpr uintptr_t maskBits = 4;
    static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
    static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
    
#if __LP64__
    uint16_t _flags;
#endif
    uint16_t _occupied;
//.......省略很多代码
}

我们了解到在编译过程中分三种情况分别是宏定义

1、#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED;
2、#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16;
3、#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4

不过无论哪种情况都会定义一个指针类型 (struct bucket_t * 或 uintptr_t) 和mask_t类型;其大小分别为8字节4字节

Note:
1、explicit_atomic 源码为struct explicit_atomic : public std::atomic {...}是一个std::atomic的继承,对定义的模板类型提供原子的操作,其内存分布大小就是模板类型大小;在这里就是指针类型的8字节
2、mask_t 的定义可以看到在64位系统中typedef uint32_t mask_t;所以就是uint32_t大小4字节

最后我们可以看到还有_flags_occupied,不用我说大家也都知道都是4字节;

所以:cache_t结构体所需内存大小为8+4+2+2 = 16;(属性顺序配的很好,结构体内存对齐刚好排满)

objc_class三个属性大小已经出来分别是sizeof(isa)=8 sizeof(superclass)=8 sizeof(cache)=16所以其三者的和为32用16进制表示即0x20

二、动态调试分析

1、在可运行的源码项目中准备代码

#import 

@interface JJPerson : NSObject
@property(nonatomic, strong) NSString* name;
@end

@implementation JJPerson
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        JJPerson *person = [[JJPerson alloc] init];
        person.name = @"KC is the best";
        NSLog(@" person 实例指针:%p", person);
        NSLog(@"JJPerson类型指针:%p", JJPerson.class);
    }
    return 0;
}

在其中任意处添加断点都行.


图1

我在其中16进制打印了JJPerson类对象首地址 即图中的0x0000000100002388
在前面的分析的到,JJPerson类对象首地址偏移0x20的地址即bits地址;在lldb调试中

(lldb) p (class_data_bits_t*)(0x0000000100002388 + 0x20)
(class_data_bits_t *) $1 = 0x00000001000023a8
(lldb) 

在objc_objc源码中

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

class_rw_t *data() const {
    return bits.data();
}
   //下面都是方法代码省略......
}

了解到bits有一个data()方法,返回的是class_rw_t类型,我们去看看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 ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;

private:
    using ro_or_rw_ext_t = objc::PointerUnion;

    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}.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}.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 *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is())) {
            return v.get();
        } else {
            return extAlloc(v.get());
        }
    }

    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())) {
            return v.get()->ro;
        }
        return v.get();
    }

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

    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is()) {
            return v.get()->methods;
        } else {
            return method_array_t{v.get()->baseMethods()};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is()) {
            return v.get()->properties;
        } else {
            return property_array_t{v.get()->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is()) {
            return v.get()->protocols;
        } else {
            return protocol_array_t{v.get()->baseProtocols};
        }
    }
};

其中我们发现了在结构体最后几个重要的方法 methods() properties() protocols()
,通过名字的猜测相信大家已经知道分别是方法属性协议注意这里返回的手势一个列表list哦!
我们现在不妨看一下这个properties是个啥吧

图2

这里面居然是一个list我们不妨取出来,看看里面到底是个啥,都有啥,下面是我lldb的调试过程

 (lldb) p $2.list   
 (property_list_t *const) $3 = 0x0000000100002138
 (lldb) p $3->count  #查看list大小
 (uint32_t) $4 = 1
 (lldb) p $3->get(0)  #查看第一个元素
 (property_t) $5 = (name = "name", attributes = "T@\"NSString\",&,N,V_name")

可以看到这里面居然存有一个元素,名称为name这不就是我定义的类型吗?

@interface JJPerson : NSObject
@property(nonatomic, strong) NSString* name;
@end

我们用同样的方法可以获取到methods信息如下

(lldb) p $1->data()->methods().list
(method_list_t *const) $11 = 0x00000001000020c0
(lldb) p $11->count
(uint32_t) $12 = 3
(lldb) p $11->get(0)
(method_t) $13 = {
  name = ".cxx_destruct"
  types = 0x0000000100000f42 "v16@0:8"
  imp = 0x0000000100000bf0 (KCObjc`-[JJPerson .cxx_destruct] at main.m:14)
}
(lldb) p $11->get(1)
(method_t) $14 = {
  name = "name"
  types = 0x0000000100000f2f "@16@0:8"
  imp = 0x0000000100000ba0 (KCObjc`-[JJPerson name] at main.m:11)
}
(lldb) p $11->get(2)
(method_t) $15 = {
  name = "setName:"
  types = 0x0000000100000f37 "v24@0:8@16"
  imp = 0x0000000100000bc0 (KCObjc`-[JJPerson setName:] at main.m:11)
}
(lldb)

发现这里居然存有3个方法、分别是[JJPerson .cxx_destruct],[JJPerson name],[JJPerson setName:]
好神奇啊!居然JJPerson实例的方法存储在JJPerson类对象objc_class中.

三 、总结

1、OC语言中我们实例化出来的对象(objc_object或者子类),它的方法存储在对应的类对象(objc_class)中.
2、类对象有实例化出来的属性的信息,但不是其具体的值(比如需要名字属性);

你可能感兴趣的:(iOS 类的结构解析)