Objective-C是一门动态语言
,类型的判断、类的成员变量、方法的内存地址都是在程序的运行阶段才最终确定,并且还能动态的添加成员变量和方法。这意味着即使调用对象一个没有实现的方法,编译也能通过,甚至一个对象它是什么类型并不是表面我们所看到的那样,只有运行之后才能决定其真正的类型。因此OC具有多态
、动态类型
、动态绑定
特性。
正是因为其动态特性
,在OC中调用方法并不是一个简单的函数调用过程,它并不直接调用方法,而是运用Runtime
机制实现函数调用,称之为消息发送
。
Runtime
Runime运行时,是OC中一套底层的C语言API,底层都是基于它来实现的。在运行时,我们所编写的代码会转换为C语言运行。具体内容可以参考:runtime概述
在OC中发送一条消息:[receiver message]
,
会转为调用底层函数:id objc_msgSend(id self, SEL op, ...)
让我们先看看这个函数的参数和返回值
id
在OC中表示任意类型的对象,其结构如下:
/// Represents an instance of a class.
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
显然id
是一个objc_objec结构体类型指针,其内部有一个Class
类型的isa指针,指向对应的类。
SEL
是对方法的包装,返回对应的编号
struct objc_class {
Class _Nonnull isa 指向自身对应的类;
#if !__OBJC2__
Class _Nullable super_class 指向父类
const char * _Nonnull name
long version
long info
long instance_size
struct objc_ivar_list * _Nullable ivars
struct objc_method_list * _Nullable * _Nullable methodLists 类中的方法信息列表
struct objc_cache * _Nonnull cache 调用过的方法信息列表
struct objc_protocol_list * _Nullable protocols
#endif
}
上面是class
的结构
消息发送过程一 --- 函数在类中实现
当发送一条消息时,object_getClass(id obj)
通过id
的isa指针找到对象的对应的类class
,在class
中先去cache
中 通过SEL
查找对应函数method
(cache中method列表是以SEL为key通过hash表来存储的,这样能提高函数查找速度),若 cache
中未找到,方法列表methodList
中查找,若能找到,则将method
加 入到cache
中,以方便下次查找。否则到对应的super_class
中查找,如此循环直到NSObject。
我们具体看OC如何用runtime实现函数调用:
自定义Person对象
@interface Person : NSObject
+ (void)eat;
- (void)eat;
- (void)run:(int)age;
@end
@implementation Person
+ (void)eat
{
NSLog(@"类方法-吃东西");
}
- (void)eat
{
NSLog(@"实例方法-吃东西");
}
- (void)run:(int)age
{
NSLog(@"跑了%d米",age);
}
@end
-------------------------UIViewController-----------------------------
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc] init];
objc_msgSend(person, @selector(eat));
objc_msgSend(person, @selector(run:),10);
Class personClass = [Person class];
objc_msgSend(personClass, @selector(eat));
}
-------------------------console-----------------------------
[1373:101045] 实例方法-吃东西
[1373:101045] 跑了10米
[1373:101045] 类方法-吃东西
以上就是发送消息调用类中实现的方法的过程。
消息发送过程二 --- 函数在类中未实现,动态解析方法
当向receiver
发送message时,若SEL对应的函数未在类或者类的父类中实现,程序并不会立即crash,而是对message进一步转发,调用_objc_msgForward(id receiver, SEL sel, ...)
,具体表现在类中对应的方法为:
1.+ resolveInstanceMethod:(SEL)sel // 对应实例方法
+ resolveClassMethod:(SEL)sel // 对应类方法
2.- (id)forwardingTargetForSelector:(SEL)aSelector
3.- (void)forwardInvocation:(NSInvocation *)anInvocation
注意:以上方法优先级依次递减,高优先级方法消息转发成功不会再执行低优先级方法
现在为Person
实例对象发送一条@selector(wy_sleep:)
消息:
[person performSelector:@selector(wy_sleep:) withObject:@100];
因为person
未实现@selector(wy_sleep:)
,消息转发转而调用+ (BOOL)resolveInstanceMethod:(SEL)sel
:,判断类是在运行时动态的添加方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(wy_sleep:)) {
/*
cls:给哪个类添加方法
SEL:添加方法的方法编号是什么
IMP:方法实现,函数入口,函数名
types:方法类型
最后一个参数请查看文档Help - API reference - runtime
*/
class_addMethod([self class], sel, (IMP)wy_sleep, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void wy_sleep(id self, SEL _cmd, id param1){
NSLog(@"调用eat %@ %@ %@",self,NSStringFromSelector(_cmd),param1);
}
-------------------------console-----------------------------
[1553:136714] 调用wy_sleep <Person: 0x60000003ed40> wy_sleep: 100
其中class_addMethod(Class cls, SEL name, IMP imp, const char *types)
为类添加新的方法实现。
消息发送过程三 --- 函数在类中未实现且无动态解析,快速转发消息
当调用的方法没有在类中实现,且没有动态添加时,则消息继续转发,判断类中是否预备了备用的消息接受者
- (id)forwardingTargetForSelector:(SEL)aSelector
简单、快速,但只能只能指定一个转发对象,无法对消息进行操作
定义对象A、B
-------------------------A-----------------------------
@interface A : NSObject
- (void)prinAName;
@end
@implementation A
- (void)prinAName
{
NSLog(@"Hello World! My name is A");
}
@end
-------------------------B-----------------------------
@interface B : NSObject
- (void)prinBName;
@end
@implementation B
- (void)prinBName
{
NSLog(@"Hello World! My name is B");
}
@end
实现Person中的的快速消息转发:
- (id)forwardingTargetForSelector:(SEL)aSelector
{
if (class_respondsToSelector([A class], aSelector)) {
return [[A alloc] init];
}
B *b = [[B alloc] init];
if ([b respondsToSelector:aSelector]) {
return b;
}
return [super forwardingTargetForSelector:aSelector];
}
向person实例发送相应的消息:
[person performSelector:@selector(prinAName)];
[person performSelector:@selector(prinBName)];
输出结果:
[1962:235819] Hello World! My name is A
[1962:235819] Hello World! My name is B
消息发送过程四 --- 函数在类中未实现且无动态解析,完整的消息转发
除了设置备用消息接受者,快速转发消息外,还能将完整的消息转发给实现的对象
-(void)forwardInvocation:(NSInvocation *)anInvocation
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *methodSignature = [A instanceMethodSignatureForSelector:aSelector];
if (methodSignature == nil) {
methodSignature = [B instanceMethodSignatureForSelector:aSelector];
}
if (methodSignature == nil) {
methodSignature = [super methodSignatureForSelector:aSelector];
}
return methodSignature;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL selector = [anInvocation selector];
A *a = [[A alloc] init];
B *b = [[B alloc] init];
if ([a respondsToSelector:selector]) {
[anInvocation invokeWithTarget:a];
} else if ([b respondsToSelector:selector]) {
[anInvocation invokeWithTarget:b];
} else {
[super forwardInvocation:anInvocation];
}
}
消息发送过程五 --- Crash
当前面消息发送的4个过程都没有实现时,程序调用- (void)doesNotRecognizeSelector:(SEL)aSelector
崩溃
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSLog(@"无法实现方法,crash");
}
总结:
OC中函数调用通过发送消息实现,分为五个步骤,如下: