對象間的通訊

對象之間需要通信葱她,這也是所有軟件的基礎搓谆。再非凡的軟件也需要通過對象通信來完成復雜的目標偶器。本章將深入討論一些設計概念,以及如何依據(jù)這些概念來設計出良好的架構(gòu)。
Block


Block 是 Objective-C 版本的 lambda 或者 closure(閉包)升敲。

 - (void)downloadObjectsAtPath:(NSString *)path 
                    completion:(void(^)(NSArray *objects, NSError *error))completion;

當你定義一個類似上面的接口的時候港庄,盡量使用一個單獨的 block 作為接口的最后一個參數(shù)。把需要提供的數(shù)據(jù)和錯誤信息整合到一個單獨 block 中茸俭,比分別提供成功和失敗的 block 要好袖迎。
以下是你應該這樣做的原因:

  • 通常這成功處理和失敗處理會共享一些代碼(比如讓一個進度條或者提示消失);
  • Apple 也是這樣做的腺晾,與平臺一致能夠帶來一些潛在的好處;
  • block 通常會有多行代碼辜贵,如果不作為最后一個參數(shù)放在后面的話悯蝉,會打破調(diào)用點;
  • 使用多個 block 作為參數(shù)可能會讓調(diào)用看起來顯得很笨拙托慨,并且增加了復雜性鼻由。

看上面的方法,完成處理的 block 的參數(shù)很常見:第一個參數(shù)是調(diào)用者希望獲取的數(shù)據(jù)厚棵,第二個是錯誤相關的信息蕉世。這里需要遵循以下兩點:
若 objects

  • 不為 nil,則 error 必須為 nil
  • 若 objects為 nil婆硬,則 error必須不為 nil

因為調(diào)用者更關心的是實際的數(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)問題向楼。

深入 Block

一些關鍵點:

  • block 是在棧上創(chuàng)建的
  • block 可以復制到堆上
  • Block會捕獲棧上的變量(或指針),將其復制為自己私有的const(變量)谐区。
  • (如果在Block中修改Block塊外的)棧上的變量和指針湖蜕,那么這些變量和指針必須用__block關鍵字申明(譯者注:否則就會跟上面的情況一樣只是捕獲他們的瞬時值)。

如果 block 沒有在其他地方被保持宋列,那么它會隨著棧生存并且當棧幀(stack frame)返回的時候消失昭抒。僅存在于棧上時,block對對象訪問的內(nèi)存管理和生命周期沒有任何影響炼杖。
如果 block 需要在棧幀返回的時候存在灭返,它們需要明確地被復制到堆上,這樣嘹叫,block 會像其他 Cocoa 對象一樣增加引用計數(shù)婆殿。當它們被復制的時候,它會帶著它們的捕獲作用域一起罩扇,retain 他們所有引用的對象婆芦。
如果一個 block引用了一個棧變量或指針怕磨,那么這個block初始化的時候會擁有這個變量或指針的const副本,所以(被捕獲之后再在棧中改變這個變量或指針的值)是不起作用的消约。(譯者注:所以這時候我們在block中對這種變量進行賦值會編譯報錯:Variable is not assignable(missing __block type specifier)肠鲫,因為他們是副本而且是const的.具體見下面的例程)。
當一個 block 被復制后或粮,__block聲明的棧變量的引用被復制到了堆里导饲,復制完成之后,無論是棧上的block還是剛剛產(chǎn)生在堆上的block(棧上block的副本)都會引用該變量在堆上的副本氯材。
(下面代碼是譯者加的)

 ... 
 CGFloat blockInt = 10;
 void (^playblock)(void) = ^{ 
     NSLog(@"blockInt = %zd", blockInt); 
 }; 
 blockInt ++;
 playblock();
 ... 
 //結(jié)果為:blockInt = 10

最重要的事情是 __block聲明的變量和指針在 block 里面是作為顯示操作真實值/對象的結(jié)構(gòu)來對待的渣锦。
block 在 Objective-C 的 runtime(運行時) 里面被當作一等公民對待:他們有一個 isa
指針,一個類也是用 isa 指針在Objective-C 運行時來訪問方法和存儲數(shù)據(jù)的氢哮。在非 ARC 環(huán)境肯定會把它搞得很糟糕袋毙,并且懸掛指針會導致 crash。__block 僅僅對 block 內(nèi)的變量起作用冗尤,它只是簡單地告訴 block:

嗨听盖,這個指針或者原始的類型依賴它們在的棧。請用一個棧上的新變量來引用它裂七。我是說皆看,請對它進行雙重解引用,不要 retain 它背零。 謝謝腰吟,哥們。

