-
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类型的成员变量_agedouble类型的属性height- 一个实例方法
- (void)personInstanceMethod - 一个类方法
+ (void)personClassMethod - 遵守
NSCopying协议, 并实现- (id)copyWithZone:(nullable NSZone *)zone方法
-
Student类中, 主要包含以下内容int类型的成员变量_nodouble类型的成员变量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方法, 所以会直接调用