「观察者(Observer)」设计模式 Swift实现

这里写目录标题

  • 介绍
    • 设计模式介绍
    • 举例
  • iOS 中已有的 观察者设计模式实现
    • Notification
      • 什么是通知机制或者说如何实现通知机制?
    • KVO
      • KVO底层实现
      • 如何实现手动KVO?

介绍

设计模式介绍

观察者设计模式(Observer Pattern)是一种行为型设计模式,它定义了对象之间的一种一对多的依赖关系,使得当一个对象的状态发生变化时,所有依赖于它的对象都会受到通知并自动更新。

在这种模式中,被观察者对象(Subject)存储其观察者对象(Observer)列表,并提供了用于添加和删除观察者对象的方法。而观察者对象则实现了一个更新方法来响应被观察者状态的变化。

举例

下面我们用一个简单的 Swift 代码例子来阐述观察者设计模式的应用。

假设我们有一个带有多个温度传感器的系统,并且我们需要监控这些传感器的温度变化。我们可以使用观察者模式来实现:

首先,我们定义一个 Subject 协议来规范被观察者对象的行为:

protocol Subject {
    func addObserver(observer: Observer)
    func removeObserver(observer: Observer)
    func notifyObservers()
}

在这里,我们定义了 addObserver()、removeObserver() 和 notifyObservers() 方法,分别用于添加、删除观察者和通知观察者。

然后,我们定义一个 Observer 协议来规范观察者对象的行为:

protocol Observer {
    func update(temperature: Double)
}

在 Observer 中,我们定义了一个 update() 方法,用于响应被观察者状态的变化。

最后,我们定义一个 WeatherStation 对象,实现 Subject 协议。在 WeatherStation 中,我们维护了一个 observers 数组,用于存储所有观察者对象,并在温度变化时调用 notifyObservers() 方法通知所有观察者:

class WeatherStation: Subject {
    var temperature: Double = 0
    var observers: [Observer] = []
    
    func addObserver(observer: Observer) {
        observers.append(observer)
    }
    
    func removeObserver(observer: Observer) {
        if let index = observers.firstIndex(where: { $0 === observer }) {
            observers.remove(at: index)
        }
    }
    
    func notifyObservers() {
        for observer in observers {
            observer.update(temperature: temperature)
        }
    }
    
    func setTemperature(temperature: Double) {
        self.temperature = temperature
        notifyObservers()
    }
}

在 WeatherStation 中,我们使用 addObserver() 方法将观察者对象添加到 observers 数组中,使用 removeObserver() 方法将观察者对象从 observers 数组中删除。而 setTemperature() 方法用于设置温度值,并在温度变化时通知所有观察者。

最后,我们定义一个 Display 作为观察者对象,实现 Observer 协议。在 Display 中,我们实现了 update() 方法,在被观察者状态改变时更新显示:

class Display: Observer {
    func update(temperature: Double) {
        print("当前温度为:\(temperature)")
    }
}

在客户端代码中,我们可以实例化一个 WeatherStation 对象,并添加多个 Display 观察者对象,然后设置温度值,从而触发所有观察者对象的更新:

let weatherStation = WeatherStation()
let display1 = Display()
let display2 = Display()

weatherStation.addObserver(observer: display1)
weatherStation.addObserver(observer: display2)

weatherStation.setTemperature(temperature: 25)

输出结果:

当前温度为:25.0
当前温度为:25.0

在这个例子中,我们使用观察者模式实现了多个观察者对同一个被观察者状态的响应,并使得被观察者状态的变化自动触发所有观察者的更新。

iOS 中已有的 观察者设计模式实现

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生变化时,会通知所有观察者对象,使它们能够自动更新自己。 简而言之,就是A和B,A对B的变化感兴趣,就注册为B的观察者,当B发生变化时通知A,告知B发生了变化。这个也叫做经典观察者模式。

Notification

对于感兴趣的A来说,在这里定义通知,也就是注册观察者(A就是观察者,怎么观察的以及观察到了会做些什么)

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(notice:) name:@"tongzhi" object:nil];

-(void)notice:(id)sender{  
  NSLog(@"%@",sender);
}

