Last active
August 9, 2022 04:42
-
-
Save pofat/d3851bfc408a259d96bd34d0da27441b to your computer and use it in GitHub Desktop.
How to handle delegate in a reactive way? Turn a delegate into Publisher.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Combine | |
import CombineExt // Check this implementation: https://github.com/CombineCommunity/CombineExt/blob/main/Sources/Operators/Create.swift | |
import CoreBluetooth | |
// Client to generate target delegate publisher | |
// Learned from https://github.com/pointfreeco/composable-core-location/blob/main/Sources/ComposableCoreLocation/Live.swift | |
struct PeripheralClient { | |
enum Action: Equatable { | |
case didUptateName | |
case didDiscoverServices(Error?) | |
case didDiscoverCharacteristicsFor(CBService, error: Error?) | |
case didUpdateValueFor(CBCharacteristic, error: Error?) // wrong | |
case didUpdate(value: Data?, for: CBCharacteristic, error: Error?) // correct | |
// ... other delegate actions | |
} | |
struct Error: Swift.Error, Equatable { | |
let error: NSError | |
init(_ error: Swift.Error) { | |
self.error = error as NSError | |
} | |
} | |
var create: (AnyHashable, CBPeripheral) -> AnyPublisher<Action, Never> | |
var destory: (AnyHashable) -> AnyPublisher<Never, Never> | |
} | |
extension PeripheralClient { | |
static let app = PeripheralClient { id, peripheral in | |
.create { subscriber in | |
let delegate = PeripheralDelegate(subscriber) | |
peripheral.delegate = delegate | |
dependencies[id] = Dependencies(delegate: delegate, peripheral: peripheral, subscriber: subscriber) | |
return AnyCancellable { | |
dependencies[id]?.peripheral.delegate = nil | |
dependencies[id] = nil | |
} | |
} | |
} destory: { id in | |
Deferred { () -> Empty<Never, Never> in | |
dependencies[id]?.subscriber.send(completion: .finished) | |
dependencies[id] = nil | |
return Empty(completeImmediately: true) | |
}.eraseToAnyPublisher() | |
} | |
} | |
// Dependencies | |
private struct Dependencies { | |
let delegate: PeripheralDelegate | |
let peripheral: CBPeripheral | |
let subscriber: Publishers.Create<PeripheralClient.Action, Never>.Subscriber | |
} | |
private var dependencies: [AnyHashable: Dependencies] = [:] | |
// The delegate implementation | |
private class PeripheralDelegate: NSObject, CBPeripheralDelegate { | |
let subscriber: Publishers.Create<PeripheralClient.Action, Never>.Subscriber | |
init(_ subscriber: Publishers.Create<PeripheralClient.Action, Never>.Subscriber) { | |
self.subscriber = subscriber | |
} | |
// MARK: CBPeripheralDelegate | |
func peripheralDidUpdateName(_ peripheral: CBPeripheral) { | |
DispatchQueue.main.async { | |
self.subscriber.send(.didUptateName) | |
} | |
} | |
func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { | |
DispatchQueue.main.async { | |
self.subscriber.send(.didDiscoverServices(error.map { PeripheralClient.Error($0) } )) | |
} | |
} | |
func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { | |
DispatchQueue.main.async { | |
self.subscriber.send(.didDiscoverCharacteristicsFor(service, error: error.map { PeripheralClient.Error($0) } )) | |
} | |
} | |
// This makes car crash! | |
// func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { | |
// DispatchQueue.main.async { | |
// self.subscriber.send(.didUpdateValueFor(characteristic, error: error.map { PeripheralClient.Error($0) } )) | |
// } | |
// } | |
// This is correct way to do it | |
func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { | |
let value = characteristic.value | |
DispatchQueue.main.async { | |
self.subscriber.send(.didUpdate(value: value, for: characteristic, error: error.map { PeripheralClient.Error($0) } )) | |
} | |
} | |
} | |
// ================= How to use it ================= | |
var cancellable: AnyCancellable? | |
let peripheral: CBPeripheral | |
let delegatePublihser = PeripheralClient.app.create(peripheral.identifier, peripheral) | |
cancellable = delegatePublihser.sink( | |
receiveValue: { action in | |
switch action { | |
case let .didUpdate(value, _, error): | |
print("This is the correct value: \(value)") | |
case let .didUpdateVAlueFor(characteristic, error): | |
let value = characteristic.value | |
print("This may be the wrong value: \(value)") | |
// other actions... | |
default: | |
break | |
} | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment