iOS OC 类原理一

1. 元类的创建时机

前面简单提到元类的创建时机是在编译器,今天我们通过一下两种方法来验证一下:

1.1 打印 元类的指针

首先看下面代码:

main函数之前打印断点,

通过p/x 打印指针,如果能获得指针, 说明已经在内存中申请了内存空间

然后x/4gx打印的内存结构,得到类 的isa,然后isa & 掩码 ISA_MASK获得元类isa,如果这个过程中能正常打印出相应的指针,则能简单验证元类的创建是在编译期创建的,打印结果如下:

1.2 command + B生成可执行文件,然后使用 MachoView 打开程序二进制可执行文件查看

由此,可以验证元类是在编译期创建的,在运行项目alloc之前已经被创建出来了

2. 指针偏移

2.1 普通指针 值拷贝
        int a = 10; //
        int b = 10; //
        LGNSLog(@"%d -- %p",a,&a);
        LGNSLog(@"%d -- %p",b,&b);
//      KC打印: 10 -- 0x7ffeefbff45c
//      KC打印: 10 -- 0x7ffeefbff458
2.2 指针拷贝
        // 对象 - 指针拷贝
        LGPerson *p1 = [LGPerson alloc];
        LGPerson *p2 = [LGPerson alloc];
        LGNSLog(@"%@ -- %p",p1,&p1);
        LGNSLog(@"%@ -- %p",p2,&p2);
//      KC打印: <LGPerson: 0x100753be0> -- 0x7ffeefbff450
//      KC打印: <LGPerson: 0x10074e740> -- 0x7ffeefbff448
2.3 指针偏移
        // 数组指针
        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 = c[i];
            int value = *(d+i);
            LGNSLog(@"%d",value);
        }
        NSLog(@"指针 - 内存偏移");
//      0x7ffeefbff470 - 0x7ffeefbff470 - 0x7ffeefbff474
//      0x7ffeefbff470 - 0x7ffeefbff474 - 0x7ffeefbff478
//      KC打印: 1
//      KC打印: 2
//      KC打印: 3
//      KC打印: 4

首地址数组的第一个元素的地址,&c[0]&c[1],相差一个元素的大小,指针d + 1,相当于偏移一个所占位数的元素的大小

扫描二维码关注公众号,回复: 10749354 查看本文章

3. 类的结构

3.1 类的结构是什么?

通过clang查看看下面代码在c++文件中的编译:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);

        NSLog(@"%@ - %p",person,pClass);
    }
    return 0;
}
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        // id, SEL
        LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
        
        Class pClass = object_getClass(person);
        
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5s_4100t0cd5rn_d7gx0n5wqh8w0000gn_T_main_60f7a3_mi_9,person,pClass);
    }
    return 0;
}

我们探究的的结构,就是Class,在cpp文件中不难发现结构是:

typedef struct objc_class *Class;

可以看出,objc_class类型的 结构体。

struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};

我们知道万物皆对象,objc_class 继承自objc_object,那么我们通过下图方法查看objc_class的源码:

struct objc_class : 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();
    }
    ··· // 方法和函数
}

源码中可以看到,有个隐藏的Class isa(为什么有个隐藏的Class isa?),
隐藏的属性必然是来自于继承继承objc_object ,看objc_object源码:

object源码:

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

那么NSObject的定义是什么样的呢?

其实NSObject的定义是结构体的一种仿写:

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

问: 为什么isaClass类型?

答:万物皆对象,Clss本身继承自object,用来接收isa可以的,早期调用isa就是为了返回,
后期优化了 nonpointer isa

问:objc_classNSObject的关系? objc_objectNSObject的关系?

NSObject 是一种objc_class 的类型,NSObject也是一个类class,底层也是objc_class
OC底层封装的Cobjc_objectNSObject底层编译的写法。
objc_objectobjc_class是底层的实现,对应当前NSObject(Class)NSObject

3.2 类的结构分析

通常我们会在中定义属性成员变量方法,

@interface LGPerson : NSObject{
    NSString *hobby;
}

@property (nonatomic, copy) NSString *nickName;

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

@end

那么在中是如何存储这些定义的属性 成员变量 方法的呢?
接下来我们来研究一下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);

        NSLog(@"%@ - %p",person,pClass);
    }
    return 0;
}

通过x/4gx pClass打印结构,通过前面的查看源码得知如下图:

objc_classClass ISAClass superclass分别占8字节
cache_t cache16字节

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

public: // 下面是函数,函数不占内存
    struct bucket_t *buckets();
    // 方法 
    ···
};

因为objc_classcache_t cache结构体,而不是结构体指针占(结构体指针占8字节), 所以cache_t cache占内存8 + 4 + 4 = 16字节

猜测:属性 成员变量存储在class_data_bits_t bits中,通过指针偏移(偏移原理类比为数组),偏移32字节获取class_data_bits_t bits

探索如下:

pClass首地址0x100001278 + 32 得到 0x100001298(16进制)bits,通过bits.data() 得到class_rw_t *data(),打印如下:

class_rw_t的结构如下:

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

打印*data():

通过命名,猜测属性应该存储在properties中,打印properties,然后并打印其中list:


同理打印methods,一系列操作后如下:

由此我们探究出了属性 方法的存储位置,那么成员变量存储在什么地方呢?

通过查看struct class_rw_t中的const class_ro_t *ro,

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};

里面分别有method_list_t * baseMethodList property_list_t *baseProperties const ivar_list_t * ivars,我们猜测方法 属性 成员变量分别存储在对应的变量中,打印ro结果如下:

由此可以看出LGPerson仅有的一个成员变量 nickName存储在bit.data()中的robaseProperties中,

那么为什么bit.data()property_array_t properties也等打印出成员变量呢?暂时先抛出个问题。

接下来我们用同样的方法分别打印ivars baseMethodList,如图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4iOVL2t-1586242928963)(https://user-gold-cdn.xitu.io/2019/12/28/16f4cc767683200b?w=745&h=355&f=png&s=43820)]
baseMethodList打印:

打印出count = 2,分别打印ivars成员变量,分别为hobby _nickName,再次验证了@property生成的属性,在系统底层会自动生成_属性成员变量,并且会自动生成setter getter

问题:从baseMethodList中并未打印出类方法 sayHappy,那么类方法存储在什么地方呢?

猜测:
实例方法存在 中,那么其实 也是元类创建出来的类对象类方法应该存在元类中。

通过下面代码,分别在元类中打印对象方法类方法

void testInstanceMethod_classToMetaclass(Class pClass){

    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);
    NSLog(@"%s",__func__);
}


打印结果
2019-12-29 12:28:17.714554+0800 LGTest[799:13098] 0x100002198-0x0-0x0-0x100002130
2019-12-29 12:28:17.715541+0800 LGTest[799:13098] testInstanceMethod_classToMetaclass

由打印结果看出,对象方法存在于中,不存在于元类中,类方法存在于元类中,不存在于中。

通过对结构的分析,得出:成员变量存在ivars中,属性存储在baseProperties中,对象方法存储在里面,类方法存储在元类里。
发布了17 篇原创文章 · 获赞 9 · 访问量 170

猜你喜欢

转载自blog.csdn.net/LiangliangSpeak/article/details/105364946