Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Investigate async/await API #54

Open
sureshjoshi opened this issue Jul 2, 2021 · 5 comments
Open

Investigate async/await API #54

sureshjoshi opened this issue Jul 2, 2021 · 5 comments
Labels

Comments

@sureshjoshi
Copy link
Member

For call/response, async await can really clean up code

@ericlewis
Copy link

ericlewis commented Sep 29, 2021

I am working on a more comprehensive package to enable this feature, but if you want to get started now, you can use the two extensions below, which somewhat resemble the Rx extensions API. Example and implementation follows. This work isn't complete, it probably needs to consider things like cancellations and what not, I hope to publish something eventually alongside an open source project of mine, making this an official extension as the APIs evolve with real world usage.

// Monitor Bluetooth state
for await state in manager.state() {
    // Skip execution until we reach a poweredOn event
    guard state == .poweredOn else { continue }

    // Scan for devices
    for await device in manager.scan() {
        // Wait till we find our device
        guard device.name == "MyDevice" else { continue }

        // Stop scanning once we have found our device...
        manager.stopScan()

        // ...and connect!
        await device.connect()

        // Discover services
        let services = try await device.services()

        for service in services {
            // Discover characteristics for a service
            let result = try await device.characteristics(for: service)

            for characterisic in result.characteristics {
                // Print each value for a given characteristic if it has one
                guard let value = try? await device.read(characterisic, in: service) else { return }
                print("\(device.id):\(service.uuid):\(characterisic.uuid): \(value)")
            }
        }
    }
}
import SwiftyTeeth

@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
public extension SwiftyTeeth {
    func state() -> AsyncStream<BluetoothState> {
        AsyncStream { continuation in
            stateChangedHandler = {
                continuation.yield($0)
            }
        }
    }

    func scan() -> AsyncStream<Device> {
        AsyncStream { continuation in
            scan {
                continuation.yield($0)
            }
        }
    }
    
    func scan(timeout: TimeInterval) async -> [Device] {
        await withCheckedContinuation { continuation in
            scan(for: timeout) {
                continuation.resume(returning: $0)
            }
        }
    }
}

@available(iOS 15, macOS 12, tvOS 15, watchOS 8, *)
public extension Device {

    @discardableResult
    func connect(timeout: TimeInterval? = nil, autoReconnect: Bool = true) async -> ConnectionState {
        await withCheckedContinuation { continuation in
            connect(with: timeout, autoReconnect: autoReconnect) {
                continuation.resume(returning: $0)
            }
        }
    }

    func services(with uuids: [UUID]? = nil) async throws -> [Service] {
        try await withCheckedThrowingContinuation { continuation in
            discoverServices(with: uuids) {
                continuation.resume(with: $0)
            }
        }
    }

    func characteristics(with uuids: [UUID]? = nil, for service: Service) async throws -> DiscoveredCharacteristic {
        try await withCheckedThrowingContinuation { continuation in
            discoverCharacteristics(with: uuids, for: service) {
                continuation.resume(with: $0)
            }
        }
    }

    func read(_ characteristic: Characteristic, in service: Service) async throws -> Data {
        try await withCheckedThrowingContinuation { continuation in
            read(from: characteristic.uuid, in: service.uuid) {
                continuation.resume(with: $0)
            }
        }
    }

    func write(_ data: Data, to characteristic: Characteristic, in service: Service) async throws {
        try await withCheckedThrowingContinuation { continuation in
            write(data: data, to: characteristic.uuid, in: service.uuid) {
                continuation.resume(with: $0)
            }
        }
    }

    func subscribe(to characteristic: Characteristic, in service: Service) -> AsyncThrowingStream<Data, Error> {
        AsyncThrowingStream { continuation in
            subscribe(to: characteristic.uuid, in: service.uuid) {
                continuation.yield(with: $0)
            }
        }
    }
}

@sureshjoshi
Copy link
Member Author

@ericlewis Legend!

Do you happen to know if Swift Package Manager yet lets us use multiple packages with multiple OS requirements? I last looked into this like 6 months ago, and it wasn't possible at the time.

I'd like to consolidate all the SwiftyTeeth extensions into a single repo - for maintainability.

@ericlewis
Copy link

Yeah, it should be possible I think. If not, then the availability checks can at least stop the code from being compiled while being a separate package.

@sureshjoshi
Copy link
Member Author

That's true - my thinking was mostly in cases where there are deps installed, but in this case, you're right - it doesn't matter

@sureshjoshi
Copy link
Member Author

Just a note for posterity, I've been using these in my apps that targeted iOS 15+, however, since async/await was back ported a bit - I've been using them in a few other apps.

Haven't landed them in the repo because of the naming conflicts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants