core.async iOS Swift 通道用于解耦组件
core.async channels in iOS Swift for decoupling components
我应该如何将 Swift 中的组件与通道或等效的消息总线实现分离?
作为来自 Clojure 的 Swift 初学者,我习惯于在启动组件时返回 core.async 通道,然后在调用者处连接它以进行控制流。
我看到 Swift 中有一个叫做 DispatchQueue
的东西,但这看起来不像消息总线,而且似乎没有缓冲。
具体来说,我在 iOS 上控制音频子系统,我需要通过可插拔架构发送惰性信号。
我找到了 a simple event bus implementation of DispatchQueue
that wraps the notify
and async
methods with some locking to protect against mutations during forEach on multiple subscribers (see Protected.swift),但我怀疑这可以在没有额外锁的情况下安全地完成:
//
// Channel.swift
// Lightning
//
// Created by Göksel Köksal on 5.03.2018.
// Copyright © 2018 GK. All rights reserved.
//
import Foundation
/// An event bus object which provides an API to broadcast messages to its subscribers.
public class Channel<Value> {
internal class Subscription {
weak var object: AnyObject?
private let queue: DispatchQueue?
private let block: (Value) -> Void
var isValid: Bool {
return object != nil
}
init(object: AnyObject?, queue: DispatchQueue?, block: @escaping (Value) -> Void) {
self.object = object
self.queue = queue
self.block = block
}
func notify(_ value: Value) {
if let queue = queue {
queue.async { [weak self] in
guard let strongSelf = self else { return }
if strongSelf.isValid {
strongSelf.block(value)
}
}
} else {
if isValid {
block(value)
}
}
}
}
internal var subscriptions: Protected<[Subscription]> = Protected([])
/// Creates a channel instance.
public init() { }
/// Subscribes given object to channel.
///
/// - Parameters:
/// - object: Object to subscribe.
/// - queue: Queue for given block to be called in. If you pass nil, the block is run synchronously on the posting thread.
/// - block: Block to call upon broadcast.
public func subscribe(_ object: AnyObject?, queue: DispatchQueue? = nil, block: @escaping (Value) -> Void) {
let subscription = Subscription(object: object, queue: queue, block: block)
subscriptions.write { list in
list.append(subscription)
}
}
/// Unsubscribes given object from channel.
///
/// - Parameter object: Object to remove.
public func unsubscribe(_ object: AnyObject?) {
subscriptions.write { list in
if let foundIndex = list.index(where: { [=10=].object === object }) {
list.remove(at: foundIndex)
}
}
}
/// Broadcasts given value to subscribers.
///
/// - Parameters:
/// - value: Value to broadcast.
/// - completion: Completion handler called after notifing all subscribers.
public func broadcast(_ value: Value) {
subscriptions.write(mode: .sync) { list in
list = list.filter({ [=10=].isValid })
list.forEach({ [=10=].notify(value) })
}
}
}
用法:
enum Message {
case didUpdateTheme(Theme)
}
let settingsChannel = Channel<Message>()
class SomeView {
func load() {
settingsChannel.subscribe(self) { message in
// React to the message here.
}
}
}
let view = SomeView()
view.load()
settingsChannel.broadcast(.didUpdateTheme(.light))
我应该如何将 Swift 中的组件与通道或等效的消息总线实现分离?
作为来自 Clojure 的 Swift 初学者,我习惯于在启动组件时返回 core.async 通道,然后在调用者处连接它以进行控制流。
我看到 Swift 中有一个叫做 DispatchQueue
的东西,但这看起来不像消息总线,而且似乎没有缓冲。
具体来说,我在 iOS 上控制音频子系统,我需要通过可插拔架构发送惰性信号。
我找到了 a simple event bus implementation of DispatchQueue
that wraps the notify
and async
methods with some locking to protect against mutations during forEach on multiple subscribers (see Protected.swift),但我怀疑这可以在没有额外锁的情况下安全地完成:
//
// Channel.swift
// Lightning
//
// Created by Göksel Köksal on 5.03.2018.
// Copyright © 2018 GK. All rights reserved.
//
import Foundation
/// An event bus object which provides an API to broadcast messages to its subscribers.
public class Channel<Value> {
internal class Subscription {
weak var object: AnyObject?
private let queue: DispatchQueue?
private let block: (Value) -> Void
var isValid: Bool {
return object != nil
}
init(object: AnyObject?, queue: DispatchQueue?, block: @escaping (Value) -> Void) {
self.object = object
self.queue = queue
self.block = block
}
func notify(_ value: Value) {
if let queue = queue {
queue.async { [weak self] in
guard let strongSelf = self else { return }
if strongSelf.isValid {
strongSelf.block(value)
}
}
} else {
if isValid {
block(value)
}
}
}
}
internal var subscriptions: Protected<[Subscription]> = Protected([])
/// Creates a channel instance.
public init() { }
/// Subscribes given object to channel.
///
/// - Parameters:
/// - object: Object to subscribe.
/// - queue: Queue for given block to be called in. If you pass nil, the block is run synchronously on the posting thread.
/// - block: Block to call upon broadcast.
public func subscribe(_ object: AnyObject?, queue: DispatchQueue? = nil, block: @escaping (Value) -> Void) {
let subscription = Subscription(object: object, queue: queue, block: block)
subscriptions.write { list in
list.append(subscription)
}
}
/// Unsubscribes given object from channel.
///
/// - Parameter object: Object to remove.
public func unsubscribe(_ object: AnyObject?) {
subscriptions.write { list in
if let foundIndex = list.index(where: { [=10=].object === object }) {
list.remove(at: foundIndex)
}
}
}
/// Broadcasts given value to subscribers.
///
/// - Parameters:
/// - value: Value to broadcast.
/// - completion: Completion handler called after notifing all subscribers.
public func broadcast(_ value: Value) {
subscriptions.write(mode: .sync) { list in
list = list.filter({ [=10=].isValid })
list.forEach({ [=10=].notify(value) })
}
}
}
用法:
enum Message {
case didUpdateTheme(Theme)
}
let settingsChannel = Channel<Message>()
class SomeView {
func load() {
settingsChannel.subscribe(self) { message in
// React to the message here.
}
}
}
let view = SomeView()
view.load()
settingsChannel.broadcast(.didUpdateTheme(.light))