iOS底层原理

iOS底层原理-003 类的结构分析

2020-09-17  本文已影响0人  杨奇

准备工作

首先定义两个类

@interface GLPerson : NSObject
{
    NSString *happy;
}
@property (nonatomic, copy) NSString *cjl_name;
- (void)sayHello;
+ (void)sayBye;
@end

@implementation GLPerson
- (void)sayHello {
    
}
+ (void)sayBye {
    
}
@end
@interface GLTeacher : GLPerson
@end
@implementation GLTeacher
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
         GLPerson *person = [GLPerson alloc];
         GLTeacher *teacher = [GLTeacher alloc];
         NSLog(@"\n%@\n%@",person,teacher);
        
    }
    return 0;
}

元类

什么是元类

直接上图

isa指向

从图中可以得出
1.person的isa指向GLPerson类
2.GLPerson的isa指向GLPerson元类
3.GLPerson元类的isa指向NSObjct根元类
4.NSObjct根元类的isa指向自己(地址相同)

可以看出最后的isa指向根元类,那这里的NSObjct和我们开发中用到的NSObjct类是同一个类么,我们两种方法验证一下
通过lldb验证


lldb验证

代码验证
用三种不同的方法获取类

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

输出结果如下


代码验证

从图中可以看出,NSObject类的元类也是NSObject根元类,NSObject根元类的元类也是NSObject根元类。所以可以得出一个结论:内存中只存在存在一份根元类NSObject,根元类的元类是根元类自己。
我们通过不同方式获取的NSObject类对象,他们的地址相同,所以NSObject在内存中只有一份。同理继承于NSObject的类对象在内存中只存在一份。

isa走位图&继承关系图

isa走位&继承关系

objc_class & objc_objectisa

走位我们理清楚了,又来了一个新的问题:为什么没有对象都有isa属性呢?
不提到两个结构体类型:objc_class & objc_object
我们之前提及NSObject的底层编译是NSObject_IMPL结构体

struct NSObject_IMPL {
    Class isa;
};
typedef struct objc_class *Class;

在objc4源码中搜索objc_class的定义,源码中对其的定义有两个版本

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; // 表示被废除
struct objc_class : objc_object {
    // Class ISA;     // 8
    Class superclass; // 8
    cache_t cache;    // 16    // 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);
    }
//太长了 省略一部分
    unsigned classArrayIndex() {
        return bits.classArrayIndex();
    }
};

从新版的定义中,可以看到 objc_class 结构体类型是继承自 objc_object的。objc_object是这么定义的:

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

objc_class

总结

所有的对象都继承于obje_object。所有的类都继承于objc_class。objc_继承于objc_class。所以说所有的对象,类,元类都继承于objc_object。objc_object有isa属性,所以对象,类,元类都有isa属性。(万物皆对象)

类信息的内容

准备工作:

定义一个LGPerson类,添加以下属性和方法

@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *nickName;
@property (nonatomic) int hobby; 
- (void)sayHello;
+ (void)sayBye;

我们要怎么能知道类的类内容呢?那现在就要打开objc_class的源码。

来自于objc-runtime-new
可以看到有四个属性:
isa属性:继承自objc_object,占8字节。
superclass 属性:Class类型的指针,占8字节。
cache属性:cache_t类型我们并不知道它占多少个字节,需要我们进入内部看。
![struct cache_t]](https://img.haomeiwen.com/i1705709/2d4f9eb891b8c468.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

可以看到是一个结构体类型,那就是8字节了。
其实不对,只有结构体指针是8字节,而这时并不是指针,结构体的大小要看内部成员的总和。所以不是8字节。
注意图中,static在全局区,所以我们这里不需要计算。
所以就剩下了以下属性:

#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets; 
    explicit_atomic<mask_t> _mask; 
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
#if __LP64__
    uint16_t _flags; 
#endif
    uint16_t _occupied; 
第一种情况

_buckets:bucket 是一个struct类型的指针,所以占8字节。

///类型转换
typedef uint32_t mask_t
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;

_mask:是mask_t类型,并不是常用类型,但是可以看到是其实是int类型所以占用4字节。
flags:uint16是short的转换。占2字节。
_occupied:也占2字节。
占用内存为:8+4+2+2 = 16字节。

第二种情况

_maskAndBuckets:uintptr_t>类型,占用8字节
占用内存为:8+4+2+2 = 16字节。

所以cache占用16字节内存
前三个属性共占用内存32字节
根据内存偏移,获取bits需要将累的首地址平移(前三位的内存和)32位就可以得到

bits:

通过lldb获取属性: property_list


读取bits存储的属性

通过lldb获取方法: methods_list


通过lldb获取方法

总结

上一篇 下一篇

猜你喜欢

热点阅读