1

I have a ble scanner that works and looks like this:

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)
}

Then in the viewModel class it filters matches from 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 }) }
        }
}

And in the view I want to connect when the scan is completed:

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)
}

It never reaches onCompleted as it will just scan for eternity even after having found and filtered the core data match. In Apple's framework coreBluetooth I could simply call manager.stopScan() after it has found what I want, but that doesn't seem to be available on the Rx counterpart. How does it work for RxSwift

Joakim Sjöstedt
  • 824
  • 2
  • 9
  • 18

2 Answers2

2

You can create a new Observable that looks for devices and then completes as soon as it finds the device(s) you're looking for. This would be something like:

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)
        }
    }

The filter operator will make sure that only lists that contain the device you're looking for are passed on and the take(1) operator will take the first emitted value and complete immediately.

The deferred call makes sure that the fetch request that is performed in the first line is not executed when you call scanAndFilter() but only when somebody actually subscribes to the resulting Observable.

MrAsterisco
  • 797
  • 8
  • 21
0

If you only want one event to exit the filter operator, then just use .take(1). The Observable will shut down after it emits a single value. If the BLE function is written correctly, it will call stopScan() when the Disposable is disposed of.

I have no idea why the other answer says to "always make sure to wrap all function that return Observables into a .deferred. I've been using RxSwift since 2015 and I've only ever needed deferred once. Certainly not every time I called a function that returned an Observable.

Daniel T.
  • 32,821
  • 6
  • 50
  • 72
  • `deferred` makes sure that the logic inside the Observable is not executed until somebody actually subscribes to it (which is the whole point of Observable streams). For example, in this case, not wrapping everything inside a `deferred` call would start the fetch request immediately after calling `scanAndFilter()`, potentially blocking the thread you’re calling the function from. There are many articles explaining this better than what I can do in a comment. – MrAsterisco Apr 29 '20 at 07:34
  • Observables are cold by default. IE, they don't start up until after someone subscribes to them. The only time you need `deferred` is if you need to restart the observable using different values captured from the surrounding code, or if you want to turn a hot observable cold but that's rather rare. That said, I'd love to read one of articles. I request references. – Daniel T. Apr 29 '20 at 10:36
  • Observables are just code, in the end. If you don't wrap your function inside a `deferred` call, all lines of code preceding the actual Observable will be executed immediately (i.e. the fetch request, in this case), breaking your statement that "all observables are cold by default". I agree that there are cases in which this isn't needed, but I do believe here it is. Anyway, I've learned about the Defer operator from this article, which talks about RxJava, but it's basically the same thing: https://blog.danlew.net/2015/07/23/deferring-observable-code-until-subscription-in-rxjava/ – MrAsterisco Apr 29 '20 at 11:18
  • A fetch request is the exemplar of a cold observable! `URLSession.shared.rx.response(request:)` does _nothing_ if called on its own. You have to subscribe to it in order for the request to happen and every subscriber starts a new request. The article is not about the observable itself, but about the input into the observable when created. For example once you create the response Observable with a URLRequest, you can't change the value of the request for that observable. Here the question is about how to dispose the observable after it emits an event. Deferred should not be your default. – Daniel T. Apr 29 '20 at 12:25
  • I'm not following: the fetch request code is executed immediately, not at the subscription; it doesn't return an Observable, as you can see from the fact that it's returning a simple array of Core Data objects. I understand that the article I've linked is not related to this case, sorry about that, I though you asked for documentation on the `deferred` operator. Here's another article that I think it's more related to this particular situation: http://adamborek.com/creating-observable-create-just-deferred/. – MrAsterisco Apr 29 '20 at 12:33
  • Anyway, going through my answer again, I understand that the first sentence is maybe a bit too drastic: it is not true that **all** functions that return Observables must be wrapped inside a `deferred` call, but I still believe it is true in this case. I'll update my question to be clearer. – MrAsterisco Apr 29 '20 at 12:36