@@ -22,73 +22,3 @@ class AppDelegate: UIResponder, UIApplicationDelegate | |||||
return true | return true | ||||
} | } | ||||
} | } | ||||
/* | |||||
// Bus.Pipe.swift | |||||
public extension Bus { | |||||
final class BindingPipe<T: Equatable>: ObservableObject { | |||||
public let input = PassthroughSubject<T, Never>() | |||||
public let output = PassthroughSubject<T, Never>() | |||||
@Published var value: T | |||||
var isInput = false | |||||
var subscriptions = [AnyCancellable]() | |||||
deinit { | |||||
print("ИГР BusBP.DEinit(\(Unmanaged.passUnretained(self).toOpaque()))") | |||||
} | |||||
init( | |||||
_ defaultValue: T, | |||||
_ keyApp: String, | |||||
_ keyUI: String | |||||
) { | |||||
value = defaultValue | |||||
/**/dbg("ИГР BusBP.init(\(Unmanaged.passUnretained(self).toOpaque())) keyA: '\(keyApp)'") | |||||
// TextField -> output. | |||||
$value | |||||
.sink { [weak self] v in | |||||
assert(Thread.isMainThread) | |||||
// Ignore values received from input. | |||||
// Only accept UI provided values. | |||||
guard | |||||
let self, | |||||
!self.isInput | |||||
else { | |||||
return | |||||
} | |||||
self.output.send(v) | |||||
} | |||||
.store(in: &subscriptions) | |||||
// input -> TextField. | |||||
input | |||||
.sink { [weak self] v in | |||||
assert(Thread.isMainThread) | |||||
guard let self else { return } | |||||
/**/dbg("ИГР BusBP.init input: '\(v)'") | |||||
self.isInput = true | |||||
self.value = v | |||||
self.isInput = false | |||||
} | |||||
.store(in: &subscriptions) | |||||
// Оповещаем в мир об изменениях от UI. | |||||
Bus.sendAsync( | |||||
&subscriptions, | |||||
keyUI, | |||||
output.eraseToAnyPublisher() | |||||
) | |||||
Bus.receive( | |||||
&subscriptions, | |||||
[keyApp], | |||||
{ [weak self] _, v in | |||||
self?.input.send(v) | |||||
/**/print("ИГР BusBP.init receive: '\(v)'") | |||||
} | |||||
) | |||||
} | |||||
} | |||||
} | |||||
*/ |
@@ -24,7 +24,7 @@ extension Bus { | |||||
} | } | ||||
public extension Bus { | public extension Bus { | ||||
static func receive<T>( | |||||
static func receiveAsync<T>( | |||||
_ subscriptions: inout Set<AnyCancellable>, | _ subscriptions: inout Set<AnyCancellable>, | ||||
_ keys: Set<String>, | _ keys: Set<String>, | ||||
_ handler: @escaping ((String, T) -> Void) | _ handler: @escaping ((String, T) -> Void) | ||||
@@ -39,11 +39,12 @@ public extension Bus { | |||||
} | } | ||||
return (v.key, value) | return (v.key, value) | ||||
} | } | ||||
.receive(on: DispatchQueue.main) | |||||
.sink { v in handler(v.0, v.1) } | .sink { v in handler(v.0, v.1) } | ||||
.store(in: &subscriptions) | .store(in: &subscriptions) | ||||
} | } | ||||
static func receiveAsync<T>( | |||||
static func receiveSync<T>( | |||||
_ subscriptions: inout Set<AnyCancellable>, | _ subscriptions: inout Set<AnyCancellable>, | ||||
_ keys: Set<String>, | _ keys: Set<String>, | ||||
_ handler: @escaping ((String, T) -> Void) | _ handler: @escaping ((String, T) -> Void) | ||||
@@ -58,18 +59,11 @@ public extension Bus { | |||||
} | } | ||||
return (v.key, value) | return (v.key, value) | ||||
} | } | ||||
.receive(on: DispatchQueue.main) | |||||
.sink { v in handler(v.0, v.1) } | .sink { v in handler(v.0, v.1) } | ||||
.store(in: &subscriptions) | .store(in: &subscriptions) | ||||
} | } | ||||
static func send(_ key: String, _ value: Any) { | |||||
Service.singleton?.send(key, value) | |||||
} | |||||
/* | |||||
static func sendAsync<T: Equatable>( | |||||
static func sendAsync<T>( | |||||
_ subscriptions: inout Set<AnyCancellable>, | _ subscriptions: inout Set<AnyCancellable>, | ||||
_ key: String, | _ key: String, | ||||
_ node: AnyPublisher<T, Never> | _ node: AnyPublisher<T, Never> | ||||
@@ -79,5 +73,36 @@ public extension Bus { | |||||
.sink { v in Service.singleton?.send(key, v) } | .sink { v in Service.singleton?.send(key, v) } | ||||
.store(in: &subscriptions) | .store(in: &subscriptions) | ||||
} | } | ||||
*/ | |||||
static func sendSync<T>( | |||||
_ subscriptions: inout Set<AnyCancellable>, | |||||
_ key: String, | |||||
_ node: AnyPublisher<T, Never> | |||||
) { | |||||
node | |||||
.sink { v in Service.singleton?.send(key, v) } | |||||
.store(in: &subscriptions) | |||||
} | |||||
static func sendOnce(_ key: String, _ value: Any) { | |||||
Service.singleton?.send(key, value) | |||||
} | |||||
} | |||||
public extension Bus { | |||||
static func registerProcessing<Src, Dst>( | |||||
_ subscriptions: inout Set<AnyCancellable>, | |||||
_ keyIn: String, | |||||
_ keyOut: String, | |||||
_ handler: @escaping ((Src) -> Dst?) | |||||
) { | |||||
Service.singleton?.broadcaster | |||||
.filter { $0.key == keyIn } | |||||
.compactMap { | |||||
guard let vIn = $0.value as? Src else { return nil } | |||||
return handler(vIn) | |||||
} | |||||
.sink { vOut in Service.singleton?.send(keyOut, vOut) } | |||||
.store(in: &subscriptions) | |||||
} | |||||
} | } |
@@ -18,6 +18,9 @@ enum MeetupId { | |||||
r += "-" | r += "-" | ||||
} | } | ||||
} | } | ||||
if r.hasSuffix("-") { | |||||
r = String(r.dropLast(1)) | |||||
} | |||||
return r | return r | ||||
} | } | ||||
@@ -29,36 +32,13 @@ enum MeetupId { | |||||
} | } | ||||
init() { | init() { | ||||
Bus.receive( | |||||
&subscriptions, | |||||
[Keys.meetupIdTextUI.rawValue], | |||||
Self.handleFormatting | |||||
Bus.registerProcessing( | |||||
&subscriptions, | |||||
Keys.meetupIdTextUI.rawValue, | |||||
Keys.meetupIdTextApp.rawValue, | |||||
MeetupId.shouldFormat | |||||
) | ) | ||||
/**/print("ИГР MeetupIF.init") | /**/print("ИГР MeetupIF.init") | ||||
} | } | ||||
static func handleFormatting(_: String, _ value: String) { | |||||
let out = MeetupId.shouldFormat(value) | |||||
/**/print("ИГР MeetupIF.handleF out/dt: '\(out)'/'\(Date())'") | |||||
Bus.send(Keys.meetupIdTextApp.rawValue, out) | |||||
} | |||||
} | |||||
/* | |||||
struct V: View { | |||||
/*@StateObject*/ var fmt = MeetupIdFormatter() | |||||
@StateObject var txt = Bus.BindingPipe("", Keys.meetupIdTextApp.rawValue, Keys.meetupIdTextUI.rawValue) | |||||
var body: some View { | |||||
VStack { | |||||
Text("Hi, the text is: '\(txt.value)'") | |||||
.fontWeight(.bold) | |||||
TextField("Placeholder", text: $txt.value) | |||||
.padding(8) | |||||
.border(Color.blue, width: 2) | |||||
} | |||||
.frame(width: 320) | |||||
.padding() | |||||
} | |||||
} | } | ||||
*/ | |||||
} | } |
@@ -7,15 +7,18 @@ final class VM: ObservableObject { | |||||
var subscriptions = Set<AnyCancellable>() | var subscriptions = Set<AnyCancellable>() | ||||
init() { | init() { | ||||
$text | |||||
// Исключаем конфликты от UI и App путём игнорирования спама. | |||||
.debounce(for: .seconds(0.3), scheduler: DispatchQueue.main) | |||||
// Нужны лишь значения от UI. | |||||
.filter { $0.hasPrefix("u:") } | |||||
// Убираем источник. | |||||
.map { String($0.dropFirst(2)) } | |||||
.sink { Bus.send(MeetupId.Keys.meetupIdTextUI.rawValue, $0) } | |||||
.store(in: &subscriptions) | |||||
Bus.sendSync( | |||||
&subscriptions, | |||||
MeetupId.Keys.meetupIdTextUI.rawValue, | |||||
$text | |||||
// Исключаем конфликты от UI и App путём игнорирования спама. | |||||
.debounce(for: .seconds(0.3), scheduler: DispatchQueue.main) | |||||
// Нужны лишь значения от UI. | |||||
.filter { $0.hasPrefix("u:") } | |||||
// Убираем источник. | |||||
.map { String($0.dropFirst(2)) } | |||||
.eraseToAnyPublisher() | |||||
) | |||||
Bus.receiveAsync( | Bus.receiveAsync( | ||||
&subscriptions, | &subscriptions, | ||||