在 Objective-C 中对象分为三类:
- Instance 对象(实例对象)
- Class 对象(类对象)
- Meta-class(元类对象)
实例对象
在 Objective-C 中,就是通过类 alloc 出来的对象,每次调用 alloc 都会产生新的实例对象。
基本创建形式:
NSObject* obj1 = [[NSObject alloc] init];
NSObject* obj2 = [[NSObject alloc] init];
其中 objc1 和 objc2 为两个不同的两个对象,占据着两块不同的内存空间。对象在内存中存储的信息包括:
- isa 指针
- 其他成员变量
关于 NSObject 的「本质」 中讨论过,对象最终都会转成 C\C++ 的结构体形式。并且,所有的实例对象中都存储着 isa 这个成员变量,因为 Objective-C 中几乎所有的类都继承自 NSObject,那么在这些类的对象的内存中肯定包含着 isa。
并且 isa 的内存地址即是该对象的地址。
类对象
类对象在前面其实已经见过面,就是 Class 对象,我们可以通过调用 Objective-C 对象的 class 方法获取,如:
Class objClass = [obj1 class];
或者:
Class objClass2 = [NSObject class];
或者借助 runtime 方法:
Class objClass3 = object_getClass(obj1);
并且,一个类的类对象在内存中只有一份,可以推测上述 objClass1、objClass2、objClass3 在内存中的地址应该是一样的。打印三者内存地址发现都是:
0x7fffb1439140
Class 对象在内存中存储的信息主要包括:
- isa 指针
- superclass 指针
- 类的属性信息(@property)、类对象的方法信息(instance method)
- 类的协议信息(protocol)、类的成员变量信息(ivar)
其中成员变量信息不指该类的成员变量的值,值是由实例变量决定的,成员变量信息是指成员变量的名字、类型... 这些描述信息。这些描述信息只需要存储一份,就可以放到类对象里面去。
元类对象
元类对象(Meta-Class),为描述类对象的对象。元类对象也是能够获取的,方法如下:
Class metaClass = object_getClass([NSObject class]);
或者
Class metaClass2 = object_getClass(objClass);
元类对象也是通过 Class 接收。
获取类对象也是通过 object_getClass() 方法,只不过参数一个是实例对象,一个是类对象。
每个类在内存中有且只有一个元类对象,元类对象和类对象内存结构其实是一样的,在内存中存储的信息主要包括:
- isa 指针
- superclass 指针
- 类的类方法信息
- ...
另,可通过 class_isMetaClass()
来检验是否为元类对象。
isa 指针
instance、class、meta-class 三者关系为:
连接三者的纽带,就是这个 isa 指针:
- instance 的 isa 指针指向 class,当调用对象方法时,通过 instance 的 isa 找到 class,最后找到对象方法的实现进行调用
- class 的 isa 指向 meta-class,当调用类方法时,通过 class 的 isa 找到 meta-class,最后找到类方法的实现进行调用
当前有 Person 类,定义、实现如下:
@interface Person : NSObject
{
@public
int _age;
}
- (void)instanceMethod;
+ (void)classMethod;
@end
@implementation Person
- (void)instanceMethod {
NSLog(@"Instance Method");
}
+ (void)classMethod {
NSLog(@"Class Method");
}
@end
实例方法 instanceMethod()
调用形为:
Person* p = [[Person alloc] init];
[p instanceMethod];
[p instanceMethod] 本质为:
objc_msgSend(p, @selector(instanceMethod));
即所谓的消息转发机制。类方法调用形为:
[Person classMethod]
其本质和实例方法相同,形如:
objc_msgSend([Person class], @selector(classMethod))
这里也可以间接的看到 isa 的作用,由于对象方法并不在对象里面,而是在类对象中存储,所以需要有个媒介联系对象和类对象,同理,类方法也并不在类对象里,而是在元类对象中存储,所以同样的也需要一个媒介来联系类对象和元类对象。
superclass 指针
superclass 指针亦是重要的一个角色,从名字便可得知,这个指针和继承和关系。那么它和 isa 又有什么不同?
新定义 Valenti 类继承自 Person:
@interface Valenti : Person
{
@public
int _height;
int _weight;
}
@end
@implementation Valenti
@end
此时的继承关系为:Valenti -> Person -> NSObject,所以 superclass 指针连接的三者关系为:
由于是继承关系,Valenti 的实例可以调用 Person 的实例方法 instanceMethod()
:
[v instanceMethod];
Q. 那么,在这层调用关系中,isa 指针和 superclass 指针起了什么作用?
A. 实例对象 v 的 isa 指针先找到 Valenti 的类对象,再通过 Valenti 的 superclass 指针找到 Person 的类对象,然后找到方法调用。同理 init()
方法,最终是找到 NSObject 的 init() 方法。
元类对象的 superclass 指针
和类对象的 superclass 指针道理相同,上节三者元类关系如下:
同理,当 Valenti 的类对象调用 Person 的 classMethod()
时,会先通过 isa 找到 Valenti 的元类,通过 superclass 指针找到 Person 的元类,最后找到对应方法调用。
最后,实例对象、类对象、元类对象的 isa 与 superclass 指针的最终指向关系为:
若没有父类,则 superclass 指针为 nil,基类的 superclass 指向基类的 class。
类别
那么类别(Category)中的类方法或者实例方法的调用 isa 和 suerclass 指针又起着什么作用?
假如此时有 NSObject 类别 NSObject+Category
:
定义:
@interface NSObject (Category)
+ (void)method;
@end
实现:
@implementation NSObject (Category)
+ (void)method {
NSLog(@"[NSObject]%p", self);
}
@end
然后声明 Person 类继承 NSObject,声明和 NSObject 类别同名的 method()
:
@interface Person : NSObject
+ (void)method;
@end
@implementation Person
+ (void)method {
NSLog(@"[Person]%p", self);
}
运行如下代码:
[Person method];
[NSObject method];
结果为:
[Person]0x1000011d8
[NSObject]0x7fff8a78e140
两个方法调用者分别是 Person 的类对象和 NSObject 的类对象,通过 isa 指针可以找到 Person 的元类对象以及 NSObject 的元类对象,最后进行方法调用。
假如把 Person 的 method() 方法实现去掉,保留声明:
@interface Person : NSObject
+ (void)method;
@end
@implementation Person
@end
运行代码,结果如下:
[NSObject]0x100001198
[NSObject]0x7fff8a78e140
此时 Person 的元类方法已经没有 method() 类方法的实现,通过 superclass 指针找到 NSObject 的元类方法,找到 method() 类方法进行调用。
我们再做些改动,将 NSObject 分类的 method() 类方法实现改成实例方法:
定义:
@interface NSObject (Category)
+ (void)method;
@end
实现:
@implementation NSObject (Category)
- (void)method {
NSLog(@"[NSObject]%p", self);
}
@end
运行代码,结果如下:
[NSObject]0x100001198
[NSObject]0x7fff8a78e140
居然调用成功,那为什么用类调用 method() 方法会成功调用实例方法 method() ?
两次打印结果,发现第一行方法调用者都是 Person 的类对象?答案很简单,给 Person 类对象发送消息的时候,首先会通过 isa 指针找到 Person 的元类对象,发现并无对应方法,然后通过 superclass 找到 NSObject 的元类对象,但还是无 method() 的类方法,然后通过 NSObject 的 superclass 指针找到 NSObject 的类对象,发现有 method() 的实例方法,进行调用。
事实证明,类别中的方法和在普通类中声明实现是一样的,isa 和 superclass 指针的作用也未曾变化。
借助 Xcode 调试 isa 和 superclass
isa
上述 isa 的关系也可以通过 Xcode 调试来证明,打印:
Person* person = [[Person alloc] init];
Class personClass = [Person class];
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p, %p, %p", person, personClass, personMetaClass);
结果为:
0x100555f90, 0x100001148, 0x100001120
分别代表着实例对象、类对象、元类对象。
实例对象 isa 存储的地址值是类对象的地址,类对象的 isa 存储的地址是 元类对象的地址,此时通过 Xcode 调试:
在 [0] (Person) 指针一栏,右键 -> Print Desciption of "[0]",借用命令打印地址:
print/x (long)person->isa
long 表示将地址转成 long 形式。
/x 表示格式化成十六进制。
结果:
相当于 person 的地址为 0x001d800100001149,地址形式略有偏差,因为在旧的 iOS 系统中实例对象的 isa 存储的值直接类对象的地址,但是从 64bit 开始,isa 需要和 ISA_MASK
进行一次位运算,才能计算出地址,同理类对象和元类对象。
在 objc 源码中有对应宏定义:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# endif
但当我们打印 print/x (long)personClass->isa
的时候却发现报错:
error: member reference base type 'Class' is not a structure or union
这是因为底层的结构体的 isa 指针是不对外暴露的,我们干脆自己定义结构体,这个结构体为底层结构体的高仿版:
struct v_objc_class {
Class isa;
};
对 personClass 进行强转:
struct v_objc_class* personClass2 = (__bridge struct v_objc_class *)(personClass);
再进行调试,打印 print/x (long)personClass2->isa
,结果如下:
再打印元类的地址:
类对象的 isa 指针存储的地址和元类对象的地址并不相同,我们猜想,假如我们通过类对象的 isa 地址和 ISA_MASK 进行位运算得到的结果是否能元类对象的地址呢?此时我们执行命令:
print/x 0x001d800100001121 & 0x00007ffffffffff8
结果为:
刚好是元类对象的地址!
superclass
按照同样的方式调试 superclass,首选我们定义两个类 Person 和 Valenti,Valenti 继承自 Person,保留上节自定义的结构体并增加 superclass
属性:
struct v_objc_class {
Class isa;
Class superclass;
};
@interface Person : NSObject
@end
@implementation Person
@end
@interface Valenti : Person
@end
@implementation Valenti
@end
加断点运行:
借助命令 print/x valentiClass->superclass
得到结果:
0x00000001000011c8 也刚好是 Person 的类对象地址。