Advertisement

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

阅读量:

Notification Publisher

在Swift框架中的Combine组件里,默认会生成一个notification center publisher实例化一个发布者对象,并且该发布者对象能够监听并响应相关的notification

复制代码
    // 创建一个订阅通知的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()
    }
      }
    }

将NoticePublisher订阅于ViewModel中,并且实现了对NoticePublisher的订阅以实现与Notification Publisher的通信功能,在SwiftUI界面向notificationCenter发送消息以触发事件处理逻辑,在处理该事件时随后将在sinks方法中接收此事件并完成相应的处理步骤

在这里插入图片描述

除了这种用法之外,在某些情况下也可以直接在SwiftUI界面通过onReceive的方式进行操作。目前在此界面定义一个通知

复制代码
    // 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

在Swift框架的Combine组件中,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 发布一个新的事件值时

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

复制代码
    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实例。此前一篇关于如何结合使用 .Timer
onReceive
的文章已经进行了详细说明,请参考:SwiftUI中结合使用Timer和onReceive

写在最后

本文着重讲述了Combining库中Notification、URLSession Publisher的应用及其搭配使用情况,并特别强调了与SwiftUI界面结合所带来的显著优势。不仅在功能上,并且在应用范围上Combining库都表现出了显著的优势,并且大幅降低了开发者的编程负担。

末了,希望能帮到有需要的朋友.如果觉得有帮助的话,还望点个赞,加个关注,笔者也会持续努力,写出更多优质的好文章.

全部评论 (0)

还没有任何评论哟~