Swift 中梅桩,定義協(xié)議 protocol 時士嚎,可以使用 extension 給它的某些方法提供默認(rèn)實現(xiàn):
protocol Printable {
func print()
func print1()
}
extension Printable {
func print() {
print("default print")
}
}
有了上面的代碼,當(dāng)創(chuàng)建一個遵從 Printable 協(xié)議的類或者是結(jié)構(gòu)體時豺型,就能獲得 print() 方法徒像。可以在需要的時候重新定義這個方法笨枯,如果不重新定義,就會使用這個默認(rèn)方法遇西。
遺憾的是 Objective-C 并沒有提供這樣的便利馅精。當(dāng)我們想為一個協(xié)議提供默認(rèn)實現(xiàn)時,通常會提供一個基類粱檀,基類遵從協(xié)議并且提供默認(rèn)實現(xiàn)洲敢。如果不想使用默認(rèn)實現(xiàn),需繼承基類茄蚯,在子類中重寫方法压彭。有沒有辦法可以像 Swift 那樣睦优,在協(xié)議聲明的地方而不需要繼承鏈,就能簡便的提供默認(rèn)實現(xiàn)呢壮不?
為協(xié)議提供默認(rèn)實現(xiàn)汗盘,可以理解為所有遵從協(xié)議的類都擁有提前定義的協(xié)議方法。Objective-C 強(qiáng)大的運行時可以獲得程序中所有的類询一,可以獲得類遵從的協(xié)議隐孽,還可以為每一個類添加方法。我們可以用一個臨時類來實現(xiàn)協(xié)議的方法健蕊,在所有的類自己的方法全部加載完后菱阵,為遵從協(xié)議的類添加臨時類的默認(rèn)方法。之所以要在類自己的方法加載完成后缩功,是為了防止類自己的實現(xiàn)被覆蓋晴及,當(dāng)方法已經(jīng)存在時,class_addMethod 會添加失敗嫡锌,這正是我們希望的虑稼。
__attribute__((constructor)) 特性修飾的函數(shù),會在 runtime 加載完所有類之后世舰,main 函數(shù)執(zhí)行之前運行动雹。通過這樣一個函數(shù),將滿足條件的類加上提供的默認(rèn)實現(xiàn)跟压。
現(xiàn)在需要一個臨時類胰蝠,來為協(xié)議實現(xiàn)默認(rèn)方法。假設(shè)有協(xié)議:
@protocol Printable
@optional
- (NSString *)desc;
@end
為協(xié)議提供臨時類震蒋,來提供默認(rèn)實現(xiàn):
@interface PrintableTemporaryClass : NSObject <Printable>
@end
@implementation PrintableTemporaryClass
- (NSString *)desc {
return @"Default desc";
}
@end
上面代碼中茸塞,print 方法前的部分對所有協(xié)議來說都是類似的的,可以定義個宏來簡化代碼:
#define extensionProtocol(NAME) \
interface NAME ## TemporaryClass : NSObject <NAME> \
@end \
@implementation NAME ## TemporaryClass \
此時查剖,臨時類的寫法會變成下面這樣:
@extensionProtocol(Printable)
- (NSString *)desc {
return @"Default desc";
}
@end
然后通過遍歷所有的類钾虐,進(jìn)而遍歷類所有的協(xié)議,并查找運行時中是否有協(xié)議對應(yīng)的臨時類(通過名字關(guān)聯(lián))笋庄,如果有效扫,遍歷該臨時類和元類的所有方法進(jìn)行添加。關(guān)鍵的 __attribute__((constructor)) 函數(shù)如下:
__attribute__((constructor)) static void _append_default_implement_method_to_class() {
unsigned classCount;
Class *classes = objc_copyClassList(&classCount);
//第一層遍歷所有的類
for (int i = 0; i < classCount; i ++) {
Class class = classes[i];
Class metaClass = object_getClass(class);
unsigned protocolCount;
Protocol * __unsafe_unretained *protocols = class_copyProtocolList(class, &protocolCount);
//第二層遍歷類中所有的協(xié)議
for (int j = 0; j < protocolCount; j ++) {
Protocol *protocol = protocols[j];
NSString *tempClassName = [NSString stringWithFormat:@"%sTemporaryClass", protocol_getName(protocol)];
Class tempClass = objc_getClass(tempClassName.UTF8String);
if (!tempClass) continue;
unsigned methodCount;
Method *methods = class_copyMethodList(tempClass, &methodCount);
//第三層遍歷臨時類的所有方法并添加
for (int k = 0; k < methodCount; k ++) {
Method method = methods[k];
class_addMethod(class, method_getName(method), method_getImplementation(method), method_getTypeEncoding(method));
}
free(methods);
Class metaTempClass = object_getClass(tempClass);
unsigned metaMethodCount;
Method *metaMethods = class_copyMethodList(metaTempClass, &metaMethodCount);
//第三層遍歷臨時類元類的所有方法并添加
for (int k = 0; k < metaMethodCount; k ++) {
Method method = metaMethods[k];
class_addMethod(metaClass, method_getName(method), method_getImplementation(method), method_getTypeEncoding(method));
}
free(metaMethods);
}
free(protocols);
}
free(classes);
}
這樣直砂,任何一個遵從 Printable 協(xié)議的類菌仁,即使沒有實現(xiàn) desc 方法,也具有了 desc 的默認(rèn)實現(xiàn)静暂。
以上包含了所有為 Objective-C 提供協(xié)議默認(rèn)實現(xiàn)功能的完整代碼济丘,核心是 __attribute__((constructor)) 修飾的函數(shù),利用運行時來為類和元類添加方法,還定義了一個簡單的宏方便代碼書寫摹迷。如果有別的意見疟赊,歡迎與我交流。