Swift Combine — Notification、URLSession、Timer等Publisher的理解与使用

news2024/11/25 3:02:24

Notification Publisher

SwiftCombine框架中,可以使用NotificationCenter.Publisher来创建一个能够订阅和接收通知的Publisher

// 创建一个订阅通知的Publisher
let notificationPublisher = NotificationCenter.default.publisher(for: Notification.Name("CustomNotification"))

接下来,我们可以订阅这个Publisher,并处理接收到的通知。

// 订阅通知
let cancellable = notificationPublisher.sink { notification in
    // 处理接收到的通知
    print("Received notification: \(notification)")
}

发送通知

// 发送通知
NotificationCenter.default.post(name: Notification.Name("CustomNotification"), object: nil)

下面代码中就是一个完整的例子:

class NotificationViewModel: ObservableObject {
  private var cancellable = Set<AnyCancellable>()

  func setUpNotification() {
    let notificationPublisher = NotificationCenter.default.publisher(for: Notification.Name("CustomNotification"))
    notificationPublisher
      .sink { notification in
        print("Received notification: \(notification)")
      }
      .store(in: &cancellable)
  }

  func sendNotification() {
    NotificationCenter.default.post(name: Notification.Name("CustomNotification"), object: nil)
  }
}

struct NotificationDemo: View {
  @StateObject private var viewModel = NotificationViewModel()

  var body: some View {
    Button("Send Notification") {
      viewModel.sendNotification()
    }
    .buttonStyle(BorderedProminentButtonStyle())
    .onAppear {
      viewModel.setUpNotification()
    }
  }
}

上面代码中在ViewModel中定义并且订阅了Notification Publisher,在SwiftUI界面触发NotificationCenter发送通知,随后在sink方法中收到了该通知。
在这里插入图片描述
除了这种用法外,有的时候也可以直接在SwiftUI界面通过onReceive方式使用。
现在SwiftUI界面定义一个Notification

// app 进入前台前的通知
let willEnterForegroundPublisher = NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)

然后设置onReceive方法:

.onReceive(willEnterForegroundPublisher, perform: { notification in
  print("Received App will enter foreground notification")
})

这样在App从后台回前台的时候就触发了这个通知,onReceive的闭包中的打印就会输出。

完整代码如下:

struct NotificationDemo1: View {
  // app回前台的通知
  let willEnterForegroundPublisher = NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)

  var body: some View {
    VStack {
      Text("Hello World")
    }
    .onReceive(willEnterForegroundPublisher, perform: { notification in
      print("Received App will enter foreground notification")
    })
  }
}

如果想在这个界面添加多个通知,那是不是要加多个onReceive方法呢?也可以不是的,比如像这样:

.onReceive(Publishers.MergeMany(willEnterForegroundPublisher, didEnterBackgroundPublisher), perform: { notification in
  print("Received App \(notification)")
})

可以通过Publishers.MergeMany方法将多个Publisher合并,然后在一个回调中处理收到通知事件。

struct NotificationDemo1: View {
  // app回前台的通知
  let willEnterForegroundPublisher = NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)

  // app进入后台通知
  let didEnterBackgroundPublisher = NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)

  var body: some View {
    VStack {
      Text("Hello World")
    }
    .onReceive(Publishers.MergeMany(willEnterForegroundPublisher, didEnterBackgroundPublisher), perform: { notification in
      print("Received App \(notification)")
    })
  }
}

在这里插入图片描述

URLSession Publisher

SwiftCombine框架中,URLSession.DataTaskPublisher提供了一种方便的方式来执行网络请求并处理返回的数据。

首先创建一个Publisher

// 创建一个网络请求Publisher
let url = URL(string: "https://......")!
let request = URLRequest(url: url)
let dataTaskPublisher = URLSession.shared.dataTaskPublisher(for: request)

接下来,我们可以订阅这个Publisher,并处理接收到的数据和错误。

