RunTime機制詳解(四)

平時我們只知道調用方法,其實內部的實現原理並不是很瞭解,方法的調用要用就是要用到接下來要講的消息轉發機制。在這裏我們可以利用消息轉發機制實現方法的動態添加。

注:所有的方法調用均採用下面的形式調用。

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

一、消息轉發機制的流程圖。(圖片來源於網絡)


二、只申明方法,而不實現方法。

(1).h文件方法聲明

@interface Cat : NSObject
#pragma mark --- 只聲明不實現
- (void)eat;

@end
(2).m文件中處理

#import "Cat.h"
#import <objc/runtime.h>
#pragma clang diagnostic ignored "-Wincomplete-implementation"
@implementation Cat
//第一步:實現此方法,在調用對象的某方法找不到時,會先調用此方法,允許
//我們動態添加方法實現
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    if ([NSStringFromSelector(sel) isEqualToString:@"eat"]) {
        class_addMethod(self, sel, (IMP)addEat, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}
//下面的方法是我們動態添加的
void addEat(id self,SEL cmd){
     NSLog(@"%@ is eating",self);
}
@end

(3)當我們申明瞭方法而沒有實現該方法是,系統就會有警告,消除這個警告也很簡單,一句代碼就搞定。

#pragma clang diagnostic ignored "-Wincomplete-implementation"

三、方法不聲明,直接將方法替換。

(1).h文件中不做任何操作

@interface Dog : NSObject
#pragma mark --- 方法替換不聲明
@end

(2)所有處理都在.m文件中。

@implementation Dog
//1、在沒有找到方法是,會先調用此方法,可用於動態添加方法
//不需要動態添加
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    return NO;
}
//2、上一步返回NO,就會進入這一步,用於指定備選響應次SEL的對象
//千萬不能返回self,否則會進入死循環
//因爲自己沒有實現這個方法纔會進入這一流程,因此成爲死循環
- (id)forwardingTargetForSelector:(SEL)aSelector{
    return nil;
}
//3、指定方法簽名,若返回nil,則不會進入下一步,而是無法處理消息
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if ([NSStringFromSelector(aSelector) isEqualToString:@"eat"]) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
//當我們實現了此方法,-doesNotRecognizeSelector:不會再被調用
//如果要測試找不到方法,可以註釋到這一個方法
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    //我們還可以改變方法選擇器
    [anInvocation setSelector:@selector(jump)];
    //改變方法選擇器後,還需要指定是哪一個對象的方法
    [anInvocation invokeWithTarget:self];
}
// 注意:兩個方法只能實現一個
//- (void)eat{
//    NSLog(@"eat");
//}
- (void)jump{
    NSLog(@"方法改變");
}
@end

四、私有方法的動態添加。

(1)在.h文件中不做任何操作

@interface Pig : NSObject
#pragma mark --- 不聲明-eat方法,但在內部有實現
@end

(2)所以的操作都在.m文件中

#import "Pig.h"
#import "Cat.h"
@implementation Pig
//第一步,我們不動態添加方法,返回NO
+ (BOOL)resolveInstanceMethod:(SEL)sel{
    return NO;
}
//第二步,備選提供響應Aseletor的對象,我們不備選,因此設置爲nil,就會進入第三步
- (id)forwardingTargetForSelector:(SEL)aSelector{
    return nil;
}
//第三步,先返回方法選擇器,如果放回nil,則表示無法處理信息
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    if ([NSStringFromSelector(aSelector) isEqualToString:@"eat"] ) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
//第四步,第三步只有返回了方法簽名,纔會進入這一步,這一步用戶調用方法
//改變調用對象等
- (void)forwardInvocation:(NSInvocation *)anInvocation{
    //我們改變調用對象爲dog
    [anInvocation invokeWithTarget:self];
//    [anInvocation invokeWithTarget:[Cat new]];
//    target可以別的類的對象,但那個類的實現了eat方法
//    - (void)invokeWithTarget:(id)target;
}
- (void)eat{
    NSLog(@"%@ is eating",self);
}
@end

代碼傳送門:https://github.com/fuzongjian/RuntimeStudy.git


發佈了64 篇原創文章 · 獲贊 10 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章