OC底层原理

OC中类的结构

2019-12-23  本文已影响0人  只写Bug程序猿
类和元类的创建时机

类和元类是在编译时就已经创建的的,来下边验证一下
创建一个mac工程,在main函数打断点

image.png
我们p/x JSPerson.class
image.png

在还没有进入main函数时就发现已经存在了指针了,证明这个时候已经存在JSPerson的类了

然后我们拿到类的 isa指针,也就是类的第一个属性0x001d8001000010d9,我们和ISA_MASK进行&运算

image.png

$1即为JSPerson的元类

这个方法可能不足以证明是在编译时产生的,那么我们利用 machoView ,来验证,我们command + b 会在Products文件夹里生成一个可执行文件,我们将这个可执行文件拖到machoView里,

image.png
section64_objc_data里看到已经存在JSPerson的类
通过lldbcommand+b+machoView两种方式验证
由此证明类和元类是在编译时期就创建的.
指针补充
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 普通指针
        // 值拷贝
        int a = 10; //
        int b = 10; //
        NSLog(@"%d -- %p",a,&a);
        NSLog(@"%d -- %p",b,&b);
//     10 -- 0x7ffeefbff49c
//     10 -- 0x7ffeefbff498
//对象 - 指针拷贝
        Person *p1 = [Person alloc];
        Person *p2 = [Person alloc];
        NSLog(@"%@ -- %p",p1,&p1);
        NSLog(@"%@ -- %p",p2,&p2);
//     <Person: 0x1020455f0> -- 0x7ffeefbff490
//     <Person: 0x102037dc0> -- 0x7ffeefbff488
    }
    return 0;
}

image.png

a不是一个指针,内存中有个值10,然后有一个变量名字叫a,然后把10给了a,10本身就在内存中的,属于值拷贝,

image.png

People讲过两次alloc生成两个不同的内存空间,p1,p2分别指向了两个不同的内存空间
&p1,&p2相当于分别指向p1,p2指针的指针

image.png
//数组指针
        int c[4] = {1,2,3,4};
        int *d   = c;
        NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
//      0x7ffeefbff4b0 - 0x7ffeefbff4b0 - 0x7ffeefbff4b4
//      首地址就是数组的第一个元素
        NSLog(@"%p - %p - %p",d,d+1,d+2);
//      0x7ffeefbff4b0 - 0x7ffeefbff4b4 - 0x7ffeefbff4b8
//      因为数组里边放的是int类型,占4个字节,所以内存地址相差4
//      地址+1 就是偏移,偏移位置就是一个所在元素的位数
        for (int i = 0; i<4; i++) {
            // int value = c[I];
            int value = *(d+i);
//          d+1 就是元素所在位置,*就是取出当前地址的值
            NSLog(@"%d",value);
//  1,2,3,4
        }
image.png
类的结构

可以clang rewrite 生成cpp文件,或者直接拿到源码分析,
class其实就是一个objc_class结构体,objc_class继承 与objc_object

struct objc_class : objc_object {
    //隐藏属性,继承与objc_object
    // Class ISA; // 8,
    Class superclass; // 8
    cache_t cache;    // 16 不是8         // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }
    ...
    ...
}
我们先来探索属性的存储
struct class_data_bits_t {
...
    class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
...
}
struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif

    void setFlags(uint32_t set) 
    {
        OSAtomicOr32Barrier(set, &flags);
    }

    void clearFlags(uint32_t clear) 
    {
        OSAtomicXor32Barrier(clear, &flags);
    }

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

bits是一个class_data_bits_t类型,里边有一个class_rw_t*data()的函数得到的是一个class_rw_t类型, class_rw_t也是一个结构体,里边有properties,那么这个肯定是我们要研究的重点了

image.png

打印当前内存段,1.代表当前类的isa,2,代表当前类的superclass . 3代表catch,4代表当前类的bits

4当前为0x000显然没值,所以我们拿到当前isa内存地址进行内存偏移,偏移位数,isa位数为8,superclass 8 位,catch为catch_t类型,为一个结构体,所占位数由内部属性决定(这里为结构体不为结构体指针(8) 所以为16字节)

