iOS-OC底层四:类的属性、方法和协议

一、对象、ISA、类、元类、根元类间的关系

核心知识点:

  • 类声明对象,给对象分配多少内存是依据类,对象的ISA指向类
  • 对象在内存中第一个8字节储存的是ISA
  • ISA中的shiftClass段就是类,即类为对象的isa&mask
  • 类也是一种对象,objc_class继承自objc_object,类的第一个8字节也是ISA,所以万物皆对象,万物皆有isa
  • 类的ISA中的shiftClass为元类,即元类为类的isa&mask,类是由元类进行声明
  • NSObject类的ISA指向NSObject类的元类,NSObject类的元类的ISA指向它自己
  • 元类的ISA指向元类的元类,我们称NSObject类的元类为根元类
  1. 元类的创建和声明是编译器完成,元类用来存储类方法的相关信息,元类本身是没有名称的,由于与相关联,所以使用了同类名一样的名称
  2. 对象之间不存在继承关系,只有类和元类之间存在继承关系
  3. 类与元类在系统中,只存在一份,即类对象只有一份
  4. 无论类继承层次多深,类的元类的ISA指向的都是NSObject根元类,由对象开始查找ISA,第三次及以后绝对指向NSObject
ISA指向和类继承关系图.png

1.1 准备验证代码

#import 
#import 

@interface TESTPerson : NSObject
@property (nonatomic, copy) NSString *testname;
@end

@implementation TESTPerson

@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        TESTPerson *obj = [TESTPerson alloc];
        TESTPerson *obj1 = [TESTPerson alloc];
        NSLog(@"class:%@",[TESTPerson class]);
        NSLog(@"class:%@",[obj class]);
        NSLog(@"class:%@", object_getClass(obj));
        NSLog(@"\n");
        NSLog(@"class:%@",[[obj class] class]);
        NSLog(@"class:%@",[[[obj class] class] class]);
        NSLog(@"class:%@",[[[[obj class] class] class] class]);
        NSLog(@"\n");
        NSLog(@"class:%@", object_getClass(object_getClass(obj)));
        NSLog(@"class:%@", object_getClass(object_getClass(object_getClass(obj))));
        NSLog(@"class:%@", object_getClass(object_getClass(object_getClass(object_getClass(obj)))));
        NSLog(@"\n");
        
    }
    return 0;
}

1.2 获取类的内存分布的三种方式:

  1. 根据对象的实例化方法:[obj class]
  2. 根据类对象的类方法:[TESTPerson class]
  3. 根据runtime中的object_getClass(obj)

为什么嵌套调用时,结果不一致?

  • class获取的永远是obj的类,因为传的是self
  • object_getClass会将obj->getIsa()当成下一次的obj,如果嵌套两层,相当于调用两次getIsa(),对应的代码obj->getIsa()->getIsa()

三种方式的源码分析:

- (Class)class {
    return object_getClass(self);
}
//第一步,走类方法class
+ (Class)class {
    return self;
}

//第二步,走objc_opt_class获取类信息
Class
objc_opt_class(id obj)
{
#if __OBJC2__
    if (slowpath(!obj)) return nil;
    Class cls = obj->getIsa();
    if (fastpath(!cls->hasCustomCore())) {
        return cls->isMetaClass() ? obj : cls;
    }
#endif
    return ((Class(*)(id, SEL))objc_msgSend)(obj, @selector(class));//走这里
}
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

1.3 Class的本质是objc_class

打开源码,在main方法里面输入Class *class。然后通过Class点进去,可以发现都是typedef struct objc_class *Class;,这说明Class(类)都是struct objc_class创建而来的。

#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

可以看到objc_class是继承于objc_object的,而objc_class本身是没有isa的,isa继承自objc_object,说明万物皆对象,对象是由类创建的,类由元类创建。

objec_class继承自object_object.png

1.4 验证类信息只有一份

验证类信息只有一份.png

二、类结构分析

2.1 重要:分析方法

分析的原理:基于类在内存中占用的地址是连续的,我们只需要从类的定义中,算出我们想查找的信息在哪个位置。具体做法就是通过struct objc_class : objc_object {} ,在这个定义类的结构体中,计算各成员变量所占的字节数,然后通过类的首地址,做相应字节的偏移,就能得到我们想要的信息。

再仔细看看objc_class的源码:

struct objc_class : objc_object {
  objc_class(const objc_class&) = delete;
  objc_class(objc_class&&) = delete;
  void operator=(const objc_class&) = delete;
  void operator=(objc_class&&) = delete;
    // 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
    
    //底下全是方法
    ......
}

C语言无法在结构体中定义函数,但是C++可以做到。C++中delete是删除地址空间,此处operator用作重载运算符。我们大概可以猜到前4句代码是析构函数功能,用作内存释放的函数。其他函数可以很确定绝对是函数的声明或者是内部函数实现。

所以我要弄清以下问题:

方法是否占用内存空间,会不会造成成员变量偏移?

通过以下代码予以验证

static声明的变量和方法不影响偏移位置.png

同样的道理:计算struct cache_t所占用的内存大小:

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic _buckets; // 是一个结构体指针类型,占8字节
    explicit_atomic _mask; //是mask_t 类型,而 mask_t 是 unsigned int 的别名,占4字节
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic _maskAndBuckets; //是指针,占8字节
    mask_t _mask_unused; //是mask_t 类型,而 mask_t 是 uint32_t 类型定义的别名,占4字节
    
#if __LP64__
    uint16_t _flags;  //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节
#endif
    uint16_t _occupied; //是uint16_t类型,uint16_t是 unsigned short 的别名,占 2个字节

计算出cache类的内存大小 = 12 + 2 + 2 = 16字节。

我们所需要查看的信息在class_data_bits_t bits;中,在bits前面有3个成员变量,isa,superclass,这两个都是Class类型,都占有8字节,cache占用16字节,那么bits的内存就是基于类的首地址偏移32字节。

2.2 class_data_bits_t具体操作

什么是class_data_bits_t?在源码中,全局搜索,共有三处。

//第一处
// class_data_bits_t is the class_t->data field (class_rw_t pointer plus flags)
// The extra bits are optimized for the retain/release and alloc/dealloc paths.

//第二处
struct class_data_bits_t {
    friend objc_class;

    // Values are the FAST_ flags above.
    uintptr_t bits;

     // 代码过多,自动省略
    ...
public:

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    // 代码过多,自动省略
    ...
};

//第三处
class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

struct class_rw_t{...} 中,有我们熟悉的methodspropertiesprotocols。所以,我们应该先获取bits中class_rw_t *类型的data,再访问data中的方法,属性和协议。

你可能感兴趣的:(iOS-OC底层四:类的属性、方法和协议)