1. 程式人生 > >iOS 開發中的訊息機制-代理、通知、block

iOS 開發中的訊息機制-代理、通知、block

關於代理

1.代理時一種設計模式。

使用場景:如果物件B要監聽物件A裡面發什麼了什麼,使用代理。如果物件A想讓物件B去幹活,使用代理。如果物件A中的按鈕或者cell被點選,要把相應的資料傳遞給物件B,使用代理。例如,我們自定義view裡面的button被點選,需要讓控制器知道,或者傳遞引數給控制器,使用代理。還有,如果A不想做什麼,需要讓B來替他執行某項操作,使用代理。補充:代理時一對一的。

如何使用?場景A中發生了什麼,B來響應這個事件

1.定義代理協議,申明代理方法,關鍵字是@protocol ,方法是使用@optional&@required修飾。其中@required是必須實現的方法。比如協議名字為--ClassADelegate

2.定義代理屬性  @property (nonatomic,weak)id<ClassADelegate>delegate;

你可能注意到代理為什麼使用weak,因為一般的代理使用weak/assign。

@property (nonatomic, weak, nullable) id <UITableViewDataSource> dataSource;

@property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;

檢視UITableView的api發現常用的代理使用的是weak.為什麼?

分析:比如控制器時UITableViewController.UITableViewController(引用)--->tableView,tableView.delegate/dataSource(引用)--->UITableViewController.如果設定成strong,那麼就是兩個強指標,造成迴圈引用。那麼解決辦法呢,一邊使用weak,我們能改的只能的代理,改成弱指標。

3.指定代理 

在B類中,例項化A,指定A的例項化物件的代理屬性為B

具體做法是

ClassA *obj = [[ClassA alloc]init];

obj.delegate = self;//self 指的是B的例項

---->做完上面的幾部會有一個警告,通常是沒有遵循協議和實現相應的@required方法

4.遵循代理協議 類名後面加上:ClassADelegate

5.在B的implementation檔案中實現代理方法。

代理實現細節-----代理要傳值一般要把自己傳出去。也就是傳遞當前類的例項

關於通知

通知--通知時一對多的,一半註冊了這個通知的物件都可以收到這個通知。就像你發出一個廣播,別人通過頻率,調到當前頻道上,就可以收聽廣播一樣。

通知的三步:

1.發出通知---

//建立一個訊息物件 NSNotification * notice = [NSNotification notificationWithName:@"123" object:nil userInfo:@{@"1":@"123"}]; //傳送訊息 [[NSNotificationCenter defaultCenter]postNotification:notice];

2.新增監聽者---

//獲取通知中心單例物件 NSNotificationCenter * center = [NSNotificationCenter defaultCenter]; //添加當前類物件為一個觀察者,name和object設定為nil,表示接收一切通知 [center addObserver:self selector:@selector(notice:) name:@"123" object:nil];

3.執行回撥---

我們可以在回撥的函式中取到userInfo內容,如下

-(void)notice:(id)notice{

 NSLog(@"%@",notice);

}

4.使用通知記得移除,不然會造成記憶體溢位

-(void)dealloc{

 [[NSNotificationCenter defaultCenter]removeObserver:self];

}

--提到通知不得不提到KVO 屬性監視器--在java中管它叫做觀察者模式

KVO 的實現也依賴於 Objective-C 強大的 Runtime 。Apple 的文件有簡單提到過 KVO 的實現

Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...

Apple 的文件真是一筆帶過,唯一有用的資訊也就是:被觀察物件的 isa 指標會指向一箇中間類,而不是原來真正的類。看來,Apple 並不希望過多暴露 KVO 的實現細節。不過,要是你用 runtime 提供的方法去深入挖掘,所有被掩蓋的細節都會原形畢露。Mike Ash 早在 2009 年就做了這麼個探究

簡單概述下 KVO 的實現:

當你觀察一個物件時,一個新的類會動態被建立。這個類繼承自該物件的原本的類,並重寫了被觀察屬性的 setter 方法。自然,重寫的 setter 方法會負責在呼叫原 setter 方法之前和之後,通知所有觀察物件值的更改。最後把這個物件的 isa 指標 ( isa 指標告訴 Runtime 系統這個物件的類是什麼 ) 指向這個新建立的子類,物件就神奇的變成了新建立的子類的例項。

原來,這個中間類,繼承自原本的那個類。不僅如此,Apple 還重寫了 -class 方法,企圖欺騙我們這個類沒有變,就是原本那個類。更具體的資訊,去跑一下 Mike Ash 的那篇文章裡的程式碼就能明白,這裡就不再重複。

