Objective-C语言是扩展于C语言的一种面向对象的编程语言,然而其方法的调用方式又和大多数面向对象语言大有不同,其采用消息传递、转发的方式进行方法的调用。因此在OC中,对象的真正行为往往发生在运行时确定而非编译时确定,所以OC有被称为一种运行时动态语言。
1、动态的Objective-C语言
我们将会对Objective-C语言的消息机制与运行时动态性进行探讨,包括消息传递与转发的基本原理、Objective-C函数调用的基本原理以及运行时的一些基础知识。
1.1消息转发机制
许多面向对象语言中方法的调用都采用obj.function这样的方式,在OC语言中却采用中括号包裹的方式,如[obj function]。实际上,OC中的每一个方法调用最后都会被转换成一条消息进行发送。
一条消息包含三部分:方法选择器、接收消息的对象及参数。objc_msgSend函数就是用来发送消息的。
如下,我们创建一个Person类,继承于NSObject,在Person类中添加个- (void)showName:(NSString *)name age:(NSInteger)age;方法,然后在调用该方法,代码如下:
- (void)showRunTimeSendMsg {
Person *p = [[Person alloc]init];
//常规调用方法
[p showName:@"Henry" age:20];
//发送消息
((void(*)(id,SEL,NSString *,NSInteger))objc_msgSend)(p,@selector(showName:age:),@"Jack",21);
}
打印信息:

运行工程,正常运行,且打印信息正常打印,说明发送消息成功。
1.2、消息传递与继承链
在介绍消息机制前,关于@selector()我们还需要深入理解下。通过@selector(方法名)可以获取到一个SEL类型的对象,SEL实际上是objc_selector结构体指针,在OC库头文件中没有找到对objc_selector结构体的定义,但我们可以合理猜测,其中很可能包含的是一个函数指针。
因此可以将SEL理解为函数签名,在程序的编译阶段,我们定义类中所有的方法会生成一个方法签名列表,这个列表是类直接关联的(从原则上来说,类的本质也是对象,它是一个单例对象),在运行时通过方法签名表来找到具体要执行的函数。
我们再来看objc_msgSend()函数,前面说过,它的第一个参数为接收消息的对象,第二个参数为方法签名,之后为传递的参数。那么OC运行时是如何根据一个对象实例来找到方法签名表,再找到要执行的方法呢?细心观察,你会发现所有的NSObject子类对象中都包含一个isa成员变量,这个isa变量时Class类型,我们的主角终于来了~ ~。Class顾名思义就是“类”类型,其实质是objc_class结构体指针:
typedef struct objc_property *objc_property_t;
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指针,这个指针指向的类实际上是元类,即构造“类”的类。我们也无需纠结这些概念,举个简单的例子:在OC中有加方法和减方法,减方法是实例对象调用的方法,在每一个“类”中都包含一个函数列表,就是上面的objc_method_list结构体数组指针,同样如果调用加方法,实际上是从类的元类中找到对应的方法列表,这个列表就是我们前面提到的方法签名列表,进行方法的执行。关于实例对象、“类”对象和元类,下图很好的表现了它们间的关系:

