-
Objective-C中的对象, 主要可以分为3种
- instance: 实例对象, 包含 isa和其他成员变量的值, ...
- class: 类对象, 包含, isa、superclass、属性、对象方法、协议、成员变量的描述, ...
- meta-class: 元类对象, 包含 isa、superclass、类方法, ...
-
可以用下图表示每种对象中包含的内容
一、准备代码
- 准备两个类,
Person
类继承自NSObject
,Student
类继承自Person
, 具体如下:
@interface Person : NSObject <NSCopying> {
int _age;
}
@property (nonatomic, assign) double height;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end
@implementation Person
- (void)personInstanceMethod {}
+ (void)personClassMethod {}
- (id)copyWithZone:(nullable NSZone *)zone {
return nil;
}
@end
@interface Student : Person {
int _no;
}
@property (nonatomic, assign) double weight;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end
@implementation Student
- (void)studentInstanceMethod {}
+ (void)studentClassMethod {}
@end
复制代码
-
Person
类中, 主要包含以下内容int
类型的成员变量_age
double
类型的属性height
- 一个实例方法
- (void)personInstanceMethod
- 一个类方法
+ (void)personClassMethod
- 遵守
NSCopying
协议, 并实现- (id)copyWithZone:(nullable NSZone *)zone
方法
-
Student
类中, 主要包含以下内容int
类型的成员变量_no
double
类型的成员变量weight
- 一个对象方法
- (void)studentInstanceMethod
- 一个类方法
+ (void)studentClassMethod
二、验证OC调用方法是发送消息机制
-
OC中方法的调用, 是通过发送消息机制实现的, 我们可以查看底层代码来验证
-
创建
Person
类的对象, 调用方法personInstanceMethod
方法
Person *person = [[Person alloc] init];
[person personInstanceMethod];
复制代码
- 当前的代码如下图:
- 我们使用终端
cd
到main.m
的文件中, 并执行命令:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp
复制代码
- 将生成的
main-arm64.cpp
文件拖到当前工程中:
- 可以在
main-arm64.cpp
文件中, 找到如下代码:
- 可以看到有一句代码:
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personInstanceMethod"));
复制代码
- 去掉类型转换后:
objc_msgSend(person, sel_registerName("personInstanceMethod"));
复制代码
- 即:
OC
中调用方法, 在底层就是发送一条消息
三、对象的isa指针是指向哪里?
问: 对象的isa指针是指向哪里?
答: instance的isa, 指向class类对象, 类对象的isa指向meta-class元类对象
-
以
NSObject
对象为例NSObject
的实例对象:isa
指向NSObject
的class类对象
NSObject
类对象:isa
指向NSObject
的meta-class
元类对象NSObject
的meta-class
元类对象:isa
指向NSObject
的meta-class
元类对象
-
以
Person
对象为例Person
的实例对象:isa
指向Person
的class类对象
Person
类对象:isa
指向Person
的meta-class
元类对象Person
的meta-class
元类对象:isa
指向NSObject
的meta-class
元类对象
-
以
Student
对象为例Student
的实例对象:isa
指向Student
的class类对象
Student
类对象:isa
指向Student
的meta-class
元类对象Student
的meta-class
元类对象:isa
指向NSObject
的meta-class
元类对象
-
instance
的isa
指向class
- 当调用对象方法时, 通过
instance
的isa
找到class
, 最后找到对象方法的实现进行调用
- 当调用对象方法时, 通过
-
class
的isa
指向meta-class
- 当调用类方法时, 通过
class
的isa
找到meta-class
, 最后找到类方法的实现进行调用
- 当调用类方法时, 通过
-
meta-class
的isa
指向基类的meta-class
四、class对象的superclass
指针
- 类对象的
superclass
指针, 指向父类的类对象
那么
class
中的superclass
指针的作用是什么呢?
- 现有如下代码:
Student *student = [[Student alloc] init];
[student studentInstanceMethod];
[student personInstanceMethod];
[student init];
复制代码
- 当
student
调用对象方法studentInstanceMethod
时, 会有以下查找方式- 通过
instance
的isa
找到Student
的class
类对象, 查看是否有studentInstanceMethod
- 发现
Student
的class
对象中有studentInstanceMethod
方法, 停止继续查找, 通过消息机制调用方法
- 通过
- 当
student
调用父类Person
的对象方法personInstanceMethod
, 会有以下查找方式- 通过
student
的isa
找到Student
的class
类方法, 查看是否有personInstanceMethod
- 发现
Student
的class
对象中没有personInstanceMethod
, 于是通过superclass
指针找到Person
的class
对象, 查看是否有personInstanceMethod
- 在
Person
的class
对象中发现personInstanceMethod
方法, 停止继续查找, 通过消息机制调用方法
- 通过
- 当
student
调用基类NSObject
的init
方法时, 会有以下查找方式- 首先, 通过
isa
指针找到Student
的class
对象, 查看是否有init
方法 - 在
Student
的class
对象中没有发现init
方法, 于是通过superclass
指针找到Person
的class
对象 - 在
Person
的class
中查找init
方法, 结果发现Person
的class
对象中也没有init
方法 - 此时, 就会通过
Person
的class
对象中的superclass
指针, 找到NSObject
的class
对象中, 查找init
方法 - 在
NSObject
的class
对象中, 找到了init
方法, 停止继续查找, 通过消息机制调用方法
- 首先, 通过
注意:
class
的superclass
会指向父类的class
对象, 最后指向的是NSObject
的class
对象, 而NSObject
的class
对象中的superclass
指针, 会指向nil
如果在发现
NSObject
的class
中也没有找到要调用的方法时, 就会报错unrecognized selector sent to instance
五、meta-class
中的superclass
指针
- 与类对象的
superclass
指针类似,meta-class
中的superclass
指针指向父类的meta-class
-
在类方法的调用上, 与实例方法调用类似
-
当
Student
的class
要调用Person
的类方法时,会先通过isa
找到Student
的meta-class
,然后通过superclass
找到Person
的meta-class
,最后找到类方法的实现进行调用
注意: 基类
NSObject
的meta-class
对象的superclass
最终指向的是NSObject
的class
对象, 而不是指向nil
六、isa、superclass总结
- isa、superclass的作用如下图:
-
intance
的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
找父类
七、验证NSObject的Meta-class对象中的superclass指向自身的Class对象
- 上面提到过: 基类的
meta-class
的superclass
指向基类的class
- 下面通过代码来进行验证
@interface Person: NSObject
+ (void)test;
@end
@implementation Person
+ (void)test {
NSLog(@"+ [Person text] - %p", self);
}
@end
复制代码
Person
类继承自NSObject
, 有一个类对方+ (void)test
, 并给出了方法的实现
NSLog(@"[Person class] - %p", [Person class]);
[Person test];
// 控制台打印:
[Person class] - 0x100001170
+ [Person text] - 0x100001170
复制代码
-
根据打印可以知道, 调用方法的正是
Person
类的Class
对象 -
现在删除
test
方法的实现部分, 只保留声明部分
@interface Person: NSObject
+ (void)test;
@end
@implementation Person
@end
复制代码
- 再次调用该方法, 会报出运行时错误,
test
方法不存在
[Person test];
// 报错: '+[Person test]: unrecognized selector sent to class 0x100001130'
复制代码
- 我们在给
NSObject
添加一个分类, 实现+ (void)test
方法
@interface NSObject (Test)
+ (void)test;
@end
@implementation NSObject (Test)
+ (void)test
{
NSLog(@"+ [NSObject test] - %p", self);
}
@end
复制代码
- 再次使用
Person
的类对象调用test
方法
NSLog(@"[Person class] - %p", [Person class]);
[Person test];
// 控制台打印:
[Person class] - 0x100001220
+ [NSObject test] - 0x100001220
复制代码
-
此时的调用顺序是:
- 1、
Person
的类对象, 通过isa
找到了Person
的元类对象, 并查找有没有test
方法 - 2、由于
Person
的元类对象中没有test
方法, 于是通过superclass
找到了父类NSObject
的元类对象 - 3、在
NSObject
的元类对象中, 发现了test
方法, 发送消息, 调用方法
- 1、
-
接着我们移除掉
NSObject
分类中的+ (void)test
方法的实现
@interface NSObject (Test)
+ (void)test;
@end
@implementation NSObject (Test)
@end
复制代码
- 此时再次使用
Person
调用+ (void)test
方法, 就会报运行时错误
[Person test];
reason: '+[Person test]: unrecognized selector sent to class 0x100001178'
复制代码
- 接着在
NSObject
的分类中, 给出一个对象方法- (void)test
的方法实现
@interface NSObject (Test)
+ (void)test;
@end
@implementation NSObject (Test)
- (void)test {
NSLog(@"- [NSObject test] - %p", self);
}
@end
复制代码
- 再次使用
Person
调用类方法+(void)test
NSLog(@"[Person class] - %p", [Person class]);
[Person test];
// 控制台打印:
[Person class] - 0x1000011b8
- [NSObject test] - 0x1000011b8
复制代码
- 此时调用成功, 说明当
NSObject
的元类对象中没有test
方法时, 就会通过superclass
指针找到NSObject
的类对象, 并查找有没有test
方法 - 由于在
NSObject
中找到了test
方法, 所以会直接调用