// 订阅网络请求
let cancellable = dataTaskPublisher
    .map(\.data) // 提取返回的数据
    .decode(type: MyResponse.self, decoder: JSONDecoder()) // 解码数据为自定义类型
    .receive(on: DispatchQueue.main) // 切换到主线程处理结果
    .sink(receiveCompletion: { completion in
        switch completion {
        case .finished:
            print("Request completed successfully")
        case .failure(let error):
            print("Request failed with error: \(error)")
        }
    }, receiveValue: { response in
        print("Received response: \(response)")
    })

dataTaskPublisher 发送一个新的事件值时,我们将其中的 Data 通过 map 的方式提取出来,并交给 decode 这个 Operator 进行处理。decode 要求上游 PublisherOutput 类型是 Data,它会使用参数中接受的 decoder (本例中是 MyResponse) 来对上游数据进行解析,生成对应类型的实例,并作为新的 Publisher 事件发布出去。然后切换到主线程处理结果,包括刷新UI等等。

把上面的代码优化一下,具体化一下,实现一个真实的网络请求示例:

import SwiftUI
import Combine
import Foundation

struct Photo: Identifiable, Decodable {
  let id: Int
  let albumId: Int
  let title: String
  let url: String
  let thumbnailUrl: String
}

class URLSessionViewModel: ObservableObject {
  private var cancellable = Set<AnyCancellable>()
  @Published var photos: [Photo] = []
  @Published var isFetching: Bool = false

  func fetchPhotoData() {
    guard let url = URL(string: "https://jsonplaceholder.typicode.com/photos") else {
      return
    }
    isFetching = true
    let request = URLRequest(url: url)
    URLSession.shared.dataTaskPublisher(for: request)
      .map(\.data)
      .decode(type: [Photo].self, decoder: JSONDecoder())
      .receive(on: DispatchQueue.main)
      .sink { completion in
        switch completion {
          case .finished:
            print("Request completed successfully")
          case .failure(let error):
            print("Request failed with error: \(error)")
          }
      } receiveValue: { photos in
        print("Received response: \(photos)")
        self.isFetching = false
        self.photos = photos
      }
      .store(in: &cancellable)
  }
}

struct URLSessionDemo: View {
  @StateObject private var viewModel = URLSessionViewModel()

  var body: some View {
    VStack {
      if viewModel.photos.isEmpty {
        if viewModel.isFetching {
          ProgressView()
        } else {
          Button("Fetch photos data") {
            viewModel.fetchPhotoData()
          }
          .buttonStyle(BorderedProminentButtonStyle())
        }
      } else {
        List(viewModel.photos) { photo in
          PhotoView(photo: photo)
        }
        .listStyle(PlainListStyle())
      }
    }
  }
}

struct PhotoView: View {
  let photo: Photo

  var body: some View {
    HStack(spacing: 16) {
      AsyncImage(url: URL(string: photo.thumbnailUrl)) { image in
        image
          .resizable()
          .aspectRatio(contentMode: .fill)
      } placeholder: {
        Rectangle()
          .fill(Color.gray.opacity(0.3))
      }
      .frame(width: 80, height: 80)

      VStack {
        Text(String(photo.id))
          .font(.title)
          .frame(maxWidth: .infinity, alignment: .leading)

        Text(photo.title)
          .font(.headline)
          .frame(maxWidth: .infinity, alignment: .leading)
          .multilineTextAlignment(.leading)
          .lineLimit(2)
      }
    }
  }
}

上面代码中定义了一个Photo类型的数据,代码中采用了URLSession Publisher的方式请求数据,并在SwiftUI上显示,效果如下:
在这里插入图片描述

Timer Publisher

Timer 类型也提供了一个方法,来创建一个按照一定间隔发送事件的 Publisher。之前有一篇文章已经详细介绍过了,详见:SwiftUI中结合使用Timer和onReceive

写在最后

