本文將介紹基于 NNPopObjc 在 Objective-C 上進(jìn)行面向協(xié)議的編程殖氏。因?yàn)槿績(jī)?nèi)容比較長(zhǎng),所以分成了上下兩個(gè)部分弄诲,本文 (上) 主要介紹了 NNPopObjc 的使用澎蛛,包括默認(rèn)協(xié)議擴(kuò)展、約束協(xié)議擴(kuò)展等躏鱼。下半部分氮采,用于介紹 NNPopObjc 的實(shí)現(xiàn)思路和原理。
引子
基于 Swift 特性染苛,Apple 在 2015 年 WWDC 上提出了面向協(xié)議編程 (Protocol Oriented Programming鹊漠,以下簡(jiǎn)稱 POP)的編程范式。相比與傳統(tǒng)的面向?qū)ο缶幊?(OOP)茶行,POP 顯得更加靈活躯概。但是由于 Objective-C 缺失協(xié)議擴(kuò)展能力,導(dǎo)致 Objective-C 無(wú)法基于 POP 范式進(jìn)行項(xiàng)目開發(fā)畔师。NNPopObjc 用于為 Objective-C 提供協(xié)議擴(kuò)展能力楞陷,從而實(shí)現(xiàn) Objective-C 的面向協(xié)議編程。
協(xié)議擴(kuò)展
通過(guò)協(xié)議擴(kuò)展茉唉,可以為協(xié)議提供類方法、對(duì)象方法以及計(jì)算屬性的默認(rèn)實(shí)現(xiàn)结执。如果遵循該協(xié)議的類提供了自己所需方法或?qū)傩苑椒ǖ膶?shí)現(xiàn)度陆,則將使用該類的實(shí)現(xiàn),不使用擴(kuò)展提供的實(shí)現(xiàn)献幔。
協(xié)議定義中方法申明分為:
@required
和@optional
懂傀,由于@required
聲明的方法必須由遵守協(xié)議的類進(jìn)行實(shí)現(xiàn),所以由@required
申明的方法一定會(huì)使用當(dāng)前類的實(shí)現(xiàn)蜡感。
NNPopObjc 提供兩個(gè)宏函數(shù)關(guān)鍵字:
- @nn_extension(...)
- @nn_where(...)
默認(rèn)協(xié)議擴(kuò)展
實(shí)現(xiàn)一個(gè)協(xié)議擴(kuò)展蹬蚁,不對(duì)擴(kuò)展進(jìn)行任何約束,那么此擴(kuò)展被認(rèn)為是該協(xié)議的默認(rèn)擴(kuò)展郑兴。
聲明一個(gè) NNCodeProtocol 協(xié)議
@protocol NNCodeProtocol <NSObject>
@optional
+ (void)sayHelloPop;
- (void)sayHelloPop;
@end
為 NNCodeProtocol 協(xié)議提供默認(rèn)實(shí)現(xiàn)
@nn_extension(NNCodeProtocol)
+ (void)sayHelloPop {
DLog(@"+[%@ %s] code says hello pop", self, sel_getName(_cmd));
}
- (void)sayHelloPop {
DLog(@"-[%@ %s] code says hello pop", [self class], sel_getName(_cmd));
}
@end
定義 NNCodeC 遵守 NNCodeProtocol 協(xié)議
// 類聲明
@interface NNCodeC : NSObject <NNCodeProtocol>
@end
// 類實(shí)現(xiàn)
@implementation NNCodeC
@end
方法調(diào)用
[NNCodeC sayHelloPop];
[[NNCodeC new] sayHelloPop];
輸出結(jié)果
+[NNCodeC sayHelloPop] code says hello pop
-[NNCodeC sayHelloPop] code says hello pop
約束協(xié)議擴(kuò)展
@nn_where 關(guān)鍵字
NNPopObjc 提供 @nn_where
關(guān)鍵字為協(xié)議擴(kuò)展提供約束犀斋,遵守協(xié)議的類需要滿足約束才能夠使用協(xié)議擴(kuò)展提供的方法實(shí)現(xiàn)。
下面示例中通過(guò) @nn_where
為 NNCodeProtocol 擴(kuò)展添加了約束情连,約束要求遵守協(xié)議的類必須是 NNCodeObjc
叽粹,其中 self
即當(dāng)前要遵守協(xié)議的類。
@nn_extension(NNCodeProtocol, @nn_where(self == [NNCodeObjc class]))
+ (void)sayHelloPop {
DLog(@"+[%@ %s] objc says hello pop", self, sel_getName(_cmd));
}
- (void)sayHelloPop {
DLog(@"-[%@ %s] objc says hello pop", [self class], sel_getName(_cmd));
}
@end
方法調(diào)用
[NNCodeC sayHelloPop];
[[NNCodeC new] sayHelloPop];
[NNCodeObjc sayHelloPop];
[[NNCodeObjc new] sayHelloPop];
NNCodeObjc 優(yōu)先使用滿足約束協(xié)議的實(shí)現(xiàn),輸出結(jié)果
+[NNCodeC sayHelloPop] code says hello pop
-[NNCodeC sayHelloPop] code says hello pop
+[NNCodeObjc sayHelloPop] objc says hello pop
-[NNCodeObjc sayHelloPop] objc says hello pop
類遵守協(xié)議列表
在約束協(xié)議擴(kuò)展的 @nn_where
關(guān)鍵字后虫几,可以跟隨多個(gè)其他協(xié)議锤灿。遵守協(xié)議的類也必須同時(shí)要遵守該列表中所有的協(xié)議。
協(xié)議擴(kuò)展增加協(xié)議列表后辆脸,在協(xié)議擴(kuò)展中可以訪問(wèn)協(xié)議列表聲明的方法但校,使協(xié)議擴(kuò)展變的更加靈活。
定義一個(gè)協(xié)議
@protocol NNCodeNameProtocol <NSObject>
@optional
@property (nonatomic, strong) NSString* name;
@end
修改 NNCodeC 遵守該協(xié)議啡氢,并實(shí)現(xiàn)協(xié)議屬性
@interface NNCodeC : NSObject < NNCodeProtocol, NNCodeNameProtocol>
@property (nonatomic, strong) NSString *name;
@end
修改 NNCodeProtocol 的約束協(xié)議擴(kuò)展状囱,訪問(wèn) name
屬性。
@nn_extension(NNCodeProtocol, @nn_where(), NNCodeNameProtocol)
+ (void)sayHelloPop {
DLog(@"+[%@ %s] code says hello pop", self, sel_getName(_cmd));
}
- (void)sayHelloPop {
DLog(@"-[%@ %s] code says hello pop, i am %@ ", [self class], sel_getName(_cmd), self.name);
}
@end
方法調(diào)用
[NNCodeC sayHelloPop];
NNCodeC *objc = [NNCodeC new];
objc.who = @"c";
[objc sayHelloPop];
輸出結(jié)果
+[NNCodeC sayHelloPop] code says hello pop
-[NNCodeC sayHelloPop] code says hello pop, i am c
協(xié)議繼承
在 NNCodeObjc 中擴(kuò)展支持協(xié)議繼承空执。
聲明一個(gè)協(xié)議 NNCodeWhoProtocol 繼承自 NNCodeProtocol
@protocol NNCodeWhoProtocol <NNCodeProtocol>
@optional
@property (nonatomic, strong) NSString* who;
@end
為 NNCodeWhoProtocol 協(xié)議提供擴(kuò)展
@nn_extension(NNCodeWhoProtocol, @nn_where(), NNCodeNameProtocol)
- (void)sayHelloPop {
DLog(@"-[%@ %s] code says hello pop", [self class], sel_getName(_cmd));
}
- (NSString *)who {
NSString *who = [NSString stringWithFormat:@"-[%@ %s] code says I am %@", [self class], sel_getName(_cmd), self.name];
return who;
}
- (void)setWho:(NSString *)who {
self.name = who;
}
@end
方法調(diào)用
[NNCodeC sayHelloPop];
NNCodeC *objc = [NNCodeC new];
objc.who = @"c";
[objc sayHelloPop];
DLog(@"%@", c.who);
輸出結(jié)果
+[NNCodeC sayHelloPop] code says hello pop
-[NNCodeC sayHelloPop] code says hello pop
-[NNCodeC who] code says I am c
同一協(xié)議多個(gè)擴(kuò)展的問(wèn)題
協(xié)議的擴(kuò)展分為兩類:
- 默認(rèn)協(xié)議擴(kuò)展
- 約束協(xié)議擴(kuò)展
同一協(xié)議可以多個(gè)擴(kuò)展浪箭,但在遵守協(xié)議類的角度,一個(gè)協(xié)議的默認(rèn)協(xié)議擴(kuò)展和約束協(xié)議擴(kuò)展分別不能為多個(gè)辨绊,(即 默認(rèn)協(xié)議擴(kuò)展的個(gè)數(shù) <= 1 && 約束協(xié)議擴(kuò)展的個(gè)數(shù) <= 1)奶栖,如果存在多個(gè)就會(huì)產(chǎn)生所謂的“菱形缺陷”問(wèn)題,程序執(zhí)行時(shí)會(huì)觸發(fā) NNPopObjc assert 斷言门坷。
一個(gè)使用示例
ProtocolNetwork 是 喵神王巍 在 MDCC 16 (移動(dòng)開發(fā)者大會(huì)) iOS 專場(chǎng)《面向協(xié)議編程與 Cocoa 的邂逅》主題演講時(shí)使用的 Demo 工程宣鄙, Demo 演示了 Swift 面向協(xié)議編程在 Cocoa 中的使用示例。
ProtocolNetworkObjc 是基于 NNPopObjc 完全復(fù)刻的 Objective-C 版 ProtocolNetwork 默蚌。
由于 ProtocolNetworkObjc 完全復(fù)刻 ProtocolNetwork冻晤,具體代碼演變及分析可參考:喵神王巍的博客 面向協(xié)議編程與 Cocoa 的邂逅 (下) 中相關(guān)內(nèi)容。限于篇幅绸吸,本文不對(duì)代碼進(jìn)行講解鼻弧。
本文下半部分將介紹 NNPopObjc 的實(shí)現(xiàn)思路和原理。