如果在定義之后但是 block 沒有被調(diào)用前捉兴,對象被釋放了蝎困,那么 block 的執(zhí)行會導致 crash。 __block
變量不會在 block 中被持有倍啥,最后... 指針禾乘、引用、解引用以及引用計數(shù)變得一團糟虽缕。

self 的循環(huán)引用

當使用代碼塊和異步分發(fā)的時候始藕,要注意避免引用循環(huán)。 總是使用 weak
來引用對象氮趋,避免引用循環(huán)伍派。(譯者注:這里更為優(yōu)雅的方式是采用影子變量@weakify/@strongify 這里有更為詳細的說明) 此外,把持有 block 的屬性設置為 nil (比如self.completionBlock = nil) 是一個好的實踐剩胁。它會打破 block 捕獲的作用域帶來的引用循環(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];
}];

多個語句的例子:

__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];
}];

你應該把這兩行代碼作為 snippet 加到 Xcode 里面并且總是這樣使用它們。

__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;

這里我們來討論下 block 里面的 self 的 __weak和 __strong 限定詞的一些微妙的地方昵观。簡而言之晾腔,我們可以參考 self 在 block 里面的三種不同情況舌稀。

  • 直接在 block 里面使用關鍵詞 self
  • 在 block 外定義一個 __weak的 引用到 self,并且在 block 里面使用這個弱引用
  • 在 block 外定義一個 __weak的 引用到 self灼擂,并在在 block 內(nèi)部通過這個弱引用定義一個 __strong
    的引用壁查。

方案 1. 直接在 block 里面使用關鍵詞 self
如果我們直接在 block 里面用 self 關鍵字,對象會在 block 的定義時候被 retain剔应,(實際上 block 是 copied 但是為了簡單我們可以忽略這個)睡腿。一個 const 的對 self 的引用在 block 里面有自己的位置并且它會影響對象的引用計數(shù)。如果這個block被其他的類使用并且(或者)彼此間傳來傳去峻贮,我們可能想要在 block 中保留 self席怪,就像其他在 block 中使用的對象一樣. 因為他們是block執(zhí)行所需要的.

dispatch_block_t completionBlock = ^{
   NSLog(@"%@", self);
}
MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController 
                   animated:YES 
                 completion:completionHandler];

沒啥大不了。但是如果通過一個屬性中的 self
保留 了這個 block(就像下面的例程一樣),對象( self )保留了 block 會怎么樣呢月洛?

self.completionHandler = ^{ 
    NSLog(@"%@", self);
}
MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController 
                   animated:YES 
                 completion:self.completionHandler];

這就是有名的 retain cycle, 并且我們通常應該避免它何恶。這種情況下我們收到 CLANG 的警告:

Capturing 'self' strongly in this block is likely to lead to a retain cycle (在 block 里面發(fā)現(xiàn)了 `self` 的強引用,可能會導致循環(huán)引用)

所以 __weak就有用武之地了嚼黔。

方案 2. 在 block 外定義一個 __weak的 引用到 self,并且在 block 里面使用這個弱引用
這樣會避免循壞引用惜辑,也是通常情況下我們的block作為類的屬性被self retain 的時候會做的唬涧。

__weak typeof(self) weakSelf = self;
self.completionHandler = ^{ 
NSLog(@"%@", weakSelf);
};
MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController 
                   animated:YES 
                 completion:self.completionHandler];

這個情況下 block 沒有 retain 對象并且對象在屬性里面 retain 了 block 。所以這樣我們能保證了安全的訪問 self盛撑。 不過糟糕的是碎节,它可能被設置成 nil 的。問題是:如何讓 self 在 block 里面安全地被銷毀抵卫。

考慮這么個情況:block 作為屬性(property)賦值的結(jié)果狮荔,從一個對象被復制到另一個對象(如 myController),在這個復制的 block 執(zhí)行之前介粘,前者(即之前的那個對象)已經(jīng)被解除分配殖氏。
下面的更有意思。

方案 3. 在 block 外定義一個 __weak的 引用到 self姻采,并在在 block 內(nèi)部通過這個弱引用定義一個 __strong的引用雅采。
你可能會想,首先慨亲,這是避免 retain cycle 警告的一個技巧婚瓜。

