iOS-RuntimeRumtime

Runtime:OC对象、类、元类的本质

2019-10-02  本文已影响0人  意一ineyee
零、Runtime是什么
一、OC对象的本质
二、OC类的本质
三、OC元类的本质
四、Runtime关于对象、类、元类的常用API

零、Runtime是什么


Runtime即运行时,它是一个库,这个库是用C、C++、汇编语言编写的,提供的API基本都是C语言的。正是由于这个库的存在,才使得OC具备了面向对象的能力,也使得OC成为了一门动态语言,比如:

2006年苹果发布了OC 2.0,其中对Runtime的很多API做了改进,并把OC 1.0中Runtime的很多API标记为“不可用”、“无效”、“将来会被废弃”等。

但是两套API的核心实现思路还是一样的,而旧API比较简单,所以我们会分析旧API,然后看看新API作了哪些变化,这里有最新的Runtime源码

一、OC对象的本质


1、OC 1.0

通过查看Runtime的源码(objc.h文件),我们得到OC对象的定义如下(伪代码):

typedef struct objc_object *id; // id类型的本质就是一个objc_object类型的结构体指针,所以它可以指向任意一个OC对象

struct objc_object {
    Class isa; // 一个Class类型的结构体指针,存储着一个地址,指向该对象所属的类

    // 自定义的成员变量,存储着该对象这些成员变量具体的值
    NSSring *_name; // “张三”
    NSSring *_sex; // “男”
    int _age; // 33
};

可见OC对象的本质就是一个objc_object类型的结构体,该结构体内部只有一个固定的成员变量isa,它是一个Class类型的结构体指针,存储着一个地址,指向该对象所属的类。当然OC对象内部还可能有很多我们自定义的成员变量,存储着该对象这些成员变量具体的值。

2、OC 2.0

通过查看Runtime的源码(objc-private.h文件),我们得到OC对象的定义如下(伪代码):

typedef struct objc_object *id;

struct objc_object {
    isa_t isa; // 一个isa_t类型的共用体

    // 自定义的成员变量,存储着该对象这些成员变量具体的值
    NSSring *_name; // “张三”
    NSSring *_sex; // “男”
    int _age; // 33
}

union isa_t {
    Class cls;
    
    unsigned long bits; // 8个字节,64位
    struct { // 其实所有的数据都存储在成员变量bits里面,因为外界只访问它,而这个结构体则仅仅是用位域来增加代码的可读性,让我们看到bits里面相应的位上存储着谁的数据
# if __arm64__
#   define ISA_MASK        0x0000000ffffffff8ULL
        unsigned long nonpointer        : 1;
        unsigned long has_assoc         : 1;
        unsigned long has_cxx_dtor      : 1;
        unsigned long shiftcls          : 33; // 对象所属类的地址信息
        unsigned long magic             : 6;
        unsigned long weakly_referenced : 1;
        unsigned long deallocating      : 1;
        unsigned long has_sidetable_rc  : 1;
        unsigned long extra_rc          : 19;
# endif
    };
};

可见OC对象的本质还是一个objc_object类型的结构体,该结构体内部也还是只有一个固定的成员变量isa只不过64位操作系统以后,对isa做了内存优化,它不再直接是一个指针,而是一个isa_t类型的共用体,它同样占8个字节,但其中只有33位用来存储对象所属类的地址信息,还有19位用来存储(对象的引用计数 - 1),其它位上则存储着各种各样的标记信息。

共用体也是C语言的一种数据类型,和结构体差不多,都可以定义很多的成员变量,但两者的主要区别就在于内存的使用。

一个结构体占用的内存等于它所有成员变量占用内存之和,而且要遵守内存对齐规则,而一个共用体占用的内存等于它最宽成员变量占用的内存。结构体里所有的成员变量各自有各自的内存,而共用体里所有的成员变量共用这一块内存。所以共用体可以更加节省内存,但是我们要把数据处理好,否则很容易出现数据覆盖。

3、对象内部的其它成员变量

我们通常说的OC对象一般是指狭义的OC对象——即实例对象,而广义的OC对象则包括实例对象、类、元类。每个类可以创建出N多个实例对象,一个实例对象占用一份内存,它们的成员变量除了isa存储的值一样外,其它成员变量都存储着该对象这些成员变量具体的值。

INEPerson *person1 = [[INEPerson alloc] init];
person1.name = @"张三";
person1.sex = @"男";
person1.age = 33; // person1的age内存中存储的是数值33

INEPerson *person2 = [[INEPerson alloc] init];
person2.name = @"李四";
person2.sex = @"男";
person2.age = 44; // person2的age内存中存储的是数值44

NSLog(@"%p", person1);// 0x10054bd70
NSLog(@"%p", person2);// 0x10054bb80

二、OC类的本质


1、OC 1.0

通过查看Runtime的源码(runtime.h文件),我们得到OC类的定义如下(伪代码):

typedef struct objc_class *Class; // Class类型的本质就是一个objc_class类型的结构体指针,所以它可以指向任意一个OC类