本文主要介绍了CombineNotificationURLSession Publisher的使用,尤其是配合SwiftUI界面的使用。不管是Notification还是URLSession都大大简化了代码,将代码流程集中化,实现了链式处理方式。

最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。

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

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

相关文章

车辆轨迹预测系列 (一):轨迹预测方法综述解析

文章目录 车辆轨迹预测系列 (一)&#xff1a;轨迹预测方法综述解析1、Contextual FactorsPhysics-related factors (物理相关因素):Road-related factors (道路相关因素):Interaction-related factors (交互相关因素): 2、Output TypesUnimodal Trajectory Prediction(单一模式…

计算机网络 交换机的VLAN配置

一、理论知识 1.VLAN的定义 ①VLAN虚拟局域网&#xff0c;是一种通过将局域网内的设备逻辑地而不是物理地划分成一个个网段从而实现虚拟工作组的技术。 ②IEEE于1999年颁布了用以标准化VLAN实现方案的802.1Q协议标准草案。 ③VLAN技术允许网络管理者将一个物理的LAN逻辑地划…

Vue DevTools

介绍 什么是 Vue DevTools&#xff1f; Vue DevTools 是一款旨在增强 Vue 开发者体验的工具&#xff0c;它是一款功能强大且用途广泛的工具&#xff0c;可以在使用 Vue 应用程序时显着提高您的生产力和调试能力。它的实时编辑、时间旅行调试和全面检查功能使其成为任何Vue.js开…

VLAN单臂路由

1、搭建网络 搭建拓扑、规划IP、划分网段 2、交换机配置 配置脚本&#xff08;设置trunk和创建vlan很重要&#xff09; Switch>enable Switch#conf t Enter configuration commands, one per line. End with CNTL/Z.//创建vlan20 Switch(config)#vlan 20 Switch(config…

react学习——08三点运算符

1、代码 let arr1[1,3,5,7,9]let arr2[2,4,6,8,10]console.log(...arr1);//展开一个数组let arr3[...arr1,...arr2]//连续数组//在函数中使用function sum (...numbers){console.log(,numbers)numbers.reduce((previousValue,currentValue)>{return previousValuecurrentVa…

网优小插件_利用Power Automate Desktop抓取物业点信息

日常在无线网络优化&#xff0c;经常需要提取某一地市&#xff0c;某个属性物业点信息&#xff08;物业点名称、地址、及经纬度信息&#xff09;&#xff0c;本文利用Power Automate Desktop&#xff08;PRA&#xff09;和百度地图经纬度拾取网站&#xff0c;通过自动的方式抓取…

[数据集][目标检测]棉花叶子害虫检测数据集VOC+YOLO格式571张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;595 标注数量(xml文件个数)&#xff1a;595 标注数量(txt文件个数)&#xff1a;595 标注类别…

多线程环境下 System.out.println 导致死锁问题分析

背景 一个文件采集系统&#xff0c;使用了多线程递归采集指定目录下的文件&#xff0c;并为每个目录创建一个线程去采集。 这个应用每隔几天就出现罢工情况&#xff0c;查看进程还在&#xff0c;堆内存空间还很充足&#xff0c;就是导出堆栈时&#xff0c;发现几乎所有的采集…

Unity3d自定义TCP消息替代UNet实现网络连接

以前使用UNet实现网络连接,Unity2018以后被弃用了。要将以前的老程序升到高版本,最开始打算使用Mirro,结果发现并不好用。那就只能自己写连接了。 1.TCP消息结构 (1). TCP消息是按流传输的,会发生粘包。那么在发射和接收消息时就需要对消息进行打包和解包。如果接收的消息…

2024 年解锁 Android 手机的 7 种简便方法

您是否忘记了 Android 手机的 Android 锁屏密码&#xff0c;并且您的手机已被锁定&#xff1f;您需要使用锁屏解锁 Android 手机&#xff1f;别担心&#xff0c;您不是唯一一个忘记密码的人。我将向您展示如何解锁 Android 手机的锁屏。 密码 PIN 可保护您的 Android 手机和 G…