struct cache_t {
    struct bucket_t *_buckets; // 8字节
    mask_t _mask;  // uint32_t  4字节
    mask_t _occupied; // 4

所以总的偏移位数为 8+8+16 = 32
16进制32表示b进两位为d
所以0x1000023b0 偏移32位之后的内存地址为0x1000023d0
然后我们将结果强转 p (class_data_bits_t *)0x1000023d0得到bits
然后调用bits.data()方法得到一个内存地址0x0000000100fa6660

image.png

然后我们打印 p * $10


image.png

我们要看的是person的 hobby和nickName

@interface JSPerson : NSObject{
    NSString *hobby;
}

@property (nonatomic, copy) NSString *nickName;

- (void)sayHello;
+ (void)sayHappy;

@end

打印他的properties

image.png
会有一个新的类型list_array_tt 点进去会发现可以遍历
const iterator& operator ++ () {
            assert(m != mEnd);
            m++;
            if (m == mEnd) {
                assert(lists != listsEnd);
                lists++;
                if (lists != listsEnd) {
                    m = (*lists)->begin();
                    mEnd = (*lists)->end();
                }
            }
            return *this;
        }

然后打印他的第一个元素发现什么也没有


image.png

那么我们打印他的list


image.png
有一个新的类型 property_list_t ,然后我们在点进去会发现一个first image.png

然后打印p $13.first

image.png
那么hobby去哪里了啊,尝试一下他的下一个元素
image.png
也没有,其实这里是打印不出来hobby的,因为他没有放在rw里边,那么我们不去properties.我们去尝试取一下ro
image.png

然后取里边的ivars

image.png
诶我们发现hobby打印了
那么nickName有没有在这里边么,我们打印下baseProperties
image.png
变量和属性是有区别的,属性会自动生成一个_nickName的成员变量
方法

image.png

静态方法存在metaClass
我们可以用runtime的api来进行打印

 const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
    Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));

    Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
    Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
    
    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    //0x100002250-0x0-0x0-0x1000021e8
    //0x0表示没有得到此方法
    //我们发现元类里边存在一个`sayHappy`的实例方法
const char *className = class_getName(pClass);
    Class metaClass = objc_getMetaClass(className);
    
    Method method1 = class_getClassMethod(pClass, @selector(sayHello));
    Method method2 = class_getClassMethod(metaClass, @selector(sayHello));

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy)); // ?
    
    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    //0x0-0x0-0x1000021e8-0x1000021e8

这里我们发现类和元类都打印了函数指针,那么就有问题了,不是说在元类么,不急,先看下class_getClassMethod的方法实现

Method class_getClassMethod(Class cls, SEL sel)
{
    if (!cls  ||  !sel) return nil;

    return class_getInstanceMethod(cls->getMeta(), sel);
}

是获取元类的实例方法

cache_t
struct objc_class : objc_object {
    // Class ISA; // 8
    Class superclass; // 8
    cache_t cache;    // 16 不是8   
}

cache字面意思是缓存,缓存什么东西呢,接下来探索一下
首先看一下他到底是个什么东西

struct cache_t {
    struct bucket_t *_buckets; // 8
    mask_t _mask;  // 4
    mask_t _occupied; // 4

public:
    struct bucket_t *buckets();
    mask_t mask();
    mask_t occupied();
    void incrementOccupied();
    void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
    void initializeToEmpty();

    mask_t capacity();
    bool isConstantEmptyCache();
    bool canBeFreed();

    static size_t bytesForCapacity(uint32_t cap);
    static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);

    void expand();
    void reallocate(mask_t oldCapacity, mask_t newCapacity);
    struct bucket_t * find(cache_key_t key, id receiver);

    static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};

里边有一个buckets,也是一个结构体

struct bucket_t {
private:
    // IMP-first is better for arm64e ptrauth and no worse for arm64.
    // SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
    MethodCacheIMP _imp;
    cache_key_t _key;
#else
    cache_key_t _key;
    MethodCacheIMP _imp;
#endif

public:
    inline cache_key_t key() const { return _key; }
    inline IMP imp() const { return (IMP)_imp; }
    inline void setKey(cache_key_t newKey) { _key = newKey; }
    inline void setImp(IMP newImp) { _imp = newImp; }

    void set(cache_key_t newKey, IMP newImp);
};

imp 函数的实现
key 缓存的key
下边验证下是否缓存,打一断点


打断点

然后我们x/4xg pClass,拿到首地址,便宜16字节 打印p (cache_t*) 0x1000023c8
然后p *$1 取出地址的值,拿到catch_t,然后p $2._buckets打印buckets,发现什么也没有,
然后断点继续走让方法调用,再次打印,发现impkey都有值了

找到catch_t
打印buckets

buckets是一个结构体数组,换一种用角标访问的形式


以角标方式访问

由此证明,catch在方法执行过一次之后进行缓存,下次调用直接在缓存中进行查找,节约了内存加快了响应速度

上一篇 下一篇

猜你喜欢

热点阅读