設計模式之觀察者模式(關於OC中的KVO\KVC\NSNotification)


學習了這么久的設計模式方面的知識,最大的感觸就是,設計模式不能脫離語言特性。近段時間所看的兩本書籍,《大話設計模式》里面的代碼是C#寫的,有一些設計模式實現起來也是采用了C#的語言特性(C#的API,抽象類,在OC中是沒有抽象類、沒有多繼承關系),《設計模式之禪》里面的代碼是JAVA寫的,與OC差距也是比較大。

但是我想,這些都不是問題,學習設計模式主要學習的是其中的思想,並將之改造成自己所熟悉語言的模式,大同小異。所需要注意的是,在學習的過程中,將之與語言結合起來,多思考、多實踐。

  1. 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更重量級一些,除了監聽外,還需負責傳遞信息等。

什么時候使用觀察者模式:

    1. 有兩種抽象類型相互依賴,將他們封裝在各自的對象中,就可以對它們單獨進行改變和復用。
    2. 對一個對象的改變需要同時改變其他對象,而不知道具體有多少對象有待改變。
    3. 一個對象必須通知其他對象,而它又不知道其他對象是什么。

MVC是由各種復雜的設計模式組合而成的復合結構,觀察者是其中的設計模式之一。視圖與控制器聯系在一起,等待會影響應用程序表現的事件發生。例如,當用戶單擊視圖上的排序按鈕時,事件會傳遞給控制器,模型在后台排序完畢后,會通知所有相關的控制器,讓它們用新的數據更新視圖。

在MVC中使用觀察者模式,每個組件都能夠被獨立復用與擴展,而對關系中的其他組件沒有太多干擾。所得到的高度可復用性與可擴展性,是把其全部邏輯放入一個類中所無法得到的。因此,向控制器添加額外的視圖時,不用修改已有的設計和代碼。同樣,不同的控制器可以使用同一個模型,而不用對使用它們的其他控制器做修改。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM