Swift 结合订阅、正确的流程和架构选择

标签 swift combine

让我们这么说:

• 我的应用程序是 Socket 服务器的客户端。

• 我可以自由地编写符合Combine 的Socket 客户端实现。我喜欢

我已经实现了 2 个解决方案,一个是 CurrentValueSubject (很简单),第二个是我不确定的自定义订阅和自定义发布者。我真的不知道哪种方法是桥接我用来处理服务器消息的代码的最佳方法。

这是我的代码:

为了模拟套接字服务器,我创建了一个假的 SocketServerManagerN 生成一些事件秒:

protocol SocketServerManagerDelegate{
    func newEvent(event:String)
}

class SocketServerManager {

    let timing: Double
    var timerHandler:Timer? = nil
    var delegates:[SocketServerManagerDelegate] = []

    init(timing:Double){
        self.timing = timing
    }

    func start(){
        // Just start a timer that calls generateEvent to simulate some events
        timerHandler = Timer.scheduledTimer(withTimeInterval: timing, repeats: true){
            [weak self] _ in
            self?.generateEvent()
        }
        timerHandler?.fire()
    }


    private func generateEvent(){
        let events = ["New Player", "Player Disconnected", "Server Error"]
        let currentEvent = events.randomElement

        for delegate in delegates{
           delegate.newEvent(event: currentEvent)
        }
    }            
}

自定义发布者和订阅

我的自定义订阅保留了对服务器管理器实例和订阅者的引用。
此外,它实现了 SocketServerManager代表。这样,当服务器有一个新事件时,它会调用现在可以发送 receive 的订阅。订阅者上的事件 (这是我有很多疑问的选择...)
class EventSubscription<S:Subscriber>:Subscription, SocketServerManagerDelegate 
    where S.Input == String{

    private var subscriber:S?
    private unowned var server:SocketServerManager

    init(sub:S, server:EventsServer){
        self.subscriber = sub
        self.server = server
    }

    func request(_ demand: Subscribers.Demand) {}

    func cancel() {
        subscriber = nil
    }

    // HERE IS WHERE I SEND THE EVENT TO THE SUBSCRIBER since this subscription 
    is a delegate of the server manager 
    func newEvent(event: Event) {
        _ = subscriber?.receive(event) 
    }
}

发布者没有什么特别的...它只会使用 receive 创建订阅功能。它还将订阅附加到在服务器上注册的代表列表中,以便 generatesEvents函数可以通过委托(delegate)(因此,通过订阅)广播事件。
// PUBLISHER CODE ----------
func receive<S>(subscriber: S)
    where S:Subscriber,
    EventsPublisher.Failure == S.Failure,
    EventsPublisher.Output == S.Input {

        let subscription = EventSubscription(sub:subscriber, server: self.server)
        server.delegates.append(subscription)
        subscriber.receive(subscription: subscription)
}

你怎么看这个实现?对我来说,这似乎很笨重,但我真的不知道如何将事件从服务器管理器连接到订阅者。

最佳答案

我将使用以下简单且易于管理的方法来做到这一点(IMO 这不是“代表”的正确位置)。

完全可测试的模块:消费者是 SwiftUI View 。经测试可与 Xcode 11.2/iOS 13.2 一起使用,但我没有看到任何平台限制。

演示:

SockerServer emulate Combine demo

这是一个粗糙的想法代码。请在线查找其他评论。

import SwiftUI
import Combine

protocol SocketServerManagerDelegate{
    func newEvent(event:String)
}

class SocketServerManager {

    // transparent subject that manages subscribers/subscriptions
    let publisher = PassthroughSubject<String, Never>()

    let timing: Double
    var timerHandler:Timer? = nil

    init(timing:Double){
        self.timing = timing
    }

    func start(){
        // Just start a timer that calls generateEvent to simulate some events
        timerHandler = Timer.scheduledTimer(withTimeInterval: timing, repeats: true){
            [weak self] _ in
            self?.generateEvent()
        }
        timerHandler?.fire()
    }

    func stop(){
        publisher.send(completion: .finished) // notifies all that finished
    }

    private func generateEvent(){
        let events = ["New Player", "Player Disconnected", "Server Error"]
        guard let currentEvent = events.randomElement() else { return }

        publisher.send(currentEvent) // send to all subscribers
    }
}

// usage
class ViewModel: ObservableObject {
    private let server = SocketServerManager(timing: 1)
    private var cancellables = Set<AnyCancellable>()

    func setup() {
        guard cancellables.isEmpty else { return } // already set up

        // add one example subscriber
        server.publisher
            .assign(to: \.value1, on: self)
            .store(in: &cancellables)

        // add another example subscriber
        server.publisher
            .sink(receiveValue: { value in
                self.value2 = value
            })
            .store(in: &cancellables)

        server.start()
    }

    @Published var value1: String = "<unknown>"
    @Published var value2: String = "<unknown>"
}

// view demo
struct TestSocketServerPublisher: View {
    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        VStack {
            Text("Observer1: \(viewModel.value1)")
            Divider()
            Text("Observer2: \(viewModel.value2)")
        }
        .onAppear {
            self.viewModel.setup()
        }
    }
}

struct TestSocketServerPublisher_Previews: PreviewProvider {
    static var previews: some View {
        TestSocketServerPublisher()
    }
}

关于Swift 结合订阅、正确的流程和架构选择,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59345963/

相关文章:

swift - Swift Bugging 中的 anchor ?

generics - 通用自定义运算符函数 : A Curious Case of a Bad * Instruction

ios - 以编程方式创建带有按钮目标的自定义 View

ios - SwiftUI 列表数据的可识别协议(protocol)扩展

ios - 在 flatMap 运算符闭包中放置打印语句后合并奇怪的编译错误

SwiftUI ObjectBinding 不会使用 combine 从可绑定(bind)对象接收 didchange 更新

ios - 从 AppDelegate、Swift 导航到 TabBarController 内的特定 View Controller

swift - 用于获取 PassthroughSubject 发布者值的单元测试

swift - URLSession.shared.dataTaskPublisher 在 IOS 13.3 上不起作用

ios - 任何对象的 Swift 二维字典