级别:★★☆☆☆
标签:「实例对象」「类」「元类」「实例对象、类、元类」
作者: ITWYW
审校: QiShare团队
前言
笔者最近看了部分 实例对象、类、元类 相关的内容。会在本文中做下分享。
首先我们看下实例对象、类、元类的定义。
类、元类的定义
元类:在面向对象程序设计中,元类(英语:metaclass)是一种实例是类的类。普通的类定义的是特定对象的行为,元类定义的则是特定的类及其对象的行为。
引自 360百科 元类
类:类(英语:class)在面向对象编程中是一种面向对象计算机编程语言的构造,是创建对象的蓝图,描述了所创建的对象共同的属性和方法。 引自维基百科 类
The metaclass is the description of the class object, just like the class is the description of ordinary instances.
引自[objc explain]: Classes and metaclasses
简单来说:
类是实例对象的描述,元类是类对象的描述。
举个例子 NSObject *obj = [NSObject new];
NSObject 是 实例对象 obj 的描述,NSObject 的元类是 NSObject 类对象的描述。
实例对象、类、元类之间的关系
我们看一下实例对象、类、元类之间的关系图。
图片引自[objc explain]: Classes and metaclasses
根据上图,我们可以梳理一下实例对象、类、元类之间的关系。
一、实例对象、类对象、元类的 isa 指向及父类指向说明
下边笔者说明下上图中 isa 和父类指向。
- 实例对象的 isa 指针指向类对象;
- 类对象的 isa 指针指向元类对象;
- 元类对象的 isa 指针指向根元类(NSObject元类);
- 根元类(NSObject元类)的 isa 指针指向自己;
- 子类的元类的父类指向父类的元类;
- NSObject的父类为nil;
- 根元类(NSObject元类)的父类指向NSObject。
0. 下文会用到的函数说明
// 注:使用如下函数需要在自己的文件中引入runtime头文件 即 #import <objc/runtime.h>
// 如果obj为实例对象返回obj对应的类对象,如果obj为类对象返回obj对应的元类对象,如果obj为元类对象,返回obj的元类对象。
OBJC_EXPORT Class _Nullable object_getClass(id _Nullable obj) ;
// 返回指定类名name的元类
OBJC_EXPORT Class _Nullable objc_getMetaClass(const char * _Nonnull name);
// 查看指定类是否为元类
OBJC_EXPORT BOOL class_isMetaClass(Class _Nullable cls);
// 返回指定类的父类
OBJC_EXPORT Class _Nullable class_getSuperclass(Class _Nullable cls) ;
我们可以使用代码验证一下实例对象、类、元类关系图的正确性。
为了验证对象、类对象、元类的isa指向及父类指向,笔者创建了 2 个类,分别是 QiSuperClass、QiSubClass。
QiSuperClass 继承自 NSObject,QiSubClass 继承自 QiSuperClass。
笔者写了如下示例代码做验证。
//! 验证实例对象、类对象、元类对象的isa及父类指向
- (void)instanceClassMetaClassISASuperClicked {
QiSubClass *subClassInstance = [QiSubClass new];
// 类对象
Class subClass = object_getClass(subClassInstance);
// 元类对象
Class subMetaClass = object_getClass(subClass);
// 元类对象的元类对象
Class subMetaClassMetaClass = object_getClass(subMetaClass);
// 1. 实例对象的 isa 指针指向类对象
QiLog(@"----1. 实例对象的 isa 指针指向类对象----");
QiLog(@"QiSubClass的类对象地址:%p,QiSubClass类对象地址:%p", subClass, [QiSubClass class]);
// 2. 类对象的 isa 指针指向元类对象;
QiLog(@"----2. 类对象的 isa 指针指向元类对象;----");
QiLog(@"QiSubClass元类对象地址:%p,QiSubClass元类对象地址:%p", subMetaClass, objc_getMetaClass("QiSubClass"));
// 3. 元类对象的 isa 指针指向根元类(NSObject元类);
QiLog(@"----3. 元类对象的 isa 指针指向根元类(NSObject元类)----");
QiLog(@"QiSubClass元类对象的元类对象地址:%p,根元类(NSObject元类)的对象地址:%p", subMetaClassMetaClass, objc_getMetaClass("NSObject"));
// 4. 根元类(NSObject元类)的 isa 指针指向自己;
QiLog(@"----4. 根元类(NSObject元类)的 isa 指针指向自己----");
QiLog(@"根元类(NSObject元类)的对象地址:%p,根元类(NSObject元类)的对象的isa指针地址:%p", objc_getMetaClass("NSObject"), object_getClass(objc_getMetaClass("NSObject")));
// 1. 子类的元类的父类指向父类的元类;
QiLog(@"----1. 子类的元类的父类指向父类的元类----");
QiLog(@"QiSubClass的元类的父类对象地址:%p,QiSuperClass的元类地址:%p", class_getSuperclass(objc_getMetaClass("QiSubClass")), objc_getMetaClass("QiSuperClass"));
// 2. NSObject的父类为nil;
QiLog(@"----2. NSObject的父类为nil----");
QiLog(@"NSObject的父类对象:%@", class_getSuperclass([NSObject class]));
// 3. 根元类(NSObject元类)的父类指向NSObject。
QiLog(@"----3. 根元类(NSObject元类)的父类指向NSObject----");
QiLog(@"根元类(NSObject元类)的父类地址:%p,NSObject类地址:%p", class_getSuperclass(objc_getMetaClass("NSObject")), [NSObject class]);
}
笔者整理后的输出结果如下:
----1. 实例对象的 isa 指针指向类对象----
QiSubClass的类对象地址:0x10dad86c0,QiSubClass类对象地址:0x10dad86c0
----2. 类对象的 isa 指针指向元类对象;----
QiSubClass元类对象地址:0x10dad8698,QiSubClass元类对象地址:0x10dad8698
----3. 元类对象的 isa 指针指向根元类(NSObject元类)----
QiSubClass元类对象的元类对象地址:0x7fff86cb8638,根元类(NSObject元类)的对象地址:0x7fff86cb8638
----4. 根元类(NSObject元类)的 isa 指针指向自己----
根元类(NSObject元类)的对象地址:0x7fff86cb8638,根元类(NSObject元类)的对象的isa指针地址:0x7fff86cb8638
----1. 子类的元类的父类指向父类的元类----
QiSubClass的元类的父类对象地址:0x10dad8508,QiSuperClass的元类地址:0x10dad8508
----2. NSObject的父类为nil----
NSObject的父类对象:(null)
----3. 根元类(NSObject元类)的父类指向NSObject----
根元类(NSObject元类)的父类地址:0x7fff86cb8660,NSObject类地址:0x7fff86cb8660
上文中的代码在一起查看可能会有些乱,下边笔者使用LLDB输出对象的地址。来分别查看实例对象、类对象、元类对象之间的关系。
1. 实例对象的 isa 指针指向类对象;
查看 QiSubClass 的类对象地址都是 0x0000000104173580。
(lldb) p/x [QiSubClass class]
(Class) $1 = 0x0000000104173580 QiSubClass
(lldb) p/x object_getClass([QiSubClass new])
(Class _Nullable) $2 = 0x0000000104173580 QiSubClass
2. 类对象的 isa 指针指向元类对象;
查看 QiSubClass 的元类对象地址都是 0x0000000104173558。
(lldb) p/x object_getClass([QiSubClass class])
(Class _Nullable) $3 = 0x0000000104173558
(lldb) p/x objc_getMetaClass("QiSubClass")
(Class _Nullable) $4 = 0x0000000104173558
3. 元类对象的 isa 指针指向根元类(NSObject元类);
查看 QiSubClass 的元类对象的元类对象的地址和NSObject的元类对象的地址都是 0x00007fff86cb8638。
(lldb) p/x object_getClass(objc_getMetaClass("QiSubClass"))
(Class _Nullable) $5 = 0x00007fff86cb8638
(lldb) p/x objc_getMetaClass("NSObject")
(Class _Nullable) $6 = 0x00007fff86cb8638
4. 根元类(NSObject元类)的 isa 指针指向自己;
查看 NSObject的元类对象的地址 和 NSObject 的元类对象的元类对象的地址都是 0x00007fff86cb8638。
(lldb) p/x objc_getMetaClass("NSObject")
(Class _Nullable) $6 = 0x00007fff86cb8638
(lldb) p/x object_getClass(objc_getMetaClass("NSObject"))
(Class _Nullable) $7 = 0x00007fff86cb8638
1. 子类的元类的父类指向父类的元类;
查看 QiSubClass 的元类的父类对象的地址 和 QiSuperClass 的元类对象的地址都是 0x00000001041733c8。
(lldb) p/x class_getSuperclass(objc_getMetaClass("QiSubClass"))
(Class _Nullable) $11 = 0x00000001041733c8
(lldb) p/x objc_getMetaClass("QiSuperClass")
(Class _Nullable) $12 = 0x00000001041733c8
2. NSObject的父类为nil;
查看 NSObject 的父类对象为nil。
(lldb) po class_getSuperclass([NSObject class])
nil
3. 根元类(NSObject元类)的父类指向NSObject。
查看 NSObject 元类的父类对象的地址 和 NSObject 类对象的地址都是 0x00007fff86cb8660。
(lldb) p/x class_getSuperclass(objc_getMetaClass("NSObject"))
(Class _Nullable) $16 = 0x00007fff86cb8660 NSObject
(lldb) p/x [NSObject class]
(Class) $17 = 0x00007fff86cb8660 NSObject
二、修改类对象、元类对象的ISA指向
下边笔者使用的如下的QiSuperClass、QiSubClass的代码,进行修改ISA指向的操作。
@interface QiSuperClass : NSObject
- (void)superMethod;
+ (void)superClassMethod;
@end
NS_ASSUME_NONNULL_END
#import "QiSuperClass.h"
@implementation QiSuperClass
- (void)superMethod {
NSLog(@"superMethod");
}
+ (void)superClassMethod {
NSLog(@"SuperClassMethod");
}
@end
NS_ASSUME_NONNULL_BEGIN
@interface QiSubClass : QiSuperClass
- (void)subMethod;
+ (void)subClassMethod;
@end
NS_ASSUME_NONNULL_END
#import "QiSubClass.h"
@implementation QiSubClass
- (void)subMethod {
NSLog(@"subMethod");
}
+ (void)subClassMethod {
NSLog(@"subClassMethod");
}
@end
推测一下如下代码的执行结果
QiSuperClass *superCls = [QiSuperClass new];
[superCls superMethod];
QiLog(@"superCls对象当前类型:%@", superCls.class);
[((QiSubClass *)superCls) subMethod];
[QiSuperClass performSelector:@selector(subClassMethod)];
测试一下,会发现上述代码会出现崩溃,因为 superCls 是 父类 QiSuperClass 的实例对象,superCls 没有子类QiSubClass 的实例方法 subMethod。同样父类 QiSuperClass 没有子类 QiSubClass 的类方法。进一步来说大家如果查看过类的方法列表、元类的方法列表就会了解其中原因(其中原因,笔者会在后续文章中做说明)。
推测一下如下代码的执行结果
QiSuperClass *superCls = [QiSuperClass new];
[superCls superMethod];
QiLog(@"superCls对象当前类型:%@", superCls.class);
// Sets the class of an object. 设置一个对象的类
object_setClass(superCls, QiSubClass.class);
QiLog(@"superCls对象当前类型:%@", superCls.class);
[((QiSubClass *)superCls) subMethod];
QiLog(@"QiSuperClass的元类对象类型:%@", objc_getMetaClass("QiSuperClass"));
object_setClass(QiSuperClass.class, objc_getMetaClass("QiSubClass"));
QiLog(@"QiSuperClass的元类对象类型:%@", objc_getMetaClass("QiSuperClass"));
[QiSuperClass performSelector:@selector(subClassMethod)];
输出内容如下:
superMethod
superCls对象当前类型:QiSuperClass
superCls对象当前类型:QiSubClass
subMethod
QiSuperClass的元类对象地址:0x10d6e3a10
QiSuperClass的元类对象地址:0x10d6e3ba0
QiSubClass的元类对象地址:0x10d6e3ba0
subClassMethod
昨天同事大成小栈 问到笔者对 object_setClass 的应用场景。笔者自己平时项目中没有使用过 object_setClass。(大家如果项目中有 object_setClass 的应用场景,欢迎评论)。笔者说下个人想法,KVO的实现过程中用到了 object_setClass。大家如果看过KVO的部分实现,可能会了解到当我们给监听指定类的后,系统会为指定的类动态创建一个监听类的子类,并且修改监听类对象的isa指针到新创建的子类。笔者用如下代码做个初步示意。以后的文章会再做更多介绍。
@interface QiRuntimeTableViewController ()
@property (nonatomic, copy) NSString *observeString;
@end
调用如下代码
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"self class:%@", [self class]);
NSLog(@"object_getClass:%@", object_getClass(self));
[self addObserver:self forKeyPath:@"observeString" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"self class:%@", [self class]);
NSLog(@"object_getClass:%@", object_getClass(self));
}
上述代码的结果为:
self class:QiRuntimeTableViewController
object_getClass:QiRuntimeTableViewController
self class:QiRuntimeTableViewController
object_getClass:NSKVONotifying_QiRuntimeTableViewController
查看结果就会发现实例对象 self 的 isa 指针发生了改变。指向了运行时新创建的类 NSKVONotifying_QiRuntimeTableViewController,笔者认为这一步就是调用了 object_setClass(self, NSClassFromString(@"NSKVONotifying_QiRuntimeTableViewController"));
笔者就不在这里放置object_setClass的源码了,笔者在下方贴出了object_getClass 的相关源码,如有兴趣,大家可继续查看或下载runtime源码查看详情。
runtime源码下载地址:https://opensource.apple.com/tarballs/objc4/
三、object_getClass 相关源码
1. object_getClass 源码
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
object_getClass 的源码的字面意思表明该函数用于获取指定对象的 isa 指针。具体的调用细节源码如下。
2. objc_class 结构体源码
如下代码表明 Class
类型是相当于是 objc_class *
类型。
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
objc_class 结构体源码如下:
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
class_rw_t *data() const {
return bits.data();
}
void setData(class_rw_t *newData) {
bits.setData(newData);
}
void setInfo(uint32_t set) {
ASSERT(isFuture() || isRealized());
data()->setFlags(set);
}
void clearInfo(uint32_t clear) {
ASSERT(isFuture() || isRealized());
data()->clearFlags(clear);
}
// 省略若干代码.....
}
3. objc_object 结构体源码
struct objc_object {
private:
isa_t isa;
public:
// ISA() assumes this is NOT a tagged pointer object
Class ISA();
// rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
Class rawISA();
// getIsa() allows this to be a tagged pointer object
Class getIsa();
uintptr_t isaBits() const;
// 更多其他代码...
}
4. getIsa() 源码
#if SUPPORT_TAGGED_POINTERS
inline Class
objc_object::getIsa()
{
if (fastpath(!isTaggedPointer())) return ISA();
extern objc_class OBJC_CLASS_$___NSUnrecognizedTaggedPointer;
uintptr_t slot, ptr = (uintptr_t)this;
Class cls;
slot = (ptr >> _OBJC_TAG_SLOT_SHIFT) & _OBJC_TAG_SLOT_MASK;
cls = objc_tag_classes[slot];
if (slowpath(cls == (Class)&OBJC_CLASS_$___NSUnrecognizedTaggedPointer)) {
slot = (ptr >> _OBJC_TAG_EXT_SLOT_SHIFT) & _OBJC_TAG_EXT_SLOT_MASK;
cls = objc_tag_ext_classes[slot];
}
return cls;
}
5. ISA() 源码
#if SUPPORT_NONPOINTER_ISA
inline Class
objc_object::ISA()
{
ASSERT(!isTaggedPointer());
#if SUPPORT_INDEXED_ISA
if (isa.nonpointer) {
uintptr_t slot = isa.indexcls;
return classForIndex((unsigned)slot);
}
return (Class)isa.bits;
#else
return (Class)(isa.bits & ISA_MASK);
#endif
}
参考学习网址
[objc explain]: Classes and metaclasses
What is a meta-class in Objective-C?
Friday Q&A 2010-11-6: Creating Classes at Runtime in Objective-C
Objective-C Runtime 运行时之一:类与对象
Objc Runtime 总结
神经病院 Objective-C Runtime 入院第一天—— isa 和 Class
推荐文章
Flutter中的RenderObjectElement与RenderObjectWidget
Flutter中的StatelessWidget及其生命周期
Flutter中的Widget
Flutter中的Element(下篇)
Flutter中的Element(上篇)
iOS 解决 [NSURL fileURLWithPath:] 对 # 的编码问题
Xcode 调整导航目录字体大小b
Swift 5.1 (21) - 泛型
Swift 5.1 (20) - 协议
Swift 5.1 (19) - 扩展
Swift 5.1 (18) - 嵌套类型
浅谈编译过程