对于变化源B来说,在B这里发出通知

//创建通知对象
NSNotification *notification = [NSNotification notificationWithName:@"tongzhi" object:nil];
 //Name是通知的名称 object是通知的发布者(是谁要发布通知,也就是对象) userInfo是一些额外的信息(通知发布者传递给通知接收者的信息内容,字典格式)
//    [NSNotification notificationWithName:@"tongzhi" object:nil userInfo:nil];
//发送通知
 [[NSNotificationCenter defaultCenter] postNotification:notification];

当然了,还要移除观察者,在dealloc里面

- (void)dealloc {
  //删除根据name和对象,如果object对象设置为nil,则删除所有叫name的,否则便删除对应的
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"tongzhi" object:nil];
}

什么是通知机制或者说如何实现通知机制?

(假如让你实现,你会怎么做)
在这里插入图片描述

KVO

KVO全称叫Key Value Observing,顾名思义就是一种观察者模式用于监听属性的变化,KVO和NSNotification有很多相似的地方,用addObserver:forKeyPath:options:context方法 去观察,用removeObserver:forKeyPath:context去移除观察者,用observeValueForKeyPath:ofObject:change:context:去响应观察者

KVO监听属性的变化非常方便,下面举个例子

//自定义MyTimer类,在.h文件中定义一个属性name
@property (nonatomic, strong) NSString *name;
//自定义ViewController,在controller的.h文件中也定义一个属性myView
@property (nonatomic, strong) UIView *myView;

//在Viewcontroller的.m文件中定义一个button,设置点击事件,在该事件中分别调用上面定义的两个属性

int i = 5;
int sum = 15;
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
        btn.frame = CGRectMake(100, 100, 100, 30);
        [btn setTitle:@"点击" forState:UIControlStateNormal];
        [btn addTarget:self action:@selector(handleTimer:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:btn];
    
    
    _label = [[UILabel alloc ] initWithFrame:CGRectMake(100, 200, 180, 30)];
    _label.text = @"当前年龄15岁";
    [self.view addSubview:_label];
    
    //创建Mytimer对象
    _ourTimer = [[MyTimer alloc ] init];
    //观察属性name
    [_ourTimer addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew || NSKeyValueChangeOldKey context:nil];
    //观察属性myView
    [self addObserver:self forKeyPath:@"myView" options:NSKeyValueObservingOptionNew || NSKeyValueChangeOldKey context:nil];
    
}
//点击事件,分别调用属性
- (void)handleTimer:(UIButton *)btn {
    _ourTimer.name = @"小明";
    self.myView = nil;
    NSLog(@"第一次设置名字");
}
//一旦属性被操作了,这里会自动响应(上面设置观察的属性才会在这响应)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"name"]) {
        NSLog(@"名字发生了改变");
        _label.text = [NSString stringWithFormat:@"当前年龄%d岁", i + sum];
        sum = i + sum;
    } else if ([keyPath isEqualToString:@"myView"]) {
        NSLog(@"我的视图");
    }
}
//移除
- (void)dealloc {
    [_ourTimer removeObserver:self forKeyPath:@"name"];
    [self removeObserver:self forKeyPath:@"myView"];
}

KVO底层实现

1、我们调用[self addObserver:self forKeyPath:@“highlighted” options:0 context:nil];
2、系统会动态自定义NSKVONotifying_A子类
3、重写setName,在内部恢复父类做法,通知观察者
4、如何让外界调用自定义A类的子类方法,修改当前对象的isa指针,指向NSKVONotifying_A
个人理解:isa指针改变,那么实例对象的isa指针指向类对象,类对象里面有实例对象的方法,所以isa指针改变,那么就回去调用新类的类对象方法,所以就能够实现调用子类的那个set方法。

重写set方法内部实现:
在这里插入图片描述
didChangeValueForKey方法内部会做出通知监听器,某个属性发生了变化。

如何实现手动KVO?

某个类实例对象直接调用这两个方法即可:
手动KVO就是调用了[self.person willChangeValueForKey:@””];
之后又调用了[self.person didChangeValueForKey:@””];
然后didChangeValueForKey内部实现就会触发KVO的回调。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/37785.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【ArcGIS Pro二次开发】(49):村规数据入库【福建省】

