NSObejct 消息轉(zhuǎn)發(fā)

1 知識(shí)準(zhǔn)備

1.1 NSMethodSignature

NSMethodSignature-方法簽名類
方法簽名中保存了方法的名稱/參數(shù)名稱/參數(shù)個(gè)數(shù)/返回值類型,協(xié)同NSInvocation來進(jìn)行消息的轉(zhuǎn)發(fā)
方法簽名一般是用來設(shè)置參數(shù)和獲取返回值的, 和方法的調(diào)用沒有太大的關(guān)系
根據(jù)方法來初始化NSMethodSignature
NSMethodSignature *signature = [ViewController instanceMethodSignatureForSelector:@selector(XXX:)];

@interface NSMethodSignature : NSObject {
@private
    void *_private;
    void *_reserved[6];
}

+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;

@property (readonly) NSUInteger numberOfArguments;
- (const char *)getArgumentTypeAtIndex:(NSUInteger)idx NS_RETURNS_INNER_POINTER;

@property (readonly) NSUInteger frameLength;

- (BOOL)isOneway;

@property (readonly) const char *methodReturnType NS_RETURNS_INNER_POINTER;
@property (readonly) NSUInteger methodReturnLength;

@end

1.2 NSInvoation

NSInvocation-根據(jù)NSMethodSignature生成,保存了方法所屬的對(duì)象/方法名稱/參數(shù)/返回值。
其實(shí)NSInvocation就是將一個(gè)方法變成一個(gè)對(duì)象。設(shè)置NSInvocation的target、SEL、arguments,最后可用invoke執(zhí)行該方法。
可用getReturnValue獲取函數(shù)執(zhí)行后的返回值。

+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;

@property (readonly, retain) NSMethodSignature *methodSignature;

- (void)retainArguments;
@property (readonly) BOOL argumentsRetained;

@property (nullable, assign) id target;
@property SEL selector;

- (void)getReturnValue:(void *)retLoc;
- (void)setReturnValue:(void *)retLoc;

- (void)getArgument:(void *)argumentLocation atIndex:(NSInteger)idx;
- (void)setArgument:(void *)argumentLocation atIndex:(NSInteger)idx;

- (void)invoke;
- (void)invokeWithTarget:(id)target;

NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
//設(shè)置方法調(diào)用者
invocation.target = self;
invocation.selector = @selector(XXX:); NSString *way = @"hh";
//這里的Index要從2開始,以為0跟1已經(jīng)被占據(jù)了,分別是self(target),selector(_cmd)
[invocation setArgument:&way atIndex:2];
//3、調(diào)用invoke方法
[invocation invoke];

1.3 self 、object_getClass、 [self class]的概念

  • 當(dāng) self 為實(shí)例對(duì)象時(shí),[self class] 與 object_getClass(self) 等價(jià),因?yàn)榍罢邥?huì)調(diào)用后者。他們的返回值是類對(duì)象。object_getClass([self class]) 得到元類.
  • 當(dāng) self 為類對(duì)象時(shí),[self class] 返回值為自身,還是 self。object_getClass(self) 與 object_getClass([self class]) 等價(jià),都是得到元類。
-(id)class
{
    return (id)isa; 
}

+ (id)class
{
    return self;
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}

2 消息轉(zhuǎn)發(fā)

Runtime 系統(tǒng)在Cache和方法分發(fā)表中(包括超類)找不到要執(zhí)行的方法時(shí),我們可以下述三種方式把消息轉(zhuǎn)發(fā)到其他對(duì)象,或者用本對(duì)象的其他方法替換。
基于這種原理可以實(shí)現(xiàn)一個(gè)doesNotRecognizeSelector的集中處理器,避免crash。

2.1 動(dòng)態(tài)方法解析

動(dòng)態(tài)方法解析會(huì)在消息轉(zhuǎn)發(fā)機(jī)制浸入前執(zhí)行。通過重寫resolveInstanceMethod:或resolveClassMethod:方法來實(shí)現(xiàn)。

@interface NSStudent : NSObject

+ (void)learnClass:(NSString *) string;
- (void)goToSchool:(NSString *) name;

@end
+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(learnClass:)) {
        class_addMethod(object_getClass([NSTeacher class]), sel, class_getMethodImplementation(object_getClass([NSTeacher class]), @selector(teacherCourse:)), "v@:");
        return YES;
    }
    return [class_getSuperclass(self) resolveClassMethod:sel];
}

+ (BOOL)resolveInstanceMethod:(SEL)aSEL
{
    if (aSEL == @selector(goToSchool:)) {
        class_addMethod([self class], aSEL, class_getMethodImplementation([self class], @selector(myInstanceMethod:)), "v@:");
        return YES;
    }
    
    return [super resolveInstanceMethod:aSEL];
}

