Object-C — KVO & oc通知,object-ckvo
索引值觀察(KVO)是基於索引值編碼的一種技術。
利用索引值觀察可以註冊成為一個對象的觀察者,在該對象的某個屬性變化時收到通知。
被觀察對象需要編寫符合KVC標準的存取方法,編寫索引值觀察分為以下三步:
(1)註冊成為觀察者。
(2)定義KVO的回調。
(3)移除觀察者。
+建立一個類Student,屬性為name,age。
@interface Student : NSObject@property(copy,nonatomic) NSString * name;@property(nonatomic) int age;@end
建立一個Parent類,作為觀察者。main函數:
Student * student = [Student new]; Parent * parent = [Parent new]; //建立觀察者模式關係,註冊觀察者 [student addObserver:parent forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [student addObserver:parent forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
第一個參數:(student)被觀察者。
第二個參數:(parent)觀察者。
第三個參數:(forKeyPath)鍵路徑參數,要觀察的鍵路徑,這裡觀察name和age。
第四個參數:(options)標識KVO希望變化如何傳遞給觀察者,可以使用|進行多選,這裡觀察變化的新值和舊值。
第五個參數:上下文記憶體區,通常為nil
+在parent.m的檔案中定義KVO的回調。
@implementation Parent//觀察者實現索引值觀察方法,要作為觀察者,需要實現這個方法。-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ id oldValue = [change objectForKey:NSKeyValueChangeOldKey]; id newValue = [change objectForKey:NSKeyValueChangeNewKey]; NSLog(@"家長觀察對象:對象:%@的屬性%@的值發生了改變,舊值為:%@,新值為:%@",object,keyPath,oldValue,newValue);}
其中change是一個字典。
+如果student的name或age發生變化(最後要移除觀察者):
Student * student = [Student new]; Parent * parent = [Parent new]; //建立觀察者模式關係,註冊觀察者 [student addObserver:parent forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [student addObserver:parent forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; student.age = 10; student.name = @"小明"; //結束之前需要移除觀察者 [student removeObserver:parent forKeyPath:@"age"]; [student removeObserver:parent forKeyPath:@"name"];
那麼parent上定義的的回調會自動執行,列印結果:
當然也可以多個對象觀察一個對象,建立teacher類,這樣就有兩個觀察者:
Student * student = [Student new]; Parent * parent = [Parent new]; Teacher * teacher = [Teacher new]; //建立觀察者模式關係,註冊觀察者 [student addObserver:parent forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [student addObserver:teacher forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; [student addObserver:parent forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil]; student.age = 10; student.name = @"小明"; //結束之前需要移除觀察者 [student removeObserver:parent forKeyPath:@"age"]; [student removeObserver:parent forKeyPath:@"name"]; [student removeObserver:teacher forKeyPath:@"age"];
****************************************
另外一種情況:建立Family類,讓student成為它的一個屬性,然後讓Family成為student的觀察者,這樣註冊觀察者的方法可以寫在Family的init方法中,移除觀察者寫在Family的dealloc方法中:
Family.h檔案:
@interface Family : NSObject@property(strong,nonatomic) Student * student;-(id) initWithStudent:(Student *)student;@end
Family.m檔案:
@implementation Family-(id)initWithStudent:(Student *)student{ if(self = [super init]){ self->_student = student; //讓當前對象成為student的觀察者 [self->_student addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:nil]; } return self;}//觀察者實現索引值觀察方法,要作為觀察者,需要實現這個方法。-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{ id oldValue = [change objectForKey:NSKeyValueChangeOldKey]; id newValue = [change objectForKey:NSKeyValueChangeNewKey]; NSLog(@"家庭觀察對象:對象:%@的屬性%@的值發生了改變,舊值為:%@,新值為:%@",object,keyPath,oldValue,newValue);}-(void)dealloc{ NSLog(@"Family dealloc..."); //移除觀察者 [self->_student removeObserver:self forKeyPath:@"name"]; //ARC所以不用寫super dealloc}@end
main:
Student * student = [Student new]; Family * family = [[Family alloc]initWithStudent:student]; student.name = @"hehe";
可以看到KVO的方式耦合度較高,類之間有直接的關係。接下來看通知。
自訂通知
通知是iOS開發架構中的一種設計模式,通知一般用於M、V、C之間的資訊傳遞。比如設定頁面、設定App皮膚。
NSNotification
使用通知之前,我們要建立通知對象。 Notification對象有兩個重要的成員變數: name 和 object。一般name用來唯一標示一個通知對象,object指通知寄件者。
Notification對象包含一個參數,就是字典(選擇性參數),這個字典中儲存一些傳值過程中的資訊,供接收者使用。系統要求這個參數是不可變字典。
NSNotificationCenter
通知建立好後就可以在必要的時候發送通知,發送通知的時候,需要一個控制中心來發送通知,這個控制中心就是通知中樞。
通知中樞是通知機制架構的大腦。它允許我們註冊通知監聽者、發送通知、移除通知監聽者。
一般系統通知不需要我們發送通知。只需要我們註冊通知監聽者、 移除通知監聽者。比如監聽視頻是否播放完成。
栗子,Teacher發送通知。學生接收:
頁面結構:
老師有一個發送方法。Teacher.h檔案:
@interface Teacher : NSObject- (void) sendMessage;@end
Teacher.m檔案:
#import "MacroDefinition.h"@implementation Teacher- (void)sendMessage{ NSNotification * myNotification = [NSNotification notificationWithName:kMyNotificationName object:self userInfo:@{@"content":@"同學們,星期一放假,不用來了"}]; //發送通知 //需要使用通知中樞來發送。 //通知中樞是一個單例模式的 NSNotificationCenter * center = [NSNotificationCenter defaultCenter]; [center postNotification:myNotification];}@end
其中NSNotification中的notificationWithName因為別的地方也會用到,所以寫成了宏定義放到了單獨的.h檔案MacroDefinition中,檔案中寫了:#define kMyNotificationName @"customNotification" 。userinfo是一個字典,可以存通知的內容。
先寫上Student的接收通知後的回調方法:
Student.h檔案:
@interface Student : NSObject//可以跟一個參數,但是只能是NSNotification對象。-(void)receiveMessage:(NSNotification *) notification;@end
Student.m檔案:
@implementation Student- (void)receiveMessage:(NSNotification *)notification{ NSLog(@"對象%@發出來名稱為:%@的通知,附加資料為:%@",notification.object,notification.name,[notification.userInfo objectForKey:@"content"]);}
main引入:
#import "MacroDefinition.h"
#import "Teacher.h"
#import "Student.h"
Teacher * teacher = [Teacher new]; Student * student1 = [Student new]; Student * student2 = [Student new]; Student * student3 = [Student new]; //訂閱通知中樞 NSNotificationCenter * center = [NSNotificationCenter defaultCenter]; //註冊,第一個參數是觀察者,第二個參數是觀察者拿到自己關心的通知之後,所要調用的方法,第三個參數為觀察者關心的通知的名稱,第四個參數為觀察者關心的發出對象。 [center addObserver:student1 selector:@selector(receiveMessage:) name:kMyNotificationName object:nil]; [center addObserver:student2 selector:@selector(receiveMessage:) name:kMyNotificationName object:nil]; [center addObserver:student3 selector:@selector(receiveMessage:) name:kMyNotificationName object:nil]; //發送 [teacher sendMessage]; //道理上來說應該移除訂閱者 [center removeObserver:student1 name:kMyNotificationName object:nil]; [center removeObserver:student2 name:kMyNotificationName object:nil]; [center removeObserver:student3 name:kMyNotificationName object:nil];
列印結果:
通知中樞是一個單例模式的,所以kMyNotificationName,main和Teacher.m中的是同一個,所以這兩個檔案用的宏定義是一樣的。
這樣這兩個類並沒有直接的關係只是通過通知中樞來傳遞訊息。關於通知的更多內容後邊再補充吧=。=
KVO和通知的區別:
1:KVO的類是由直接聯絡的,耦合度較高。而通知是沒有直接聯絡的。
2:KVO是只要屬性發生改變,觀察者就會被響應。通知是被觀察者先主動發出通知,然後觀察者註冊監聽後再來進行響應,比KVO多了發送通知的一步,但是其優點是監聽不局限於屬性的變化,還可以對多種多樣的狀態變化進行監聽,監聽範圍廣,使用也更靈活。