当我们用中括号[]
调用OC函数的时候,实际上会进入消息发送和消息转发流程:
消息发送(Messaging),runtime系统会根据
SEL
查找对用的IMP
,查找到,则调用函数指针进行方法调用;若查找不到,则进入动态消息解析和转发流程,如果动态解析和消息转发失败,则程序crash并记录日志。
在进入正题之前,我们先思考两个问题:
-
类实例可以调用类方法吗? 类可以调用实例方法吗? 为什么?
-
下面代码输出什么?
@interface Father : NSObject
@end
@implementation Father
@end
@interface Son : Father
- (void)showClass;
@end
@implementation Son
- (void)showClass {
NSLog(@"self class = %@, super class = %@", [self class], [super class]);
}
...
Son *son = [Son new];
[son showClass]; // 这里输出什么?
...
消息相关数据结构
SEL
SEL
被称之为消息选择器,它相当于一个key,在类的消息列表中,可以根据这个key,来查找到对应的消息实现。
在runtime中,SEL的定义是这样的:
/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;
它是一个不透明的定义,似乎苹果故意隐藏了它的实现。目前SEL仅是一个字符串。
<font color=red>这里要注意,即使消息的参数不同或方法所属的类也不同,但只要方法名相同,SEL
也是一样的。所以,SEL
单独并不能作为唯一的Key,必须结合消息发送的目标Class,才能找到最终的IMP
。</font>
我们可以通过OC编译器命令@selector()
或runtime函数sel_registerName
,来获取一个SEL
类型的方法选择器。
method_t
当需要发送消息的时候,runtime会在Class的方法列表中寻找方法的实现。在方法列表中方法是以结构体method_t
存储的。
struct method_t {
SEL name;
const char *types;
IMP imp;
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
可以看到method_t
包含一个SEL
作为key,同时有一个指向函数实现的指针IMP
。method_t
还包含一个属性const char *types;
types是一个C字符串,用于表明方法的返回值和参数类型。一般是这种格式的:
v24@0:8@16
关于SEL type,可以参考Type Encodings
IMP
IMP实际是一个函数指针,用于实际的方法调用。在runtime中定义是这样的:
/// A pointer to the function of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);
#endif
IMP
是由编译器生成的,如果我们知道了IMP
的地址,则可以绕过runtime消息发送的过程,直接调用函数实现。关于这一点,我们稍后会谈到。
在消息发送的过程中,runtime就是根据id
和SEL
来唯一确定IMP
并调用之的。
消息
当我们用[]
向OC对象发送消息时,编译器会对应的代码修改为objc_msgSend
, 其定义如下:
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
其实,除了objc_msgSend
,编译器还会根据实际情况,将消息发送改写为下面四个msgSend之一:
<font color=red>objc_msgSend
objc_msgSend_stret</font>
<font color=blue>objc_msgSendSuper
objc_msgSendSuper_stret</font>
当我们将消息发送给super class的时候,编译器会将消息发送改写为**SendSuper
的格式,如调用[super viewDidLoad]
,会被编译器改写为objc_msgSendSuper
的形式。
objc_msgSendSuper
的定义如下:
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
可以看到,调用super方法时,msgSendSuper
的第一个参数不是id self
,而是一个objc_super *
。objc_super
定义如下:
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
__unsafe_unretained _Nonnull Class super_class;
};
objc_super
包含两个数据,receiver
指调用super方法的对象,即子类对象,而super_class
表示子类的Super Class。
这就说明了在消息过程中调用了 super方法和没有调用super方法,还是略有差异的。我们将会在下面讲解。
至于**msgSend
中以_stret
结尾的,表明方法返回值是一个结构体类型。
在objc_msgSend
的内部,会依次执行:
- 检测selector是否是应该忽略的,比如在Mac OS X开发中,有了垃圾回收机制,就不会响应
retain
,release
这些函数。 - 判断当前
receiver
是否为nil
,若为nil
,则不做任何响应,即向nil发送消息,系统不会crash。 - 检查
Class
的method cache,若cache未命中,则进而查找Class
的method list
。 - 若在
Class
的method list
中未找到对应的IMP
,则进行消息转发 - 若消息转发失败,程序crash
objc_msgSend
objc_msgSend
的伪代码实现如下:
id objc_msgSend(id self, SEL cmd, ...) {
if(self == nil)
return 0;
Class cls = objc_getClass(self);
IMP imp = class_getMethodImplementation(cls, cmd);
return imp?imp(self, cmd, ...):0;
}
而在runtime源码中,objc_msgSend
方法其实是用汇编写的。为什么用汇编?一是因为objc_msgSend
的返回值类型是可变的,需要用到汇编的特性;二是因为汇编可以提高代码的效率。
对应arm64,其汇编源码是这样的(有所删减):
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
cmp x0, #0 // nil check and tagged pointer check
b.le LNilOrTagged // (MSB tagged pointer looks negative)
ldr x13, [x0] // x13 = isa
and x16, x13, #ISA_MASK // x16 = class
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
LNilOrTagged:
b.eq LReturnZero // nil check
END_ENTRY _objc_msgSend
虽然不懂汇编,但是结合注释,还是能够猜大体意思的。
首先,系统通过cmp x0, #0
检测receiver
是否为nil
。如果为nil
,则进入LNilOrTagged
,返回0;
如果不为nil
,则现将receiver
的isa
存入x13
寄存器;
在x13
寄存器中,取出isa
中的class
,放到x16
寄存器中;
调用CacheLookup NORMAL
,在这个函数中,首先查找class
的cache,如果未命中,则进入objc_msgSend_uncached
。
objc_msgSend_uncached
也是汇编,实现如下:
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band x16 is the class to search
MethodTableLookup
br x17
END_ENTRY __objc_msgSend_uncached
其内部调用了MethodTableLookup
, MethodTableLookup
是一个汇编的宏定义,其内部会调用C语言函数_class_lookupMethodAndLoadCache3
:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
最终,会调用到lookUpImpOrForward
来寻找class
的IMP
实现或进行消息转发。
lookUpImpOrForward
lookUpImpOrForward
方法的目的在于根据class
和SEL
,在class
或其super class
中找到并返回对应的实现IMP
,同时,cache所找到的IMP
到当前class
中。如果没有找到对应IMP
,lookUpImpOrForward
会进入消息转发流程。
lookUpImpOrForward
的简化版实现如下:
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// 先在class的cache中查找imp
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
runtimeLock.read();
if (!cls->isRealized()) {
runtimeLock.unlockRead();
runtimeLock.write();
// 如果class没有被relize,先relize
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
// 如果class没有init,则先init
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
}
retry:
runtimeLock.assertReading();
// relaized并init了class,再试一把cache中是否有imp
imp = cache_getImp(cls, sel);
if (imp) goto done;
// 先在当前class的method list中查找有无imp
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
// 在当前class中没有找到imp,则依次向上查找super class的方法列表
{
unsigned attempts = unreasonableClassCount();
// 进入for循环,沿着继承链,依次向上查找super class的方法列表
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// 先找super class的cache
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// 在super class 的cache中找到imp,将imp存储到当前class(注意,不是super class)的cache中
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// 在Super class的cache中没有找到,调用getMethodNoSuper_nolock在super class的方法列表中查找对应的实现
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// 在class和其所有的super class中均未找到imp,进入动态方法解析流程resolveMethod
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
// 如果在class,super classes和动态方法解析 都不能找到这个imp,则进入消息转发流程,尝试让别的class来响应这个SEL
// 消息转发结束,cache结果到当前class
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlockRead();
return imp;
}
通过上的源码,我们可以很清晰的知晓runtime的消息处理流程:
- 尝试在当前receiver对应的class的cache中查找imp
- 尝试在class的方法列表中查找imp
- 尝试在class的所有super classes中查找imp(先看Super class的cache,再看super class的方法列表)
- 上面3步都没有找到对应的imp,则尝试动态解析这个SEL
- 动态解析失败,尝试进行消息转发,让别的class处理这个SEL
在查找class的方法列表中是否有SEL的对应实现时,是调用函数getMethodNoSuper_nolock
:
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
方法实现很简单,就是在class
的方法列表methods
中,根据SEL
查找对应的imp
。
PS:这里顺便说一下Category
覆盖类原始方法的问题,由于在methods
中是线性查找的,会返回第一个和SEL
匹配的imp
。而在class
的realizeClass
方法中,会调用methodizeClass
来初始化class
的方法列表。在methodizeClass
方法中,会将Category
方法和class
方法合并到一个列表,同时,会确保Category
方法位于class
方法前面,这样,在runtime寻找SEL
的对应实现时,会先找到Category
中定义的imp
返回,从而实现了原始方法覆盖的效果。 关于Category的底层实现,我们会Objective-C runtime机制(4)——深入理解Category中讲解。
关于消息的查找,可以用下图更清晰的解释:
这里写图片描述runtime用isa找到receiver对应的class,用superClass找到class的父类。
这里用<font color=#3299cc>蓝色的表示实例方法的消息查找流程:通过类对象实例的isa查找到对象的class,进行查找。</font>
用<font color=#ff00ff>紫色表示类方法的消息查找流程: 通过类的isa找到类对应的<font color=#a67d3d>元类</font>, 沿着元类的super class链一路查找</font>
关于<font color=#a67d3d>元类</font>,我们在上一章中已经提及,元类是“类的类”。因为在runtime中,类也被看做是一种对象,而对象就一定有其所属的类,因此,类所属的类,被称为类的<font color=#a67d3d>元类(meta class)</font>。
<font color=red>我们所定义的类方法,其实是存储在元类的方法列表中的。</font>
关于元类的更多描述,可以查看这里。
类调用实例方法
这里有一个很好玩的地方,注意到在SEL查找链的最上方:Root Class
和Root meta Class
。
我们上面说到,对于类方法,是沿着紫色的路线依次查找Super类方法列表。一路上各个节点,都是<font color=#a67d3d>元类(meta class)</font>。而注意到,Root meta Class
的super class竟然是Root class
! 也就是说,当在Root meta Class
中找不到类方法时,会转而到Root class
中查找类方法。而在Root class
中存储的,其实都是<font color=blue>实例方法</font>。
换句话说,我们在通过类方法的形式调用Root class中的实例方法,在OC中, 也是可以被解析的!
比如,在NSObject
中,有一个实例方法:
@interface NSObject <NSObject> {
...
- (IMP)methodForSelector:(SEL)aSelector;
...
}
然后,我们自定义类:
@interface MyObj : NSObject
- (void)showY;
@end
@implementation MyObj
- (void)showY {
NSLog(@"AB");
}
@end
我们分别通过类方法的形式调用methodForSelector
和 showY
:
[MyObj methodForSelector:@selector(test)];
[MyObj showY];
会发现,编译器允许methodForSelector
的调用,并能够正常允许。
而对于showY
,则会编译错误。
至于原因,就是因为对于Root meta class
,其实会到Root class
中寻找对应的SEL实现。
类似的,还有一个好玩的例子,在子类中,我们重写NSObject
的respondsToSelector
方法,然后通过类方法和实例方法两种形式来调用,看看分别会发生什么情况:
@interface NSObject <NSObject> {
...
- (BOOL)respondsToSelector:(SEL)aSelector;
...
}
@interface MyObj : NSObject
@end
@implementation MyObj
- (BOOL)respondsToSelector:(SEL)aSelector {
NSLog(@"It is my overwrite");
return YES;
}
@end
然后通过类方法和实例方法分别调用
[MyObj methodForSelector:@selector(test)];
MyObj *obj = [[MyObj alloc] init];
[obj showY];
这两种调用方式会有什么不同?
这当做一个思考题,如果大家理解了上面IMP的查找流程,那么应该能够知道答案。
objc_msgSendSuper
看完了objc_msgSend
方法的调用流程,我们再来看一下objc_msgSendSuper
是如何调用的。
当我们在代码里面显示的调用super 方法时,runtime就会调用objc_msgSendSuper
来完成消息发送。
objc_msgSendSuper
的定义如下:
OBJC_EXPORT id _Nullable
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
当我使用super
关键字时,在这里,super并不代表某个确定的对象,而是编译器的一个符号,编译器会将super替换为objc_super *类型
来传入objc_msgSendSuper
方法中。
而objc_super
结构体定义如下:
struct objc_super {
/// Specifies an instance of a class.
__unsafe_unretained _Nonnull id receiver;
/// Specifies the particular superclass of the instance to message.
__unsafe_unretained _Nonnull Class super_class;
};
第一个成员id receiver,表明要将消息发送给谁。它应该是我们的类实例(注意,是当前类实例,而不是super)
第二个成员Class super_class,表明要到哪里去寻找SEL所对应的IMP。它应该是我们类实例所对应类的super class。(即要直接到super class中寻找IMP,而略过当前class的method list)
简单来说,当调用super method时,runtime会到super class中找到IMP,然后发送到当前class的实例上。因此,虽然IMP的实现是用的super class,但是,最终作用对象,仍然是当前class 的实例。这也就是为什么
NSLog(@"%@ %@",[self class], [super class]);
会输出同样的内容,即[self class]
的内容。
我们来看一下objc_msgSendSuper
的汇编实现:
ENTRY _objc_msgSendSuper
MESSENGER_START
ldr r9, [r0, #CLASS] // r9 = struct super->class
CacheLookup NORMAL
// cache hit, IMP in r12, eq already set for nonstret forwarding
ldr r0, [r0, #RECEIVER] // load real receiver
MESSENGER_END_FAST
bx r12 // call imp
CacheLookup2 NORMAL
// cache miss
ldr r9, [r0, #CLASS] // r9 = struct super->class
ldr r0, [r0, #RECEIVER] // load real receiver
MESSENGER_END_SLOW
b __objc_msgSend_uncached
END_ENTRY _objc_msgSendSuper
可以看到,它就是在struct super->class的method list 中寻找对应的IMP,而real receiver
则是super->receiver
,即当前类实例。
如果在super class的cache中没有找到IMP的话,则同样会调用__objc_msgSend_uncached
,这和objc_msgSend
是一样的,最终都会调用到
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
只不过,这里传入lookUpImpOrForward
里面的cls,使用了super class
而已。
动态解析
如果在类的继承体系中,没有找到相应的IMP,runtime首先会进行消息的动态解析。<font color=red>所谓动态解析,就是给我们一个机会,将方法实现在运行时动态的添加到当前的类中。</font>然后,runtime会重新尝试走一遍消息查找的过程:
// 在class和其所有的super class中均未找到imp,进入动态方法解析流程resolveMethod
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry;
}
在源码中,可以看到,runtime会调用_class_resolveMethod
,让用户进行动态方法解析,而且设置标记triedResolver = YES
,仅执行一次。当动态解析完毕,<font color=blue>不管用户是否作出了相应处理</font>,runtime,都会goto retry
, 重新尝试查找一遍类的消息列表。
根据是调用的实例方法或类方法,runtime会在对应的类中调用如下方法:
+ (BOOL)resolveInstanceMethod:(SEL)sel // 动态解析实例方法
+ (BOOL)resolveClassMethod:(SEL)sel // 动态解析类方法
resolveInstanceMethod
+ (BOOL)resolveInstanceMethod:(SEL)sel
用来动态解析实例方法,我们需要在运行时动态的将对应的方法实现添加到类实例所对应的类的消息列表中:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(singSong)) {
class_addMethod([self class], sel, class_getMethodImplementation([self class], @selector(unrecoginzedInstanceSelector)), "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)unrecoginzedInstanceSelector {
NSLog(@"It is a unrecoginzed instance selector");
}
resolveClassMethod
+ (BOOL)resolveClassMethod:(SEL)sel
用于动态解析类方法。 我们同样需要将类的实现动态的添加到相应类的消息列表中。
<font color=blue>但这里需要注意,调用类方法的‘对象’实际也是一个类,而类所对应的类应该是元类
。要添加类方法,我们必须把方法的实现添加到元类
的方法列表中。</font>
在这里,我们就不能够使用[self class]
了,它仅能够返回当前的类。而是需要使用object_getClass(self)
,它其实会返回isa所指向的类,即类所对应的元类
(注意,因为现在是在类方法里面,self所指的是Class,而通过object_getClass(self)
获取self的类,自然是元类
)。
+ (BOOL)resolveClassMethod:(SEL)sel {
if (sel == @selector(payMoney)) {
class_addMethod(object_getClass(self), sel, class_getMethodImplementation(object_getClass(self), @selector(unrecognizedClassSelector)), "v@:");
return YES;
}
return [class_getSuperclass(self) resolveClassMethod:sel];
}
+ (void)unrecognizedClassSelector {
NSLog(@"It is a unrecoginzed class selector");
}
这里主要弄清楚,类
,元类
,实例方法
和类方法
在不同地方存储,就清楚了。
关于class
方法和object_getClass
方法的区别:
当self
是实例对象时,[self class]
与object_getClass(self)
等价,因为前者会调用后者,都会返回对象实例所对应的类。
当self
是类对象时,[self class]
返回类对象自身,而object_getClass(self)
返回类所对应的元类
。
消息转发
当动态解析失败,则进入消息转发流程。所谓消息转发,是将当前消息转发到其它对象进行处理。
- (id)forwardingTargetForSelector:(SEL)aSelector // 转发实例方法
+ (id)forwardingTargetForSelector:(SEL)aSelector // 转发类方法,id需要返回类对象
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if(aSelector == @selector(mysteriousMethod:)){
return alternateObject;
}
return [super forwardingTargetForSelector:aSelector];
}
+ (id)forwardingTargetForSelector:(SEL)aSelector {
if(aSelector == @selector(xxx)) {
return NSClassFromString(@"Class name");
}
return [super forwardingTargetForSelector:aSelector];
}
如果forwardingTargetForSelector
没有实现,或返回了nil
或self
,则会进入另一个转发流程。
它会依次调用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
,然后runtime会根据该方法返回的值,组成一个NSInvocation
对象,并调用- (void)forwardInvocation:(NSInvocation *)anInvocation
。<font color='red'>注意,当调用到forwardInvocation时,无论我们是否实现了该方法,系统都默认消息已经得到解析,不会引起crash。</font>
整个消息转发流程可以用下图表示:
这里写图片描述
<font color=orange>注意,和动态解析不同,由于消息转发实际上是将消息转发给另一种对象处理。而动态解析仍是尝试在当前类范围内进行处理。</font>
消息转发 & 多继承
通过消息转发流程,我们可以模拟实现OC的多继承机制。详情可以参考官方文档。
直接调用IMP
runtime的消息解析,究其根本,实际上就是根据SEL查找到对应的IMP,并调用之。如果我们可以直接知道IMP的所在,就不用再走消息机制这一层了。似乎不走消息机制会提高一些方法调用的速度,但现实是这样的吗?
我们比较一下:
CGFloat BNRTimeBlock (void (^block)(void)) {
mach_timebase_info_data_t info;
if (mach_timebase_info(&info) != KERN_SUCCESS) return -1.0;
uint64_t start = mach_absolute_time ();
block ();
uint64_t end = mach_absolute_time ();
uint64_t elapsed = end - start;
uint64_t nanos = elapsed * info.numer / info.denom;
return (CGFloat)nanos / NSEC_PER_SEC;
} // BNRTimeBlock
Son *mySon1 = [Son new];
setter ss = (void (*)(id, SEL, BOOL))[mySon1 methodForSelector:@selector(setFilled:)];
CGFloat timeCost1 = BNRTimeBlock(^{
for (int i = 0; i < 1000; ++i) {
ss(mySon1, @selector(setFilled:), YES);
}
});
CGFloat timeCost2 = BNRTimeBlock(^{
for (int i = 0; i < 1000; ++i) {
[mySon1 setFilled:YES];
}
});
将timeCost1和timeCost2打印出来,你会发现,仅仅相差0.000001秒,几乎可以忽略不计。这样是因为在消息机制中,有缓存的存在。
总结
在本文中,我们了解了OC语言中方法调用实现的底层机制——消息机制。并了解了self method和super method的异同。
最后,让我们回答文章开头的两个问题:
- 类实例可以调用类方法吗? 类可以调用实例方法吗? 为什么?
类实例不可用调用类方法,因为类实例查找消息IMP的流程仅会沿着继承链查找class 的method list,而对于类方法来说,是存于meta class的method list 的,因此类实例通过objc_msgSend方法是找不到对应的实现的。
类大多数情况下是不能够调用实例方法的,除非实例方法定义在root class——NSObject中。因为,当调用类方法时,会在meta class的继承链的method list 查找对应的IMP,而root meta class对应的super class是NSObject,因此在NSObject中定义的实例方法,其实是可以通过类方法形式来调用的。
- 下面代码输出什么?
@interface Father : NSObject
@end
@implementation Father
@end
@interface Son : Father
- (void)showClass;
@end
@implementation Son
- (void)showClass {
NSLog(@"self class = %@, super class = %@", [self class], [super class]);
}
...
Son *son = [Son new];
[son showClass]; // 这里输出什么?
...
会输出
self class = Son, super class = Son
至于原因,可以到本文关于objc_msgSendSuper
相关讲解中查看。
参考文献
Objective-C Runtime
Objective-C 消息发送与转发机制原理
object_getClass与objc_getClass的不同
网友评论