上面介绍的消息发送机制其实十分不完整,OC是支持继承的,因此如果在当前对象的类的方法列表中没有找到此消息对应的方法签名,则系统会通过Superclass一层层继续向上,直到找到相应的方法或者到达继承链的顶端。
1.1.3、拯救未知消息的三根救命稻草
因此我们可以深入的分析消息的传递过程,如果消息的接收对象可以处理这个消息,即在其isa指针对应的类中可以查找到这个方法,那找到对应方法直接执行。如果接收对象无法处理,其父类、父父类等都无法处理,如果出现这种情况,OC为了增强语言的动态性,程序并不会马上Crash,在Crash前,有三次机会可以挽救。
第一根救命稻草:
如上所述,如果对象的整个继承链都无法处理当前消息,那么首先会调用接收对象所属的+ (BOOL)resolveInstanceMethod:(SEL)sel 方法(对应实例方法),+ (BOOL)resolveClassMethod:(SEL)sel方法(对应类方法),这个方法中,开发者有机会为类动态添加方法。如果动态添加了方法,则可以在这个方法中返回YES,那么这条消息依然会被成功处理。例如:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
- (void)showSelf;
@end
NS_ASSUME_NONNULL_END
#import "Person.h"
#import <objc/message.h>
@implementation Person
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if ([NSStringFromSelector(sel) isEqualToString:@"showSelf"]) {
SEL aSel = NSSelectorFromString(@"showMy");
Method aMethod = class_getInstanceMethod(self, aSel);
class_addMethod(self, sel, method_getImplementation(aMethod), "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
- (void)showMy {
NSLog(@"showMy");
}
@end
当我们调用showSelf方法时,发现程序可以正常运行。
IMP和SEL并不同,SEL可以理解为函数签名,其与函数名相关联,而IMP是函数所在地址的指针。简单理解,通过IMP我们可以直接拿到函数的地址。
第二根救命稻草
当通过运行时添加方法被否定后,系统会接着调用forwardingTargetForSelector方法,这个方法用来对消息进行转发,在OC中强大的消息转发机制的奥秘就在这里。forwardingTargetForSelector方法需要返回一个id类型的对象,系统会将当前对象服务处理的消息转发给这个方法返回的对象,如果这个返回的对象可以处理,那么程序依然可以执行。示例如下:
我们在Dog类中添加方法和实现 showSelf
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Dog : NSObject
- (void)showSelf;
@end
NS_ASSUME_NONNULL_END
#import "Dog.h"
@implementation Dog
- (void)showSelf {
NSLog(@"showSelf In DogClass");
}
@end
在Person类中实现如下方法:
- (id)forwardingTargetForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"showSelf"]) {
return [Dog new];
}
return [super forwardingTargetForSelector:aSelector];
}
forwardingTargetForSelector方法可以返回一个对象,OC会将当前对象无法处理的消息转发给这个方法返回的对象。如果返回nil,则表示不进行消息转发,那么就要用到第三根救命稻草了。
第3根救命稻草
如果错过了前面的动态添加方法和快速转发两根救命稻草,那么我们还有最后一次机会。系统会调用methodSignatureForSelector方法,这个方法的主要用途是询问这个选择器是否是有效的,我们需要返回一个NSMethodSignature对象,这个对象是函数签名的抽象。
如果我们返回了有效的函数签名,那么接着系统会调用forwardInvocation方法,这就是拯救程序的最后一根稻草了,这个函数会直接将消息包装成NSInvocation对象传入,我们直接将其发送给可以处理此消息的对象即可。示例如下:
//询问此选择器是否有效
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
if ([NSStringFromSelector(aSelector) isEqualToString:@"showSelf"]) {
return [[Dog new] methodSignatureForSelector:aSelector];
}
return [super methodSignatureForSelector:aSelector];
}
//处理消息
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([NSStringFromSelector(anInvocation.selector) isEqualToString:@"showSelf"]) {
[anInvocation invokeWithTarget:[Dog new]];
}else {
[super forwardInvocation:anInvocation];
}
}
1.4你真的需要救命稻草吗
通过上面的接收,我们对OC的消息机制有了全面深入的了解。正常情况下我们不会使用到这些函数的。
最后如果我们没有使用上面的救命稻草,那么当向某个对象发送无法处理的消息时,系统最终会调用到NSObjce类的doseNotRecognizeSelector方法,这个方法会抛出异常。我们可以重写该方法自定义输出:
- (void)doesNotRecognizeSelector:(SEL)aSelector {
NSLog(@"not have a method name %@",NSStringFromSelector(aSelector));
if ([NSStringFromSelector(aSelector) isEqualToString:@"showSelf"]) {
return;
}
[super doesNotRecognizeSelector:aSelector];
}
网友评论