zoukankan      html  css  js  c++  java
  • 设计模式之观察者模式(关于OC中的KVO(Observer)KVCNSNotification)

     

     

    学习了这么久的设计模式方面的知识,最大的感触就是,设计模式不能脱离语言特性。近段时间所看的两本书籍,《大话设计模式》里面的代码是C#写的,有一些设计模式实现起来也是采用了C#的语言特性(C#的API,抽象类,在OC中是没有抽象类、没有多继承关系),《设计模式之禅》里面的代码是JAVA写的,与OC差距也是比较大。

    但是我想,这些都不是问题,学习设计模式主要学习的是其中的思想,并将之改造成自己所熟悉语言的模式,大同小异。所需要注意的是,在学习的过程中,将之与语言结合起来,多思考、多实践。

    1. KVC

    KVC:  key values coding   键值编码,间接通过字符串对应的key取出、修改其对应的属性。

    作用: 可以访问和修改私有成员变量、readOnly成员变量的值。(替换系统自带的导航栏、替换系统自带的Tabbar等)

    示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @interface ZYPerson : NSObject
    @property (nonatomiccopyreadonlyNSString *name;
     
    - (instancetype)initWithName:(NSString *)name;
    @end
     
     
    #import "ZYPerson.h"
     
    @implementation ZYPerson
    - (instancetype)initWithName:(NSString *)name
    {
        if (self = [super init]) {
            _name = name;
        }
        return self;
    }
    @end
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #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怎么实现监听一个对象值的改变:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #import <Foundation/Foundation.h>
     
    @interface ZYPerson : NSObject
    @property (nonatomiccopyreadonlyNSString *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里面的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    #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告诉其他对象,被改变的具体情况。

    先上代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    #import <Foundation/Foundation.h>
     
    extern NSString const ZYAgeDidChangeNotification;
     
    @interface ZYPerson : NSObject
    @property (nonatomiccopyreadonlyNSString *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里面的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    #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中使用观察者模式,每个组件都能够被独立复用与扩展,而对关系中的其他组件没有太多干扰。所得到的高度可复用性与可扩展性,是把其全部逻辑放入一个类中所无法得到的。因此,向控制器添加额外的视图时,不用修改已有的设计和代码。同样,不同的控制器可以使用同一个模型,而不用对使用它们的其他控制器做修改。

  • 相关阅读:
    PHP 上传文件限制
    vim设置golang语法高亮 (Centos)
    linux下安装配置go语言环境
    Vim升华之树形目录插件NERDTree安装图解(ubuntu)
    linux下配置Node.js环境
    RabbitMQ的使用(二)- RabbitMQ服务在单机中做集群
    RabbitMQ的使用(一)- RabbitMQ服务安装
    Exceptionless(二)
    Exceptionless
    SQL Server2012如何打开2016的profiler文件
  • 原文地址:https://www.cnblogs.com/sjcries/p/5398220.html
Copyright © 2011-2022 走看看