聊聊delegate, block, notification, KVO
一
高內(nèi)聚低耦合是軟件設(shè)計(jì)永恒的主題之一。耦合高的代碼在完成初始階段并不一定顯現(xiàn)出什么問題,甚至看起來比低耦合版的代碼更為簡潔直接,開發(fā)起來也更為快捷。但是,隨著版本迭代,低耦合設(shè)計(jì)一開始開發(fā)快捷的副作用會(huì)逐漸反噬,模塊的維護(hù)和添加新功能會(huì)越來越困難,越來越難以調(diào)試,直到有一天,只剩兩個(gè)選擇,要么重構(gòu),要么死...然而實(shí)際生活中重構(gòu)并不一定給到時(shí)間,產(chǎn)品經(jīng)理也不一定會(huì)理解...
二
今天討論iOS框架中用于解耦的四大機(jī)制
- delegate 代理
- block
- notification 通知
- KVO 鍵-值觀察
delegate與block通常被放在一起討論,delegate是代理,block則提供了另外的一種技術(shù)手段起到跟delegate相同的作用(當(dāng)然block有更多其他用途)。這兩者屬于委托模式。
notification通知中心,KVO是cocoa框架中的黑科技,屬性鍵-值觀察,這兩個(gè)有更多的共性,經(jīng)常被放在一起討論,屬于觀察者模式。
委托從技術(shù)實(shí)現(xiàn)角度,常常被歸入適配器的設(shè)計(jì)模式。適配器模式是一種實(shí)現(xiàn)方法,不是目的,無論是委托模式,還是觀察者模式,最終目的指向的都是耦合問題。
三
委托模式簡單地描述:
A與B兩個(gè)類交互,本來A類能獨(dú)立完成的功能,依舊在A中運(yùn)行,但是把這部分功能的實(shí)現(xiàn)寫在B類中,那么既不影響A的運(yùn)行,又可以把這部分代碼從A類中分離出去。下次A與C發(fā)生交互也沒有問題,只不過委托部分在B與C中的實(shí)現(xiàn)不同。A就可以被多處使用,這就是解耦的好處。
所有delegate能完成的任務(wù),block可以完美的替代。
例如,使用delegate方式的類
@class TouchBox;
@protocol TouchBoxDelegate <NSObject>
- (void)touchedBox:(TouchBox *)box;
@end
@interface TouchBox : UIView
@property (nonatomic, weak) id<TouchBoxDelegate> delegate;
@end
@implementation TouchBox
- (void)touchBox {
if (_delegate && [_delegate respondsToSelector:@selector(touchedBox:)]) {
[_delegate touchedBox:self];
}
}
@end
如果使用block可以完全等價(jià)
@interface TouchBox : UIView
@property (nonatomic, copy) void(^touchedBox)(TouchBox *);
@end
@implementation TouchBox
- (void)touchBox {
if (self.touchedBox) {
self.touchedBox(self);
}
}
@end
區(qū)別在于如果實(shí)現(xiàn)了委托的類中,有幾個(gè)TouchBox,block形式可以很好的實(shí)現(xiàn)委托的分離,而delegate形式的實(shí)現(xiàn)則會(huì)聚合在一個(gè)位置。
- (void)createBox0 {
TouchBox *box0 = [TouchBox new];
box0.tag = 0;
}
...
- (void)createBox1 {
TouchBox *box1 = [TouchBox new];
box1.tag = 1;
}
...
#pragma mark - TouchBoxDelegate
- (void)touchedBox:(TouchBox *)box {
if (box.tag == 0) {
...
} else if (box.tag == 1) {
...
} else if (box.tag == 2) {
...
} else if (box.tag == 3) {
...
}
}
實(shí)際情況下,這種實(shí)現(xiàn)委托的方法會(huì)變得非常龐大,甚至?xí)U(kuò)張到幾百行。
如果換成block,委托的實(shí)現(xiàn)會(huì)寫在創(chuàng)建的地方。
- (void)createBox0 {
TouchBox *box0 = [TouchBox new];
box0.touchedBox = ^(TouchBox *box) {
...
};
}
- (void)createBox1 {
TouchBox *box1 = [TouchBox new];
box1.touchedBox = ^(TouchBox *box) {
...
};
}
有利有弊,block分散委托的實(shí)現(xiàn),調(diào)試時(shí)可能不如delegate形式集中,而且block使用起來有retain cycle的隱患。如果block中有創(chuàng)建其他控件,而該控件也用block來實(shí)現(xiàn)委托,可能出現(xiàn)block嵌套的情況,可能會(huì)使情況變得復(fù)雜。
block嵌套
- (void)createBox {
TouchBox *box0 = [TouchBox new];
box0.touchedBox = ^(TouchBox *box) {
TouchBox *box1 = [TouchBox new];
box1.touchedBox = ^(TouchBox *box) {
TouchBox *box2 = [TouchBox new];
box2.touchedBox = ^(TouchBox *box) {
...
};
};
};
}
四
委托模式相對(duì)于觀察者模式來說,委托對(duì)象更加“主動(dòng)”地參與了整個(gè)調(diào)用過程,知道被委托對(duì)象是誰,擁有被委托對(duì)象的引用,由于委托的方法更為具體,參數(shù)的傳遞更為方便。
觀察者模式中,對(duì)象則顯得更“被動(dòng)”,淡化了自身的動(dòng)作,疏離了實(shí)現(xiàn)操作的觀察對(duì)象,參數(shù)傳遞也簡化為了非常抽象的單一Object
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject。鍵-值觀察則沒有額外的參數(shù)可言。
通知的發(fā)生過程是,對(duì)象向一個(gè)中心對(duì)象發(fā)出通知,由通知中心像所有觀察者提供通知,而對(duì)象并不知道觀察者的存在。而鍵-值觀察連通知都不用在程序?qū)崿F(xiàn)時(shí)主動(dòng)發(fā)出(iOS的框架運(yùn)行時(shí)被觀察的變量的setter方法會(huì)發(fā)出通知)。兩者的區(qū)別在于notification通知主要從廣義上關(guān)注程序事件,發(fā)出通知的選擇位置自由度更大,而鍵-值觀察綁定于特定對(duì)象屬性的值,是一種在特定方法中發(fā)出通知的方法(setter方法)。
這些方法本身沒有優(yōu)劣之分,但是有“主動(dòng)”與“被動(dòng)”之分,參數(shù)傳遞的難易程度之分,皆是iOS程序中風(fēng)格各異的解耦之利器。