0

I'm looking for some verification of what i'm doing. Also, there's a lot of related topics here and there but none is complete / addresses all challenges.

Overview / Requirements:

  • BLE (L2CAP) Central able to talk to multiply simultaneously connected peripherals
  • L2CAP uses NSStream
  • NSStream should run on background (to avoid blocking UI / avoid UI blocking streams)
  • NSStream requires RunLoop therefore it needs a dedicated non-main Thread

Expectations:

  • NSStreams have all system resources so that they can run as smooth as possible.

So, after much research and prototyping:

thread = Thread(block: { [weak self] in
        let runLoop = RunLoop.current
        let timer = Timer(timeInterval: 0.01, repeats: true, block: { [weak self] _ in self?.run() })
        
        inputStream.schedule(in: runLoop, forMode: .default)
        outputStream.schedule(in: runLoop, forMode: .default)

        CFRunLoopAddTimer(runLoop.getCFRunLoop(), timer, .defaultMode)
        CFRunLoopRun()
    })

And exit:

    timer?.invalidate()
    CFRunLoopRemoveTimer(runLoop.getCFRunLoop(), timer, .defaultMode)
    CFRunLoopStop(runLoop.getCFRunLoop())
    thread?.cancel()

This works - thread/RunLoop runs / stream(s) receive events and when it's time to stop, RunLoop ends / thread is cancelled. My concerns / questions regarding RunLoop and custom Thread(s):

  1. I've seen many sources just using RunLoop.main to schedule NSStream(s). That seems incorrect and unnecessary. But I don't have much knowledge to support it (it works pretty much the same whether I use main or background, but I'd really prefer to use background to avoid weird intermittent behaviors).

  2. Obviously creating threads is not ideal (since we have GCD) but it's required by NSStream API. I've seen sources using DispatchQueue.global() to access CurrentRunLoop but apparently GCD has a limited number of worker threads and such practice is not recommended.

  3. Is it okay to schedule both streams in the same RunLoop? Or is one thread per stream better? Or could I schedule 3 peripherals (6x streams) on a single thread?

  4. Timer - Is there any recommendation as to how often the timer should tick? 10ms for BLE communications seems fine but is it? As far as I see the CPU usage is 1% when I run this code so that's not an issue. But also running it at 1ms would be an overkill? Is there better way (not using Timer)?

  5. There is probably a hard limit as to how many threads I can spawn like this (before they are re-used). I don't think i'll ever have more than handful connected peripherals (also limited by BLE hardware) but still. On the other hand, in a unit-test I can spawn 1000 threads in this way (simultaneously), and the code runs fine on simulator. Is this normal?

Pawel Klapuch
  • 380
  • 4
  • 15
  • You probably don't need to create your own RunLoop. See https://stackoverflow.com/a/54172450/3418066 – Paulw11 Aug 22 '22 at 21:44
  • Is it really necessary to work with BLE input and output streams in the same thread or `let queue = DispatchQueue(label: "BLE")` is enough? I know that the same queue does not guarantee the same thread, but I have not found any information that we need to use the same thread during a Bluetooth connection. – Supdef Nov 01 '22 at 23:12

0 Answers0