這不是重點,這個 self 的強引用是在block 執(zhí)行時 被創(chuàng)建的刑棵,但是否使用 self 在 block 定義時就已經(jīng)定下來了巴刻, 因此self (在block執(zhí)行時) 會被 retain.
Apple 文檔 中表示 "為了 non-trivial cycles ,你應該這樣" :

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... 
      }
};

首先蛉签,我覺得這個例子看起來是錯誤的胡陪。如果 block 本身在 completionHandler 屬性中被 retain 了沥寥,那么 self 如何被 delloc 和在 block 之外賦值為 nil 呢? completionHandler 屬性可以被聲明為 assign
或者 unsafe_unretained
的,來允許對象在 block 被傳遞之后被銷毀督弓。
我不能理解這樣做的理由营曼,如果其他對象需要這個對象(self),block 被傳遞的時候應該 retain 對象愚隧,所以 block 應該不被作為屬性存儲蒂阱。這種情況下不應該用 __weak
/__strong

總之,其他情況下狂塘,希望 weakSelf 變成 nil 的話录煤,就像第二種情況解釋那么寫(在 block 之外定義一個弱應用并且在 block 里面使用)。
還有荞胡,Apple的 "trivial block" 是什么呢妈踊。我們的理解是 trivial block 是一個不被傳送的 block ,它在一個良好定義和控制的作用域里面泪漂,weak 修飾只是為了避免循環(huán)引用廊营。
雖然有 Kazuki Sakamoto 和 Tomohiko Furumoto) 討論的 在線 參考, Matt Galloway 的 (Effective Objective-C 2.0Pro Multithreading and Memory Management for iOS and OS X ,大多數(shù)開發(fā)者始終沒有弄清楚概念萝勤。
在 block 內(nèi)用強引用的優(yōu)點是露筒,搶占執(zhí)行的時候的魯棒性。在 block 執(zhí)行的時候, 再次溫故下上面的三個例子:
**方案 1. 直接在 block 里面使用關鍵詞 self
**
如果 block 被屬性 retain敌卓,self 和 block 之間會有一個循環(huán)引用并且它們不會再被釋放慎式。如果 block 被傳送并且被其他的對象 copy 了,self 在每一個 copy 里面被 retain
方案 2. 在 block 外定義一個 __weak的 引用到 self趟径,并且在 block 里面使用這個弱引用
不管 block 是否通過屬性被 retain 瘪吏,這里都不會發(fā)生循環(huán)引用。如果 block 被傳遞或者 copy 了蜗巧,在執(zhí)行的時候掌眠,weakSelf 可能已經(jīng)變成 nil。
block 的執(zhí)行可以搶占惧蛹,而且對 weakSelf 指針的調(diào)用時序不同可以導致不同的結(jié)果(如:在一個特定的時序下 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 外定義一個 __weak的引用到 self,并在在 block 內(nèi)部通過這個弱引用定義一個 __strong的引用香嗓。

不管 block 是否通過屬性被 retain 迅腔,這里也不會發(fā)生循環(huán)引用。如果 block 被傳遞到其他對象并且被復制了靠娱,執(zhí)行的時候沧烈,weakSelf 可能被nil,因為強引用被賦值并且不會變成nil的時候像云,我們確保對象 在 block 調(diào)用的完整周期里面被 retain了锌雀,如果搶占發(fā)生了蚂夕,隨后的對 strongSelf 的執(zhí)行會繼續(xù)并且會產(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(搶占的時候,strongSelf 還是非 nil 的)
       [strongSelf doSomethingElse]; // strongSelf != nil 
     } 
     else { 
        // Probably nothing... 
        return;
     }
};

在ARC條件中惩歉,如果嘗試用 -> 符號訪問一個實例變量等脂,編譯器會給出非常清晰的錯誤信息:

Dereferencing a __weak pointer is not allowed due to possible null value caused by race condition, assign it to a strong variable first. (對一個 __weak 指針的解引用不允許的,因為可能在競態(tài)條件里面變成 null, 所以先把他定義成 strong 的屬性)

可以用下面的代碼展示

__weak typeof(self) weakSelf = self;
myObj.myBlock =  ^{
    id localVal = weakSelf->someIVar;
};

在最后

  • 方案 1: 只能在 block 不是作為一個 property 的時候使用撑蚌,否則會導致 retain cycle上遥。

  • 方案 2: 當 block 被聲明為一個 property 的時候使用。

  • 方案 3: 和并發(fā)執(zhí)行有關争涌。當涉及異步的服務的時候粉楚,block 可以在之后被執(zhí)行,并且不會發(fā)生關于 self 是否存在的問題亮垫。

委托和數(shù)據(jù)源


委托模式 是 Apple 的框架里面使用廣泛的模式模软,同時它是四人幫的書“設計模式”中的重要模式之一。委托代理模式是單向的饮潦,消息的發(fā)送方(委托方)需要知道接收方(代理方)是誰撵摆,反過來就沒有必要了。對象之間耦合較松害晦,發(fā)送方僅需知道它的代理方是否遵守相關 protocol 即可。
本質(zhì)上暑中,委托代理模式僅需要代理方提供一些回調(diào)方法壹瘟,即代理方需要實現(xiàn)一系列空返回值的方法。
不幸的是 Apple 的 API 并沒有遵守這個原則鳄逾,開發(fā)者也效仿 Apple 進入了一個誤區(qū)稻轨。典型的例子就是 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;

當委托者詢問代理者一些信息的時候殴俱,這就暗示著信息是從代理者流向委托者而非相反的過程。 這(譯者注:委托者 ==Data==> 代理者)是概念性的不同枚抵,須用另一個新的名字來描述這種模式:數(shù)據(jù)源模式线欲。
可能有人會說 Apple 有一個 UITableViewDataSouce protocol 來做這個(雖然使用委托模式的名字),但是實際上它的方法是用來提供真實的數(shù)據(jù)應該如何被展示的信息的汽摹。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView;

此外李丰,以上兩個方法 Apple 混合了展示層和數(shù)據(jù)層,這顯的非常糟糕逼泣,但是很少的開發(fā)者感到糟糕趴泌。而且我們在這里把空返回值和非空返回值的方法都天真地叫做委托方法舟舒。

為了分離概念,我們應該這樣做:

  • 委托模式(delegate pattern):事件發(fā)生的時候嗜憔,委托者需要通知代理者秃励。
  • 數(shù)據(jù)源模式(datasource pattern): 委托者需要從數(shù)據(jù)源對象拉取數(shù)據(jù)。

這個是實際的例子:

@class ZOCSignUpViewController;

@protocol ZOCSignUpViewControllerDelegate <NSObject>
- (void)signUpViewControllerDidPressSignUpButton:(ZOCSignUpViewController *)controller;
@end

@protocol ZOCSignUpViewControllerDataSource <NSObject>
- (ZOCUserCredentials *)credentialsForSignUpViewController:(ZOCSignUpViewController *)controller;
@end

@interface ZOCSignUpViewController : UIViewController
@property (nonatomic, weak) id<ZOCSignUpViewControllerDelegate> delegate;
@property (nonatomic, weak) id<ZOCSignUpViewControllerDataSource> dataSource;
@end

代理方法必須以調(diào)用者(即委托者)作為第一個參數(shù)吉捶,就像上面的例子一樣夺鲜。否則代理者無法區(qū)分不同的委托者實例。換句話說帚稠,調(diào)用者(委托者)沒有被傳遞給代理谣旁,那就沒有方法讓代理處理兩個不同的委托者,所以下面這種寫法人神共怒:

- (void)calculatorDidCalculateValue:(CGFloat)value;

默認情況下滋早,代理者需要實現(xiàn) protocol 的方法榄审。可以用 @required 和 @optional 關鍵字來標記方法是否是必要的還是可選的(默認是 @required: 必需的)杆麸。

@protocol ZOCSignUpViewControllerDelegate <NSObject>
@required
- (void)signUpViewController:(ZOCSignUpViewController *)controller didProvideSignUpInfo:(NSDictionary *)dict;
@optional
- (void)signUpViewControllerDidPressSignUpButton:(ZOCSignUpViewController *)controller;
@end

對于可選的方法搁进,委托者必須在發(fā)送消息前檢查代理是否確實實現(xiàn)了特定的方法(否則會 crash):

if ([self.delegate respondsToSelector:@selector(signUpViewControllerDidPressSignUpButton:)]) {
    [self.delegate signUpViewControllerDidPressSignUpButton:self];
}

繼承

有時候你可能需要重載代理方法∥敉罚考慮有兩個 UIViewController 子類的情況:UIViewControllerA 和 UIViewControllerB饼问,有下面的類繼承關系。

UIViewControllerB < UIViewControllerA < UIViewController

UIViewControllerA 遵從 UITableViewDelegate 并且實現(xiàn)了

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

你可能會想要在 UIViewControllerB 中提供一個不同的實現(xiàn)揭斧,這個實現(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 )沒有實現(xiàn)這個方法呢莱革?此時調(diào)用 [super respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)]方法,將使用 NSObject 的實現(xiàn)讹开,在 self 上下文深入查找并且明確 self 實現(xiàn)了這個方法(因為 UITableViewControllerA 遵從 UITableViewDelegate )盅视,但是應用將在下一行發(fā)生崩潰,并提示如下錯誤信息:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UIViewControllerB tableView:heightForRowAtIndexPath:]: unrecognized selector sent to instance 0x8d82820'
*** 由于未捕獲異常 `NSInvalidArgumentException(無效的參數(shù)異常)`導致應用終止旦万,理由是:向?qū)嵗?ox8d82820 發(fā)送了無法識別的 selector `- [UIViewControllerB tableView:heightForRowAtIndexPath:]`