當你觀察一個物件時,一個新的類會動態被建立。這個類繼承自該物件的原本的類,並重寫了被觀察屬性的 setter 方法。自然,重寫的 setter 方法會負責在呼叫原 setter 方法之前和之後,通知所有觀察物件值的更改。最後把這個物件的 isa 指標 ( isa 指標告訴 Runtime 系統這個物件的類是什麼 ) 指向這個新建立的子類,物件就神奇的變成了新建立的子類的例項。

原來,這個中間類,繼承自原本的那個類。不僅如此,Apple 還重寫了 -class 方法,企圖欺騙我們這個類沒有變,就是原本那個類。更具體的資訊,去跑一下 Mike Ash 的那篇文章裡的程式碼就能明白,這裡就不再重複。

KVO-缺陷

KVO 很強大,沒錯。知道它內部實現,或許能幫助更好地使用它,或在它出錯時更方便除錯。但官方實現的 KVO 提供的 API 實在不怎麼樣。

比如,你只能通過重寫 -observeValueForKeyPath:ofObject:change:context: 方法來獲得通知。想要提供自定義的 selector ,不行;想要傳一個 block ,門都沒有。而且你還要處理父類的情況 - 父類同樣監聽同一個物件的同一個屬性。但有時候,你不知道父類是不是對這個訊息有興趣。雖然 context 這個引數就是幹這個的,也可以解決這個問題 - 在 -addObserver:forKeyPath:options:context: 傳進去一個父類不知道的 context。但總覺得框在這個 API 的設計下,程式碼寫的很彆扭。至少至少,也應該支援 block 吧。

有不少人都覺得官方 KVO 不好使的。Mike Ash 的 Key-Value Observing Done Right,以及獲得不少分享討論的 KVO Considered Harmful 都把 KVO 拿出來吊打了一番。所以在實際開發中 KVO 使用的情景並不多,更多時候還是用 Delegate 或 NotificationCenter。

在ObjC中使用KVO操作常用的方法如下:

  • 註冊指定Key路徑的監聽器: addObserver: forKeyPath: options:  context:
  • 刪除指定Key路徑的監聽器: removeObserver: forKeyPathremoveObserver: forKeyPath: context: 
  • 回撥監聽: observeValueForKeyPath: ofObject: change: context:

關於block 

// 下面這個網址很詳細
http://www.cocoachina.com/ios/20150109/10891.html
block是一段特殊的程式碼塊。使用起來有點像函式,在swift語言中管他叫做閉包 
一般是在A中巨集定義一個block屬性,比如在B中例項化A,並設定block的屬性也就是block被呼叫時執行的程式碼塊。咋洗需要呼叫的地方拿到block名傳入對應需要的引數,即可完成回撥。

補充block屬性的申明要用weak,因為block 是存在於棧區的,如果在MRC環境下不會對其進行拷貝,在ARC環境下就會做拷貝操作。 MRC情況下,不做拷貝,就會成為區域性變數,生命週期與棧繫結,不能跨方法訪問。 另一個需要注意的問題是關於執行緒安全,在宣告Block屬性時需要確認“在呼叫Block時另一個執行緒有沒有可能去修改Block?”這個問題,如果確定不會有這種情況發生的話,那麼Block屬性宣告可以用nonatomic。如果不肯定的話(通常情況是這樣的),那麼你首先需要宣告Block屬性為atomic,也就是先保證變數的原子性(Objective-C並沒有強制規定指標讀寫的原子性,C#有)。
比如這樣一個Block型別:
typedef void (^MyBlockType)(int);

屬性宣告:
@property (copy) MyBlockType myBlock;

這裡ARC和非ARC宣告都是一樣的,當然注意在非ARC下要release Block。

但是,有了atomic來保證基本的原子性還是沒有達到執行緒安全的,接著在呼叫時需要把Block先賦值給本地變數,以防止Block突然改變。因為如果不這樣的話,即便是先判斷了Block屬性不為空,在呼叫之前,一旦另一個執行緒把Block屬性設空了,程式就會crash,如下程式碼:
if (self.myBlock)
{
//此時,走到這裡,self.myBlock可能被另一個執行緒改為空,造成crash
//注意:atomic只會確保myBlock的原子性,這種操作本身還是非執行緒安全的
self.myBlock(123);
}

所以正確的程式碼是(ARC):
MyBlockType block = self.myBlock;
//block現在是本地不可變的
if (block)
{
block(123);
}

在非ARC下則需要手動retain一下,否則如果屬性被置空,本地變數就成了野指標了,如下程式碼:
//非ARC
MyBlockType block = [self.myBlock retain];
if (block)
{
block(123);
}
[block release];