Objective-C Runtime(4)

在前幾篇中我們主要是介紹的原理部分和一些小的應用,這篇介紹的Runtime中常用的方法交換,首先我們引入以下問題方便我們理解。

1.要觀察每一個控制器的創建和銷燬並在控制檯輸出?

我們先思考一些,現在我們介紹一個簡單的方法交換

實例一:基本方法交換

比如現在我們有兩個類 A 和 B 都是繼承Person

@interface A : Person

-(void)active;

@end
@implementation A

-(void)active{
    NSLog(@"Run");
}

@end
@interface B : Person

-(void)active;
@end

@implementation B

-(void)active{
    NSLog(@"jump");
}

@end
A *a = [[A alloc] init];
[a active];

B *b = [[B alloc] init];
[b active];

這樣調用正常情況下,a會輸出run;b輸出jump。那麼這個時候我們通過Runtime中得方法交換來改變各自的實現。

首先我們引入 < objc/runtime.h >頭文件,然後會用的函數在上一篇中介紹到,我們直接使用

Method mA = class_getInstanceMethod(A , @selector(active));
Method mB = class_getInstanceMethod(A , @selector(active));

method_exchangeImplementations(mA, mB);

[a active];

[b active];

我們去執行就會發現 a輸出jump;b輸出run。這就是一個最簡單的方法的交換。我們繼續對上面問題的思考

問題思考一

我們都已通過基類在基類裏面對viewDidLoad中添加打印,然後繼成基類使用。

在BaseViewController中添加

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"%s",__FUNCTION__);
}

- (void)dealloc
{
    NSLog(@"%s",__FUNCTION__);
}

然後控制器集成使用就可以了,但是往往在實際開發中,項目的管理中總是會有一些控制器並沒有集成基類,在項目的後期大量的控制器散落在各個地方檢查起來是非常不方便的。那麼這個時候我們就要考慮是否有更加方便的方法?

問題思考二

我們通過Runtime特性方法交換,使用我們自己從寫的方法交換系統viewDidLoad方法。在viewDidLoad生命週期之前調用。

- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)myViewDidLoad {
    [super viewDidLoad];
    NSLog(@"%s",__FUNCTION__);
}

我們在loadView中執行交換

-(void)loadView{
    [super loadView];

    Method sys = class_getInstanceMethod(self.class , @selector(viewDidLoad));
    Method my = class_getInstanceMethod(self.class , @selector(myViewDidLoad));

    method_exchangeImplementations(sys, my);
}

通過這樣也在控制檯打印出了 -[ViewController myViewDidLoad] 相應的方法名稱。

但是這樣如果我們在本類viewDidLoad中編寫方法就無法被調用,這樣做也得不償失。這樣做行不通。

問題思考三

那麼我們可以不可在自己定義方法中手動調用viewdidLoad然後執行相信的操作呢?

- (void)myViewDidLoad {
    [self viewDidLoad];
    NSLog(@"%s",__FUNCTION__);
}

猜想一下運行結果…………,坑虧了,我們來查看奔潰的斷點

我們的預期是:打印執行方法名,我們來分析一下奔潰的原因然後對症的去改正:

首先我們在loadView中對 viewDidLoad 和myViewDidLoad的進行交換,也就是說,在執行viewDidLoad的時候myViewDidLoad的方法,我們通過僞代碼的形式說明

//當執行viewDidView的時候
- (void)viewDidLoad {

    //在loadView中已經執行了方法交換,也就是會執行下方的代碼
    [self viewDidLoad];
    //自己調用自己無限循環呀!
    NSLog(@"%s",__FUNCTION__);
}


- (void)myViewDidLoad {

    //方法交換會執行viewdidLoad的方法
    [super viewDidLoad];
    ……
    ……    
}

這個時候我們就發現問題的所在了,那麼我們如何修改呢,首先我們通過僞代碼的形式寫出我們預期的效果,也就是方法交換之後的執行結果

//當執行viewDidView的時候
- (void)viewDidLoad {

    //執行完成方法交換,我們希望執行打印方法名
    NSLog(@"%s",__FUNCTION__);

    //然後執行原有viewDidLoad中的方法
    //在執行方法交換已經把viewDidLoad的實現交換在myViewDidLoad中我們通過調用得到實現
    [self myViewDidLoad];
}


- (void)myViewDidLoad {

    //方法交換會執行viewdidLoad的方法
    [super viewDidLoad];
    ……
    ……    
}

通過以上的分析,對現在的代碼進行改造

-(void)loadView{
    [super loadView];

    Method sys = class_getInstanceMethod(self.class , @selector(viewDidLoad));
    Method my = class_getInstanceMethod(self.class , @selector(myViewDidLoad));

    method_exchangeImplementations(sys, my);
}

- (void)viewDidLoad {
    [super viewDidLoad];
     NSLog(@"viewDidLoad 方法執行");

}
- (void)myViewDidLoad {
    //方法交換執行打印
    NSLog(@"%s",__FUNCTION__);
    //調用原有viewDidLoad中的方法
    [self myViewDidLoad];
    //這樣看起來確實有點奇怪,自己調用自己,這也就說明,方法交換使得我們的程序可讀性降低
}

我麼來看一下執行結果

如我們所期待的,得到我們想要的結果。這是在一個類中完成,我們要在所有的類中都要添加這樣的方法,這個時候我們想到使用分類(Category)來添加我們的方法。

右擊 [目錄] -> [New File] -> [Objective File]
file:自己起嘍
File Type : 選擇Category
Class :選擇UIViewController

創建之後我們把之前的方法複製過來

- (void)myViewDidLoad {
    //方法交換執行打印
    NSLog(@"%s",__FUNCTION__);
    //調用原有viewDidLoad中的方法
    [self myViewDidLoad];

我們要確保程序在運行過程中��️執行一次,這個時候我們考慮在APP的生命週期didFinishLaunchingWithOptions中添加方法

//使用單例模式
static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Method sys = class_getInstanceMethod( [UIViewController class], @selector(viewDidLoad));
        Method my = class_getInstanceMethod( [UIViewController class] , @selector(myViewDidLoad));

        method_exchangeImplementations(sys, my);
    });

這個時候看運行結果,在電機按鈕跳轉到其他頁面的時候也成功打印了執行的方法。

總結

以上是對方法交換的實踐,發現問題請及時留言,之後的幾篇會介紹runtime的其他使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章