一次方法適配實踐

前言


回顧筆者的runtime系列文章,發(fā)現(xiàn)實踐略少,恰好近來一位朋友入職新公司后進行codereview時遇到了一個問題,和他討論后制定了一個使用runtime的方案來解決問題,正好記錄下這個方案。

問題

在朋友的項目中存在一個異步獲取沙盒文件的接口,偽實現(xiàn)如下:

#define BLOCK_SAFE_CALLS(_b_, _f_, _e_) if (_b_) { _b_(_f_, _e_); }

- (void)asyncFetchAllFoldersWithCompleteBlock: (void(^)(NSArray *, NSError *))complete {
    BEGIN_OPERATION_DISPATCHER
    XXXFetchFlodersOperation * operation = [self.XXXSession fetchAllFoldersOperation];
    [operation start: ^(NSError * error, NSArray * folders) {
        BLOCK_SAFE_CALLS(completeBlock, folders, error);
    }];
    END_OPERATION_DISPATCHER
}

由于未知原因,在運行期間,這個方法總在前至多3次調(diào)用時出現(xiàn)error,為了避免調(diào)用該方法時還需要在回調(diào)中實現(xiàn)重新嘗試的代碼,需要把重試代碼的業(yè)務(wù)放到這個方法中。

方案1:不修改原接口的基礎(chǔ)上添加遞歸調(diào)用

#define BLOCK_SAFE_CALLS(_b_, _f_, _e_) if (_b_) { _b_(_f_, _e_); }

- (void)asyncFetchAllFoldersWithCompleteBlock: (void(^)(NSArray *, NSError *))completeBlock {
    [self asyncFetchAllFoldersWithCompleteBlock: completeBlock retryTime: 3];
}

- (void)asyncFetchAllFoldersWithCompleteBlock: (void (^)(NSArray *, NSError *))completeBlock retryTime: (int)retryTime {
    BEGIN_OPERATION_DISPATCHER
    XXXFetchFlodersOperation * operation = [self.XXXSession fetchAllFoldersOperation];
    [operation start: ^(NSError * error, NSArray * folders) {
        if (error && retryTime > 0) {
            [self asyncFetchAllFoldersWithCompleteBlock: completeBlock retryTime: retryTime - 1];
        } else {
            BLOCK_SAFE_CALLS(completeBlock, folders, error);
        }
    }];
    END_OPERATION_DISPATCHER
}

借鑒于遞歸思想,提供一個額外的接口傳入一個標(biāo)記(代碼中為retryTime)以此作為是否在調(diào)用發(fā)生錯誤后重新嘗試。且上面的方案對現(xiàn)有代碼的改動是最小的,幾乎無侵害。(然而朋友說不允許修改原接口實現(xiàn),因此方案作廢)

方案2:提供額外的接口來完成操作

@interface XXXXX: NSObject

- (void)asyncFetchAllFoldersWithCompleteBlock: (void(^)(NSArray *, NSError *))completeBlock NS_DEPRECATED_IOS(2_0, 5_0);
- (void)asyncFetchAllFoldersWithCompleteBlock: (void (^)(NSArray *, NSError *))completeBlock retryTime: (int)retryTime;

@end

@implementation XXXXX

- (void)asyncFetchAllFoldersWithCompleteBlock: (void (^)(NSArray *, NSError *))completeBlock retryTime: (int)retryTime {
    NSParameterAssert(completeBlock);
    [self asyncFetchAllFoldersWithCompleteBlock: ^(NSArray * folders, NSError * error) {
        if (error && retryTime > 0) {
            NSLog(@"failed error: %@", error);
            [self asyncFetchAllFoldersWithCompleteBlock: completeBlock retryTime: retryTime - 1];
        } else {
            completeBlock(folders, error);
        }
    }];
}

@end

此方案通過宏定義NS_DEPRECATED_IOS標(biāo)記原接口為摒棄方法,但是這樣一來所有調(diào)用原接口的代碼都要重新進行修改:


不談工作量,朋友說他只有修改當(dāng)前類實現(xiàn)文件的權(quán)力,其他外界代碼不允許修改。因此,方案作廢

方案3:method_swizzling

由于原接口代碼以及接口調(diào)用不允許改動,留給我們選擇的余地就不多了,恰好還有AOP的方式可以來解決這個問題。當(dāng)然相比起其他兩個方案代碼數(shù)量要多得多,通過交換方法實現(xiàn)的方式將方法的調(diào)用實際上轉(zhuǎn)到我們新增的接口中:

+ (void)load {
    aop_method_exchange([self class], @selector(AOPAsyncFetchAllFoldersWithCompleteBlock:), @selector(asyncFetchAllFoldersWithCompleteBlock:));
}

- (void)AOPAsyncFetchAllFoldersWithCompleteBlock: (void (^)(NSArray *, NSError *))completeBlock {
    NSParameterAssert(completeBlock);
    [self asyncFetchAllFoldersWithCompleteBlock: ^(NSArray * folders, NSError * error) {
        completeBlock(folders, error);
    } retryTime: 3];
}

- (void)asyncFetchAllFoldersWithCompleteBlock: (void (^)(NSArray *, NSError *))completeBlock retryTime: (int)retryTime {
    NSParameterAssert(completeBlock);
    [self AOPAsyncFetchAllFoldersWithCompleteBlock: ^(NSArray * folders, NSError * error) {
        if (error && retryTime > 0) {
            [self asyncFetchAllFoldersWithCompleteBlock: completeBlock retryTime: retryTime - 1];
        } else {
            completeBlock(folders, error);
        }
    }];
}

實際上方案3是結(jié)合了方案1與方案2的優(yōu)點以及避開了兩者的缺點,即使刪除新增的代碼,原有代碼不會受到任何影響。缺點在于如果方法本身已經(jīng)被hook過了,那么可能會出現(xiàn)意料之外的錯誤

尾言

離上次寫博客過去也有一個多月了,期間經(jīng)歷了忙碌的春節(jié),以及項目趕工,都沒什么時間靜下來寫博客。最近筆者還報了自考本科,目標(biāo)是當(dāng)一個會畫畫的碼農(nóng),從此就失去了周末的雙休了。哎,心疼一下自己。最后放上新手的畫畫作業(yè),高能預(yù)警!!!


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容