iOS RxSwift, как подключить Core bluetooth к последовательностям Rx? - PullRequest
0 голосов
/ 10 сентября 2018

Я пытаюсь создать наблюдаемую последовательность, чтобы указать состояние Bluetooth на устройстве. Я использую ReplaySubject<CBManagerState>, но мне любопытно, есть ли что-то лучше, так как я слышу плохие вещи об использовании onNext()

Как правильно подключить делегатов обратного вызова к наблюдаемому домену RxSwift?

class BluetoothStatusMonitor: NSObject, CBPeripheralManagerDelegate {
let bluetoothStatusSequence = ReplaySubject<CBManagerState>.create(bufferSize: 1)

var bluetoothPeripheralManager: CBPeripheralManager?

    func checkBluetoothStatus()
    {
        //silently check permissions, without alert

        let options = [CBCentralManagerOptionShowPowerAlertKey:0]
        bluetoothPeripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: options)

    }
    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {

        bluetoothStatusSequence.onNext(peripheral.state)
    }
}

1 Ответ

0 голосов
/ 11 сентября 2018

Это как раз то, для чего Предметы хороши.Они существуют прежде всего для преобразования не-Rx-кода в Rx-код.Тем не менее, RxCocoa имеет тип DelegateProxy, который предназначен для выполнения большой работы, необходимой для правильного выполнения делегатов.Все еще трудно понять, как именно его реализовать, но как только вы освоите его, они будут весьма полезны ...

Я должен признать, что большая часть кода для меня - черная магия, но этоработает.Я пытаюсь объяснить как можно больше в комментариях ниже.

import RxSwift
import RxCocoa
import CoreBluetooth

    // The HasDelegate protocol is an associated type for the DelegateProxyType
extension CBPeripheralManager: HasDelegate {
    public typealias Delegate = CBPeripheralManagerDelegate
}

class CBPeripheralManagerDelegateProxy
    : DelegateProxy<CBPeripheralManager, CBPeripheralManagerDelegate>
    , DelegateProxyType
    , CBPeripheralManagerDelegate {

    init(parentObject: CBPeripheralManager) {
        super.init(parentObject: parentObject, delegateProxy: CBPeripheralManagerDelegateProxy.self)
    }

    deinit {
        _didUpdateState.onCompleted()
    }

    static func registerKnownImplementations() {
        register { CBPeripheralManagerDelegateProxy(parentObject: $0) }
    }

        // a couple of static functions for getting and setting a delegate on the object.
    static func currentDelegate(for object: CBPeripheralManager) -> CBPeripheralManagerDelegate? {
        return object.delegate
    }

    static func setCurrentDelegate(_ delegate: CBPeripheralManagerDelegate?, to object: CBPeripheralManager) {
        object.delegate = delegate
    }

        // required delegate functions must be implemented in the class. This is where Subjects come in.
    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
        _didUpdateState.onNext(peripheral.state)
    }

    fileprivate let _didUpdateState = PublishSubject<CBManagerState>()
}

extension Reactive where Base: CBPeripheralManager {
    var delegate: CBPeripheralManagerDelegateProxy {
        return CBPeripheralManagerDelegateProxy.proxy(for: base)
    }

    var state: Observable<CBManagerState> {
        return delegate._didUpdateState
    }

    var didUpdateState: Observable<Void> {
        return delegate._didUpdateState.map { _ in }
    }

        // optional methods are setup using the `methodInvoked` function on the delegate
    var willRestoreState: Observable<[String: Any]> {
        return delegate.methodInvoked(#selector(CBPeripheralManagerDelegate.peripheralManager(_:willRestoreState:)))
            .map { $0[1] as! [String: Any] }
    }

    var didStartAdvertising: Observable<Error?> {
        return delegate.methodInvoked(#selector(CBPeripheralManagerDelegate.peripheralManagerDidStartAdvertising(_:error:)))
            .map { $0[1] as? Error }
    }

    // I didn't implement all of the optionals. Use the above as a template to implement the rest.
}

Насколько я могу судить, функция methodInvoked выполняет некоторую магию метапрограммирования объекта, чтобы установить метод во время выполнения.Это сделано потому, что многие классы iOS, имеющие делегаты, ведут себя по-разному в зависимости от того, был ли определен метод на делегате (независимо от того, что делает метод), поэтому мы не хотим просто предоставлять прокси каждый метод впротокол.

Конечно, как только у вас есть вышеперечисленное на месте.Вы можете делать все стандартные вещи RX с вашим периферийным менеджером:

bluetoothManager.rx.state
    .subscribe(onNext: { state in print("current state:", state) })
    .disposed(by: disposeBag)

bluetoothManager.rx.didStartAdvertising
    .subscribe(onNext: { error in
        if let error = error {
            print("there was an error:", error)
        }
    }
    .disposed(by: disposeBag)
...