首先感谢祖国,可以无忧无虑的码代码 ~
If I have seen further, it is by standing on the shoudlers of giants.
1. 方法调用的流程
在OC
中,消息直到运行的时候才绑定到方法的实现上,编译器会将消息表达式转换成一个消息函数的调用objc_msgSend
(
),这个函数将消息的接收者和方法名作为基础参数
如果需要识别参数的话,需要将如下配置设置为NO
(TARGETS -> Build Settings -> Enable Strict Checking of objc_msgSend Calls
)
/* Basic Messaging Primitives
*
* On some architectures, use objc_msgSend_stret for some struct return types.
* On some architectures, use objc_msgSend_fpret for some float return types.
* On some architectures, use objc_msgSend_fp2ret for some float return types.
*
* These functions must be cast to an appropriate function pointer type
* before being called.
*/
#if !OBJC_OLD_DISPATCH_PROTOTYPES
OBJC_EXPORT void
objc_msgSend(void /* id self, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
#else
/**
* Sends a message with a simple return value to an instance of a class.
*
* @param self A pointer to the instance of the class that is to receive the message.
* @param op The selector of the method that handles the message.
* @param ...
* A variable argument list containing the arguments to the method.
*
* @return The return value of the method.
*
* @note When it encounters a method call, the compiler generates a call to one of the
* functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.
* Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper;
* other messages are sent using \c objc_msgSend. Methods that have data structures as return values
* are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
*/
OBJC_EXPORT id _Nullable
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
/**
* Sends a message with a simple return value to the superclass of an instance of a class.
*
* @param super A pointer to an \c objc_super data structure. Pass values identifying the
* context the message was sent to, including the instance of the class that is to receive the
* message and the superclass at which to start searching for the method implementation.
* @param op A pointer of type SEL. Pass the selector of the method that will handle the message.
* @param ...
* A variable argument list containing the arguments to the method.
*
* @return The return value of the method identified by \e op.
*
* @see objc_msgSend
*/
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);
#endif
这个函数完成了动态绑定的所有事情:
- 首先
objc_msgSend
找到selector
的对应的方法实现,因为同一个方法可能在不同的类中有不同的实现,所以需要依赖于接收者来确定实现 -
objc_msgSend
调用方法的实现,并将接收者对象及方法的所有参数传递给方法的实现 - 将实现的返回值作为自己的返回值
再看下 结构体objc_class
/// An opaque type that represents an Objective-C class.
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;
/* Use `Class` instead of `struct objc_class *` */
这个结构体中的 指向父类的指针super_class
和 类方法分发列表objc_method_list
需要在消息分发中用到
当创建一个新的对象的时候,首先分配内存,并且初始化成员变量和isa
,isa
指针也会被初始化,可以让对象访问类和类的继承体系
为了提高消息处理的效率,运行时会缓存使用过的selector
以及对于的的方法地址
当消息发送给一个对象的时候:
-
objc_msgSend
会通过对象的isa
指针获取到类结构体,然后在消息的分发表中查找方法的selector
- 如果没找到就会通过
objc_class
中指向父类的指针找到该对象的父类,并且在父类的消息分发表中查找方法的selector
,沿着类的继承体系直到NSObject
,一旦定位到方法的selector
,函数就会获得了实现的入口,然后传入相应的参数来执行方法的具体实现,并将实现的返回值作为自己的返回值 - 如果最后没有定位到
selector
会走消息转发流程
runtime
中方法动态绑定可以让代码更加灵活,比如可以把消息转发给需要对象,可以随意交换一个方法的实现。但是灵活性的提升带来的是性能上的一些损耗,毕竟需要去查找方法的实现,虽然缓存在一定程度上优化了,但是还是不如函数调用来的直接。如果不想使用动态绑定,可以获取方法的实现地址IMP
,然后像调用函数一样来调用IMP
2. 消息的转发流程
当一个对象能接收一个消息的时候,就会走正常的方法调用流程,但是如果一个对象无法接收指定消息时候,就会走 消息的转发流程
如果是以[objc doSometing]
的方式调用方法,如果objc
无法响应消息的时候,编译器会报错
如果是以- (id)performSelector:(SEL)aSelector;
等类似的形式来调用,需要等到运行时才能确定objc
是否可以收到消息,如果不能的话就crash
默认情况下,对象接收到未知消息的时候,运行时会调用NSObject
的- (void)doesNotRecognizeSelector:(SEL)aSelector;
,控制台会看到这样提示-[ViewController showSomething]: unrecognized selector sent to instance 0x7fe9e640d610
可以在使用之前进行判断
if ([self respondsToSelector:@selector(doSomething)]) {
[self performSelector:@selector(doSomething)];
}
当然如果进行判断的话,后续的消息转发流程就GG
了
当一个对象无法接收消息的时候,就会触发 消息转发(message forwarding
)机制,这时候可以告诉对象如何处理未知消息,避免程序crash
消息转发机制可以分为三个步骤:动态方法解析、备用接收者、完整转发
- 动态方法解析
对象在接收到未知消息的时候,首先会调用对象所属类的两个类方法(NSObject
)+ (BOOL)resolveClassMethod:(SEL)sel
或+ (BOOL)resolveInstanceMethod:(SEL)sel
,在这个方法中,可以为该未知消息添加一个处理方法,这个方法应该是已经实现了,然后在运行时使用class_addMethod
动态地添加到类中即可
以实例方法举例
// 在 FFWinterModel 中
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSString *selectorStr = NSStringFromSelector(sel);
if ([selectorStr isEqualToString:@"doSomethingWithSnow"]) {
class_addMethod([self class], sel, (IMP)runtimeDoSomethingWithSnow, "v@:");
}
return [super resolveInstanceMethod:sel];
}
void runtimeDoSomethingWithSnow (id self, SEL _cmd) {
NSLog(@"let's make a snowman!");
}
// 在vc中调用不存在的方法
FFWinterModel *winterInfo = [FFWinterModel new];
[winterInfo performSelector:@selector(doSomethingWithSnow)];
// let's make a snowman!
// 类方法未响应
+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
// 实例方法未响应
+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
- 备用接收者
如果在 动态方法解析中 无法处理消息,运行时会继续调用- (id)forwardingTargetForSelector:(SEL)aSelector
如果一个对象实现了这个方法,并返回一个非空的值,那么这个对象就会作为消息的新接收者,并且消息会被分发到这个对象,且这个对象不能使self
自身。这个方法在对象内部使用,可能还有一系列其他的对象可以处理,可以借助这些对象来处理未知消息,并返回,这个在外部看来还是由对象亲自处理一样
这一步适合于,只想将消息转发到另一个能处理该消息的对象上,但是这一步无法对消息的进行处理,比如操作消息的参数和返回值
// 在 FFWinterModel 中
- (id)forwardingTargetForSelector:(SEL)aSelector {
NSString *aSelectorStr = NSStringFromSelector(aSelector);
if ([aSelectorStr isEqualToString:@"shallWeGoTraveling"]) {
// shallWeGoTraveling 这个方法 FFAutumnModel对象 可以实现,返回这个对象,让该对象响应
return [FFAutumnModel new];
}
return [super forwardingTargetForSelector:aSelector];
}
// 在 vc 中
FFWinterModel *winterInfo = [FFWinterModel new];
[winterInfo performSelector:@selector(shallWeGoTraveling)];
// yeah, let's go traveling
-
完整转发
如果的都无法处理未知消息,那么只能启用完整的消息转发机制了,需要调用方法- (void)forwardInvocation:(NSInvocation *)anInvocation
,运行时会在这一步给消息接收者最后一次机会将消息转换给其他对象对象会创建一个表示消息的
NSInvocation
对象,把与尚未处理的消息有关的全部细节全都封装到anInvocation
中,包括selector
、target
、parameters
我们可以在
-forwardInvocation
方法中选择将消息转发给其他对象,这个方法可以 定位 可以响应封装在anInvocation
中的消息对象,这个对象不需要能处理所有的未知消息;使用anInvocation
作为参数将消息发送到选中的对象中,anInvocation
会保留调用结果,运行时会将这一返回结果发送到消息的原始发送者,并且在这个方法中,可以实现更加复杂的功能,对消息内容进行修改,比如 修改参数后再去触发消息如果发现某个消息不应该由本类处理,则应该去调用父类的同名方法,以便在继承体系内的没个类都有机会处理此次调用
并且必须重写
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
,消息转发机制会从这个方法中获取信息来创建NSInvocation
对象,因此需要重写该方法,为指定的aSelector
提供一个合适的方法签名(在forwardInvocation:
消息发送前,runtime
会向对象发送methodSignatureForSelector:
消息,并取到返回的方法签名用于生成NSInvocation对象
,所以在重写forwardInvocation:
的同时也要重写methodSignatureForSelector:
方法)而
NSObject
的forwardInvocation:
只是简单地调用了doesNotRecognizeSelector
,不会转发任何消息,如果不在这三个步骤中处理未知消息就会引起一个异常- forwardInvocation:
就像一个未知消息的分发中心,可以将这些未知的消息转发给其他对象,也可以将所有的未知消息都发给同一个对象,或者其他什么的 ~ (取决于具体的实现)
eg:
// FFWinterModel中
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
NSMethodSignature *sign = [super methodSignatureForSelector:aSelector];
NSLog(@"methodSignatureForSelector --> %@", NSStringFromSelector(aSelector));
if (!sign) {
if ([FFSummerModel instancesRespondToSelector:aSelector]) {
sign = [FFSummerModel instanceMethodSignatureForSelector:aSelector];
}
}
return sign;
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
if ([FFSummerModel instancesRespondToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:[FFSummerModel new]];
}
}
// vc中
FFWinterModel *winterInfo = [FFWinterModel new];
[winterInfo performSelector:@selector(shouldGoSwimming)];
// let us go swimming!
3. 消息转发和多重继承
在第二步 备用者接收 和 第三部 完整转发 中,可以允许一个对象与其他对象创建关联来处理未知消息,但是表面上看来依旧是该对象在处理 ---> 通过这种关系,可以模拟“多重继承”的某些特性,让对象可以“继承”其他对象的特性来处理一些事情
不过这两者有一个很重要的区别,多重继承将不同的功能继承到一个对象中,会让对象变得过大,涉及到的东西过多;而消息转发将动能分解到独立的小对象中,并通过某种方式将这些对象连接起来,并且做相应的转消息转发。虽然消息转发类似于继承,但是NSObject
的一些方法可以区分两者,如respondsToSelector:
和isKindOfClass:
只能用于继承体系
不定期更新 不合适的地方 还请指点~ 感激不尽
愿祖国繁荣昌盛~
o(* ̄3 ̄)o