前言
回顧筆者的
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
}
借鑒于遞歸思想婉刀,提供一個額外的接口傳入一個標記(代碼中為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
標記原接口為摒棄方法潘悼,但是這樣一來所有調(diào)用原接口的代碼都要重新進行修改:
不談工作量律秃,朋友說他只有修改當前類實現(xiàn)文件的權(quán)力,其他外界代碼不允許修改治唤。因此棒动,方案作廢
方案3:method_swizzling
由于原接口代碼以及接口調(diào)用不允許改動,留給我們選擇的余地就不多了宾添,恰好還有AOP
的方式可以來解決這個問題船惨。當然相比起其他兩個方案代碼數(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é),以及項目趕工,都沒什么時間靜下來寫博客恶座。最近筆者還報了自考本科搀暑,目標是當一個會畫畫的碼農(nóng),從此就失去了周末的雙休了跨琳。哎自点,心疼一下自己。最后放上新手的畫畫作業(yè)脉让,高能預(yù)警9鹆病!侠鳄!