一、通过源码理解instance(实例对象) 、class object (类对象)、metaclass(元类对象)
苹果开源代码(https://opensource.apple.com/tarballs/)
1.instance 实例对象
1.1定义
实例对象是通过类alloc出来的对象,每次调用alloc都会产生新的实例对象
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
以上object1、object2都是实例对象,占用两块儿不同的内存
1.2、底层实现
首先,终端定位到需要转化文件的文件夹下,用以下命令行将OC代码转成C++/C代码
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 输出的CPP文件
以NSObject对象为例,我们用以上方法看看它的底层实现
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
}
return 0;
}
转成C++代码后,我们找到了这个对象的实现
struct NSObject_IMPL {
Class isa;
};
//Class是指针类型,指向objc_class类型的结构体
typedef struct objc_class *Class;
obj对象内部实际上只有一个isa指针,指向objc_class类型的结构体。 那isa指针到底指向谁,它又有什么用呢? 在下文中我们会讲到
1.3、更复杂的继承结构
我们举一反三,设计一个父类Father,继承于NSObject,再设计一个子类son继承于父类,看看他们的底层实现
@interface Father : NSObject {
int _age;
}
@end
@interface Son : Father {
double _height;
}
@end
把代码转成C++,看看内部实现。直接查找类名_IMPL
struct Father_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
};
struct Son_IMPL {
struct Father_IMPL Father_IVARS;
double _height;
};
上述代码相当于
struct Father_IMPL {
Class isa;
int _age;
};
struct Son_IMPL {
Class isa;
int _age;
double _height;
};
所以实例对象的本质是结构体,(在c++文件中查找类名_IMPL就能找到这个结构体),里面有一个isa指针和其他成员变量。所以实例对象在内存中存储的内容是 isa指针 + 其他成员变量。
如下图
我们平时说打印出来的实例对象的地址开始就是指的是isa的地址,即isa的地址排在最前面,就是我们实例对象的地址
2、class-类对象
从第一部分我们了解到,实例对象只存储isa指针和成员变量,那类里面的方法啊,属性啊等等一些类信息存放在哪里?为什么这些信息没有存放在实例对象里?
因为实例对象可能有很多个,不可能每创建一个实例对象都存一份方法、属性.....的。 这些只需要存一份就可以了。一个类有且只有一个类对象。把属性啊方法啊等信息存在类对象中也再合适不过了。
2.1 创建类对象
NSObject *obj = [[NSObject alloc] init];
Class objClass1 = [NSObject class];
Class objClass2 = [obj class];
Class objClass3 = object_getClass(obj);
我们可以打印一下,objClass1,objClass12,objClass13他们的内存地址都是一样的。也验证了一个类有且一有一个类对象
2.2 类对象的本质
如上我们可以看出,类对象中存放了
1、isa指针
2、super指针
3、类的属性信息(@property)、类的对象方法信息(instance method)
4、类的协议信息(protocol)、类的成员变量信息(ivar)
........
类对象里面也有一个isa指针,还有一个super指针,那他们分别指向哪里,又有什么作用呢? 我们稍后就会讲到。 当然这里还有一个疑问,既然类对象里面存放的是对象方法信息,那类方法信息存放在哪里呢?
3、meta-class-元类对象
构建
Class objectMetaClass = object_getClass([NSObject class]);
如上,objectMetaClass 就是 NSObject的元类对象,并且 每个类只有一个元类对象
元类对象和类对象的结构是一样的,都是objc_class类型的结构体,元类对象存放类方法信息
1、isa指针
2、super指针
3、类的类方法信息(class method)
.........
meta-class对象和class对象的内存结构是一样的,所以meta-class中也有类的属性信息,类的对象方法信息等成员变量,但是其中的值可能是空的。
4、isa指针和super指针
实例对象,类对象,元类对象中都有isa指针,类对象和元类对象中有super指针。他们分别指向哪里?
4.1、 实例对象的isa指针指向
eg1:子类Son中有一个实例方法- (void)sonTest ,创建一个实例对象son,然后用这个对象调用方法[son sonTest];。
类对象中存储着实例方法信息。当实例对象调用实例方法时
实例方法存储在类对象中。实例对象调用实例方法时,实例对象对象通过isa指针找到类对象,进而找到类对象中相应的实例方法进行调用。
实例对象的isa指针指向它的类对象。
4.2、类对象的isa指针指向
元类对象中存储着类方法信息。当类对象调用类方法时,类对象通过isa指针找到元类对象,进而找到元类对象中相应的类方法进行调用,类对象的isa指针指向它的元类对象。
4.3、元类对象的isa指针指向
元类对象的isa指针指向基类的元类对象(eg:Son的元类对象和Father的元类对象都指向NSObject的元类对象)
4.4、类对象的super指针指向
eg4:父类Father中有一个实例方法- (void)fatherTest ,创建一个子类实例对象son,然后用这个对象调用方法[son fatherTest];。
从4.1我们知道,son对象的isa指针会找到它的类对象,但是类对象中没有fatherTest这个对象方法,所以类对象会通过它的super指针找到父类的类对象,而fatherTest这个方法是存放在Father的类对象中的,进而调用。类对象的super指针是指向父类的类对象的。
特例:当这个类没有父类时(基类),则指向nil
4.5、元类对象的super指针指向
元类对象的super指针指向父类的元类对象
特例:基类的元类对象super指针指向基类的类对象
总结如下:
- 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 - instance调用对象方法的轨迹
isa找到class,方法不存在,就通过superclass找父类 - class调用类方法的轨迹
isa找meta-class,方法不存在,就通过superclass找父类
二、通过object_getClass探究isa指针指向问题
介绍几个函数
objc_getMetaClass(const char * _Nonnull name)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
object_getClass(id _Nullable obj)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
class_isMetaClass(Class _Nullable cls)
OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
- (Class)superclass
+ (Class)class
- objc_getMetaClass用于获取元类对象
- class_isMetaClass用于判断Class对象是否为元类
- object_getClass用于获取对象的isa指针指向的对象
- -(Class)superclass 用于获取对象的superclass指针所指向的对象
- +(Class)class 获取类对象
1.有代码如下:
// EDStudent 继承于 EDPerson ,EDPerson 继承于 NSObject
NSObject *object1 = [[NSObject alloc] init]; // 基类 实例对象
Class object2 = object_getClass(object1); // object1 的isa指针 所指向的对象
Class object3 = object_getClass(object2); // object2 的isa指针 所指向的对象
Class object4 = [NSObject class]; // 基类 类对象
Class object5 = objc_getMetaClass("NSObject"); //基类 元类对象
Class object6 = object_getClass(object5); // 元类对象的isa指针 所指向的对象
NSLog(@"runtimeTest1 %p %p %p %p %p %p %d",object1,object2,object3,object4,object5,object6,class_isMetaClass(object3));
运行结果如下:
runtimeTest1 0x60000311c410 0x7fff86b6c660 0x7fff86b6c638 0x7fff86b6c660 0x7fff86b6c638 0x7fff86b6c638 1
上文我们不难发现,object2 和object4 的内存地址是一样的,object3、object5、object6的内存地址是一样的,object4 是类对象,object5是元类对象~
也就是说基类的实例对象object1的isa指针是指向基类的类对象object2(object4),基类的类对象object2的isa指针是指向基类的元类对象object3(object5),基类的元类对象object5的isa指针指向基类的元类对象本身object6(object5)
2.有代码如下
// EDStudent 继承于 EDPerson ,EDPerson 继承于 NSObject
EDPerson *p1 = [[EDPerson alloc] init]; // 实例对象
Class p2 = object_getClass(p1); // p1 的isa指针 所指向的对象
Class p3 = object_getClass(p2); // p2 的isa指针 所指向的对象
Class p4 = [EDPerson class]; // 类对象
Class p5 = objc_getMetaClass("EDPerson"); // 元类对象
Class p6 = object_getClass(p5); // 元类对象p5的isa指针 所指向的对象
Class objc = objc_getMetaClass("NSObject"); // 基类 元类对象
NSLog(@"runtimeTest2 %p %p %p %p %p %p %p %d",p1,p2,p3,p4,p5,p6,objc,class_isMetaClass(p3));
运行结果如下:
runtimeTest2 0x600003b339a0 0x1066168b0 0x106616888 0x1066168b0 0x106616888 0x7fff86b6c638 0x7fff86b6c638 1
上文我们不难发现,p2和p4的内存地址一样,p3和p5的内存地址一样,p6和objc内存地址一样,p4是类对象,p5是元类对象,objc是基类的元类对象
也就是说 实例对象的isa指针是指向类对象的,类对象的isa指针是指向元类对象的,元类对象的isa指针是指向基类的元类对象
综上:
实例对象的isa指针是指向类对象,类对象的isa指针是指向元类对象,元类对象的isa指针都是指向基类的元类对象
3.有代码如下
// EDStudent 继承于 EDPerson ,EDPerson 继承于 NSObject
Class objc1 = [EDStudent class]; // EDStudent 类对象 objc1
Class objc2 = [objc1 superclass]; // objc1 的 superclass指针 所指向的对象 (EDPerson 类对象) objc2
Class objc3 = [objc2 superclass]; // objc2 的 superclass指针 所指向的对象 (NSObject 类对象) objc3
Class objc4 = [objc3 superclass]; // objc3 的 superclass指针 所指向的对象 (nil) objc4
Class objc5 = [EDPerson class]; // EDPerson 类对象 objc5
Class objc6 = [NSObject class]; // NSObject 类对象 objc6
NSLog(@"runtimeTest3 %p %p %p %p %p %p",objc1,objc2,objc3,objc4,objc5,objc6);
运行结果如下:
runtimeTest3 0x102965950 0x102965900 0x7fff86b6c660 0x0 0x102965900 0x7fff86b6c660
上文我们不难发现,objc2、objc5内存地址一样,objc3、objc6内存地址一样,objc4是nil。objc5 是EDPerson类对象,objc6 是NSObject类对象。
也就是说子类EDStudent的类对象的superclass指针是指向父类EDPerson的类对象,父类EDPerson的类对象的superclass指针是指向基类NSObject的类对象,基类NSObject的类对象的superclass指针是指向nil
4.有代码如下
// EDStudent 继承于 EDPerson ,EDPerson 继承于 NSObject
Class objc1 = objc_getMetaClass("EDStudent"); // EDStudent 元类对象 objc1
Class objc2 = [objc1 superclass]; // objc1 的 superclass指针 所指向的对象 (EDPerson 元类对象)
Class objc3 = [objc2 superclass]; // objc2 的 superclass指针 所指向的对象 (NSObject 元类对象)
Class objc4 = [objc3 superclass]; // objc3 的 superclass指针 所指向的对象 (NSObject 类对象) 基类的元类对象的superclass指针是指向基类的类对象的
Class objc5 = objc_getMetaClass("EDPerson"); // EDPerson 元类对象 objc5
Class objc6 = objc_getMetaClass("NSObject"); // NSObject 元类对象 objc6
Class objc7 = [NSObject class]; // NSObject 类对象 objc7
NSLog(@"runtimeTest4 %p %p %p %p %p %p %p",objc1,objc2,objc3,objc4,objc5,objc6,objc7);
运行结果如下:
runtimeTest4 0x102965928 0x1029658d8 0x7fff86b6c638 0x7fff86b6c660 0x1029658d8 0x7fff86b6c638 0x7fff86b6c660
上文不难发现,objc2、objc5内存地址一样,objc3、objc6内存地址一样,objc4、objc7内存地址一样。objc5是EDPerson元类对象,objc6是NSObject元类对象,objc7是NSObject类对象
也就是说,子类EDStudent的元类对象的superclass指针是指向父类EDPerson的元类对象,父类EDPerson的元类对象的superclass指针指向基类NSObject的元类对象,基类的元类对象的superclass指针指向基类NSObject的类对象
综上:
子类的类对象的superclass指针是指向父类的类对象,父类的类对象的superclass指针指向基类的类对象,基类的类对象的superclass指针是指向nil
子类的元类对象的superclass指针是指向父类的元类对象,父类的元类对象的superclass指针是指向基类的元类对象,基类的元类对象的superclass指针是指向基类的类对象
三、实例方法和类方法的调用
有代码如下:
@interface NSObject (runtime)
- (void)test;
+ (void)test;
@end
@implementation NSObject (runtime)
- (void)test{
NSLog(@"NSObject 实例方法 Test 执行");
}
+ (void)test{
NSLog(@"NSObject 类方法 Test 执行");
}
@end
@interface EDPerson : NSObject
@end
@implementation EDPerson
- (void)test {
NSLog(@"EDPerson 实例方法 Test 执行");
}
+ (void)test{
NSLog(@"EDPerson 类方法 Test 执行");
}
@end
@interface EDStudent : EDPerson
@end
@implementation EDStudent
- (void)test {
NSLog(@"EDStudent 实例方法 Test 执行");
}
+ (void)test{
NSLog(@"EDStudent 类方法 Test 执行");
}
@end
- (void)runtimeTest5 {
[[EDStudent new] test];
[EDStudent test];
}
当前控制台会打印“ EDStudent 实例方法 Test 执行 ” 和 “EDStudent 类方法 Test 执行”
如果注释掉EDStudent类的- (void)test 和+ (void)test,控制台会打印 “EDPerson 实例方法 Test 执行”和“EDPerson 类方法 Test 执行”
如果再注释掉EDPerson类的- (void)test 和+ (void)test,控制台会打印“NSObject 实例方法 Test 执行” 和 “NSObject 类方法 Test 执行”
如果此时再注释掉NSObject类的+ (void)test,此时[EDStudent test] 并不会报错,控制台会打印“NSObject 实例方法 Test 执行” 和 “NSObject 实例方法 Test 执行”
综上:
实例方法的调用轨迹:实例对象通过isa指针找到类对象,在类对象的方法列表中查找该方法,如果找不到,就通过superclass指针继续向上查找。
类方法的调用轨迹:类对象通过isa指针找到元类对象,在元类对象的方法列表中查找该方法,如果找不到,就通过superclass指针继续向上查找。
四、相关知识点
在苹果开源代码(https://opensource.apple.com/tarballs/)中下载objc4
1. class
代码如下:
Class objc1 = [EDStudent class];
Class objc2 = [[EDStudent new] class];
NSLog(@"runtimeTest6 %p %p",objc1,objc2);
运行结果:
runtimeTest6 0x10ae388c0 0x10ae388c0
我们不难发现,objc1 和 objc2的内存地址是一样的
为什么呢,我们看下源码
- object_getClass
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
object_getClass 返回的是当前对象的isa指针所指向的对象
- class
+ (Class)class {
return self;
}
- (Class)class {
return object_getClass(self);
}
类方法class,是返回当前类的类对象
实例方法class,也是返回当前类的类对象。object_getClass(self),是获取self的isa指针所指向的对象,而self是当前类的实例对象,实例对象的isa指针是指向类对象,因而此时object_getClass(self)是获取当前类的对象的。
综上
class方法是返回当前类的类对象
2.superclass
代码如下:
Class objc1 = [EDStudent superclass];
Class objc2 = [[EDStudent new] superclass];
NSLog(@"runtimeTest7 %p %p",objc1,objc2);
运行结果:
runtimeTest7 0x10ae38870 0x10ae38870
我们不难发现,objc1 和 objc2的内存地址是一样的。
来我们继续看源码
- superclass
+ (Class)superclass {
return self->getSuperclass();
}
- (Class)superclass {
return [self class]->getSuperclass();
}
类方法superclass的实质是self->getSuperclass(),此时self是类对象,也就是说类方法superclass 返回的是当前类的类对象的superclass指针所指向的对象。
实例方法superclass的实质是[self class]->getSuperclass(),此时self是实例对象,[self class]的实质是object_getClass(self),因而此时的[self class] 返回的是实例对象self的isa指针所指向的对象,即当前类的类对象,也就是说,实例方法superclass 返回的也是当前类的类对象的superclass指针所指向的对象。
综上
superclass 方法是返回当前类的类对象的superclass指针所指向的对象
3.isKindOfClass 和 isMemberOfClass
代码如下:
BOOL res1 = [[EDStudent new] isKindOfClass:[EDStudent class]];
BOOL res2 = [[EDStudent class] isKindOfClass:[EDStudent class]];
BOOL res3 = [[NSObject class] isKindOfClass:[NSObject class]];
NSLog(@"runtimeTest8 %d %d %d",res1,res2,res3);
运行结果:
runtimeTest8 1 0 1
代码如下:
BOOL res1 = [[EDStudent new] isMemberOfClass:[EDStudent class]];
BOOL res2 = [[EDStudent class] isMemberOfClass:[EDStudent class]];
BOOL res3 = [[NSObject class] isMemberOfClass:[NSObject class]];
NSLog(@"runtimeTest9 %d %d %d",res1,res2,res3);
运行结果:
runtimeTest9 1 0 0
是不是和我们平时的认知有些不一样,别急,想要理解为什么是这样,我们来看下源码
- isKindOfClass
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = self->ISA(); tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->getSuperclass()) {
if (tcls == cls) return YES;
}
return NO;
}
在源码上,我们不难发现,isKindOfClass的类方法和实例方法的区别在于for循环的初始值tcls,类方法isKindOfClass的for循环初始值是tcls = self->ISA(),初始值tcls是当前对象的isa指针所指向的对象,实例方法isKindOfClass的for循环初始值是tcls = [self class],由于是实例方法,因而[self class]可以改写为object_getClass(self),而object_getClass表示当前对象的isa指针所指向的对象,因而isKindOfClass的类方法和实例方法for循环的初始值tcls都是当前对象的isa指针所指向的对象
综上
isKindOfClass方法是一个for循环查找,由当前对象的isa指针所指向的对象(tcls = self->ISA() 或 tcls = [self class])开始,通过superclass指针(tcls = tcls->getSuperclass())向上查找,如果找到的对象和cls相同就返回YES,否则返回NO
- isMemberOfClass
+ (BOOL)isMemberOfClass:(Class)cls {
return self->ISA() == cls;
}
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
和 isKindOfClass 类似,实例方法isMemberOfClass 中的[self class] 可以改写为object_getClass(self),而object_getClass(self)表示当前对象的isa指针所指向的对象,因而self->ISA() 和 [self class] 都是表示当前对象的isa指针所指向的对象
综上
isMemberOfClass方法是判断当前对象的isa指针所指向的对象和cls是否相同
看到这,不知道大家有没有一个小疑问,当前类的类对象,不管是通过何种方式获取的,它们的内存地址都是一样的,当前类的元类对象,也是如此~
类对象保存的是创建实例对象所需要的信息(实例方法 协议等),因而它只需要一份,不一定是单例
元类对象保存的是创建类对象所需的信息(类方法),因而它也只需一份,不一定是单例