當物件收到無法解讀的訊息後,就會嘗試將訊息轉發。整體流程是這樣的:
1.+ (BOOL)resolveInstanceMethod:(SEL)sel或+ (BOOL)resolveClassMethod:(SEL)sel
2.- (id)forwardingTargetForSelector:(SEL)aSelector
3.- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
4.- (void)forwardInvocation:(NSInvocation *)invocation
@interface Man : NSObject
- (void)doSomething;
@end
@implementation Man
- (void)doSomething
{
NSLog(@"doSomething in Class Man");
}
@end
@interface Person : NSObject
@end
@interface Person ()
@property (nonatomic,strong)Man *m;
@end
@implementation Person
- (instancetype)init
{
if (self = [super init])
{
self.m = [[Man alloc] init];
}
return self;
}
void MethodIMP(id self,SEL _cmd)
{
NSLog(@"doSomething at resolveInstanceMethod:");
}
// 物件收到無法處理的訊息後,首先將呼叫類方法:+ (BOOL)resolveInstanceMethod:(SEL)sel
// 該方法的引數就是那個未知的選擇子,其返回值為bool型別,表示這個類是否能新增一個例項方法用以處理此選擇子。
// 在繼續往下執行轉發機制之前,有機會新增一個處理此選擇子的方法。
// 假如尚未實現的方法是類方法,那麼會呼叫另外一個方法
// + (BOOL)resolveClassMethod:(SEL)sel
// 此方案常用來實現@dynamic屬性。
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
BOOL res = [super resolveInstanceMethod:sel];
if (sel == @selector(doSomething))
{
NSLog(@"add method at resolveInstanceMethod:");
// OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)
// types 定義該數返回值型別和引數型別的字串,這裡比如"v@:",其中v就是void,帶表返回型別就是空,@代表引數,這裡指的 是id(self),這裡:指的是方法SEL(_cmd),比如:
// int method(id self, SEL _cmd, NSString *string)
// 那麼新增這個函式的方法就應該是ass_addMethod([self class], @selector(newMethod), (IMP)newMethod, "i@:@");
class_addMethod([self class], sel, (IMP)MethodIMP, "v@:");
res = YES;
}
return res;
}
/*
當前接收者還有第二次機會進行處理.
- (id)forwardingTargetForSelector:(SEL)aSelector
方法引數代表未知的選擇子,若當前接收者能找到被援物件,則將其返回,若找不到就返回nil。在一個物件內部,可能還有一系列其他物件,該物件可經由此方法將能夠處理某選擇子的相關內部物件返回。
*/
- (id)forwardingTargetForSelector:(SEL)aSelector
{
id recv = nil;
if (aSelector == @selector(doSomething))
{
recv = self.m;
}
return recv;
}
/*
這一步是最後一次機會。首先呼叫
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
訊息獲得函式的引數和返回值型別。如果返回nil,則會發出- (void)doesNotRecognizeSelector:(SEL)aSelector,
這時也就掛掉了。如果返回了一個函式簽名,就會建立一個NSInvocation物件併傳送
- (void)forwardInvocation:(NSInvocation *)invocation給目標物件。
*/
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
NSMethodSignature *res = [super methodSignatureForSelector:aSelector];
if (aSelector == @selector(doSomething))
{
res = [self.m methodSignatureForSelector:aSelector];
}
return res;
}
- (void)forwardInvocation:(NSInvocation *)invocation
{
if([self.m respondsToSelector:invocation.selector])
{
[invocation invokeWithTarget:self.m];
}
else
{
[self doesNotRecognizeSelector:invocation.selector];
}
}
@end
int main(int argc, const char * argv[])
{
@autoreleasepool
{
Person *person = [[Person alloc] init];
[person performSelector:@selector(doSomething)];
}
return 0;
}
複製程式碼