這種情況下我們需要來詢問特定的類實例是否可以響應對應的 selector闹击。下面的代碼提供了一個小技巧:

- (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òu)的方式成艘,因為這種方式代理方法不需要被重寫赏半。

多重委托

數(shù)據(jù)源模式強制一對一的關系,當發(fā)送者請求信息時有且只能有一個對象來響應淆两。對于代理模式而言這會有些不同断箫,我們有足夠的理由要去實現(xiàn)很多代理者等待(唯一委托者的)回調(diào)的場景。

一些情況下至少有兩個對象對特定委托者的回調(diào)感興趣琼腔,而后者(即委托者)需要知道他的所有代理瑰枫。這種方法在分布式系統(tǒng)下更為適用并且廣泛使用于大型軟件的復雜信息流程中。

多重委托可以用很多方式實現(xiàn),但讀者更在乎找到適合自己的個人實現(xiàn)光坝。Luca Bernardi 在他的 LBDelegateMatrioska中提供了上述范式的一個非常簡潔的實現(xiàn)尸诽。
這里給出一個基本的實現(xiàn),方便你更好地理解這個概念。即使在Cocoa中也有一些在數(shù)據(jù)結(jié)構(gòu)中保存 weak 引用來避免 引用循環(huán)的方法盯另, 這里我們使用一個類來保留代理對象的 weak 引用(就像單一代理那樣):

