How to exit from `RunLoop`

I'm subclassing InputStream from iOS Foundation SDK for my needs. I need to implement functionality that worker thread can sleep until data appear in the stream. The test I'm using to cover the functionality is below:

    func testStreamWithRunLoop() {
        let inputStream = BLEInputStream() // custom input stream subclass
        inputStream.delegate = self
        let len = Int.random(in: 0..<100)
        let randomData = randData(length: len) // random data generation
        
        let tenSeconds = Double(10)
        let oneSecond = TimeInterval(1)
        runOnBackgroundQueueAfter(oneSecond) {
            inputStream.accept(randomData) // input stream receives the data
        }
        let dateInFuture = Date(timeIntervalSinceNow: tenSeconds) // time in 10 sec
        inputStream.schedule(in: .current, forMode: RunLoop.Mode.default) // 
        RunLoop.current.run(until: dateInFuture) // wait for data appear in input stream
        XCTAssertTrue(dateInFuture.timeIntervalSinceNow > 0, "Timeout. RunLoop didn't exit in 1 sec. ")
    }

Here it is the overriden methods of InputStream

    public override func schedule(in aRunLoop: RunLoop, forMode mode: RunLoop.Mode) {
        self.runLoop = aRunLoop // save RunLoop object
        var context = CFRunLoopSourceContext() // make context
        self.runLoopSource = CFRunLoopSourceCreate(nil, 0, &context) // version 0 CFRunLoopSource
        let cfloopMode: CFRunLoopMode = CFRunLoopMode(mode as CFString) 
        CFRunLoopAddSource(aRunLoop.getCFRunLoop(), self.runLoopSource!, cfloopMode)
    }

public func accept(_ data: Data) { 
        guard data.count > 0 else { return }
        
        self.data += data
        delegate?.stream?(self, handle: .hasBytesAvailable)
        
        if let runLoopSource {
            CFRunLoopSourceSignal(runLoopSource)
        }
        if let runLoop {
            CFRunLoopWakeUp(runLoop.getCFRunLoop())
        }
    }

But calling CFRunLoopSourceSignal(runLoopSource) and CFRunLoopWakeUp(runLoop.getCFRunLoop()) not get exit from runLoop. Runloop exits after 10 seconds.

[![Test is failing][1]][1] Does anybody know where I'm mistaking ?

Thanks all!

PS: [2]: Here the Xcode project on GitHub

I'm subclassing InputStream from iOS Foundation SDK for my needs.

Why?

Subclassing InputStream is a tricky business at the best of times [1] and it’s fundamentally tied to the run loop which is not well aligned with Apple’s future directions [2]. Building new input stream subclasses today seems like an odd choice when we have AsyncSequence. I’d only do this if you need an input steram to integrate with some existing stream-based subsystem, and even then I’d probably learn towards using a bound pair rather than a input stream subclass. Hence my question.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Well, assuming you want async (run loop) support. A synchronous stream subclass is super easy.

[2] I’m not saying that run loops are going away, just that we have better constructs available right now and we plan to lean into those constructs in the future.

How to exit from `RunLoop`
 
 
Q