作為一個(gè)開發(fā)者,我們致力于編寫簡(jiǎn)潔并且良好架構(gòu)的代碼。很多設(shè)計(jì)模式都可以實(shí)現(xiàn)這一點(diǎn),其中最好的一種是組合模式。組合模式使編寫出的代碼更易于遵循功能單一原則并且可以使我們的類簡(jiǎn)化。
為了替代一個(gè)功能上服務(wù)于不同模塊(就像 data sources 和 delegates )的繁冗的視圖控制器,我們將這些模塊劃分到不同的類中。這個(gè)視圖控制器就可可以僅僅負(fù)責(zé)這些類的配置和協(xié)調(diào)它們工作。畢竟,代碼寫的越少,調(diào)試和維護(hù)代碼的工作量就越少。
行為是一個(gè)負(fù)責(zé)實(shí)現(xiàn)一個(gè)指定功能的對(duì)象,比如你可以有一個(gè)實(shí)現(xiàn)視差動(dòng)畫的行為。
在這篇文章中所描述的各種行為將可以通過(guò)使用 Interface Builder 從而減少大量的代碼書寫,同時(shí)使與非編碼人員的協(xié)同工作更高效。然而,就算你不使用 Interface Builder,你也能從中獲益頗多。
許多行為只需要進(jìn)行設(shè)置,而不再需要寫額外的代碼,而對(duì)這些行為的配置可以通過(guò) Interface Builder 或者(相同設(shè)置方法的)代碼完整地實(shí)現(xiàn)。在大多數(shù)情況下,你不必再去用額外的屬性引用它們。
大多數(shù)的 iOS 工程中都有繁冗的視圖控制器類,因?yàn)榇蠹叶剂?xí)慣于將 80% 的應(yīng)用邏輯寫在這里。這卻是一個(gè)很嚴(yán)肅的問(wèn)題,因?yàn)樵谖覀兊拇a中視圖控制器這部分的代碼復(fù)用是最少的,并且很難對(duì)它們(代碼)進(jìn)行測(cè)試與維護(hù)。
這里描述的行為幫助我們避免這種場(chǎng)景,那這樣做可以給我們帶來(lái)什么好處呢?
使用行為模式意味著將更多的代碼從視圖控制器中剝離到其他相應(yīng)的類中。如果你堅(jiān)持使用這種行為模式,你將最終實(shí)現(xiàn)一個(gè)非常輕量級(jí)的視圖控制器。舉個(gè)例子,我所寫的視圖控制器一般不超過(guò) 100 行代碼。
由于行為的職責(zé)單一,所以它可以非常容易地實(shí)現(xiàn)與特定行為和特定應(yīng)用邏輯的解耦。這使你可以在不同的應(yīng)用中使用相同的行為代碼。
行為都是一些功能簡(jiǎn)單的類,其工作原理像一個(gè)黑盒。這意味單元測(cè)試很容易的完整的覆蓋這部分邏輯。你可以不需要?jiǎng)?chuàng)建真實(shí)的視圖,而只需提供模擬對(duì)象就能對(duì)它們進(jìn)行測(cè)試,
如果我們決定通過(guò) Interface Builder 來(lái)使用行為,我們就可以教會(huì)我們的設(shè)計(jì)師如何去更改應(yīng)用邏輯。設(shè)計(jì)師可以增刪行為并且修改參數(shù),而并不需要理解任何 Objective-C 的相關(guān)內(nèi)容。
這對(duì)工作流程有著巨大的好處,針對(duì)小團(tuán)隊(duì)來(lái)說(shuō)尤為明顯。
行為是一些簡(jiǎn)單的對(duì)象并且不需要太多定制的代碼,但是這里有一些概念可以真正的幫助行為更易使用,并且使用起來(lái)更具威力。
許多開發(fā)者輕視了 Interface Builder 甚至從來(lái)不去學(xué)習(xí)它,正因如此,他們通常不了解 Interface Builder 功能到底有多強(qiáng)大。
運(yùn)行時(shí)屬性是 Interface Builder 使用中最關(guān)鍵的特性之一。它們?yōu)槟闾峁┝艘粋€(gè)構(gòu)建自定義類甚至設(shè)置 iOS 內(nèi)置類的屬性的途徑。舉個(gè)例子,你是否曾為你的層設(shè)置圓角呢?你其實(shí)可以直接在 IB 中進(jìn)行簡(jiǎn)單的運(yùn)行時(shí)屬性配置來(lái)實(shí)現(xiàn)這一點(diǎn)。
http://wiki.jikexueyuan.com/project/objc/images/13-10.png" alt="" />
當(dāng)在 Interface Builder 中創(chuàng)建行為時(shí),你將重度依賴運(yùn)行時(shí)屬性去設(shè)置行為選項(xiàng)。最終將會(huì)帶來(lái)更多的典型運(yùn)行時(shí)屬性:
http://wiki.jikexueyuan.com/project/objc/images/13-11.png" alt="" />
如果一個(gè)對(duì)象是被 Interface Builder 所創(chuàng)建出來(lái)的,除非另外的一個(gè)對(duì)象對(duì)其強(qiáng)引用,否則它將會(huì)在被創(chuàng)建后立刻移除。這點(diǎn)對(duì)于需要一直在視圖控制器上工作的行為來(lái)講并不理想,這樣的行為會(huì)希望和視圖控制器擁有同樣的生命周期。
我們可以嘗試在視圖控制器上創(chuàng)建一個(gè)對(duì)行為強(qiáng)引用的屬性,但這同樣也不完美,有如下的理由:
不要選擇在視圖控制器上設(shè)置強(qiáng)引用來(lái)手動(dòng)綁定一個(gè)行為,取而代之,如果有需要的話,我們可以在配置過(guò)程中將行為自己賦值為視圖控制器的關(guān)聯(lián)對(duì)象 (associated object) 。
這意味著,如果我們需要移除一個(gè)指定的行為,我們只需要移除對(duì)應(yīng)的配置行為的代碼或者 Interface Builder 對(duì)象就可以了,而無(wú)需任何額外的改變。
實(shí)現(xiàn)如下:
@interface KZBehavior : UIControl
//! object that this controller life will be bound to
@property(nonatomic, weak) IBOutlet id owner;
@end
@implementation KZBehavior
- (void)setOwner:(id)owner
{
if (_owner != owner) {
[self releaseLifetimeFromObject:_owner];
_owner = owner;
[self bindLifetimeToObject:_owner];
}
}
- (void)bindLifetimeToObject:(id)object
{
objc_setAssociatedObject(object, (__bridge void *)self, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)releaseLifetimeFromObject:(id)object
{
objc_setAssociatedObject(object, (__bridge void *)self, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
在這里我們使得已關(guān)聯(lián)的對(duì)象對(duì)一個(gè)指定所有者的對(duì)象構(gòu)建了一個(gè)強(qiáng)引用。
行為可以發(fā)送事件,這個(gè)特性非常有用,比如當(dāng)一個(gè)動(dòng)畫完成時(shí)我們就能收到通知。我們可以在 IB 里面通過(guò)構(gòu)建繼承于 UIControl 的行為類來(lái)啟用這個(gè)特性。一個(gè)特性的行為可以調(diào)用:
[self sendActionsForControlEvents:UIControlEventValueChanged];
這將允許你將行為關(guān)聯(lián)到你的視圖控制器里面的代碼。
那么,什么樣的事情用行為實(shí)現(xiàn)會(huì)最簡(jiǎn)單呢?
這里我們將展示在一個(gè) UIViewController 類(不是自定義的類)中添加一個(gè)視差動(dòng)畫是一件多么容易的事情:
或者需要從你的相冊(cè)或者相機(jī)中獲取一張圖片?
上述的行為都很簡(jiǎn)單粗暴,但是,你是否也想知道當(dāng)我們需要更多進(jìn)階特性的時(shí)候需要做什么?只要肯做,行為的功能可以非常強(qiáng)大,讓我們來(lái)看看一些更復(fù)雜的例子。
如果你的行為需要一個(gè)代理,如 UIScrollViewDelegate ,你將會(huì)很快陷入一個(gè)困境,你無(wú)法在一個(gè)特定屏幕上擁有多余一個(gè)的行為。但是,我們可以通過(guò)實(shí)現(xiàn)一個(gè)簡(jiǎn)單多路代理(遍歷 NSInvocation 命令數(shù)組進(jìn)行匹配,譯者注)對(duì)象來(lái)解決這個(gè)問(wèn)題。
@interface MultiplexerProxyBehavior : KZBehavior
//! targets to propagate messages to
@property(nonatomic, strong) IBOutletCollection(id) NSArray *targets;
@end
@implementation MultiplexerProxyBehavior
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
NSMethodSignature *sig = [super methodSignatureForSelector:sel];
if (!sig) {
for (id obj in self.targets) {
if ((sig = [obj methodSignatureForSelector:sel])) {
break;
}
}
}
return sig;
}
- (BOOL)respondsToSelector:(SEL)aSelector
{
BOOL base = [super respondsToSelector:aSelector];
if (base) {
return base;
}
return [self.targets.firstObject respondsToSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
for (id obj in self.targets) {
if ([obj respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:obj];
}
}
}
@end
通過(guò)創(chuàng)建一個(gè)多路代理的實(shí)例,你可以把它作為一個(gè) scroll view (或者其他有代理的對(duì)象) 的代理,然后將代理的調(diào)用轉(zhuǎn)發(fā)給所有的行為對(duì)象。
行為是一個(gè)非常有趣的概念,它可以簡(jiǎn)化你的代碼庫(kù),并可以允許在不同的應(yīng)用間實(shí)現(xiàn)許多代碼復(fù)用。它們(行為類)將會(huì)引領(lǐng)你在團(tuán)隊(duì)中與非編碼人員進(jìn)行更有效率的工作,并允許他們對(duì)應(yīng)用的行為進(jìn)行調(diào)整和修改。