Objective-C中的訊息轉發

丟的錢找到了發表於2018-04-07
當物件收到無法解讀的訊息後,就會嘗試將訊息轉發。整體流程是這樣的:
1.+ (BOOL)resolveInstanceMethod:(SEL)sel或+ (BOOL)resolveClassMethod:(SEL)sel
2.- (id)forwardingTargetForSelector:(SEL)aSelector
3.- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
4.- (void)forwardInvocation:(NSInvocation *)invocation
#import <Foundation/Foundation.h>
#import <objc/runtime.h>

#pragma mark -- Man
@interface Man : NSObject

- (void)doSomething;

@end

@implementation Man

- (void)doSomething
{
    NSLog(@"doSomething in Class Man");
}

@end

#pragma mark -- Person
@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

#pragma mark -- main
int main(int argc, const char * argv[])
{
    @autoreleasepool
    {
        Person *person = [[Person alloc] init];
        [person performSelector:@selector(doSomething)];
    }
    return 0;
}
複製程式碼

相關文章