SundialKit

SundialKit

跨 Apple 平台的响应式通信库。

SwiftPM Twitter GitHub GitHub issues GitHub Workflow Status

Codecov CodeFactor Grade codebeat badge Code Climate maintainability Code Climate technical debt Code Climate issues Reviewed by Hound

Communication between iPhone and Apple Watch using Demo App

目录

简介

为了更方便地在响应式用户界面中使用,尤其是在 SwiftUICombine 中,我创建了一个库,它抽象并映射了常见的连接 API。特别是在我的 Heartwitch 应用中,我映射了 WatchConnectivityNetwork 的功能,以跟踪用户连接到互联网的能力,以及他们的 iPhone 通过 WatchConnectivity 连接到他们的 Apple Watch 的能力。

特性

以下是此库目前已实现的功能

安装

Swift Package Manager 是 Apple 的去中心化依赖管理器,用于将库集成到你的 Swift 项目中。现在它已与 Xcode 13 完全集成。

要使用 SPM 将 SundialKit 集成到你的项目中,请在你的 Package.swift 文件中指定它

let package = Package(
  ...
  dependencies: [
    .package(url: "https://github.com/brightdigit/SundialKit.git", from: "0.2.0")
  ],
  targets: [
      .target(
          name: "YourTarget",
          dependencies: ["SundialKit", ...]),
      ...
  ]
)

用法

监听网络变化

过去,ReachabilityAFNetworking 被用于判断设备的网络连接。SundialKit 使用 Network 框架来监听连接的变化,提供所有可用的信息。

SundialKit 提供了一个 NetworkObserver,允许你监听与网络相关的各种发布者 (publisher)。如果你特别使用 SwiftUI,这将特别有用。使用 SwiftUI,你可以创建一个 ObservableObject,其中包含一个 NetworkObserver

import SwiftUI
import SundialKit

class NetworkConnectivityObject : ObservableObject {
  // our NetworkObserver
  let connectivityObserver = NetworkObserver()
  
  // our published property for pathStatus initially set to `.unknown`
  @Published var pathStatus : PathStatus = .unknown

  init () {
    // set the pathStatus changes to our published property
    connectivityObserver
      .pathStatusPublisher
      .receive(on: DispatchQueue.main)
      .assign(to: &self.$pathStatus)
  }
  
  // need to start listening
  func start () {
    self.connectivityObserver.start(queue: .global())
  }
}

有 3 个重要的部分

  1. 名为 connectivityObserverNetworkObserver
  2. init 中,我们使用 Combine 监听发布者 (publisher) 并将每个新的 pathStatus 存储到我们的 @Published 属性。
  3. 一个需要调用的 start 方法,用于开始监听 NetworkObserver

因此,对于我们的 SwiftUI View,我们需要在 onAppearstart 监听,并可以在 View 中使用 pathStatus 属性

struct NetworkObserverView: View {
  @StateObject var connectivityObject = NetworkConnectivityObject()
    var body: some View {
      // Use the `message` property to display text of the `pathStatus`
      Text(self.connectivityObject.pathStatus.message).onAppear{
        // start the NetworkObserver
        self.connectivityObject.start()
      }
    }
}

除了 pathStatus 之外,你还可以访问

使用 NetworkPing 验证连接

除了使用 NWPathMonitor,你还可以通过实现 NetworkPing 来设置定期 ping。这是一个调用 ipify API 以验证是否存在 IP 地址的示例

struct IpifyPing : NetworkPing {
  typealias StatusType = String?

  let session: URLSession
  let timeInterval: TimeInterval

  public func shouldPing(onStatus status: PathStatus) -> Bool {
    switch status {
    case .unknown, .unsatisfied:
      return false
    case .requiresConnection, .satisfied:
      return true
    }
  }

  static let url : URL = .init(string: "https://api.ipify.org")!

  func onPing(_ closure: @escaping (String?) -> Void) {
    session.dataTask(with: IpifyPing.url) { data, _, _ in
      closure(data.flatMap{String(data: $0, encoding: .utf8)})
    }.resume()
  }
}

接下来,在我们的 ObservableObject 中,我们可以创建一个 NetworkObserver 来使用它

  @Published var nwObject = NetworkObserver(ping:
    // use the shared `URLSession` and check every 10.0 seconds
    IpifyPing(session: .shared, timeInterval: 10.0)
   )

iPhone 和 Apple Watch 之间的通信

除了网络连接,SundialKit 还提供了一个更简单的响应式接口到 WatchConnectivity。这包括

  1. 各种连接状态,如 isReachableisInstalled 等。
  2. 在 iPhone 和配对的 Apple Watch 之间发送消息
  3. 轻松地将设备之间的消息编码和解码为 WatchConnectivity 友好的字典。

Showing changes to isReachable using SundialKit

我们先来谈谈 WatchConnectivity 状态是如何工作的。

连接状态

使用 WatchConnectivity,有各种属性可以告诉你设备之间的连接状态。这是一个类似于使用 isReachablepathStatus 的示例

import SwiftUI
import SundialKit

class WatchConnectivityObject : ObservableObject {
  // our ConnectivityObserver
  let connectivityObserver = ConnectivityObserver()
  // our published property for isReachable initially set to false
  @Published var isReachable : Bool = false
  init () {
    // set the isReachable changes to our published property
    connectivityObserver
      .isReachablePublisher
      .receive(on: DispatchQueue.main)
      .assign(to: &self.$isReachable)
  }
  
