學習了這么久的設計模式方面的知識,最大的感觸就是,設計模式不能脫離語言特性。近段時間所看的兩本書籍,《大話設計模式》里面的代碼是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中使用觀察者模式,每個組件都能夠被獨立復用與擴展,而對關系中的其他組件沒有太多干擾。所得到的高度可復用性與可擴展性,是把其全部邏輯放入一個類中所無法得到的。因此,向控制器添加額外的視圖時,不用修改已有的設計和代碼。同樣,不同的控制器可以使用同一個模型,而不用對使用它們的其他控制器做修改。
