上下文- 前情概述:
上一篇文章 OC对象占用内存原理 (一文彻底搞懂) 中我们讲到 OC 对象本身是一个结构体, 这个结构体只有一个 isa
指针, 关于 OC 对象实际开辟内存原理我们搞清楚了. 那么本篇文章就结合 runtime
来实际探讨一下 OC 对象方法查找的具体流程以及方法调用的原理.
isa
任何数据结构,只要在恰当的位置具有一个指针指向一个
class
,那么,它都可以被认为是一个对象。objc_class
和objc_object
的struct
指针都能称为对象,我们为了区分可以把它们分别叫做类对象和实例对象,其中实例对象的isa
指针指向类对象 (这个指向下面会讲)。
在 OC 中,一个对象所属于哪个类,是由它的 isa
指针指向的。这个 isa
指针指向这个对象所属的 class
。
个人建议: 可以适当结合英文意思来理解, isa -> is a what? -> 你是个啥.
实例对象 | 类对象 | 元类 的基础概念区分
简单来个 :
提前创建了一个类 LBPerson
继承与 NSObject
. 然后创建这个对象.
LBPerson * obj = [[LBPerson alloc] init];
- 实例对象 :
obj
, 其isa
指向类对象 - 类对象 :
LBPerson
, 其isa
指向元类 - 元类 : 元类就是类对象所属的类 , 其
isa
指向根源类, 也就是NSObject
的元类.
如果我们在创建一个子类,继承与
LBPerson
, 那么这个子类就是上图中的 Subclass(class)
, 实例化出来的对象就是 Instance Of Subclass
.
需要注意的是:
- 任何元类的
isa
都指向根元类. 根源类的isa
指向它自己. 以此来实现一个完整的内循环, 所有的类,都会有类型,isa
不会为空.NSObject
的父类为nil
.- 根元类的父类指向
NSObject
.
那么这三种类型的对象分别都持有什么呢? 比如说 方法到底存在哪个对象中? 属性存在哪个对象? 接下来我们一一探讨.
类对象
类对象我们通过源码来查看其中保存了哪些内容,
shift
+ cmd
+ O
, 输入 objc.
找到 38 行:
typedef struct objc_class *Class;
cmd + 点 objc_class
, 来到类对象的结构体中.
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
稍微介绍一下各个属性的意思:
-
isa
:上文讲述过 -
super_class
:父类,如果该类已经是最顶层的根类,那么它为nil
。 -
version
:类的版本信息,默认为 0 -
info
:供运行期使用的一些位标识。 -
instance_size
:该类的实例变量大小 -
ivars
:成员变量的列表 -
methodLists
:方法列表 -
cache
:缓存结构体桶 -
protocols
:协议列表
类对象总结:
是一个功能完整的对象。特殊之处在于它们是在运行时由编译器创建的,它没有自己的实例变量(这里区别于类的成员变量,他们是属于实例对象的,而不是属于类对象的,类方法是属于类对象自己的),但 类对象中存着成员变量与实例方法列表。
存储内容如图:
实例对象
先来看控制台提供给我们的.
我们看到实例对象中有属性, 和
isa
. 这也跟我们上一篇博客中探讨 OC 对象占用内存原理 得出的结果是一模一样.
实例对象总结:
当我们在代码中新创建一个实例对象时,拷贝了实例所属的类的成员变量,但不拷贝类定义的方法。调用实例方法时,根据实例的isa
指针去寻找方法对应的函数指针。
存储内容如图:
元类
OC 的类方法是使用元类的根本原因, 而这也是元类中存储的最重要的内容. 当你给类发消息时,消息是在寻找这个类的元类的方法列表. 类对象是元类的实例. 因此元类存储着类方法.
存储内容如下:
元类对象总结:
OC 的类方法是使用元类的根本原因,因为其中存储着对应的类对象调用的方法即类方法。其他时候都倾向于隐藏元类,因此真实世界没有人发送消息给元类对象。元类的定义和创建看起来都是编译器自动完成的,无需人为干涉。
要获取一个类的元类,可使用如下定义的函数:
Class objc_getMetaClass(const char* name); //name为类的名字
此外还有一个获取对象所属的类的函数:
Class object_getClass(id obj) ;
由于类对象是元类的实例,所以当传入的参数为类名时,返回的就是指向该类所属的元类的指针。
总结
基于此, 我们可以根据其存储内容稍微总结一下
1️⃣: 实例对象的
isa
指向类对象,当调用对象方法,通过实例对象的isa
找到类对象,最终找到对对象方法进行调用2️⃣: 类对象的
isa
指向元类,调用类方法,通过类对象中的isa
找到元类,最终找到元类中的类方法进行调用3️⃣: 当子类的对象要调用父类的对象方法,先通过子类的
isa
找到父类的class
. 然后通过superClass
找到父类的class
最后找到消息进行调用.
最后要提醒注意的一点是:
根类不一定是
NSObject
,因为后面介绍的objc_allocateClassPair
函数也可以创建出一个根类。
元类的实际应用
类对象和元类对象的相关方法:
- ① :
object_getClass
跟随实例的isa
指针,返回此实例所属的类,对于实例对象 (instance
) 返回的是类 (class
) ,对于类 (class
) 则返回的是元类 (metaclass
) .
- ② : class 方法对于实例对象 (
instance
) 会返回类 (class
) , 但对于类 (class
) 则不会返回元类 (metaclass
), 而只会返回类本身,即[@"instance" class]
返回的是__NSCFConstantString
, 而[NSString class]
返回的是NSString
。
- ③ :
class_isMetaClass
可判断某类是否为元类.
- ④ : 使用
objc_allocateClassPair
可在运行时创建新的类与元类对,使用class_addMethod
和class_addIvar
可向类中增加方法和实例变量,最后使用objc_registerClassPair
注册后,就可以使用此类了。这体现了 OC 作为运行时语言的强大之一:在代码运行中动态创建类并添加方法。
至此, 三种不同的类的基础我们已经讲完, 下一篇会基于此基础, 探索方法查找流程.