面试好文

iOS 的实例对象、类、元类

2020-12-30  本文已影响0人  QiShare

级别:★★☆☆☆
标签:「实例对象」「类」「元类」「实例对象、类、元类」
作者: 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 类对象的描述。

实例对象、类、元类之间的关系

我们看一下实例对象、类、元类之间的关系图。

instance_class_metaClass

图片引自[objc explain]: Classes and metaclasses

根据上图,我们可以梳理一下实例对象、类、元类之间的关系。

一、实例对象、类对象、元类的 isa 指向及父类指向说明

下边笔者说明下上图中 isa 和父类指向。

  1. 实例对象的 isa 指针指向类对象;
  2. 类对象的 isa 指针指向元类对象;
  3. 元类对象的 isa 指针指向根元类(NSObject元类);
  4. 根元类(NSObject元类)的 isa 指针指向自己;
  1. 子类的元类的父类指向父类的元类;
  2. NSObject的父类为nil;
  3. 根元类(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) - 嵌套类型
浅谈编译过程

上一篇 下一篇

猜你喜欢

热点阅读