Skip to content

Instantly share code, notes, and snippets.

@pofat
Last active August 9, 2022 04:42
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pofat/d3851bfc408a259d96bd34d0da27441b to your computer and use it in GitHub Desktop.
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.
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