之前用Arcpy脚本工具做了一个村规数据入库和主要图纸生成工具。 在使用过程中&#xff0c;感觉对电脑环境比较高&#xff0c;换电脑用经常会一些莫名其妙的错误&#xff0c;bug修得很累。近来随着ArcGIS Pro SDK的熟悉&#xff0c;就有了移植的想法。 这里先把村规数据入库工…

TabLayout+ViewPager实现滚动页面

目录 一、TabLayout介绍 二、TabLayout的常用属性和方法 常用属性&#xff1a; 常用方法&#xff1a; 三、适配器介绍 &#xff08;一&#xff09;、PagerAdapter介绍&#xff1a; &#xff08;二&#xff09;、FragmentPagerAdapter介绍&#xff1a; &#xff08;三&am…

Python结巴中文分词笔记

&#x1f4da; jieba库基本介绍 &#x1f310; jieba库概述 Jieba是一个流行的中文分词库&#xff0c;它能够将中文文本切分成词语&#xff0c;并对每个词语进行词性标注。中文分词是自然语言处理的重要步骤之一&#xff0c;它对于文本挖掘、信息检索、情感分析等任务具有重要…

Android.mk 文件使用解析

和你一起终身学习&#xff0c;这里是程序员Android 经典好文推荐&#xff0c;通过阅读本文&#xff0c;您将收获以下知识点: 一、Android.mk 简介二、Android.mk 的基本格式三、Android.mk 深入学习一四、 Android.mk 深入学习二五、 Android.mk 深入学习三六、 Android.mk 判断…

引入头文件#include <iostream>的时候发生了什么?

<iostream> namespace std {extern istream cin;extern ostream cout;extern ostream cerr;extern ostream clog;extern wistream wcin;extern wostream wcout;extern wostream wcerr;extern wostream wclog;};cin是什么&#xff1f; cin extern istream cin; The objec…

关于Windows 11 docker desktop 运行doris 容器时vm.max_map_count=2000000的设置问题

需要一个简单的测试环境&#xff0c;于是准备用docker启动一个1fe 1be的简单玩一下 如果be容器启动后再去修改 /etc/sysctl.conf sysctl -w vm.max_map_count2000000 这个参数是没用的&#xff0c;be仍然会启动失败 这时可以打开cmd wsl --list C:\Users\pc>wsl --list …

TeeChart for.NET Crack

TeeChart for.NET Crack TeeChart for.NET为各种图表需求提供了图表控件&#xff0c;包括金融、科学和统计等重要的垂直领域。它可以处理您的数据&#xff0c;在各种平台上无缝创建信息丰富、引人入胜的图表&#xff0c;包括Windows窗体、WPF、带有HTML5/Javascript渲染的ASP.N…

用户、角色、权限、菜单--数据库设计

用户角色关联表--user_role id-------------------主键 user_id------------用户ID role_id-------------角色ID create_time------创建时间 is_deleted--------状态&#xff08;0&#xff1a;未删除 1&#xff1a;删除&#xff09; 角色权限关联表--role_permission id------…

JVM回收算法(标记-清除算法, 复制算法, 标记-整理算法)

1.标记-清除算法 最基础的算法&#xff0c;分为两个阶段&#xff0c;“标记”和“清除” 原理&#xff1a; - 标记阶段&#xff1a;collector从mutator根对象开始进行遍历&#xff0c;对从mutator根对象可以访问到的对象都打上一个标识&#xff0c;一般是在对象的header中&am…

LiveGBS流媒体平台GB/T28181功能-作为上级平台对接海康大华华为宇视等下级平台监控摄像机NVR硬件执法仪等GB28181设备

LiveGBS作为上级平台对接海康大华华为宇视等下级平台监控摄像机NVR硬件执法仪等GB28181设备 1、背景说明2、部署国标平台2.1、安装使用说明2.2、服务器网络环境2.3、信令服务配置 3、监控摄像头设备接入3.1、海康GB28181接入示例3.2、大华GB28181接入示例3.3、华为IPC GB28181接…

Mybatis架构简介

