對(duì)象之間需要通信,這也是所有軟件的基礎(chǔ)。再非凡的軟件也需要通過(guò)對(duì)象通信來(lái)完成復(fù)雜的目標(biāo)。本章將深入討論一些設(shè)計(jì)概念,以及如何依據(jù)這些概念來(lái)設(shè)計(jì)出良好的架構(gòu)。
Blocks 是 Objective-C 版本的 lambda 或者 closure(閉包)。
使用 block 定義異步接口:
- (void)downloadObjectsAtPath:(NSString *)path
completion:(void(^)(NSArray *objects, NSError *error))completion;
當(dāng)你定義一個(gè)類似上面的接口的時(shí)候,盡量使用一個(gè)單獨(dú)的 block 作為接口的最后一個(gè)參數(shù)。把需要提供的數(shù)據(jù)和錯(cuò)誤信息整合到一個(gè)單獨(dú) block 中,比分別提供成功和失敗的 block 要好。
以下是你應(yīng)該這樣做的原因:
看上面的方法,完成處理的 block 的參數(shù)很常見(jiàn):第一個(gè)參數(shù)是調(diào)用者希望獲取的數(shù)據(jù),第二個(gè)是錯(cuò)誤相關(guān)的信息。這里需要遵循以下兩點(diǎn):
objects
不為 nil,則 error
必須為 nilobjects
為 nil,則 error
必須不為 nil因?yàn)檎{(diào)用者更關(guān)心的是實(shí)際的數(shù)據(jù),就像這樣:
- (void)downloadObjectsAtPath:(NSString *)path
completion:(void(^)(NSArray *objects, NSError *error))completion {
if (objects) {
// do something with the data
}
else {
// some error occurred, 'error' variable should not be nil by contract
}
}
此外,Apple 提供的一些同步接口在成功狀態(tài)下向 error 參數(shù)(如果非 NULL) 寫入了垃圾值,所以檢查 error 的值可能出現(xiàn)問(wèn)題。
一些關(guān)鍵點(diǎn):
如果 block 沒(méi)有在其他地方被保持,那么它會(huì)隨著棧生存并且當(dāng)棧幀(stack frame)返回的時(shí)候消失。當(dāng)在棧上的時(shí)候,一個(gè) block 對(duì)訪問(wèn)的任何內(nèi)容不會(huì)有影響。如果 block 需要在棧幀返回的時(shí)候存在,它們需要明確地被復(fù)制到堆上,這樣,block 會(huì)像其他 Cocoa 對(duì)象一樣增加引用計(jì)數(shù)。當(dāng)它們被復(fù)制的時(shí)候,它會(huì)帶著它們的捕獲作用域一起,retain 他們所有引用的對(duì)象。如果一個(gè) block指向一個(gè)棧變量或者指針,那么這個(gè)block初始化的時(shí)候它會(huì)有一份聲明為 const 的副本,所以對(duì)它們賦值是沒(méi)用的。當(dāng)一個(gè) block 被復(fù)制后,__block
聲明的棧變量的引用被復(fù)制到了堆里,復(fù)制之后棧上的以及產(chǎn)生的堆上的 block 都會(huì)引用這個(gè)堆上的變量。
用 LLDB 來(lái)展示 block 是這樣子的:
最重要的事情是 __block
聲明的變量和指針在 block 里面是作為顯示操作真實(shí)值/對(duì)象的結(jié)構(gòu)來(lái)對(duì)待的。
block 在 Objective-C 里面被當(dāng)作一等公民對(duì)待:他們有一個(gè) isa
指針,一個(gè)類也是用 isa
指針來(lái)訪問(wèn) Objective-C 運(yùn)行時(shí)來(lái)訪問(wèn)方法和存儲(chǔ)數(shù)據(jù)的。在非 ARC 環(huán)境肯定會(huì)把它搞得很糟糕,并且懸掛指針會(huì)導(dǎo)致 Crash。__block
僅僅對(duì) block 內(nèi)的變量起作用,它只是簡(jiǎn)單地告訴 block:
嗨,這個(gè)指針或者原始的類型依賴它們?cè)诘臈!U?qǐng)用一個(gè)棧上的新變量來(lái)引用它。我是說(shuō),請(qǐng)對(duì)它進(jìn)行雙重解引用,不要 retain 它。 謝謝,哥們。
如果在定義之后但是 block 沒(méi)有被調(diào)用前,對(duì)象被釋放了,那么 block 的執(zhí)行會(huì)導(dǎo)致 Crash。 __block
變量不會(huì)在 block 中被持有,最后... 指針、引用、解引用以及引用計(jì)數(shù)變得一團(tuán)糟。
當(dāng)使用代碼塊和異步分發(fā)的時(shí)候,要注意避免引用循環(huán)。 總是使用 weak
引用會(huì)導(dǎo)致引用循環(huán)。 此外,把持有 blocks 的屬性設(shè)置為 nil (比如 self.completionBlock = nil
) 是一個(gè)好的實(shí)踐。它會(huì)打破 blocks 捕獲的作用域帶來(lái)的引用循環(huán)。
例子:
__weak __typeof(self) weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
[weakSelf doSomethingWithData:data];
}];
不要這樣做:
[self executeBlock:^(NSData *data, NSError *error) {
[self doSomethingWithData:data];
}];
多個(gè)語(yǔ)句的例子:
__weak __typeof(self)weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSomethingWithData:data];
[strongSelf doSomethingWithData:data];
}
}];
不要這樣做:
__weak __typeof(self)weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
[weakSelf doSomethingWithData:data];
[weakSelf doSomethingWithData:data];
}];
你應(yīng)該把這兩行代碼作為 snippet 加到 Xcode 里面并且總是這樣使用它們。
__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;
這里我們來(lái)討論下 block 里面的 self 的 __weak
和 __strong
限定詞的一些微妙的地方。簡(jiǎn)而言之,我們可以參考 self 在 block 里面的三種不同情況。
__weak
的 引用到 self,并且在 block 里面使用這個(gè)弱引用__weak
的 引用到 self,并在在 block 內(nèi)部通過(guò)這個(gè)弱引用定義一個(gè) __strong
的引用。1. 直接在 block 里面使用關(guān)鍵詞 self
如果我們直接在 block 里面用 self 關(guān)鍵字,對(duì)象會(huì)在 block 的定義時(shí)候被 retain,(實(shí)際上 block 是 copied 但是為了簡(jiǎn)單我們可以忽略這個(gè))。一個(gè) const 的對(duì) self 的引用在 block 里面有自己的位置并且它會(huì)影響對(duì)象的引用計(jì)數(shù)。如果 block 被其他 class 或者/并且傳送過(guò)去了,我們可能想要 retain self 就像其他被 block 使用的對(duì)象,從他們需要被block執(zhí)行
dispatch_block_t completionBlock = ^{
NSLog(@"%@", self);
}
MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController
animated:YES
completion:completionHandler];
不是很麻煩的事情。但是, 當(dāng) block 被 self 在一個(gè)屬性 retain(就像下面的例子)呢
self.completionHandler = ^{
NSLog(@"%@", self);
}
MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController
animated:YES
completion:self.completionHandler];
這就是有名的 retain cycle, 并且我們通常應(yīng)該避免它。這種情況下我們收到 CLANG 的警告:
Capturing 'self' strongly in this block is likely to lead to a retain cycle (在 block 里面發(fā)現(xiàn)了 `self` 的強(qiáng)引用,可能會(huì)導(dǎo)致循環(huán)引用)
所以可以用 weak
修飾
2. 在 block 外定義一個(gè) __weak
的 引用到 self,并且在 block 里面使用這個(gè)弱引用
這樣會(huì)避免循環(huán)引用,也是我們通常在 block 已經(jīng)被 self 的 property 屬性里面 retain 的時(shí)候會(huì)做的。
__weak typeof(self) weakSelf = self;
self.completionHandler = ^{
NSLog(@"%@", weakSelf);
};
MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController
animated:YES
completion:self.completionHandler];
這個(gè)情況下 block 沒(méi)有 retain 對(duì)象并且對(duì)象在屬性里面 retain 了 block 。所以這樣我們能保證了安全的訪問(wèn) self。 不過(guò)糟糕的是,它可能被設(shè)置成 nil 的。問(wèn)題是:如果和讓 self 在 block 里面安全地被銷毀。
舉個(gè)例子, block 被一個(gè)對(duì)象復(fù)制到了另外一個(gè)(比如 myControler)作為屬性賦值的結(jié)果。之前的對(duì)象在可能在被復(fù)制的 block 有機(jī)會(huì)執(zhí)行被銷毀。
下面的更有意思。
3. 在 block 外定義一個(gè) __weak
的 引用到 self,并在在 block 內(nèi)部通過(guò)這個(gè)弱引用定義一個(gè) __strong
的引用
你可能會(huì)想,首先,這是避免 retain cycle 警告的一個(gè)技巧。然而不是,這個(gè)到 self 的強(qiáng)引用在 block 的執(zhí)行時(shí)間 被創(chuàng)建。當(dāng) block 在定義的時(shí)候, block 如果使用 self 的時(shí)候,就會(huì) retain 了 self 對(duì)象。
Apple 文檔 中表示 "為了 non-trivial cycles ,你應(yīng)該這樣" :
MyViewController *myController = [[MyViewController alloc] init...];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler = ^(NSInteger result) {
MyViewController *strongMyController = weakMyController;
if (strongMyController) {
// ...
[strongMyController dismissViewControllerAnimated:YES completion:nil];
// ...
}
else {
// Probably nothing...
}
};
首先,我覺(jué)得這個(gè)例子看起來(lái)是錯(cuò)誤的。如果 block 本身被 completionHandler 屬性里面 retain 了,那么 self 如何被 delloc 和在 block 之外賦值為 nil 呢? completionHandler 屬性可以被聲明為 assign
或者 unsafe_unretained
的,來(lái)允許對(duì)象在 block 被傳遞之后被銷毀。
我不能理解這樣做的理由,如果其他對(duì)象需要這個(gè)對(duì)象(self),block 被傳遞的時(shí)候應(yīng)該 retain 對(duì)象,所以 block 應(yīng)該不被作為屬性存儲(chǔ)。這種情況下不應(yīng)該用 __weak
/__strong
總之,其他情況下,希望 weakSelf 變成 nil 的話,就像第二種情況解釋那么寫(在 block 之外定義一個(gè)弱應(yīng)用并且在 block 里面使用)。
還有,Apple的 "trivial block" 是什么呢。我們的理解是 trivial block 是一個(gè)不被傳送的 block ,它在一個(gè)良好定義和控制的作用域里面,weak 修飾只是為了避免循環(huán)引用。
雖然有 Kazuki Sakamoto 和 Tomohiko Furumoto) 討論的 一 些 的 在線 參考, Matt Galloway 的 (Effective Objective-C 2.0 和 Pro Multithreading and Memory Management for iOS and OS X ,大多數(shù)開(kāi)發(fā)者始終沒(méi)有弄清楚概念。
在 block 內(nèi)用強(qiáng)引用的優(yōu)點(diǎn)是,搶占執(zhí)行的時(shí)候的魯棒性??瓷厦娴娜齻€(gè)例子,在 block 執(zhí)行的時(shí)候
1. 直接在 block 里面使用關(guān)鍵詞 self
如果 block 被屬性 retain,self 和 block 之間會(huì)有一個(gè)循環(huán)引用并且它們不會(huì)再被釋放。如果 block 被傳送并且被其他的對(duì)象 copy 了,self 在每一個(gè) copy 里面被 retain
2. 在 block 外定義一個(gè) __weak
的 引用到 self,并且在 block 里面使用這個(gè)弱引用
沒(méi)有循環(huán)引用的時(shí)候,block 是否被 retain 或者是一個(gè)屬性都沒(méi)關(guān)系。如果 block 被傳遞或者 copy 了,在執(zhí)行的時(shí)候,weakSelf 可能會(huì)變成 nil。
block 的執(zhí)行可以搶占,并且后來(lái)的對(duì) weakSelf 的不同調(diào)用可以導(dǎo)致不同的值(比如,在 一個(gè)特定的執(zhí)行 weakSelf 可能賦值為 nil )
__weak typeof(self) weakSelf = self;
dispatch_block_t block = ^{
[weakSelf doSomething]; // weakSelf != nil
// preemption, weakSelf turned nil
[weakSelf doSomethingElse]; // weakSelf == nil
};
3. 在 block 外定義一個(gè) __weak
的 引用到 self,并在在 block 內(nèi)部通過(guò)這個(gè)弱引用定義一個(gè) __strong
的引用。
不論管 block 是否被 retain 或者是一個(gè)屬性,這樣也不會(huì)有循環(huán)引用。如果 block 被傳遞到其他對(duì)象并且被復(fù)制了,執(zhí)行的時(shí)候,weakSelf 可能被nil,因?yàn)閺?qiáng)引用被復(fù)制并且不會(huì)變成nil的時(shí)候,我們確保對(duì)象 在 block 調(diào)用的完整周期里面被 retain了,如果搶占發(fā)生了,隨后的對(duì) strongSelf 的執(zhí)行會(huì)繼續(xù)并且會(huì)產(chǎn)生一樣的值。如果 strongSelf 的執(zhí)行到 nil,那么在 block 不能正確執(zhí)行前已經(jīng)返回了。
__weak typeof(self) weakSelf = self;
myObj.myBlock = ^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSomething]; // strongSelf != nil
// preemption, strongSelf still not nil(搶占的時(shí)候,strongSelf 還是非 nil 的)
[strongSelf doSomethingElse]; // strongSelf != nil
}
else {
// Probably nothing...
return;
}
};
在一個(gè) ARC 的環(huán)境中,如果嘗試用 ->
符號(hào)來(lái)表示,編譯器會(huì)警告一個(gè)錯(cuò)誤:
Dereferencing a __weak pointer is not allowed due to possible null value caused by race condition, assign it to a strong variable first. (對(duì)一個(gè) __weak 指針的解引用不允許的,因?yàn)榭赡茉诟?jìng)態(tài)條件里面變成 null, 所以先把他定義成 strong 的屬性)
可以用下面的代碼展示
__weak typeof(self) weakSelf = self;
myObj.myBlock = ^{
id localVal = weakSelf->someIVar;
};
在最后【疑問(wèn)】
1: 只能在 block 不是作為一個(gè) property 的時(shí)候使用,否則會(huì)導(dǎo)致 retain cycle。
2: 當(dāng) block 被聲明為一個(gè) property 的時(shí)候使用。
委托是 Apple 的框架里面使用廣泛的模式,同時(shí)它是一個(gè)重要的 四人幫的書(shū)“設(shè)計(jì)模式”中的模式。委托模式是單向的,消息的發(fā)送方(委托方)需要知道接收方(委托),反過(guò)來(lái)就不是了。對(duì)象之間沒(méi)有多少耦合,因?yàn)榘l(fā)送方只要知道它的委托實(shí)現(xiàn)了對(duì)應(yīng)的 protocol。
本質(zhì)上,委托模式只需要委托提供一些回調(diào)方法,就是說(shuō)委托實(shí)現(xiàn)了一系列空返回值的方法。
不幸的是 Apple 的 API 并沒(méi)有尊重這個(gè)原則,開(kāi)發(fā)者也效仿 Apple 進(jìn)入了歧途。一個(gè)典型的例子是 UITableViewDelegate 協(xié)議。
一些有 void 返回類型的方法就像回調(diào)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(NSIndexPath *)indexPath;
但是其他的不是
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender;
當(dāng)委托者詢問(wèn)委托對(duì)象一些信息的時(shí)候,這就暗示著信息是從委托對(duì)象流向委托者,而不會(huì)反過(guò)來(lái)。 這個(gè)概念就和委托模式有些不同,它是一個(gè)另外的模式:數(shù)據(jù)源。
可能有人會(huì)說(shuō) Apple 有一個(gè) UITableViewDataSouce protocol 來(lái)做這個(gè)(雖然使用委托模式的名字),但是實(shí)際上它的方法是用來(lái)提供真實(shí)的數(shù)據(jù)應(yīng)該如何被展示的信息的。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;
此外,以上兩個(gè)方法 Apple 混合了展示層和數(shù)據(jù)層,這顯的非常糟糕,但是很少的開(kāi)發(fā)者感到糟糕。而且我們?cè)谶@里把空返回值和非空返回值的方法都天真地叫做委托方法。
為了分離概念,我們應(yīng)該這樣做:
這個(gè)是實(shí)際的例子:
@class ZOCSignUpViewController;
@protocol ZOCSignUpViewControllerDelegate <NSObject>
- (void)signUpViewControllerDidPressSignUpButton:(ZOCSignUpViewController *)controller;
@end
@protocol ZOCSignUpViewControllerDataSource <NSObject>
- (ZOCUserCredentials *)credentialsForSignUpViewController:(ZOCSignUpViewController *)controller;
@end
@protocol ZOCSignUpViewControllerDataSource <NSObject>
@interface ZOCSignUpViewController : UIViewController
@property (nonatomic, weak) id<ZOCSignUpViewControllerDelegate> delegate;
@property (nonatomic, weak) id<ZOCSignUpViewControllerDataSource> dataSource;
@end
在上面的例子里面,委托方法需要總是有一個(gè)調(diào)用方作為第一個(gè)參數(shù),否則委托對(duì)象可能被不能區(qū)別不同的委托者的實(shí)例。此外,如果調(diào)用者沒(méi)有被傳遞到委托對(duì)象,那么就沒(méi)有辦法讓一個(gè)委托對(duì)象處理兩個(gè)不同的委托者了。所以,下面這樣的方法就是人神共憤的:
- (void)calculatorDidCalculateValue:(CGFloat)value;
默認(rèn)情況下,委托對(duì)象需要實(shí)現(xiàn) protocol 的方法。可以用@required
和 @optional
關(guān)鍵字來(lái)標(biāo)記方法是否是必要的還是可選的。
@protocol ZOCSignUpViewControllerDelegate <NSObject>
@required
- (void)signUpViewController:(ZOCSignUpViewController *)controller didProvideSignUpInfo:(NSDictionary *);
@optional
- (void)signUpViewControllerDidPressSignUpButton:(ZOCSignUpViewController *)controller;
@end
對(duì)于可選的方法,委托者必須在發(fā)送消息前檢查委托是否確實(shí)實(shí)現(xiàn)了特定的方法(否則會(huì)Crash):
if ([self.delegate respondsToSelector:@selector(signUpViewControllerDidPressSignUpButton:)]) {
[self.delegate signUpViewControllerDidPressSignUpButton:self];
}
有時(shí)候你可能需要重載委托方法??紤]有兩個(gè) UIViewController 子類的情況:UIViewControllerA 和 UIViewControllerB,有下面的類繼承關(guān)系。
UIViewControllerB < UIViewControllerA < UIViewController
UIViewControllerA
conforms to UITableViewDelegate
and implements - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
.
UIViewControllerA
遵從 UITableViewDelegate
并且實(shí)現(xiàn)了 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
.
你可能會(huì)想要提供一個(gè)和 UIViewControllerB
不同的實(shí)現(xiàn)。一個(gè)實(shí)現(xiàn)可能是這樣子的:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat retVal = 0;
if ([super respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)]) {
retVal = [super tableView:self.tableView heightForRowAtIndexPath:indexPath];
}
return retVal + 10.0f;
}
但是如果超類(UIViewControllerA
)沒(méi)有實(shí)現(xiàn)這個(gè)方法呢?
調(diào)用過(guò)程
[super respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)]
會(huì)用 NSObject 的實(shí)現(xiàn),尋找,在 self
的上下文中無(wú)疑有它的實(shí)現(xiàn),但是 app 會(huì)在下一行 Crash 并且報(bào)下面的錯(cuò):
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIViewControllerB tableView:heightForRowAtIndexPath:]: unrecognized selector sent to instance 0x8d82820'
這種情況下我們需要來(lái)詢問(wèn)特定的類實(shí)例是否可以響應(yīng)對(duì)應(yīng)的 selector。下面的代碼提供了一個(gè)小技巧:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat retVal = 0;
if ([[UIViewControllerA class] instancesRespondToSelector:@selector(tableView:heightForRowAtIndexPath:)]) {
retVal = [super tableView:self.tableView heightForRowAtIndexPath:indexPath];
}
return retVal + 10.0f;
}
就像上面的丑陋的代碼,一個(gè)委托方法也比重載方法好。
多重委托是一個(gè)非常基礎(chǔ)的概念,但是,大多數(shù)開(kāi)發(fā)者對(duì)此非常不熟悉而使用 NSNotifications。就像你可能注意到的,委托和數(shù)據(jù)源是對(duì)象之間的通訊模式,但是只涉及兩個(gè)對(duì)象:委托者和委托。
數(shù)據(jù)源模式強(qiáng)制一對(duì)一的關(guān)系,發(fā)送者來(lái)像一個(gè)并且只是一個(gè)對(duì)象來(lái)請(qǐng)求信息。但是委托模式不一樣,它可以完美得有多個(gè)委托來(lái)等待回調(diào)操作。
至少兩個(gè)對(duì)象需要接收來(lái)自特定委托者的回調(diào),并且后一個(gè)需要知道所有的委托,這個(gè)方法更好的適用于分布式系統(tǒng)并且更加廣泛用于大多數(shù)軟件的復(fù)雜信息流傳遞。
多重委托可以用很多方式實(shí)現(xiàn),讀者當(dāng)然喜歡找到一個(gè)好的個(gè)人實(shí)現(xiàn),一個(gè)非常靈巧的多重委托實(shí)現(xiàn)可以參考 Luca Bernardi 在他的 LBDelegateMatrioska 的原理。
一個(gè)基本的實(shí)現(xiàn)在下面給出。Cocoa 在數(shù)據(jù)結(jié)構(gòu)中使用弱引用來(lái)避免引用循環(huán),我們使用一個(gè)類來(lái)作為委托者持有委托對(duì)象的弱引用。
@interface ZOCWeakObject : NSObject
@property (nonatomic, weak, readonly) id object;
+ (instancetype)weakObjectWithObject:(id)object;
- (instancetype)initWithObject:(id)object;
@end
@interface ZOCWeakObject ()
@property (nonatomic, weak) id object;
@end
@implementation ZOCWeakObject
+ (instancetype)weakObjectWithObject:(id)object {
return [[[self class] alloc] initWithObject:object];
}
- (instancetype)initWithObject:(id)object {
if ((self = [super init])) {
_object = object;
}
return self;
}
- (BOOL)isEqual:(id)object {
if (self == object) {
return YES;
}
if (![object isKindOfClass:[object class]]) {
return NO;
}
return [self isEqualToWeakObject:(ZOCWeakObject *)object];
}
- (BOOL)isEqualToWeakObject:(ZOCWeakObject *)object {
if (!object) {
return NO;
}
BOOL objectsMatch = [self.object isEqual:object.object];
return objectsMatch;
}
- (NSUInteger)hash {
return [self.object hash];
}
@end
一個(gè)簡(jiǎn)單的使用 weak 對(duì)象來(lái)完成多重引用的組成部分:
@protocol ZOCServiceDelegate <NSObject>
@optional
- (void)generalService:(ZOCGeneralService *)service didRetrieveEntries:(NSArray *)entries;
@end
@interface ZOCGeneralService : NSObject
- (void)registerDelegate:(id<ZOCServiceDelegate>)delegate;
- (void)deregisterDelegate:(id<ZOCServiceDelegate>)delegate;
@end
@interface ZOCGeneralService ()
@property (nonatomic, strong) NSMutableSet *delegates;
@end
@implementation ZOCGeneralService
- (void)registerDelegate:(id<ZOCServiceDelegate>)delegate {
if ([delegate conformsToProtocol:@protocol(ZOCServiceDelegate)]) {
[self.delegates addObject:[[ZOCWeakObject alloc] initWithObject:delegate]];
}
}
- (void)deregisterDelegate:(id<ZOCServiceDelegate>)delegate {
if ([delegate conformsToProtocol:@protocol(ZOCServiceDelegate)]) {
[self.delegates removeObject:[[ZOCWeakObject alloc] initWithObject:delegate]];
}
}
- (void)_notifyDelegates {
...
for (ZOCWeakObject *object in self.delegates) {
if (object.object) {
if ([object.object respondsToSelector:@selector(generalService:didRetrieveEntries:)]) {
[object.object generalService:self didRetrieveEntries:entries];
}
}
}
}
@end
在 registerDelegate:
和 deregisterDelegate:
方法的幫助下,連接/解除組成部分很簡(jiǎn)單:如果委托對(duì)象不需要接收委托者的回調(diào),僅僅需要'unsubscribe'.
這在一些不同的 view 等待同一個(gè)回調(diào)來(lái)更新界面展示的時(shí)候很有用:如果 view 只是暫時(shí)隱藏(但是仍然存在),它可以僅僅需要取消對(duì)回調(diào)的訂閱。
更多建議: