一、oc类和对象的底层实现
我们平时编写的OC代码,底层实现其实都是C\C++代码。OC中的对象和类都是基于C\C++的结构体来实现的。
@interface Person : NSObject
{
@public
int _age;
NSString *_name;
}
@property(nonatomic,assign)float height;
@end
@implementation Person
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *ojb = [[NSObject alloc] init];
}
return 0;
}
我们可以利用Xcode内置的clang编译器将上面oc代码转化成C++文件
clang -rewrite-objc main.m -o main.cpp //这样生成的c++文件是跨平台的,会过于庞大,建议要指定平台
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp //指定iphone平台64位 这样生成的c++文件就会小很多
转化后在cpp文件中很容易找代表Person类的结构体,结构体里面存放着person类的属性
struct Person_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
NSString *_name;
float _height;
};
代表Person类的结构体中存储的第一个变量还是一个结构体
struct NSObject_IMPL {
Class isa;
};
而在NSObject.h中我们找到NOObject类的声明
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
我们可以得知NSObject类也是由结构体来实现的。
通过上图我们可以得知oc中的继承关系在c++中是这样表达的:子类的结构体中第一个变量是其父类的结构体。
结论:OC中声明一个类,对应到C++就是声明一个结构体,结构体内存放类的成员变量,OC中创建一个对象,对象到C++就是用创建该类对象的结构体的变量,结构体变量中存放了类的成员变量的值。
二、instance对象、类对象、元类对象
- instance对象通过类alloc出来的对象,每次调用alloc都会产生新的instance对象
//ob1 ob2 是两个instance对象,分别占据着两块不同的内存空间
NSObject *ob1 = [[NSObject alloc] init];
NSObject *ob2 = [[NSObject alloc] init];
instance对象在内存中存储的信息包括:isa指针和其他成员变量的值
- 每个类在内存中有且只有一个类对象(class对象)
//类对象的获取方式
NSObject *ob1 = [[NSObject alloc] init];
Class objectClass1 = [ob1 class];
Class objectClass2 = [NSObject class];
Class objectClass3 = object_getClass(ob1);
//通过打印可以发现objectClass1 objectClass2 objectClass3的内存地址一样
类对象在内存中存储的信息:isa指针、superclass指针、类的属性信息(@property)、类的对象方法信息(instance method)、类的协议信息(protocol)、类的成员变量信息(ivar)
注意:这里的成员变量的信息和属性信息指的是成员变量的名字、类型。。。
- 每个类在内存中有且只有一个元类对象(meta-class对象)
//将类对象当做参数转入,获得元类对象
Class objectMetaClass = object_getClass(类对象);
meta-class对象和class对象的内存结构是一样的,因为他们都是Class类型,只是meta-class对象中一下这些都没有存储值【类的属性信息(@property)、类的对象方法信息(instance method)、类的协议信息(protocol)、类的成员变量信息(ivar)】 元类对象在内存中主要存储的信息:isa指针、superClass指针、类的类方法信息(class method)
Q1:成员变量的值存在实例对象中:因为每个实例对象的成员变量的值是可以不同的。
Q2:类的属性信息(@property)、类的对象方法信息(instance method)、类的协议信息(protocol)、类的成员变量信息(ivar)存在类对象中:这些信息类是确定且唯一
Q3:类的类方法信息(class method)存在元类对象中:???
下面从源码层面证明下class对象和meta-class对象的结构,源码下载地址
我们看到Class本质上就是一个objc_class类型的结构体指针。下面是objc_class的大体源码
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // 方法缓存
class_data_bits_t bits; // 用户获取类的具体信息
class_rw_t *data() const {
return bits.data();
}
}
struct class_rw_t {
//获取只读信息表
const class_ro_t *ro() const {
auto v = get_ro_or_rwe();
if (slowpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>()->ro;
}
return v.get<const class_ro_t *>();
}
//获取方法数组 这是获取到的是一个二维数组 包括了分类和原有类的方法
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->methods;
} else {
return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
}
}
//获取属性数组 这是获取到的是一个二维数组 包括了分类和原有类的属性
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->properties;
} else {
return property_array_t{v.get<const class_ro_t *>()->baseProperties};
}
}
//获取协议数组 这是获取到的是一个二维数组 包括了分类和原有类的协议
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>()->protocols;
} else {
return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
}
}
};
struct class_rw_ext_t {
const class_ro_t *ro;
method_array_t methods; //二维的方法数组
property_array_t properties; //二维的属性数组
protocol_array_t protocols; //二维的协议数组
char *demangledName;
uint32_t version;
};
//这里的信息是只读的
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //类占用内存大小
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原理-Category
三、对象isa指针
我们发现instance对象、class对象、meta-class对象在内存中都存储了一个isa指针。
//给Person类声明一个类方法和实例方法 调用 转成C++文件
Person *p = [[Person alloc] init];
[p instanceMethod];
[Person classMethod];
//转化后
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("instanceMethod"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("classMethod"));
//精简下
objc_msgSend(p,sel_registerName("instanceMethod"));
objc_msgSend(objc_getClass("Person"),sel_registerName("instanceMethod"));
调用对象方法:给instance对象发送一个消息!那么instance对象又是如何找到存储在class对象中的实例方法呢。 调用类方法:给class对象发送一个消息!那么class对象又是如何找到存储在meta-class对象中的类方法呢。
答案就是通过isa指针: instance对象的isa指针指向class对象,当调用对象方法时,通过instance对象的isa指针找到class对象,进而找到对象方法进行调用; class对象的isa指针指向meta-class对象,当调用类方法是,通过class对象的isa指针找到meta-class对象,进而找到类方法进行调用。
这就解释了类方法为何存放在meta-class对象中。
- 通过isa查找的具体过程
通过上图,我们看到了class对象中的isa的值跟meta-class对象的内存地址一致,是简单的指向关系,而instance对象中的isa的值跟class对象的内存地址不一致,说明通过instance对象的isa指针查找class对象还有一个过程。
从64bit开始,isa需要进行一次位运算才能拿到class对象meta-class对象的真实地址。
ios系统和macos系统中ISA_MASK值不同
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
我这里用的模拟器运行的demo 也就是macos系统 所以是
& 0x00007ffffffffff8
四、类对象和元类对象的superClass指针
类对象的superClass指针指向了其父类的类对象; 这也是OC中能实现继承的关键。
当Student类的instance对象要调用其父类Person类的对象方法时,会通过instance对象的isa指针先找到Student的class对象,然后再通过Student的class对象的superClass指针找到Person的class对象,进行实例方法调用。
元类对象的superClass指针指向了其父类的元类对象; 这也是OC中能实现继承类方法的关键。
当Student类的class对象要调用其父类Person类的类方法时,会通过class对象的isa指针先找到Student的meta-class对象,然后再通过Student的meta-class对象的superClass指针找到Person的mete-class对象,进行类方法调用。
五、isa指针和superClass指针总结
总结下isa指针superclass指针就会有下面一张经典的图
- instance对象的isa指针指向class对象
- class对象的isa指针指向meta-class对象
- meta-class对象的isa指针指向基类的meta-class对象
- class对象的superClass指针指向父类的class对象 基类没有父类,superClass指针为nil
- meta-class对象的superClass指针指向父类的meta-class对象 基类的meta-class对象的superClass指针指向基类的class对象