定義一個(gè)NSStudent的類,申明了learnClass:和goToSchool:兩個(gè)方法,但是沒有提供實(shí)現(xiàn)。在.m文件中重寫了resolveClassMethod和resolveInstanceMethod兩個(gè)方法。在resolveClassMethod中給指定的SEL添加了IMP實(shí)現(xiàn),這個(gè)可以是當(dāng)前類也可以指定其他類,本例中指定的是當(dāng)前類的myClassMethod:。如果 respondsToSelector: 或 instancesRespondToSelector:方法被執(zhí)行,動(dòng)態(tài)方法解析器將會(huì)被首先給予一個(gè)提供該方法選擇器對(duì)應(yīng)的IMP的機(jī)會(huì)。如果你想讓該方法選擇器被傳送到轉(zhuǎn)發(fā)機(jī)制,那么就讓resolveInstanceMethod:返回NO。

2.2 重定向

在消息轉(zhuǎn)發(fā)機(jī)制執(zhí)行前,Runtime 系統(tǒng)會(huì)再給我們一次偷梁換柱的機(jī)會(huì),即通過重載
- (id)forwardingTargetForSelector:(SEL)aSelector或者
+ (id)forwardingTargetForSelector:(SEL)aSelector方法替換消息的接受者為其他對(duì)象。

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    [self class];
    
    if(aSelector == @selector(goToSchool:)){
        return self;
        return [[NSTeacher alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

通過這種方式只能返回了去執(zhí)行同名方法的類對(duì)象,所以這種方式的局限在于,接受者對(duì)象也應(yīng)該有一個(gè)同名并且參數(shù)相同的方法,即SEL一致。

2.3 轉(zhuǎn)發(fā)

如果一個(gè)selecter在2.1和2.2步驟都沒找到對(duì)應(yīng)的IMP,則進(jìn)入-(void)forwardInvocation:(NSInvocation *)anInvocation方法。

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    NSInvocation *methodInvocation = [NSInvocation invocationWithMethodSignature:anInvocation.methodSignature];
    [methodInvocation setSelector:anInvocation.selector];
    if ([NSTeacher instancesRespondToSelector:anInvocation.selector]) {
        [methodInvocation invokeWithTarget:[[NSTeacher alloc] init]];
    }
}

但是當(dāng)我們實(shí)現(xiàn)了forwardInvocation之后,發(fā)現(xiàn)還是會(huì)因?yàn)閐oesNotRecognizeSelector crash掉。這是為什么呢?原來在forwardInvocation:消息發(fā)送前,Runtime系統(tǒng)會(huì)向?qū)ο蟀l(fā)送methodSignatureForSelector:消息,并取到返回的方法簽名用于生成NSInvocation對(duì)象。所以我們?cè)谥貙慺orwardInvocation:的同時(shí)也要重寫methodSignatureForSelector:方法。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    
    if (!signature) {
        if ([NSTeacher instancesRespondToSelector:aSelector]) {
            signature = [NSTeacher instanceMethodSignatureForSelector:aSelector];
        }
    }
    
    return signature;
}
  • 當(dāng)一個(gè)對(duì)象由于沒有相應(yīng)的方法實(shí)現(xiàn)而無法響應(yīng)某消息時(shí),運(yùn)行時(shí)系統(tǒng)將通過forwardInvocation:消息通知該對(duì)象。
  • 每個(gè)對(duì)象都從NSObject類中繼承了forwardInvocation:方法。然而,NSObject中的方法實(shí)現(xiàn)只是簡(jiǎn)單地調(diào)用了doesNotRecognizeSelector:。通過實(shí)現(xiàn)我們自己的forwardInvocation:方法,我們可以在該方法實(shí)現(xiàn)中將消息轉(zhuǎn)發(fā)給其它對(duì)象。
  • forwardInvocation:方法就像一個(gè)不能識(shí)別的消息的分發(fā)中心,將這些消息轉(zhuǎn)發(fā)給不同接收對(duì)象。或者它也可以象一個(gè)運(yùn)輸站將所有的消息都發(fā)送給同一個(gè)接收對(duì)象。它可以將一個(gè)消息翻譯成另外一個(gè)消息,或者簡(jiǎn)單的”吃掉“某些消息,因此沒有響應(yīng)也沒有錯(cuò)誤。
  • forwardInvocation:方法也可以對(duì)不同的消息提供同樣的響應(yīng),這一切都取決于方法的具體實(shí)現(xiàn)。該方法所提供是將不同的對(duì)象鏈接到消息鏈的能力。

3 總結(jié)

從網(wǎng)上找了一張圖可以準(zhǔn)確描述上邊所述。

![Uploading Paste_Image_877174.png . . .]
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,789評(píng)論 0 9
  • 我們常常會(huì)聽說 Objective-C 是一門動(dòng)態(tài)語(yǔ)言,那么這個(gè)「動(dòng)態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,232評(píng)論 0 7
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 772評(píng)論 0 2
  • 本文轉(zhuǎn)載自:http://yulingtianxia.com/blog/2014/11/05/objective-...
    ant_flex閱讀 799評(píng)論 0 1
  • 本文詳細(xì)整理了 Cocoa 的 Runtime 系統(tǒng)的知識(shí),它使得 Objective-C 如虎添翼,具備了靈活的...
    lylaut閱讀 827評(píng)論 0 4