@interface ZOCWeakObject : NSObject

@property (nonatomic, readonly, weak) id object;
//譯者注:這里原文并沒有很好地實踐自己在本書之前章節(jié)所討論的關于property屬性修飾符的
//人體工程學法則: 從左到右: 原子性 ===》 讀寫權(quán)限 (別名) ===》 內(nèi)存管理權(quán)限符
+ (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

使用 weak 對象來實現(xiàn)多重代理的簡單組件:

@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: 方法的幫助下性含,連接/解除組件之間的聯(lián)系很簡單:在某些時間點上,如果代理不需要接收委托者的回調(diào)鸳惯,僅僅需要'unsubscribe'.

當不同的 view 等待同一個回調(diào)來更新界面展示的時候商蕴,這很有用:如果 view 只是暫時隱藏(但是仍然存在),它僅僅需要取消對回調(diào)的訂閱芝发。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绪商,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子辅鲸,更是在濱河造成了極大的恐慌格郁,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件独悴,死亡現(xiàn)場離奇詭異例书,居然都是意外死亡,警方通過查閱死者的電腦和手機刻炒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門决采,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人坟奥,你說我怎么就攤上這事树瞭。” “怎么了爱谁?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵移迫,是天一觀的道長。 經(jīng)常有香客問我管行,道長,這世上最難降的妖魔是什么邪媳? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任捐顷,我火速辦了婚禮,結(jié)果婚禮上雨效,老公的妹妹穿的比我還像新娘迅涮。我一直安慰自己,他們只是感情好徽龟,可當我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布叮姑。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪传透。 梳的紋絲不亂的頭發(fā)上耘沼,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音朱盐,去河邊找鬼群嗤。 笑死,一個胖子當著我的面吹牛兵琳,可吹牛的內(nèi)容都是我干的狂秘。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼躯肌,長吁一口氣:“原來是場噩夢啊……” “哼者春!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起清女,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤钱烟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后校仑,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體忠售,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年迄沫,在試婚紗的時候發(fā)現(xiàn)自己被綠了稻扬。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡羊瘩,死狀恐怖泰佳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情尘吗,我是刑警寧澤逝她,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站睬捶,受9級特大地震影響黔宛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜擒贸,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一臀晃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧介劫,春花似錦徽惋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春宦棺,著一層夾襖步出監(jiān)牢的瞬間瓣距,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工渺氧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留旨涝,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓侣背,卻偏偏與公主長得像白华,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子贩耐,可洞房花燭夜當晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容