引言
Objective-C 是一门动态语言,在运行时我们才知道具体的对象和方法。在Objective-C 中,动态性是由 runtime 相关的库赋予的。runtime维护者程序运行的相关机制,其中最重要的就是消息机制。
![](https://img.haomeiwen.com/i2633775/23eca868a3b46c3e.jpeg)
消息发送
在iOS中,方法的调用就是消息发送的过程。即调用
objc_msgSend(void /* id self, SEL op, ... */ )。
我们先来看一下SEL的定义:
typedef struct objc_selector *SEL;
objc_selector是一个映射到方法的C字符串。@selector()选择子只与函数名有关。这也是OC不支持函数重载的原因。因为即使变量不同也会导致他们具有相同的选择器。 -- 有点扯远了,当个小知识点来看吧。
SEL能够找寻到方法地址,来实现调用,调用过程是什么样呢,我们首先来了解一下Objective-C 的类结构。
在Objective-C中,每个对象都有一个isa指针,指向该对象的类,类中描述了该对象的特点,如成员变量的列表、成员函数的列表等。例如NSObject就是一个包含isa指针的结构体。
@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
Class isa OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
同样的,在Objective-C中,类同样接收消息,所以每个类实际上也是一个对象我们称之为类对象,每个类也有它的isa指针。
typedef struct objc_class *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;
由于类也是一个对象,所以它也必须是一个类的实例,这个类就是元类(metaclass)。元类中保存了类方法的列表。
我们来看下经典的结构图:
![](https://img.haomeiwen.com/i2633775/f36826c5b0230984.png)
由此得知:
- 类对象中定义了对象的实例方法,元类中定义了类方法。
- 对象方法的调用先从该类对象中方法列表中开始,没找到的话就会继续从该类对象的父类中查找。当然了,由于效率的问题,每个消息都遍历一次objc_method_list并不合理。所以需要把经常被调用的函数缓存下来,去提高函数查询的效率。这也就是objc_class中另一个重要成员objc_cache做的事情,把sel的method_name作为key,method_imp作为value给存起来。当再次收到消息的时候,可以直接在cache里找到,避免去遍历objc_method_list。同样的,类方法的调用会先从该元类中查找,没找到的话就会从该元类的父类中查找。
消息转发
在调用对象拿到对应的selector之后,如果自己无法执行这个方法,那么该条消息要被转发。或者临时动态的添加方法实现。如果转发到最后依旧没法处理,程序就会崩溃。
如以下例子:
新建一个Person类继承于NSObject,并声明一个msgTest方法(不实现);
@interface Person : NSObject
- (void)msgTest;
@end
调用该方法:
- (void)viewDidLoad {
[super viewDidLoad];
Person * p = [Person new];
[p msgTest];
}
此时我们将项目跑起来就会发现,项目是能通过编译的,但是会崩溃掉
-[Person msgTest]: unrecognized selector sent to instance 0x6000020543e0
在方法在调用时,系统会查看这个对象能否接收这个消息(没有实现这个方法),如果不能接收,就会调用下面这几个方法,会采用拯救模式,给你“补救”的机会。
第一次补救: 动态方法解析
/*
cls:要添加方法的类
name:选择器
imp:方法实现,IMP在objc.h中的定义是:typedef id (*IMP)(id, SEL, ...);该方法至少有两个参数,self(id)和_cmd(SEL)
types:方法,参数和返回值的描述,"v@:"表示返回值为void,没有参数
*/
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(msgTest)){
return class_addMethod([self class],sel, (IMP)reTest, "v@:");
}
return [super resolveInstanceMethod:sel];
}
void reTest(id self, SEL _cmd) {
NSLog(@"test");
}
可看到打印数据:
2019-10-22 22:10:17.526103+0800 learn[47237:860941] test
注: resolveInstanceMethod处理对象方法,resolveClassMethod处理类方法。
第二次补救: 消息重定向
我们继续以实例方法举例:
创建一个新的类RePerson,该类包含有msgTest的实现方法。
#import "RePerson.h"
@implementation RePerson
- (void)msgTest{
NSLog(@"rePerson");
}
@end
我们在Person类里实现两个步骤:
- resolveInstanceMethod返回值设为NO。
2.forwardingTargetForSelector返回值为RePerson对象。
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(msgTest)){
return NO;
}
return [super resolveInstanceMethod:sel];
}
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(msgTest)){
return [RePerson new];
}
return [super forwardingTargetForSelector:aSelector];
}
这样就可以得到结果:
2019-10-23 00:56:07.855455+0800 learn[47519:906599] rePerson
第三次补救: 消息转发
也是改变调用对象,使该消息在新对象上调用;不同是forwardInvocation方法带有一个NSInvocation对象,这个对象保存了这个方法调用的所有信息,包括SEL,参数和返回值描述等。
同样的,我们利用上文中描述的RePerson类,实现以下方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
if (anInvocation.selector == @selector(msgTest)){
[anInvocation invokeWithTarget:[RePerson new]];
return;
}
[super forwardInvocation:anInvocation];
}
同样的,我们也可以拿到如下答案:
2019-10-23 01:09:16.310219+0800 learn[47574:911006] rePerson
网友评论