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

news2024/9/24 1:20:53

这里写目录标题

  • 介绍
    • 设计模式介绍
    • 举例
  • 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的回调。
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

win10电脑出现网络问题时,如何解决?

我们的Windows可能会出现各种网络连接问题&#xff1a; 尝试连接Wi-Fi网络时出现错误&#xff1a;Windows无法连接到此网络&#xff1b;您可以通过Wifi访问互联网&#xff0c;但通过电缆访问以太网却无法正常工作&#xff1b;尝试通过电缆连接互联网时出现错误&#xff1a; Wi…

图数据库:neo4j学习笔记

参考资料&#xff1a;neo4j 教程_w3cschool Springboot集成Neo4j_喝醉的咕咕鸟的博客-CSDN博客 SpringBoot 整合 Neo4j_springboot neo4j_$懒小猿$的博客-CSDN博客 图数据库Neo4j实战&#xff08;全网最详细教程&#xff09;_neo4j使用教程_星川皆无恙的博客-CSDN博客 代码片段…

Flink DataStream之输出数据到File中

新建类 package test01;import org.apache.flink.api.common.serialization.SimpleStringEncoder; import org.apache.flink.configuration.Configuration; import org.apache.flink.configuration.MemorySize; import org.apache.flink.connector.file.sink.FileSink; import…

Model, ViewModel, EnvironmentObject 的使用

1. Model 数据模型的定义与使用 1.1 案例 struct UserModel: Identifiable{let id: String UUID().uuidStringlet dispalyName: Stringlet userName: Stringlet followerCount: Intlet isVerified: Bool }/// 数据模型 struct ModelBootcamp: View {State var users:[Use…

web 禁用 OPTIONS方法启用【原理扫描】

Web服务器上启用了HTTP OPTIONS方法。 OPTIONS方法提供了Web服务器支持的方法列表&#xff0c;它表示对有关由Request-URI标识的请求/响应链上可用的通信选项的信息的请求。 直接在IIS上进行关闭即可&#xff1a;

osg osgDB::readImageFile 返回空指针 解决中

在 osg功能开发中,需要用到 纹理 加载图片&#xff0c;最神奇的之前 好好的。 现在 把osg 编译成了 osg 342vs2013x86 环境 就出现幺蛾子了&#xff0c;之前是使用的 osg364vs2013x86。结果 命令行运行 加载图片 直接 有 warning 提示。还在处理中&#xff01; 提示 找不到文…

Nginx 的Nacos配置

进入nginx 配置目录 cd /usr/local/nginx/conf 2. 编辑nginx配置文件 vi nginx.conf 3. 增加对Nacos 的代理 upstream nacosServerList {server 192.168.172.102:8848;server 192.168.172.103:8848;server 192.168.172.104:8848; } # Nacos地址服务器寻址配置 server {#监听端…

微信小程序第六节——个体账号如何实现用户自定义内容

&#x1f4cc; 微信小程序第一节 ——自定义顶部、底部导航栏及获取胶囊位置信息。 &#x1f4cc; 微信小程序第二节 —— 微信小程序第二节 —— 自定义组件。 &#x1f4cc; 微信小程序第三节 —— 页面跳转的那些事儿。 &#x1f4cc; 微信小程序第四节 —— 网络请求那些事…

matlab GUI入门

matlab GUI入门 两种方法 法一&#xff1a;使用guide 法二&#xff1a;使用appdesigner&#xff08;推荐&#xff0c;更直观&#xff09; winopen(cd) 打开当前路径。 ctrlI 代码自动对齐 matlab 导入数据文件 导入图片数据 用imread&#xff08;&#xff09;函数导入…

第 3 章 Spark 通讯架构

3.1 Spark 通信架构概述 3.1 Spark 通信架构概述 Spark 中通信框架的发展&#xff1a; ➢ Spark 早期版本中采用 Akka 作为内部通信部件。 ➢ Spark1.3 中引入 Netty 通信框架&#xff0c;为了解决 Shuffle 的大数据传输问题使用 ➢ Spark1.6 中 Akka 和 Netty 可以配置使用。…