  func activate () {
    // activate the WatchConnectivity session
    try! self.connectivityObserver.activate()
  }
}

同样,有 3 个重要的部分

  1. 名为 connectivityObserverConnectivityObserver
  2. init 中,我们使用 Combine 监听发布者 (publisher) 并将每个新的 isReachable 存储到我们的 @Published 属性。
  3. 一个需要调用的 activate 方法,用于激活 WatchConnectivity 的会话。

因此,对于我们的 SwiftUI View,我们需要在 onAppearactivate 会话,并可以在 View 中使用 isReachable 属性

struct WatchConnectivityView: View {
  @StateObject var connectivityObject = WatchConnectivityObject()
  var body: some View {
    Text(
      connectivityObject.isReachable ? 
        "Reachable" : "Not Reachable"
    )
    .onAppear{
      self.connectivityObject.activate()
    }
  }
}

除了 isReachable 之外,你还可以访问

此外,还有一组发布者 (publisher),用于在 iPhone 和配对的 Apple Watch 之间发送、接收和回复消息。

发送和接收消息

要通过我们的 ConnectivityObserver 发送和接收消息,我们可以访问两个属性

SundialKit 使用 [String:Any] 字典来发送和接收消息,该字典使用类型别名 ConnectivityMessage。让我们扩展之前的 WatchConnectivityObject 并使用这些属性

class WatchConnectivityObject : ObservableObject {

  // our ConnectivityObserver
  let connectivityObserver = ConnectivityObserver()

  // our published property for isReachable initially set to false
  @Published var isReachable : Bool = false

  // our published property for the last message received
  @Published var lastReceivedMessage : String = ""

  init () {
    // set the isReachable changes to our published property
    connectivityObserver
      .isReachablePublisher
      .receive(on: DispatchQueue.main)
      .assign(to: &self.$isReachable)

    // set the lastReceivedMessage based on the dictionary's _message_ key
    connectivityObserver
      .messageReceivedPublisher
      .compactMap({ received in
        received.message["message"] as? String
      })
      .receive(on: DispatchQueue.main)
      .assign(to: &self.$lastReceivedMessage)
  }
  
  func activate () {
    // activate the WatchConnectivity session
    try! self.connectivityObserver.activate()
  }

  func sendMessage(_ message: String) {
    // create a dictionary with the message in the message key
    self.connectivityObserver.sendingMessageSubject.send(["message" : message])
  }
}

我们现在可以使用更新后的 WatchConnectivityObject 创建一个简单的 SwiftUI View

struct WatchMessageDemoView: View {
  @StateObject var connectivityObject = WatchMessageObject()
  @State var message : String = ""
  var body: some View {
    VStack{
      Text(connectivityObject.isReachable ? "Reachable" : "Not Reachable").onAppear{
        self.connectivityObject.activate()
      }
      TextField("Message", text: self.$message)
      Button("Send") {
        self.connectivityObject.sendMessage(self.message)
      }
      
      Text("Last received message:")
      Text(self.connectivityObject.lastReceivedMessage)
    }
  }
}

使用 Messagable 进行通信

我们甚至可以使用 MessageDecoder 抽象 ConnectivityMessage。为此,我们需要创建一个实现 Messagable 的特殊类型

struct Message : Messagable {
  internal init(text: String) {
    self.text = text
  }
  
  static let key: String = "_message"
  
  enum Parameters : String {
    case text
  }
  
  init?(from parameters: [String : Any]?) {
    guard let text = parameters?[Parameters.text.rawValue] as? String else {
      return nil
    }
    
    self.text = text
  }
  
  func parameters() -> [String : Any] {
    return [
      Parameters.text.rawValue : self.text
    ]
  }
  
  let text : String
}

实现 Messagable 有三个要求

现在我们已经实现了 Messagable,我们可以在我们的 WatchConnectivityObject 中使用它

class WatchConnectivityObject : ObservableObject {

  // our ConnectivityObserver
  let connectivityObserver = ConnectivityObserver()

  // create a `MessageDecoder` which can decode our new `Message` type
  let messageDecoder = MessageDecoder(messagableTypes: [Message.self])

  // our published property for isReachable initially set to false
  @Published var isReachable : Bool = false

  // our published property for the last message received
  @Published var lastReceivedMessage : String = ""

  init () {
    // set the isReachable changes to our published property
    connectivityObserver
      .isReachablePublisher
      .receive(on: DispatchQueue.main)
      .assign(to: &self.$isReachable)

    
    connectivityObserver
      // get the ``ConnectivityReceiveResult/message`` part of the ``ConnectivityReceiveResult``
      .map(\.message)
      // use our `messageDecoder` to call ``MessageDecoder/decode(_:)``
      .compactMap(self.messageDecoder.decode)
      // check it's our `Message`
      .compactMap{$0 as? Message}
      // get the `text` property
      .map(\.text)
      .receive(on: DispatchQueue.main)
      // set it to our published property
      .assign(to: &self.$lastReceivedMessage)
  }
  
  func activate () {
    // activate the WatchConnectivity session
    try! self.connectivityObserver.activate()
  }

  func sendMessage(_ message: String) {
    // create a dictionary using ``Messagable/message()``
    self.connectivityObserver.sendingMessageSubject.send(Message(text: message).message())
  }
}

许可证

此代码根据 MIT 许可证分发。有关更多信息,请参阅 LICENSE 文件。