设计模式之观察者模式(关于OC中的KVO\KVC\NSNotification)

一.KVC


  • KVC: key values coding 键值编码,间接通过字符串对应的key取出、修改其对应的属性。
  • 作用: 可以访问和修改私有成员变量、readOnly成员变量的值。(替换系统自带的导航栏、替换系统自带的Tabbar等)
@interface ZBPerson : NSObject
@property (nonatomic, copy, readonly) NSString *name;
 
- (instancetype)initWithName:(NSString *)name;
@end
 
 
#import "ZBPerson.h"
 
@implementation ZBPerson
- (instancetype)initWithName:(NSString *)name
{
    if (self = [super init]) {
        _name = name;
    }
    return self;
}
@end
#import "ViewController.h"
#import "ZBPerson.h"
@interface ViewController ()
 
@end
 
@implementation ViewController
 
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
     
    ZBPerson *personOne = [[ZBPerson alloc] initWithName:@"张三"];
     
    NSLog(@"%@",personOne.name);
     
     
    //  然后,我发现名字写错了,需要修改
//    personOne.name = @"王五";       //  如果这么写,发现编译器报错,报错很正常,我写的是readOnly
     
    //  那么,在不改变原来代码的结构上,如何修改?在这里,KVC就有用处了
[personOne setValue:@"王五" forKey:@"name"];//或[personOne setValue:@"王五" forKeyPath:@"name"];
     
    NSLog(@"%@",personOne.name);
}
 
 
@end

这仅仅只是一个示例,KVC当然是强大的,UIKit框架里面很多属性是readOnly、私有的,往往我们在开发中会觉得有一些属性不好用,想改变吧,要么是readOnly,要是是私有的,难道重新写一套?但是耗时耗力,项目需要赶进度的话,就得加班。这个时候,KVC的作用就大了,我们可以自定义那些特定需求的控件,然后用KVC将系统自带的换掉,换成自定义的,简单快速轻松就可以搞定了。当然,要是系统没有对应属性的控件,就只能自定义了。


二.KVO

  • KVO是用来做属性监听的,用完后必须要移除它。
    其实现原理:KVO是基于runtime机制实现的,当某个类的对象第一次被观察时,系统就会在运行期动态的创建一个该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter方法,派生类在重写基类的setter方法中实现真正的通知机制。

代码实现KVO监听一个对象值的改变:

#import <Foundation/Foundation.h>
 
@interface ZBPerson : NSObject
@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, assign) int age;
 
- (instancetype)initWithName:(NSString *)name;
@end
 
 
#import "ZBPerson.h"
 
@implementation ZBPerson
- (instancetype)initWithName:(NSString *)name
{
    if (self = [super init]) {
        _name = name;
    }
    return self;
}
@end
#import "ViewController.h"
#import "ZBPerson.h"
@interface ViewController ()
@property (nonatomic, strong) ZBPerson *personOne;
@end
 
@implementation ViewController
 
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
     
    self.personOne = [[ZBPerson 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了一次的情况下。


三.NSNotification

  • 一个类的属性发生改变,我们也可以使用NSNotification告诉其他对象,被改变的具体情况。
    代码如下:
#import <Foundation/Foundation.h>
 
extern NSString * const ZBAgeDidChangeNotification;
 
@interface ZBPerson : NSObject
@property (nonatomic, copy, readonly) NSString *name;
@property (nonatomic, assign) int age;
 
- (instancetype)initWithName:(NSString *)name;
@end
 
 
#import "ZBPerson.h"
 
NSString * const ZBAgeDidChangeNotification = @"ZBAgeDidChangeNotification";
 
@implementation ZBPerson
- (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:ZBAgeDidChangeNotification object:nil userInfo:nil];
}
@end

#import "ViewController.h"
#import "ZBPerson.h"
@interface ViewController ()
@property (nonatomic, strong) ZBPerson *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:ZBAgeDidChangeNotification object:nil];
     
    self.personOne = [[ZBPerson alloc] initWithName:@"张三"];
    self.personOne.age = 10;
}
 
- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
 
- (void)doSomethingWhenContextDidChanged
{
    NSLog(@"doSomethingWhenContextDidChanged");
}
@end

这样,也是可以监听到对象属性的改变的,甚至,我们在用delegate来监控一些状态的改变也是可以做到的,这些都可以说是OC中的监听者模式。
只是说,需要注意,如果是跨控制器之间的监听、或者传递信息,建议用NSNotification更好,如果是view与它的ViewController之间的监听,用委托(也就是delegate)更好。


观察者模式

  • KVO、NSNotification、委托都可以说是OC里面的监听者模式,NSNotification更重量级一些,除了监听外,还需负责传递信息等。
  • 什么时候使用观察者模式:
    • 有两种抽象类型相互依赖,将他们封装在各自的对象中,就可以对它们单独进行改变和复用。
    • 对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变。
      一个对象必须通知其他对象,而它又不知道其他对象是什么。
  • MVC是由各种复杂的设计模式组合而成的复合结构,观察者是其中的设计模式之一。视图与控制器联系在一起,等待会影响应用程序表现的事件发生。例如,当用户单击视图上的排序按钮时,事件会传递给控制器,模型在后台排序完毕后,会通知所有相关的控制器,让它们用新的数据更新视图。
  • 在MVC中使用观察者模式,每个组件都能够被独立复用与扩展,而对关系中的其他组件没有太多干扰。所得到的高度可复用性与可扩展性,是把其全部逻辑放入一个类中所无法得到的。因此,向控制器添加额外的视图时,不用修改已有的设计和代码。同样,不同的控制器可以使用同一个模型,而不用对使用它们的其他控制器做修改。

链接 密码

原文链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容