文章目录 1.整体架构图2. 基础支撑层2.1 类型转换模块2.2 日志模块2.3 反射工具模块2.4 Binding 模块2.5 数据源模块2.6缓存模块2.7 解析器模块2.8 事务管理模块3. 核心处理层3.1 配置解析3.2 SQL 解析与 scripting 模块3.3 SQL 执行3.4 插件4. 接口层1.整体架构图 MyBatis 分…

程序员的自我修养(2)

目标文件的学习 1.什么是目标文件以及格式 目标文件为编译器编译后生成的文件&#xff0c;就是window下的.obj&#xff0c;linux下的.o文件。与可执行文件格式几乎一样&#xff0c;因为只是缺少链接过程。所以可执行文件&#xff0c;动态链接库&#xff0c;静态链接库&#xf…

【从零到Offer】反射那些事

什么是反射&#xff1f; ​ 反射简单来说&#xff0c;就是在代码运行期间&#xff0c;通过动态指定任意一个类&#xff0c;从而构建对象&#xff0c;并了解该类的成员变量和方法&#xff0c;甚至可以调用任意一个对象的属性和方法。以String对象为例子&#xff0c;传统构造方式…

计算机网络 - http协议 与 https协议(2)

前言 本篇介绍了构造http请求的的五种方式&#xff0c;简单的使用postman构造http请求&#xff0c;进一步了解https, 学习https的加密过程&#xff0c;了解对称密钥与非对称密钥对于加密是如何进行的&#xff0c;如有错误&#xff0c;请在评论区指正&#xff0c;让我们一起交流…

SPEC CPU 2006 在 CentOS 5.0 x86_64 古老系统测试【3】静态编译 invalid run

上篇 SPEC CPU 2006 在 CentOS 5.0 x86_64 古老系统测试【2】_hkNaruto的博客-CSDN博客 修改gcc41.cfg&#xff0c;全部添加上-static 测试指令 runspec -c gcc41.cfg -T all -n 3 -r 1 -I -i ref all 结果&#xff1a;正常运行并生成报告 invalid run Invalid SPEC CFP2006…

自学网络安全(成为黑客)

一、前言 黑客这个名字一直是伴随着互联网发展而来&#xff0c;给大家的第一印象就是很酷&#xff0c;而且技术精湛&#xff0c;在网络世界里无所不能。目前几乎所有的公司企业甚至国家相关部门都会争相高薪聘请技术精湛的黑客作为互联网机构的安全卫士&#xff0c;所以黑客也…

rapid_latex_ocr: 更快更好用的公式图像转latex工具

Rapid Latex OCR rapid_latex_ocr是一个将公式图像转为latex格式的工具。仓库中的推理代码来自修改自LaTeX-OCR&#xff0c;模型已经全部转为ONNX格式&#xff0c;并对推理代码做了精简&#xff0c;推理速度更快&#xff0c;更容易部署。仓库只有基于ONNXRuntime或者OpenVINO推…

【Linux】基础开发工具——make

文章目录 前言&#xff1a;一、认识make和makefile二、依赖关系和依赖方法三、make工作原理 前言&#xff1a; 上一期分享了在Linux下编译源代码的两个工具&#xff0c;gcc和g。每次编译源代码&#xff0c;都要输入一串很长的指令&#xff0c;这个过程显然是十分复杂&#xff…

【Go|第8期】Lorca读取HTML的三种方式

日期&#xff1a;2023年7月16日 作者&#xff1a;Commas 签名&#xff1a;(ง •_•)ง 积跬步以致千里,积小流以成江海…… 注释&#xff1a;如果您觉得有所帮助&#xff0c;帮忙点个赞&#xff0c;也可以关注我&#xff0c;我们一起成长&#xff1b;如果有不对的地方&#xf…

天天刷题-->LeetCode(最长回文子串)

个人名片&#xff1a; &#x1f405;作者简介&#xff1a;一名大二在校生&#xff0c;热爱生活&#xff0c;爱好敲码&#xff01; \ &#x1f485;个人主页 &#x1f947;&#xff1a;holy-wangle ➡系列内容&#xff1a; &#x1f5bc;️ tkinter前端窗口界面创建与优化 &…