struct objc_class {
    Class isa;
    Class super_class;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;
    const ivar_list_t *ivars;

    cache_t cache;
   
    const char *name;
    long instance_size;
    long version;
    long info;
};

可见OC类的本质就是一个objc_class类型的结构体,该结构体内部有若干个成员变量,其中有几个是我们重点关注的:

2、OC 2.0

通过查看Runtime的源码(objc-runtime-new.h文件),我们得到OC类的定义如下(伪代码):

typedef struct objc_class *Class;

struct objc_class : objc_object {
//    isa_t isa; // objc_class继承自objc_object,所以不考虑内存对齐的前提下,可以直接把isa成员变量搬过来
    Class superclass;
    
    class_data_bits_t bits; // 存储着该类的具体信息,按位与掩码FAST_DATA_MASK便可得到class_rw_t
    
    cache_t cache; // 存储着该类所有的方法缓存信息
}

// class_rw_t结构体就是该类的可读可写信息(rw即readwrite)
struct class_rw_t {
    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;
}

// class_ro_t结构体就是该类的只读信息(ro即readonly)
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; // 存储着该类原来的属性信息
}

可见OC类的本质还是一个objc_class类型的结构体,只不过它的内部结构套了好几层,但我们重点关注的那几个成员变量都还是可以顺利找到的。

其实在编译时,bits成员变量按位与掩码得到的是class_ro_t结构体,该结构体内部存储着我们在类本身定义的方法、属性、协议、成员变量。而在运行时系统才生成了一个class_rw_t结构体,并把类本身的方法、属性、协议和分类里的方法、属性、协议合并到class_rw_t结构体的中,同时设置class_rw_t结构体为可读可写,class_ro_t结构体为只读,bits成员变量按位与掩码得到class_rw_t结构体。

3、获取某个对象所属的类

每个类只有一个,它在内存中只有一份。我们可以通过实例对象的-class方法或Runtime的APIobject_getClass函数来获取某个对象所属的类:

#import <objc/runtime.h>

NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];

Class class1 = [object1 class];
Class class2 = [object2 class];
Class class3 = object_getClass(object1);
Class class4 = object_getClass(object1);

NSLog(@"%p", class1);// 0x7fff93310140
NSLog(@"%p", class2);// 0x7fff93310140
NSLog(@"%p", class3);// 0x7fff93310140
NSLog(@"%p", class4);// 0x7fff93310140

三、OC元类的本质


所谓元类,是指一个类所属的类,我们每创建一个类,系统就会自动帮我们创建好该类所属的类——即元类。元类和类的本质其实都是objc_class,只不过它们的用途不一样,类的methods成员变量里存储着该类所有的实例方法,而元类的methods成员变量里存储着该类所有的类方法。

获取某个类所属的类——元类

每个元类也只有一个,它在内存中也只有一份。我们可以通过Runtime的APIobject_getClass函数来获取某个类所属的类——元类,只不过要把一个类作为参数传进去:

#import <objc/runtime.h>

Class metaClass = object_getClass([NSObject class]);
NSLog(@"%p", metaClass);// 0x7fff933100f0

玩儿一下,既然说元类是类的类,那能不能通过class方法来获取元类呢?

Class metaClass1 = [[NSObject class] class];
NSLog(@"%p", metaClass1);// 0x7fff93310140

我们发现metaClass1metaClass的地址值是不一样的,这就表明不能通过class方法来获取元类。因为[NSObject class]里的NSObject并不是一个实例对象,而是一个类,所以它调用的class方法其实不是实例方法-class,而是类方法+class实例方法-class确实是返回该对象所属的类,而类方法+class则是返回该类本身。下面是Runtime的源码(NSObject.mm文件):

// 返回类本身
+ (Class)class {
    return self; 
}
// 返回该对象所属的类
- (Class)class {
    return object_getClass(self); 
}


// 这两个也可以认为是统一的,反正都是返回父类,你管它是当前类的父类还是当前对象所属类的父类呢
+ (Class)superclass {
    return self->superclass; 
}
- (Class)superclass {
    return [self class]->superclass; 
}


// 这两个是统一的,可以理解为:当前对象是不是某个类的实例
+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass(self) == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}


// 这两个是统一的,可以理解为:当前对象是不是某个类或其子类的实例
+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

四、Runtime关于对象、类、元类的常用API


// 获取一个对象所属的类
Class object_getClass(id obj);
// 设置一个对象所属的类
Class object_setClass(id obj, Class cls);
// 获取一个类的父类
Class class_getSuperclass(Class cls);


// 判断一个对象是不是Class
BOOL object_isClass(id obj);
// 判断一个类是不是MetaClass
BOOL class_isMetaClass(Class cls);


// 动态创建一个类(参数:父类,类名,额外的内存空间通常传0即可)
Class objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes);
// 注册一个类(创建好一个类,在注册之前,我们通常会调用class_addIvar、class_addMethod等函数为类添加成员变量、方法等)
void objc_registerClassPair(Class cls);
// 销毁一个类
void objc_disposeClassPair(Class cls);
上一篇 下一篇

猜你喜欢

热点阅读