【数据结构】第十九弹---C语言实现冒泡排序算法

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】 目录 1、冒泡排序基本思想 2、代码的初步实现 3、代码的优化 4、代码的测试 5、时空复杂度分析 6、模拟实现qsort 6.1、冒泡排序函数 6.2、交换数…

Android SurfaceFlinger——服务启动流程(二)

SurfaceFlinger 是 Android 系统中的一个核心服务&#xff0c;负责管理图形缓冲区的合成和屏幕显示&#xff0c;是 Android 图形系统的关键组件。 一、启动流程 SurfaceFlinger 作为一个系统服务&#xff0c;在 Android 启动早期由 init 进程通过 servicemanager 启动。它是作…

Vue3中的常见组件通信(超详细版)

Vue3中的常见组件通信 概述 ​ 在vue3中常见的组件通信有props、mitt、v-model、 r e f s 、 refs、 refs、parent、provide、inject、pinia、slot等。不同的组件关系用不同的传递方式。常见的撘配形式如下表所示。 组件关系传递方式父传子1. props2. v-model3. $refs4. 默认…

[Django学习]前端+后端两种方式处理图片流数据

方式1&#xff1a;数据库存放图片地址,图片存放在Django项目文件中 1.首先&#xff0c;我们现在models.py文件中定义模型来存放该图片数据,前端传来的数据都会存放在Django项目文件里的images文件夹下 from django.db import modelsclass Image(models.Model):title models.C…

深度神经网络——什么是小样本学习?

引言 小样本学习是指使用极少量的训练数据来开发人工智能模型的各种算法和技术。小样本学习致力于让人工智能模型在接触相对较少的训练实例后识别和分类新数据。小样本训练与训练机器学习模型的传统方法形成鲜明对比&#xff0c;传统方法通常使用大量训练数据。小样本学习是 主…

aws的alb,多个域名绑定多个网站实践

例如首次创建的alb负载均衡只有www.xxx.com 需要添加 负载 test2.xxx.com aws的Route 53产品解析到负载均衡 www.xxx.com 添加CNAME&#xff0c;到负载均衡的dns字段axx test2.xxx.com 添加CNAME&#xff0c;到负载均衡的dns字段axx 主要介绍目标组和规则 创建alb就不介…

MacOS 中 Agent 图标删除

这个是战网没有完全卸载赶紧导致的 在访达中点击前往文件夹&#xff0c;输入&#xff1a; /Users/Shared将对应的目录删掉即可。会提示需要输入密码。

Java 从零开始写一个简单的图书管理系统

了解一下 先来了解要实现一个怎样的图书管理系统 从中可以看到有操作的 使用者 和 不同 的 功能 而不同的使用者有不同的 菜单 那要如何实现呢&#xff1f; 请继续看下去 如何实现 首先了解我们 需要什么 图书系统需要 书&#xff0c;放书的 书架 &#xff0c;用户 中有 管…

Nutch爬虫在大数据采集中的应用案例

引言 在当今信息爆炸的时代&#xff0c;大数据的价值日益凸显。网络作为信息的海洋&#xff0c;蕴藏着丰富的数据资源。Nutch&#xff0c;作为一个开源的Java编写的网络爬虫框架&#xff0c;以其高效的数据采集能力和良好的可扩展性&#xff0c;成为大数据采集的重要工具。本文…

系统烧写工具--MfgTool

系统烧写工具--MfgTool 1 介绍1.1 概述1.2 UUU 特性1.3 UUU 功能1.4 UUU 命令1.5 MFGTools 功能 2 MFGTools 目录结构及说明2.1 MFGTools 目录结构重要文件烧写自己系统 2.2 说明2.3 分析配置文件2.3.1 UiCfg.ini2.3.2 cfg.ini2.3.3 ucl2.xml 3 MfgTool 工作流程4 烧录流程4.1 …