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

Swift Combine Subscriptions, right flow and architectural choices

假设:

• 我的应用程序是套接字服务器的客户端。

• 我可以随意编写 Socket 客户端实现以适应 Combine,因为我更喜欢

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

这是我的代码:

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

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 函数可以通过委托(因此,通过订阅)广播事件。

// 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 这不是 "delegate"s 的正确位置)。

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

演示:

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

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()
    }
}