bug的合规描述

bug的合格描述&#xff1a; 发现问题的版本bug的合格描述&#xff1a; 开发人员需要知道出现问题的版本&#xff0c;才能够获取对应版本的代码来重现故障问题出现的环境 环境分为硬件环境和软件环境&#xff0c;详细的环境描述有利于故障的重现(如果是web项目&#xff0c;需…

到手价的监测要求和方法

品牌在做电商价格监测时&#xff0c;为什么要对到手价进行监测&#xff0c;这其中的原因还是很显现的&#xff0c;各平台的促销信息众多&#xff0c;如果只监测页面价的低价行为&#xff0c;那将有非常多的低价链接不会被发现&#xff0c;而这也会导致品牌做渠道管控时失去公平…

如何随机切换代理IP以避免被封禁?

在网络爬虫和数据抓取的领域&#xff0c;使用代理IP技术是非常常见的做法。使用代理IP可以有效地绕过网站的访问限制&#xff0c;提高访问速度和稳定性。然而&#xff0c;如果我们在访问网站时只使用一个代理IP&#xff0c;那么可能会被网站封禁&#xff0c;从而导致访问失败。…

开源预训练框架 MMPRETRAIN官方文档(高级指南)

1、准备数据集 1、自定义数据集&#xff08;下面都是分类数据的自定义数据集准备&#xff09; CustomDataset是一个通用数据集类&#xff0c;供您使用自己的数据集。要使用CustomDataset&#xff0c;您需要根据以下两种格式组织数据集文件&#xff1a; 1、子文件夹格式 在这…

正则表达式概念以及语法的使用

目录 1.概念 2. 为什么使用正则表达式&#xff1f; 3. 语法 1.普通字符 非打印字符 2. 特殊字符 3. 限定符 4. 定位符 5. 运算优先级 3.匹配规则 1. 基本模式匹配 2. 字符簇 3. 确定重复出现 1.概念 正则表达式(Regular Expression)是一种文本模式&#xff0c;包…

MAYA挖掘机绑定

打组 少选一个 放中心点 把它放组里 放中心点 创建骨骼 放骨骼 旋转不会带动上面骨骼 中心点的位置 骨骼和组做约束 活塞运行 放中心点 相互目标 管子短&#xff0c;需要加长 又短了 设置中心点 创建IK 制作控制器 让控制器带动模型动 手柄 IK 少一个控制器 删除 不用的…

途乐证券杠杆开户-A股首份半年报出炉 康缘药业净利同比增30.6%

中药职业迎成绩兑现期&#xff0c;多家公司上半年盈利估计倍增 7月12日晚&#xff0c;沪深两市首份半年报出炉。康缘药业半年报显示&#xff0c;公司上半年完成营收25.53亿元&#xff0c;同比添加21.74%&#xff1b;完成归母净利润2.76亿元&#xff0c;同比添加30.6%。 康缘药…

Endnote更新所有信息不全的参考文献(中英文文献信息不全)

方法一&#xff1a;手动 找到reference一个一个输入&#xff08;这里针对某些没有doi的文献&#xff0c;有doi的也可以&#xff0c;只要你愿意&#xff09; 方法二&#xff1a;自动 中文文献------选择txt格式导入endnote&#xff0c;在endnote里选择endnote import&#xf…

得物自建 DTS 平台的技术演进 | 精选

0 前言 DTS是数据传输平台(Data Transfer Platform的缩写) 随着得物App的用户流量增长&#xff0c;业务选择的数据库越来越多样化&#xff0c;异构数据源之间的数据同步需求也逐渐增多。为了控制成本并更好地支持业务发展&#xff0c;我们决定自建DTS平台。本文主要从技术选型、…

Qt6之通用文件格式.dat

dat文件&#xff0c;在Windows中大量存在&#xff0c;到处都有。如下图edge浏览器安装目录下就有一个dat文件&#xff0c;如果你强行打开&#xff0c;发现它是乱码的。 一、什么是dat文件 DAT 文件格式只是一种通用格式&#xff0c;它在文件中包含任何类型的数据&#xff0c;它…