學習了這么久的設計模式方面的知識,最大的感觸就是,設計模式不能脫離語言特性。近段時間所看的兩本書籍,《大話設計模式》里面的代碼是C#寫的,有一些設計模式實現起來也是采用了C#的語言特性(C#的API,抽象類,在OC中是沒有抽象類、沒有多繼承關系),《設計模式之禪》里面的代碼是JAVA寫的,與OC差距也是比較大。
但是我想,這些都不是問題,學習設計模式主要學習的是其中的思想,並將之改造成自己所熟悉語言的模式,大同小異。所需要注意的是,在學習的過程中,將之與語言結合起來,多思考、多實踐。
- KVC
KVC: key values coding 鍵值編碼,間接通過字符串對應的key取出、修改其對應的屬性。
作用: 可以訪問和修改私有成員變量、readOnly成員變量的值。(替換系統自帶的導航欄、替換系統自帶的Tabbar等)
示例代碼:
@interface ZYPerson : NSObject @property (nonatomic, copy, readonly) NSString *name; - (instancetype)initWithName:(NSString *)name; @end #import "ZYPerson.h" @implementation ZYPerson - (instancetype)initWithName:(NSString *)name { if (self = [super init]) { _name = name; } return self; } @end
#import "ViewController.h" #import "ZYPerson.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. ZYPerson *personOne = [[ZYPerson alloc] initWithName:@"張三"]; NSLog(@"%@",personOne.name); // 然后,我發現名字寫錯了,需要修改 // personOne.name = @"王五"; // 如果這么寫,發現編譯器報錯,報錯很正常,我寫的是readOnly // 那么,在不改變原來代碼的結構上,如何修改?在這里,KVO就有用處了 [personOne setValue:@"王五" forKeyPath:@"name"]; NSLog(@"%@",personOne.name); } @end
這僅僅只是一個示例,KVC當然是強大的,UIKit框架里面很多屬性是readOnly、私有的,往往我們在開發中會覺得有一些屬性不好用,想改變吧,要么是readOnly,要是是私有的,難道重新寫一套?但是耗時耗力,項目需要趕進度的話,就得加班。這個時候,KVC的作用就大了,我們可以自定義那些特定需求的控件,然后用KVC將系統自帶的換掉,換成自定義的,簡單快速輕松就可以搞定了。當然,要是系統沒有對應屬性的控件,就只能自定義了。
2. KVO
KVO是用來做屬性監聽的,用完后必須要移除它。
其實現原理:KVO是基於runtime機制實現的,當某個類的對象第一次被觀察時,系統就會在運行期動態的創建一個該類的一個派生類,在這個派生類中重寫基類中任何被觀察屬性的setter方法,派生類在重寫基類的setter方法中實現真正的通知機制。
如此,來看看代碼里面KVO怎么實現監聽一個對象值的改變:
#import <Foundation/Foundation.h> @interface ZYPerson : NSObject @property (nonatomic, copy, readonly) NSString *name; @property (nonatomic, assign) int age; - (instancetype)initWithName:(NSString *)name; @end #import "ZYPerson.h" @implementation ZYPerson - (instancetype)initWithName:(NSString *)name { if (self = [super init]) { _name = name; } return self; } @end
viewController里面的代碼:
#import "ViewController.h" #import "ZYPerson.h" @interface ViewController () @property (nonatomic, strong) ZYPerson *personOne; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. self.personOne = [[ZYPerson alloc] initWithName:@"張三"]; self.personOne.age = 10; // personOne添加一個監聽器,監聽age屬性的變化,options 是屬性怎么樣的變化 [self.personOne addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil]; //當屬性變化了,會調用observeValueForKeyPath方法 self.personOne.age = 20; } - (void)dealloc { //必須要移除監聽器 [self.personOne removeObserver:self forKeyPath:@"age"]; } /** * 當被監聽屬性發生改變的時候,會調用此方法 * * @param keyPath 屬性名 * @param object 屬性所屬的對象 * @param change 屬性的修改情況 * @param context */ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { // 當你在controller中添加多個KVO時,所有的回調都是走這個方法,那就必須對觸發回調函數的來源進行判斷 if (object == self.personOne && [keyPath isEqualToString:@"age"]) { [self doSomethingWhenContextDidChanged]; } else{ /** * 我們假設當前類還有父類,並且父類也有自己綁定了一些其他KVO呢?我們看到,這個回調函數體中只有一個判斷,如果這個if不成立,這次KVO事件的觸發就會到此中斷了。但事實上,若當前類無法捕捉到這個KVO,那很有可能是在他的superClass,或者super-superClass...中 */ [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } } - (void)doSomethingWhenContextDidChanged { NSLog(@"doSomethingWhenContextDidChanged"); } @end
上述,就是一個KVO的完整實現,但事實上,還是有瑕疵的,潛在的問題有可能出現在dealloc中對KVO的注銷上。KVO的一種缺陷(其實不能稱為缺陷,應該稱為特性)是,當對同一個keypath進行兩次removeObserver時會導致程序crash,這種情況常常出現在父類有一個kvo,父類在dealloc中remove了一次,子類又remove了一次的情況下。
3. NSNotification
一個類的屬性發生改變,我們也可以使用NSNotification告訴其他對象,被改變的具體情況。
先上代碼:
#import <Foundation/Foundation.h> extern NSString * const ZYAgeDidChangeNotification; @interface ZYPerson : NSObject @property (nonatomic, copy, readonly) NSString *name; @property (nonatomic, assign) int age; - (instancetype)initWithName:(NSString *)name; @end #import "ZYPerson.h" NSString * const ZYAgeDidChangeNotification = @"ZYAgeDidChangeNotification"; @implementation ZYPerson - (instancetype)initWithName:(NSString *)name { if (self = [super init]) { _name = name; } return self; } //重寫age的setter方法,在這里發送age被更改的notification - (void)setAge:(int)age { _age = age; [[NSNotificationCenter defaultCenter] postNotificationName:ZYAgeDidChangeNotification object:nil userInfo:nil]; } @end
viewController里面的代碼:
#import "ViewController.h" #import "ZYPerson.h" @interface ViewController () @property (nonatomic, strong) ZYPerson *personOne; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. // 接受消息 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doSomethingWhenContextDidChanged) name:ZYAgeDidChangeNotification object:nil]; self.personOne = [[ZYPerson alloc] initWithName:@"張三"]; self.personOne.age = 10; //當屬性變化了,會調用observeValueForKeyPath方法 self.personOne.age = 20; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)doSomethingWhenContextDidChanged { NSLog(@"doSomethingWhenContextDidChanged"); } @end
這樣,也是可以監聽到對象屬性的改變的,甚至,我們在用delegate來監控一些狀態的改變也是可以做到的,這些都可以說是OC中的監聽者模式。
只是說,需要注意,如果是跨控制器之間的監聽、或者傳遞信息,建議用NSNotification更好,如果是view與它的ViewController之間的監聽,用委托(也就是delegate)更好。
4. 觀察者模式
KVO、NSNotification、委托都可以說是OC里面的監聽者模式,NSNotification更重量級一些,除了監聽外,還需負責傳遞信息等。
什么時候使用觀察者模式:
-
- 有兩種抽象類型相互依賴,將他們封裝在各自的對象中,就可以對它們單獨進行改變和復用。
- 對一個對象的改變需要同時改變其他對象,而不知道具體有多少對象有待改變。
- 一個對象必須通知其他對象,而它又不知道其他對象是什么。
MVC是由各種復雜的設計模式組合而成的復合結構,觀察者是其中的設計模式之一。視圖與控制器聯系在一起,等待會影響應用程序表現的事件發生。例如,當用戶單擊視圖上的排序按鈕時,事件會傳遞給控制器,模型在后台排序完畢后,會通知所有相關的控制器,讓它們用新的數據更新視圖。
在MVC中使用觀察者模式,每個組件都能夠被獨立復用與擴展,而對關系中的其他組件沒有太多干擾。所得到的高度可復用性與可擴展性,是把其全部邏輯放入一個類中所無法得到的。因此,向控制器添加額外的視圖時,不用修改已有的設計和代碼。同樣,不同的控制器可以使用同一個模型,而不用對使用它們的其他控制器做修改。