Как остановить сканер RxSwift после того, как он обнаружит совпадение? - PullRequest
1 голос
/ 28 апреля 2020

У меня есть ble сканер, который работает и выглядит следующим образом:

func scan(serviceId: String) -> Observable<[BleHandler.BlePeripheral]> {
    knownDevices = []
    return waitForBluetooth()
        .flatMap { _ in self.scanForPeripheral(serviceId: serviceId) }
        .map { _ in self.knownDevices }
}

private func waitForBluetooth() -> Observable<BluetoothState> {
    return self.manager
        .observeState()
        .startWith(self.manager.state)
        .filter { $0 == .poweredOn }
        .take(1)
}

Затем в viewModel class он фильтрует совпадения из core data:

func scanAndFilter() -> Observable<[LocalDoorCoreDataObject]> {
        let persistingDoors: [LocalDoorCoreDataObject] = coreDataHandler.fetchAll(fetchRequest: NSFetchRequest<LocalDoorCoreDataObject>(entityName: "LocalDoorCoreDataObject"))

    return communicationService
        .scanForDevices(register: false)
        .map{ peripherals in
            print("? THIS WILL GO ON FOR ETERNITY", peripherals.count)
            self.knownDevices = peripherals
            return persistingDoors
                .filter { door in peripherals.contains(where: { $0.identifier.uuidString == door.dPeripheralId }) }
        }
}

И в view я хочу подключиться, когда сканирование завершится:

private func scanAndConnect(data: LocalDoorCoreDataObject) {
    viewModel.scanRelay().subscribe(
        onNext: {
            print("?SCANNED NAME", $0.first?.dName)},
        onCompleted: {
            print("?COMPLETED SCAN")
            self.connectToFilteredPeripheral(localDoor: data)
    }).disposed(by: disposeBag)
}

Оно никогда не достигнет onCompleted, так как будет просто сканировать вечность даже после того, как найдет и filtered the core data совпадение. В рамках Apple coreBluetooth я мог бы просто позвонить manager.stopScan() после того, как он нашел то, что я хочу, но это, похоже, не доступно для Rx аналога. Как это работает для RxSwift

Ответы [ 2 ]

2 голосов
/ 28 апреля 2020

Вы можете создать новый Observable, который ищет устройства, а затем завершает работу, как только находит устройства, которые вы ищете. Это будет что-то вроде:

func scanAndFilter() -> Observable<[LocalDoorCoreDataObject]> {
        return Observable.deferred { in
            let persistingDoors: [LocalDoorCoreDataObject] = coreDataHandler.fetchAll(fetchRequest: NSFetchRequest<LocalDoorCoreDataObject>(entityName: "LocalDoorCoreDataObject"))

            return communicationService
                .scanForDevices(register: false)
                .filter { /* verify if the device(s) you're looking for is/are in this list */ }
                .take(1)
        }
    }

Оператор filter обеспечит передачу только тех списков, которые содержат искомое устройство, а take(1) оператор примет первое переданное значение и немедленно завершит его.

Вызов deferred гарантирует, что запрос на выборку, который выполняется в первой строке, не выполняется при вызове scanAndFilter(), но только когда кто-то действительно подписывается на получившуюся Observable.

0 голосов
/ 29 апреля 2020

Если вы хотите, чтобы только одно событие вышло из оператора фильтра, просто используйте .take(1). Observable закроется после того, как он выдаст одно значение. Если функция BLE написана правильно, она вызовет stopScan() при утилизации Одноразового.

Я понятия не имею, почему другой ответ говорит «всегда проверяйте, чтобы все функции, возвращающие Наблюдаемые, были .deferred. Я использую RxSwift с 2015 года, и мне когда-либо нужно было откладывать только один раз. Конечно, не каждый раз, когда я вызываю функцию, которая возвращает Observable.

...