From 655484334ba27af70dd0e5f16b15c071cda4ad8d Mon Sep 17 00:00:00 2001 From: SJ Date: Fri, 17 Nov 2023 09:19:54 -0500 Subject: [PATCH] Updated repo for Xcode 15 and re-formatted (#65) - Ran XCode auto-upgrade - Bumped to latest swift-format config - Formatted with latest swift-format rules --- .swift-format | 13 ++ Examples/Central/Package.swift | 4 +- Examples/Central/Sources/Central/main.swift | 1 - Examples/Peripheral/Package.swift | 4 +- .../Sources/Peripheral/logger.swift | 2 +- .../Peripheral/Sources/Peripheral/main.swift | 18 +- Sources/Common/Characteristic.swift | 7 +- Sources/Common/Deque.swift | 64 +++--- Sources/Common/Service.swift | 2 +- Sources/SwiftyTeeth/Device.swift | 190 +++++++++++------- .../Extensions/CBCharacterisic+String.swift | 8 +- .../Extensions/CBService+String.swift | 6 +- .../OperationQueue+SwiftyQueue.swift | 6 +- .../SwiftyTeeth/Internal/SwiftyQueue.swift | 10 +- Sources/SwiftyTeeth/QueueItem.swift | 43 ++-- Sources/SwiftyTeeth/SwiftyTeeth.swift | 186 ++++++++++------- Sources/SwiftyTeeth/SwiftyTeethLogger.swift | 25 ++- Sources/SwiftyTeeth/SwiftyTeethable.swift | 2 +- Sources/SwiftyTooth/SwiftyTooth.swift | 163 +++++++++------ Sources/SwiftyTooth/SwiftyToothLogger.swift | 25 ++- .../Extension/Device+Identifiable.swift | 2 - SwiftyTeeth-Sample/PeripheralView.swift | 9 +- SwiftyTeeth.xcodeproj/project.pbxproj | 12 +- .../xcschemes/SwiftyTeeth-Sample.xcscheme | 2 +- .../xcschemes/SwiftyTeeth.xcscheme | 2 +- .../xcschemes/SwiftyTooth.xcscheme | 2 +- SwiftyTeethTests/SwiftyTeethTests.swift | 1 + SwiftyToothTests/SwiftyToothTests.swift | 1 + 28 files changed, 491 insertions(+), 319 deletions(-) diff --git a/.swift-format b/.swift-format index fcb7d8c..c317ad8 100644 --- a/.swift-format +++ b/.swift-format @@ -13,10 +13,17 @@ "lineBreakBeforeEachGenericRequirement" : false, "lineLength" : 100, "maximumBlankLines" : 1, + "multiElementCollectionTrailingCommas" : true, + "noAssignmentInExpressions" : { + "allowedFunctions" : [ + "XCTAssertNoThrow" + ] + }, "prioritizeKeepingFunctionOutputTogether" : false, "respectsExistingLineBreaks" : true, "rules" : { "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLiteralForEmptyCollectionInit" : false, "AlwaysUseLowerCamelCase" : true, "AmbiguousTrailingClosureOverload" : true, "BeginDocumentationCommentWithOneLineSummary" : false, @@ -30,18 +37,23 @@ "NeverUseForceTry" : false, "NeverUseImplicitlyUnwrappedOptionals" : false, "NoAccessLevelOnExtensionDeclaration" : true, + "NoAssignmentInExpressions" : true, "NoBlockComments" : true, "NoCasesWithOnlyFallthrough" : true, "NoEmptyTrailingClosureParentheses" : true, "NoLabelsInCasePatterns" : true, "NoLeadingUnderscores" : false, "NoParensAroundConditions" : true, + "NoPlaygroundLiterals" : true, "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : false, "OneCasePerLine" : true, "OneVariableDeclarationPerLine" : true, "OnlyOneTrailingClosureArgument" : true, "OrderedImports" : true, + "ReplaceForEachWithForLoop" : true, "ReturnVoidInsteadOfEmptyTuple" : true, + "TypeNamesShouldBeCapitalized" : true, "UseEarlyExits" : false, "UseLetInEveryBoundCaseVariable" : true, "UseShorthandTypeNames" : true, @@ -51,6 +63,7 @@ "UseWhereClausesInForLoops" : false, "ValidateDocumentationComments" : false }, + "spacesAroundRangeFormationOperators" : false, "tabWidth" : 4, "version" : 1 } diff --git a/Examples/Central/Package.swift b/Examples/Central/Package.swift index 2cf4458..db7b33d 100644 --- a/Examples/Central/Package.swift +++ b/Examples/Central/Package.swift @@ -25,11 +25,11 @@ let package = Package( .macOS(.v10_15), ], dependencies: [ - .package(name: "SwiftyTeeth", path: "../../"), + .package(name: "SwiftyTeeth", path: "../../") ], targets: [ .executableTarget( name: "Central", - dependencies: ["SwiftyTeeth"]), + dependencies: ["SwiftyTeeth"]) ] ) diff --git a/Examples/Central/Sources/Central/main.swift b/Examples/Central/Sources/Central/main.swift index 0c7a8a1..bb2f5cf 100644 --- a/Examples/Central/Sources/Central/main.swift +++ b/Examples/Central/Sources/Central/main.swift @@ -25,5 +25,4 @@ instance.stateChangedHandler = { (state) in } } - RunLoop.main.run() diff --git a/Examples/Peripheral/Package.swift b/Examples/Peripheral/Package.swift index 0da9d25..eef9a0a 100644 --- a/Examples/Peripheral/Package.swift +++ b/Examples/Peripheral/Package.swift @@ -25,11 +25,11 @@ let package = Package( .macOS(.v10_15), ], dependencies: [ - .package(name: "SwiftyTeeth", path: "../../"), + .package(name: "SwiftyTeeth", path: "../../") ], targets: [ .executableTarget( name: "Peripheral", - dependencies: [.product(name: "SwiftyTooth", package: "SwiftyTeeth")]), + dependencies: [.product(name: "SwiftyTooth", package: "SwiftyTeeth")]) ] ) diff --git a/Examples/Peripheral/Sources/Peripheral/logger.swift b/Examples/Peripheral/Sources/Peripheral/logger.swift index 7fb88c2..b958c0e 100644 --- a/Examples/Peripheral/Sources/Peripheral/logger.swift +++ b/Examples/Peripheral/Sources/Peripheral/logger.swift @@ -1,6 +1,6 @@ // // logger.swift -// +// // // Created by SJ on 2020-03-27. // diff --git a/Examples/Peripheral/Sources/Peripheral/main.swift b/Examples/Peripheral/Sources/Peripheral/main.swift index 2c559e2..56a8d96 100644 --- a/Examples/Peripheral/Sources/Peripheral/main.swift +++ b/Examples/Peripheral/Sources/Peripheral/main.swift @@ -5,7 +5,6 @@ // Created by SJ on 2020-03-27. // - import Foundation import SwiftyTooth @@ -35,33 +34,34 @@ let tx = Characteristic( properties: [ .write(onWrite: { (request, response) in print("In on onWrite \(String(describing: request))") - + guard let value = request, - value.count == 1 else { + value.count == 1 + else { print("No data available") // TODO: Failure response? return } - - // TODO: Clean up this UInt8 + + // TODO: Clean up this UInt8 increment(by: [UInt8](value)[0]) - + // If increment is really long, this could technically time out - should increment the counter, respond success, THEN notify response(.success(())) }), .writeNoResponse(onWrite: { (request) in print("In on onWriteNoResponse \(String(describing: request))") - }) + }), ] ) var counter = UInt8.min func increment(by value: UInt8) { - for _ in 0..<128 { // Sending 128 elements to test notification queue + for _ in 0..<128 { // Sending 128 elements to test notification queue counter += value let data = Data([counter]) - instance.emit(data: data, on: rx) + _ = instance.emit(data: data, on: rx) } } diff --git a/Sources/Common/Characteristic.swift b/Sources/Common/Characteristic.swift index 7b285db..04f99d5 100644 --- a/Sources/Common/Characteristic.swift +++ b/Sources/Common/Characteristic.swift @@ -19,7 +19,7 @@ public enum Property { case notify(onNotify: NotifyHandler) case write(onWrite: WriteHandler) case writeNoResponse(onWrite: WriteNoResponseHandler) - + var isReadable: Bool { switch self { case .read, .notify: @@ -28,7 +28,7 @@ public enum Property { return false } } - + var isWriteable: Bool { return !isReadable } @@ -37,8 +37,7 @@ public enum Property { public struct Characteristic { public let uuid: UUID public let properties: [Property] - - + public init(uuid: UUID, properties: [Property]) { self.uuid = uuid self.properties = properties diff --git a/Sources/Common/Deque.swift b/Sources/Common/Deque.swift index 6a9c740..e6c4c04 100644 --- a/Sources/Common/Deque.swift +++ b/Sources/Common/Deque.swift @@ -1,7 +1,7 @@ /* Briefly using a Dequeue from Ray Wenderlich to test out the notification queue. Need to create a "proper" queue with a thread-safe implementation, and a valid "peek" down the road... (Issue #45) - + https://github.com/raywenderlich/swift-algorithm-club/tree/master/Deque Deque (pronounced "deck"), a double-ended queue This particular implementation is simple but not very efficient. Several @@ -9,45 +9,45 @@ linked list or a circular buffer. */ public struct Deque { - private var array = [T]() + private var array = [T]() - public var isEmpty: Bool { - return array.isEmpty - } + public var isEmpty: Bool { + return array.isEmpty + } - public var count: Int { - return array.count - } + public var count: Int { + return array.count + } - public mutating func enqueue(_ element: T) { - array.append(element) - } + public mutating func enqueue(_ element: T) { + array.append(element) + } - public mutating func enqueueFront(_ element: T) { - array.insert(element, at: 0) - } + public mutating func enqueueFront(_ element: T) { + array.insert(element, at: 0) + } - public mutating func dequeue() -> T? { - if isEmpty { - return nil - } else { - return array.removeFirst() + public mutating func dequeue() -> T? { + if isEmpty { + return nil + } else { + return array.removeFirst() + } } - } - public mutating func dequeueBack() -> T? { - if isEmpty { - return nil - } else { - return array.removeLast() + public mutating func dequeueBack() -> T? { + if isEmpty { + return nil + } else { + return array.removeLast() + } } - } - public func peekFront() -> T? { - return array.first - } + public func peekFront() -> T? { + return array.first + } - public func peekBack() -> T? { - return array.last - } + public func peekBack() -> T? { + return array.last + } } diff --git a/Sources/Common/Service.swift b/Sources/Common/Service.swift index 3e51818..8cd8afc 100644 --- a/Sources/Common/Service.swift +++ b/Sources/Common/Service.swift @@ -10,7 +10,7 @@ import Foundation public struct Service { public let uuid: UUID public let characteristics: [Characteristic] - + public init(uuid: UUID, characteristics: [Characteristic]) { self.uuid = uuid self.characteristics = characteristics diff --git a/Sources/SwiftyTeeth/Device.swift b/Sources/SwiftyTeeth/Device.swift index 2281b6b..3cccc2c 100644 --- a/Sources/SwiftyTeeth/Device.swift +++ b/Sources/SwiftyTeeth/Device.swift @@ -6,8 +6,8 @@ // // -import Foundation import CoreBluetooth +import Foundation public typealias DiscoveredCharacteristic = (service: Service, characteristics: [Characteristic]) public typealias ServiceDiscovery = ((Result<[Service], Error>) -> Void) @@ -17,17 +17,16 @@ public enum ConnectionError: Error { case disconnected } - // Note: The design of this class will (eventually, and if reasonable) attempt to keep APIs unaware of CoreBluetooth // while at the same time making use of them internally as a convenience // NotificationHandler is an example of this, as it could be a dictionary using a String key - but that just adds extra indirection internally, // where all CBCharacterisics need to be dereferenced by uuid then uuidString. Internally, this adds no value - and adds risk. open class Device: NSObject { fileprivate let tag = "SwiftyDevice" - + let peripheral: CBPeripheral var discoveredServices = [UUID: Service]() - + private let manager: SwiftyTeeth public var connectionStateChangedHandler: ((ConnectionState) -> Void)? { @@ -39,13 +38,13 @@ open class Device: NSObject { private var connectionHandler: ((ConnectionState) -> Void)? private var notificationHandler = [CBCharacteristic: ((Result) -> Void)]() - + // Connection parameters fileprivate var autoReconnect = false - + lazy var queue: SwiftyQueue = { let instance = OperationQueue() - instance.maxConcurrentOperationCount = 1 // Ensure serial queue + instance.maxConcurrentOperationCount = 1 // Ensure serial queue return instance }() @@ -98,7 +97,10 @@ extension Device { extension Device { // Annoyingly, iOS has the connection functionality sitting on the central manager, instead of on the peripheral // TODO: Should the completion be optional? - public func connect(with timeout: TimeInterval? = nil, autoReconnect: Bool = true, complete: ((ConnectionState) -> Void)?) { + public func connect( + with timeout: TimeInterval? = nil, autoReconnect: Bool = true, + complete: ((ConnectionState) -> Void)? + ) { Log(v: "Calling connect", tag: tag) connectionStateChangedHandler?(.connecting) self.connectionHandler = complete @@ -116,14 +118,13 @@ extension Device { } } - // MARK: - GATT operations extension Device { // TODO: Make CBUUID into strings - public func discoverServices(with uuids: [UUID]? = nil, complete: ServiceDiscovery?) { + public func discoverServices(with uuids: [UUID]? = nil, complete: ServiceDiscovery?) { let item = QueueItem<[Service]>( - name: "discoverServices", // TODO: Need better than a hardcoded string + name: "discoverServices", // TODO: Need better than a hardcoded string execution: { (cb) in guard self.isConnected == true else { @@ -131,19 +132,24 @@ extension Device { cb(.failure(ConnectionError.disconnected)) return } - Log(v: "discoverServices: \(self.peripheral) \n \(String(describing: self.peripheral.delegate))", tag: self.tag) + Log( + v: + "discoverServices: \(self.peripheral) \n \(String(describing: self.peripheral.delegate))", + tag: self.tag) let cbuuids = uuids == nil ? nil : uuids!.map { CBUUID(nsuuid: $0) } self.peripheral.discoverServices(cbuuids) - }, + }, callback: { (result, done) in complete?(result) done() - }) + }) queue.pushBack(item) } - public func discoverCharacteristics(with uuids: [UUID]? = nil, for service: Service, complete: CharacteristicDiscovery?) { + public func discoverCharacteristics( + with uuids: [UUID]? = nil, for service: Service, complete: CharacteristicDiscovery? + ) { let item = QueueItem( name: service.uuid.uuidString, execution: { (cb) in @@ -153,28 +159,37 @@ extension Device { return } - guard let cbService = self.peripheral.services?.find(uuidString: service.uuid.uuidString) else { - Log(w: "Service not found on peripheral - cannot discoverCharacteristics", tag: self.tag) - cb(.failure(ConnectionError.disconnected)) // TODO: Replace this error + guard + let cbService = self.peripheral.services?.find( + uuidString: service.uuid.uuidString) + else { + Log( + w: "Service not found on peripheral - cannot discoverCharacteristics", + tag: self.tag) + cb(.failure(ConnectionError.disconnected)) // TODO: Replace this error return } Log(v: "discoverCharacteristics", tag: self.tag) let cbuuids = uuids == nil ? nil : uuids!.map { CBUUID(nsuuid: $0) } self.peripheral.discoverCharacteristics(cbuuids, for: cbService) - }, + }, callback: { (result, done) in complete?(result) done() - }) - + }) + queue.pushBack(item) } - public func read(from characteristic: UUID, in service: UUID, complete: ((Result) -> Void)?) { + public func read( + from characteristic: UUID, in service: UUID, complete: ((Result) -> Void)? + ) { guard let targetService = peripheral.services?.find(uuidString: service.uuidString), - let targetCharacteristic = targetService.characteristics?.find(uuidString: characteristic.uuidString) else { - return + let targetCharacteristic = targetService.characteristics?.find( + uuidString: characteristic.uuidString) + else { + return } let item = QueueItem( @@ -190,15 +205,21 @@ extension Device { callback: { (result, done) in complete?(result) done() - }) + }) queue.pushBack(item) } - public func write(data: Data, to characteristic: UUID, in service: UUID, type: CBCharacteristicWriteType = .withResponse, complete: ((Result) -> Void)? = nil) { + public func write( + data: Data, to characteristic: UUID, in service: UUID, + type: CBCharacteristicWriteType = .withResponse, + complete: ((Result) -> Void)? = nil + ) { guard let targetService = peripheral.services?.find(uuidString: service.uuidString), - let targetCharacteristic = targetService.characteristics?.find(uuidString: characteristic.uuidString) else { - return + let targetCharacteristic = targetService.characteristics?.find( + uuidString: characteristic.uuidString) + else { + return } let item = QueueItem( @@ -210,18 +231,23 @@ extension Device { return } self.peripheral.writeValue(data, for: targetCharacteristic, type: type) - }, callback: { (result, done) in - complete?(result) - done() - }) + }, + callback: { (result, done) in + complete?(result) + done() + }) queue.pushBack(item) } // TODO: Adding some pre-conditions libraries/toolkits could streamline the initial clutter - public func subscribe(to characteristic: UUID, in service: UUID, complete: ((Result) -> Void)?) { + public func subscribe( + to characteristic: UUID, in service: UUID, complete: ((Result) -> Void)? + ) { guard let targetService = peripheral.services?.find(uuidString: service.uuidString), - let targetCharacteristic = targetService.characteristics?.find(uuidString: characteristic.uuidString) else { - return + let targetCharacteristic = targetService.characteristics?.find( + uuidString: characteristic.uuidString) + else { + return } guard isConnected == true else { @@ -242,8 +268,10 @@ extension Device { // TODO: Faster probably to just iterate through the notification handler instead of current method public func unsubscribe(from characteristic: UUID, in service: UUID) { guard let targetService = peripheral.services?.find(uuidString: service.uuidString), - let targetCharacteristic = targetService.characteristics?.find(uuidString: characteristic.uuidString) else { - return + let targetCharacteristic = targetService.characteristics?.find( + uuidString: characteristic.uuidString) + else { + return } guard isConnected == true else { @@ -264,10 +292,9 @@ extension Device { } } - // MARK: - NSObject overrides extension Device { - open override var hash : Int { + open override var hash: Int { return id.hash } @@ -280,19 +307,24 @@ extension Device { } } - // TODO: Instead of creating internal functions, could register connection/disconnection handlers with the Manager? // MARK: - Connection Handler Proxy -internal extension Device { +extension Device { func didConnect() { - Log(v: "didConnect: Calling connection handler: Is handler nil? \(connectionHandler == nil)", tag: tag) + Log( + v: + "didConnect: Calling connection handler: Is handler nil? \(connectionHandler == nil)", + tag: tag) connectionHandler?(.connected) connectionStateChangedHandler?(.connected) } func didDisconnect() { - Log(v: "didDisconnect: Calling disconnection handler: Is handler nil? \(connectionHandler == nil)", tag: tag) + Log( + v: + "didDisconnect: Calling disconnection handler: Is handler nil? \(connectionHandler == nil)", + tag: tag) queue.cancelAll() connectionHandler?(.disconnected) @@ -303,10 +335,9 @@ internal extension Device { } } - // TODO: If multiple peripherals are connected, should there be a peripheral validation done? // MARK: - CBPeripheralDelegate Proxy -internal extension Device { +extension Device { func didUpdateName() { @@ -325,25 +356,28 @@ internal extension Device { } func didDiscoverServices(error: Error?) { -// discoveredServices.removeAll() + // discoveredServices.removeAll() - let services = peripheral.services?.compactMap { (cbService) -> Service? in - Log(v: "Service Discovered: \(cbService.uuid.uuidString)", tag: tag) - guard let uuid = UUID(cbuuid: cbService.uuid) else { - return nil - } - return Service(uuid: uuid, characteristics: []) - } ?? [] + let services = + peripheral.services?.compactMap { (cbService) -> Service? in + Log(v: "Service Discovered: \(cbService.uuid.uuidString)", tag: tag) + guard let uuid = UUID(cbuuid: cbService.uuid) else { + return nil + } + return Service(uuid: uuid, characteristics: []) + } ?? [] - discoveredServices = Dictionary(uniqueKeysWithValues: zip(services.map {$0.uuid}, services)) + discoveredServices = Dictionary( + uniqueKeysWithValues: zip(services.map { $0.uuid }, services)) var result: Result<[Service], Error> = .success(Array(discoveredServices.values)) if let error = error { result = .failure(error) } - let item = queue.items.first { (operation) -> Bool in - operation.isExecuting && operation.name == "discoverServices" + let item = + queue.items.first { (operation) -> Bool in + operation.isExecuting && operation.name == "discoverServices" } as? QueueItem<[Service]> item?.notify(result) } @@ -357,43 +391,50 @@ internal extension Device { return } - let characteristics = service.characteristics?.compactMap { cbCharacteristic -> Characteristic? in - Log(v: "Characteristic Discovered: \(cbCharacteristic.uuid.uuidString)", tag: tag) - guard let uuid = UUID(cbuuid: cbCharacteristic.uuid) else { - return nil - } - return Characteristic(uuid: uuid, properties: []) - } ?? [] + let characteristics = + service.characteristics?.compactMap { cbCharacteristic -> Characteristic? in + Log(v: "Characteristic Discovered: \(cbCharacteristic.uuid.uuidString)", tag: tag) + guard let uuid = UUID(cbuuid: cbCharacteristic.uuid) else { + return nil + } + return Characteristic(uuid: uuid, properties: []) + } ?? [] let service = Service(uuid: uuid, characteristics: characteristics) discoveredServices[uuid] = service - var result: Result = .success((service: service, characteristics: characteristics)) + var result: Result = .success( + (service: service, characteristics: characteristics)) if let error = error { result = .failure(error) } - let item = queue.items.first { (operation) -> Bool in - operation.isExecuting && operation.name == service.uuid.uuidString + let item = + queue.items.first { (operation) -> Bool in + operation.isExecuting && operation.name == service.uuid.uuidString } as? QueueItem item?.notify(result) } func didUpdateValueFor(characteristic: CBCharacteristic, error: Error?) { - Log(v: "didUpdateValueFor: \(characteristic.uuid.uuidString) with: \(String(describing: characteristic.value))", tag: tag) + Log( + v: + "didUpdateValueFor: \(characteristic.uuid.uuidString) with: \(String(describing: characteristic.value))", + tag: tag) var result: Result = .success(characteristic.value ?? Data()) if let e = error { result = .failure(e) } - let item = queue.items.first { (operation) -> Bool in - operation.isExecuting && operation.name == characteristic.compositeId + let item = + queue.items.first { (operation) -> Bool in + operation.isExecuting && operation.name == characteristic.compositeId } as? QueueItem item?.notify(result) notificationHandler[characteristic]?(result) } - + func didWriteValueFor(characteristic: CBCharacteristic, error: Error?) { Log(v: "didWriteValueFor: \(characteristic.uuid.uuidString)", tag: tag) @@ -402,12 +443,13 @@ internal extension Device { result = .failure(e) } - let item = queue.items.first { (operation) -> Bool in - operation.isExecuting && operation.name == characteristic.compositeId - } as? QueueItem + let item = + queue.items.first { (operation) -> Bool in + operation.isExecuting && operation.name == characteristic.compositeId + } as? QueueItem item?.notify(result) } - + // This is equivalent to a direct READ from the characteristic func didUpdateNotificationStateFor(characteristic: CBCharacteristic, error: Error?) { Log(v: "didUpdateNotificationStateFor: \(characteristic.uuid.uuidString)", tag: tag) diff --git a/Sources/SwiftyTeeth/Extensions/CBCharacterisic+String.swift b/Sources/SwiftyTeeth/Extensions/CBCharacterisic+String.swift index 29dede8..07c3979 100644 --- a/Sources/SwiftyTeeth/Extensions/CBCharacterisic+String.swift +++ b/Sources/SwiftyTeeth/Extensions/CBCharacterisic+String.swift @@ -8,7 +8,7 @@ import CoreBluetooth -internal extension CBCharacteristic { +extension CBCharacteristic { var compositeId: String { // TODO: What is the correct way to handle this change? Need to support iOS15 (optional) and pre-iOS15 (non-optional) // https://developer.apple.com/documentation/corebluetooth/cbcharacteristic/1518728-service?changes=latest_minor @@ -16,14 +16,14 @@ internal extension CBCharacteristic { // TODO: Why would the service no longer exist? Already destroyed? return maybeService?.uuid.uuidString ?? "" + uuid.uuidString } - + func equals(_ uuidString: String) -> Bool { return self.uuid.uuidString.lowercased() == uuidString.lowercased() } } -internal extension Array where Element: CBCharacteristic { +extension Array where Element: CBCharacteristic { func find(uuidString: String) -> CBCharacteristic? { - return self.first(where: {$0.equals(uuidString)}) + return self.first(where: { $0.equals(uuidString) }) } } diff --git a/Sources/SwiftyTeeth/Extensions/CBService+String.swift b/Sources/SwiftyTeeth/Extensions/CBService+String.swift index a78decf..763c44a 100644 --- a/Sources/SwiftyTeeth/Extensions/CBService+String.swift +++ b/Sources/SwiftyTeeth/Extensions/CBService+String.swift @@ -8,14 +8,14 @@ import CoreBluetooth -internal extension CBService { +extension CBService { func equals(_ uuidString: String) -> Bool { return self.uuid.uuidString.lowercased() == uuidString.lowercased() } } -internal extension Array where Element: CBService { +extension Array where Element: CBService { func find(uuidString: String) -> CBService? { - return self.first(where: {$0.equals(uuidString)}) + return self.first(where: { $0.equals(uuidString) }) } } diff --git a/Sources/SwiftyTeeth/Extensions/OperationQueue+SwiftyQueue.swift b/Sources/SwiftyTeeth/Extensions/OperationQueue+SwiftyQueue.swift index 00e9955..3285a7c 100644 --- a/Sources/SwiftyTeeth/Extensions/OperationQueue+SwiftyQueue.swift +++ b/Sources/SwiftyTeeth/Extensions/OperationQueue+SwiftyQueue.swift @@ -8,11 +8,11 @@ import Foundation extension OperationQueue: SwiftyQueue { - + var items: [Operation] { return operations } - + func pushBack(_ item: Operation) { Log(v: "SwiftyQueue: Adding item to existing \(items.count) items in queue") self.addOperation(item) @@ -21,7 +21,7 @@ extension OperationQueue: SwiftyQueue { Log(v: "SwiftyQueue: \(item.name ?? "(none)") is in the queue") } } - + func cancelAll() { Log(v: "SwiftyQueue: Cancelling all \(items.count) items in queue") for item in items { diff --git a/Sources/SwiftyTeeth/Internal/SwiftyQueue.swift b/Sources/SwiftyTeeth/Internal/SwiftyQueue.swift index bf508eb..76bf7b9 100644 --- a/Sources/SwiftyTeeth/Internal/SwiftyQueue.swift +++ b/Sources/SwiftyTeeth/Internal/SwiftyQueue.swift @@ -8,8 +8,8 @@ import Foundation internal protocol SwiftyQueue { - var items: [Operation] {get} - + var items: [Operation] { get } + func pushBack(_ item: Operation) func cancelAll() } @@ -21,10 +21,10 @@ public enum FailureHandler { } internal protocol Queueable { - var timeout: TimeInterval {get} - var doOnFailure: FailureHandler {get} + var timeout: TimeInterval { get } + var doOnFailure: FailureHandler { get } // priority - + func execute() func cancel() } diff --git a/Sources/SwiftyTeeth/QueueItem.swift b/Sources/SwiftyTeeth/QueueItem.swift index 476faa5..1106dda 100644 --- a/Sources/SwiftyTeeth/QueueItem.swift +++ b/Sources/SwiftyTeeth/QueueItem.swift @@ -11,30 +11,32 @@ private enum State: String { case none = "None" case ready = "Ready" case executing = "Executing" - case finishing = "Finishing" // On route to finished, but haven't notified Queue + case finishing = "Finishing" // On route to finished, but haven't notified Queue case finished = "Finished" - + fileprivate var keyPath: String { return "is" + self.rawValue } } public class QueueItem: Operation { public typealias CallbackBlock = (Result, () -> Void) -> Void public typealias ExecutionBlock = ((Result) -> Void) -> Void - + @available(*, deprecated, message: "Don't use this") - override open var completionBlock: (@Sendable() -> Void)? { + override open var completionBlock: (@Sendable () -> Void)? { get { return nil } set { - fatalError("completionBlock has some funky behaviour regarding threading and execution - prefer using regular init") + fatalError( + "completionBlock has some funky behaviour regarding threading and execution - prefer using regular init" + ) } } - + override public var isAsynchronous: Bool { return true } override public var isExecuting: Bool { return state == .executing } override public var isFinished: Bool { return state == .finished } - + fileprivate var state = State.ready { willSet { willChangeValue(forKey: state.keyPath) @@ -47,31 +49,32 @@ public class QueueItem: Operation { didChangeValue(forKey: oldValue.keyPath) } } - + let timeout: TimeInterval = 0.0 let doOnFailure: FailureHandler = .nothing - + private let execution: ExecutionBlock? private let callback: CallbackBlock? - + private let mutex = DispatchSemaphore(value: 1) - + public init( name: String? = nil, //delay: TimeInterval = 0.0, // When the task is kicked off, start running after 'delay' -// timeout: TimeInterval = 0.0, // After starting, give up after 'timeout' -// doOnFailure: FailureHandler = .nothing, // Retry/reschedule on failure + // timeout: TimeInterval = 0.0, // After starting, give up after 'timeout' + // doOnFailure: FailureHandler = .nothing, // Retry/reschedule on failure priority: QueuePriority = .normal, - execution: ExecutionBlock? = nil, // TODO: Does this being nil make sense?? - callback: CallbackBlock? = nil) { - + execution: ExecutionBlock? = nil, // TODO: Does this being nil make sense?? + callback: CallbackBlock? = nil + ) { + self.execution = execution self.callback = callback super.init() self.name = name self.queuePriority = priority } - + public override func main() { guard isCancelled == false else { done() @@ -85,7 +88,7 @@ public class QueueItem: Operation { super.cancel() done() } - + // Call this after Execute is completed to allow Queue to continue public func done() { state = .finished @@ -94,7 +97,7 @@ public class QueueItem: Operation { // To be overridden extension QueueItem: Queueable { - + func execute() { Log(v: "QueueItem: Executing \(self.name ?? "(none)")") if let execution = execution { @@ -111,7 +114,7 @@ extension QueueItem: Queueable { done() } } - + func notify(_ result: Result) { Log(v: "QueueItem: Notifying \(self.name ?? "(none)")") if let cb = callback { diff --git a/Sources/SwiftyTeeth/SwiftyTeeth.swift b/Sources/SwiftyTeeth/SwiftyTeeth.swift index 31fd4dc..c812acf 100644 --- a/Sources/SwiftyTeeth/SwiftyTeeth.swift +++ b/Sources/SwiftyTeeth/SwiftyTeeth.swift @@ -6,15 +6,15 @@ // // -import Foundation import CoreBluetooth +import Foundation var swiftyTeethLogger: Logger? open class SwiftyTeeth: NSObject { public static let shared = SwiftyTeeth() - + public var stateChangedHandler: ((BluetoothState) -> Void)? { didSet { stateChangedHandler?(state) @@ -31,7 +31,7 @@ open class SwiftyTeeth: NSObject { public var state: BluetoothState { return BluetoothState(rawValue: centralManager.state.rawValue) ?? .unknown } - + // TODO: Hold a private set, and expose a list? public var scannedDevices = Set() @@ -40,16 +40,16 @@ open class SwiftyTeeth: NSObject { } // TODO: Should be a list? Can connect to > 1 device - private var connectedDevices = [String:Device]() + private var connectedDevices = [String: Device]() private var scanChangesHandler: ((Device) -> Void)? private var scanCompleteHandler: (([Device]) -> Void)? - + public override init() { } } -public extension SwiftyTeeth { - class var logger: Logger? { +extension SwiftyTeeth { + public class var logger: Logger? { get { return swiftyTeethLogger } @@ -60,27 +60,31 @@ public extension SwiftyTeeth { } // MARK: - Manager Utility functions -public extension SwiftyTeeth { - func retrievePeripherals(withIdentifiers uuids: [UUID]) -> [Device] { +extension SwiftyTeeth { + public func retrievePeripherals(withIdentifiers uuids: [UUID]) -> [Device] { let cbPeripherals = centralManager.retrievePeripherals(withIdentifiers: uuids) return cbPeripherals.map { Device(manager: self, peripheral: $0) } } } // MARK: - Manager Scan functions -public extension SwiftyTeeth { +extension SwiftyTeeth { - func scan() { + public func scan() { scannedDevices.removeAll() - centralManager.scanForPeripherals(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]) + centralManager.scanForPeripherals( + withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey: true]) } - - func scan(changes: ((Device) -> Void)?) { + + public func scan(changes: ((Device) -> Void)?) { scanChangesHandler = changes scan() } - - func scan(for timeout: TimeInterval = 10, changes: ((Device) -> Void)? = nil, complete: @escaping ([Device]) -> Void) { + + public func scan( + for timeout: TimeInterval = 10, changes: ((Device) -> Void)? = nil, + complete: @escaping ([Device]) -> Void + ) { scanChangesHandler = changes scanCompleteHandler = complete // TODO: Should this be on main, or on CB queue? @@ -90,48 +94,46 @@ public extension SwiftyTeeth { scan() } - func stopScan() { + public func stopScan() { // TODO: Cancel asyncAfter if in progress? centralManager.stopScan() scanCompleteHandler?(Array(scannedDevices)) - + // Reset Handlers scanChangesHandler = nil scanCompleteHandler = nil } } - // MARK: - Internal Connection functions extension SwiftyTeeth { - + // Using these internal functions, so that we can track devices 'in use' func connect(to device: Device) { // Add device to dictionary only if it isn't there -// if connectedDevices[device.id] == nil { - connectedDevices[device.id] = device -// } + // if connectedDevices[device.id] == nil { + connectedDevices[device.id] = device + // } Log(v: "Connecting to device - \(device.id)") centralManager.connect(device.peripheral, options: nil) } - + // Using these internal functions, so that we can track devices 'in use' func disconnect(from device: Device) { // Add device to dictionary only if it isn't there -// if connectedDevices[device.id] == nil { - connectedDevices[device.id] = device -// } + // if connectedDevices[device.id] == nil { + connectedDevices[device.id] = device + // } Log(v: "Disconnecting from device - \(device.id)") centralManager.cancelPeripheralConnection(device.peripheral) } } - // MARK: - Central manager extension SwiftyTeeth: CBCentralManagerDelegate { public func centralManagerDidUpdateState(_ central: CBCentralManager) { - switch (central.state) { + switch central.state { case .unknown: Log(v: "Bluetooth state is unknown.") case .resetting: @@ -149,95 +151,135 @@ extension SwiftyTeeth: CBCentralManagerDelegate { default: Log(v: "Bluetooth state is not in supported switches") } - + guard let state = BluetoothState(rawValue: central.state.rawValue) else { return } stateChangedHandler?(state) } - - public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) { + + public func centralManager( + _ central: CBCentralManager, didDiscover peripheral: CBPeripheral, + advertisementData: [String: Any], rssi RSSI: NSNumber + ) { guard peripheral.name != nil else { return } - + let device = Device(manager: self, peripheral: peripheral) scannedDevices.insert(device) scanChangesHandler?(device) } - + public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) { - Log(v: "centralManager: didConnect to \(peripheral.identifier) with maximum write value of \(peripheral.maximumWriteValueLength(for: .withoutResponse)) and \(peripheral.maximumWriteValueLength(for: .withResponse))") + Log( + v: + "centralManager: didConnect to \(peripheral.identifier) with maximum write value of \(peripheral.maximumWriteValueLength(for: .withoutResponse)) and \(peripheral.maximumWriteValueLength(for: .withResponse))" + ) connectedDevices[peripheral.identifier.uuidString]?.didConnect() } - - public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) { + + public func centralManager( + _ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error? + ) { Log(v: "centralManager: didFailToConnect to \(peripheral.identifier)") connectedDevices[peripheral.identifier.uuidString]?.didDisconnect() } - - public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) { + + public func centralManager( + _ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error? + ) { Log(v: "centralManager: didDisconnect from \(peripheral.identifier)") connectedDevices[peripheral.identifier.uuidString]?.didDisconnect() } - - public func centralManager(_ central: CBCentralManager, willRestoreState dict: [String : Any]) { + + public func centralManager(_ central: CBCentralManager, willRestoreState dict: [String: Any]) { } } // TODO: If multiple peripherals are connected, should there be a peripheral validation done? // MARK: - CBPeripheralDelegate extension SwiftyTeeth: CBPeripheralDelegate { - + public func peripheralDidUpdateName(_ peripheral: CBPeripheral) { connectedDevices[peripheral.identifier.uuidString]?.didUpdateName() } - - public func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) { - connectedDevices[peripheral.identifier.uuidString]?.didModifyServices(invalidatedServices: invalidatedServices) + + public func peripheral( + _ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService] + ) { + connectedDevices[peripheral.identifier.uuidString]?.didModifyServices( + invalidatedServices: invalidatedServices) } - + public func peripheralDidUpdateRSSI(_ peripheral: CBPeripheral, error: Error?) { connectedDevices[peripheral.identifier.uuidString]?.didUpdateRSSI(error: error) } - + public func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) { connectedDevices[peripheral.identifier.uuidString]?.didReadRSSI(RSSI: RSSI, error: error) } - + public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { connectedDevices[peripheral.identifier.uuidString]?.didDiscoverServices(error: error) } - - public func peripheral(_ peripheral: CBPeripheral, didDiscoverIncludedServicesFor service: CBService, error: Error?) { - connectedDevices[peripheral.identifier.uuidString]?.didDiscoverIncludedServicesFor(service: service, error: error) + + public func peripheral( + _ peripheral: CBPeripheral, didDiscoverIncludedServicesFor service: CBService, error: Error? + ) { + connectedDevices[peripheral.identifier.uuidString]?.didDiscoverIncludedServicesFor( + service: service, error: error) } - - public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) { - connectedDevices[peripheral.identifier.uuidString]?.didDiscoverCharacteristicsFor(service: service, error: error) + + public func peripheral( + _ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error? + ) { + connectedDevices[peripheral.identifier.uuidString]?.didDiscoverCharacteristicsFor( + service: service, error: error) } - - public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) { - connectedDevices[peripheral.identifier.uuidString]?.didUpdateValueFor(characteristic: characteristic, error: error) + + public func peripheral( + _ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, + error: Error? + ) { + connectedDevices[peripheral.identifier.uuidString]?.didUpdateValueFor( + characteristic: characteristic, error: error) } - - public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) { - connectedDevices[peripheral.identifier.uuidString]?.didWriteValueFor(characteristic: characteristic, error: error) + + public func peripheral( + _ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error? + ) { + connectedDevices[peripheral.identifier.uuidString]?.didWriteValueFor( + characteristic: characteristic, error: error) } - - public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { - connectedDevices[peripheral.identifier.uuidString]?.didUpdateNotificationStateFor(characteristic: characteristic, error: error) + + public func peripheral( + _ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, + error: Error? + ) { + connectedDevices[peripheral.identifier.uuidString]?.didUpdateNotificationStateFor( + characteristic: characteristic, error: error) } - - public func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) { - connectedDevices[peripheral.identifier.uuidString]?.didDiscoverDescriptorsFor(characteristic: characteristic, error: error) + + public func peripheral( + _ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, + error: Error? + ) { + connectedDevices[peripheral.identifier.uuidString]?.didDiscoverDescriptorsFor( + characteristic: characteristic, error: error) } - - public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) { - connectedDevices[peripheral.identifier.uuidString]?.didUpdateValueFor(descriptor: descriptor, error: error) + + public func peripheral( + _ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error? + ) { + connectedDevices[peripheral.identifier.uuidString]?.didUpdateValueFor( + descriptor: descriptor, error: error) } - - public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) { - connectedDevices[peripheral.identifier.uuidString]?.didWriteValueFor(descriptor: descriptor, error: error) + + public func peripheral( + _ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error? + ) { + connectedDevices[peripheral.identifier.uuidString]?.didWriteValueFor( + descriptor: descriptor, error: error) } } diff --git a/Sources/SwiftyTeeth/SwiftyTeethLogger.swift b/Sources/SwiftyTeeth/SwiftyTeethLogger.swift index 408bf20..7d3d19b 100644 --- a/Sources/SwiftyTeeth/SwiftyTeethLogger.swift +++ b/Sources/SwiftyTeeth/SwiftyTeethLogger.swift @@ -6,22 +6,37 @@ // // -public func Log(v message: String, tag: String = "SwiftyTeeth", path: String = #file, function: String = #function, line: Int = #line) { +public func Log( + v message: String, tag: String = "SwiftyTeeth", path: String = #file, + function: String = #function, line: Int = #line +) { swiftyTeethLogger?.verbose("\(tag): \(message)") } -public func Log(d message: String, tag: String = "SwiftyTeeth", path: String = #file, function: String = #function, line: Int = #line) { +public func Log( + d message: String, tag: String = "SwiftyTeeth", path: String = #file, + function: String = #function, line: Int = #line +) { swiftyTeethLogger?.debug("\(tag): \(message)") } -public func Log(i message: String, tag: String = "SwiftyTeeth", path: String = #file, function: String = #function, line: Int = #line) { +public func Log( + i message: String, tag: String = "SwiftyTeeth", path: String = #file, + function: String = #function, line: Int = #line +) { swiftyTeethLogger?.info("\(tag): \(message)") } -public func Log(w message: String, tag: String = "SwiftyTeeth", path: String = #file, function: String = #function, line: Int = #line) { +public func Log( + w message: String, tag: String = "SwiftyTeeth", path: String = #file, + function: String = #function, line: Int = #line +) { swiftyTeethLogger?.warning("\(tag): \(message)") } -public func Log(e message: String, tag: String = "SwiftyTeeth", path: String = #file, function: String = #function, line: Int = #line) { +public func Log( + e message: String, tag: String = "SwiftyTeeth", path: String = #file, + function: String = #function, line: Int = #line +) { swiftyTeethLogger?.error("\(tag): \(message)") } diff --git a/Sources/SwiftyTeeth/SwiftyTeethable.swift b/Sources/SwiftyTeeth/SwiftyTeethable.swift index 3bb8e0e..10251f6 100644 --- a/Sources/SwiftyTeeth/SwiftyTeethable.swift +++ b/Sources/SwiftyTeeth/SwiftyTeethable.swift @@ -14,7 +14,7 @@ public protocol SwiftyTeethable { } extension SwiftyTeethable { - + /// Central and peripheral bluetooth manager. public var swiftyTeeth: SwiftyTeeth { return SwiftyTeeth.shared diff --git a/Sources/SwiftyTooth/SwiftyTooth.swift b/Sources/SwiftyTooth/SwiftyTooth.swift index 15d23cf..3c6670a 100644 --- a/Sources/SwiftyTooth/SwiftyTooth.swift +++ b/Sources/SwiftyTooth/SwiftyTooth.swift @@ -18,7 +18,7 @@ private struct QueueItem { open class SwiftyTooth: NSObject { public static let shared = SwiftyTooth() - + // Only allow a single connected Central right now // Seems like connectivity needs to be inferred, there isn't an "isConnected" anywhere // This is just used to determine if we should notify out values to a subscribed central @@ -28,10 +28,10 @@ open class SwiftyTooth: NSObject { fileprivate var readHandlers = [UUID: ReadHandler]() fileprivate var writeHandlers = [UUID: WriteHandler]() fileprivate var writeNoResponseHandlers = [UUID: WriteNoResponseHandler]() - + // Using a very quick thread-unsafe queue just to test this out conceptually, to see how it works fileprivate var notificationQueue = Deque() - + public var stateChangedHandler: ((BluetoothState) -> Void)? { didSet { stateChangedHandler?(state) @@ -49,13 +49,13 @@ open class SwiftyTooth: NSObject { public var state: BluetoothState { return BluetoothState(rawValue: peripheralManager.state.rawValue) ?? .unknown } - + public override init() { } } -public extension SwiftyTooth { - class var logger: Logger? { +extension SwiftyTooth { + public class var logger: Logger? { get { return swiftyToothLogger } @@ -66,71 +66,71 @@ public extension SwiftyTooth { } // MARK: - SwiftyTooth Advertise functions -public extension SwiftyTooth { +extension SwiftyTooth { - var isAdvertising: Bool { + public var isAdvertising: Bool { return peripheralManager.isAdvertising } - - func advertise(name: String, uuids: [CBUUID] = [], for timeout: TimeInterval = 0) { + + public func advertise(name: String, uuids: [CBUUID] = [], for timeout: TimeInterval = 0) { DispatchQueue.main.asyncAfter(deadline: .now() + timeout) { self.stopAdvertising() } advertise(name: name, uuids: uuids) } - - func advertise(name: String, uuids: [CBUUID] = []) { + + public func advertise(name: String, uuids: [CBUUID] = []) { peripheralManager.startAdvertising([ - CBAdvertisementDataLocalNameKey:name, - CBAdvertisementDataServiceUUIDsKey: uuids + CBAdvertisementDataLocalNameKey: name, + CBAdvertisementDataServiceUUIDsKey: uuids, ]) } - - func stopAdvertising() { + + public func stopAdvertising() { peripheralManager.stopAdvertising() } } // MARK: - SwiftyTooth Service/Characteristics -public extension SwiftyTooth { - func add(service: Service) { +extension SwiftyTooth { + public func add(service: Service) { let cbService = CBMutableService(type: CBUUID(nsuuid: service.uuid), primary: true) cbService.characteristics = [] - + for char in service.characteristics { var cbProperties = CBCharacteristicProperties() var cbAttributePermissions = CBAttributePermissions() - + for prop in char.properties { - switch(prop) { + switch prop { case .read(let handler): cbProperties.update(with: .read) cbAttributePermissions.update(with: .readable) readHandlers[char.uuid] = handler - + case .notify(let handler): cbProperties.update(with: .notify) cbAttributePermissions.update(with: .readable) notifyHandlers[char.uuid] = handler - + case .write(let handler): cbProperties.update(with: .write) cbAttributePermissions.update(with: .writeable) writeHandlers[char.uuid] = handler - + case .writeNoResponse(let handler): cbProperties.update(with: .writeWithoutResponse) cbAttributePermissions.update(with: .writeable) writeNoResponseHandlers[char.uuid] = handler } } - + let cbChar = CBMutableCharacteristic( type: CBUUID(nsuuid: char.uuid), properties: cbProperties, value: nil, permissions: cbAttributePermissions) - + cbService.characteristics?.append(cbChar) characteristics.append(cbChar) } @@ -140,42 +140,45 @@ public extension SwiftyTooth { } // MARK: - SwiftyTooth Emitting Data -public extension SwiftyTooth { +extension SwiftyTooth { private func emitQueuedItems() { while notificationQueue.count != 0 { guard let item = notificationQueue.dequeue() else { return } - if peripheralManager.updateValue(item.data, for: item.characteristic, onSubscribedCentrals: nil) == false { + if peripheralManager.updateValue( + item.data, for: item.characteristic, onSubscribedCentrals: nil) == false + { notificationQueue.enqueueFront(item) return } } } - + // Sends data to all subscribed centrals for this characteristic - func emit(data: Data, on characteristic: Characteristic) -> Bool { - guard let mutableCharacteristic = characteristics.first(where: { (char) -> Bool in - char.uuid.uuidString == characteristic.uuid.uuidString - }) else { + public func emit(data: Data, on characteristic: Characteristic) -> Bool { + guard + let mutableCharacteristic = characteristics.first(where: { (char) -> Bool in + char.uuid.uuidString == characteristic.uuid.uuidString + }) + else { Log(w: "No such characteristic found with UUID \(characteristic.uuid)") return false } - + notificationQueue.enqueue(QueueItem(data: data, characteristic: mutableCharacteristic)) emitQueuedItems() return true } } - // MARK: - Peripheral manager extension SwiftyTooth: CBPeripheralManagerDelegate { - + // MARK: - Peripheral Manager State Changes public func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { - switch (peripheral.state) { + switch peripheral.state { case .unknown: Log(i: "Bluetooth state is unknown.") case .resetting: @@ -191,57 +194,77 @@ extension SwiftyTooth: CBPeripheralManagerDelegate { default: Log(i: "Bluetooth state is not in supported switches") } - + guard let state = BluetoothState(rawValue: peripheral.state.rawValue) else { return } stateChangedHandler?(state) } - - public func peripheralManager(_ peripheral: CBPeripheralManager, willRestoreState dict: [String : Any]) { + + public func peripheralManager( + _ peripheral: CBPeripheralManager, willRestoreState dict: [String: Any] + ) { Log(v: "Will restore state") } - + // MARK: - Peripheral Manager Services - - public func peripheralManager(_ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error?) { + + public func peripheralManager( + _ peripheral: CBPeripheralManager, didAdd service: CBService, error: Error? + ) { Log(v: "didAdd \(service.uuid.uuidString)") } - + // MARK: - Peripheral Manager Advertisments - - public func peripheralManagerDidStartAdvertising(_ peripheral: CBPeripheralManager, error: Error?) { + + public func peripheralManagerDidStartAdvertising( + _ peripheral: CBPeripheralManager, error: Error? + ) { Log(v: "Started advertising with \(String(describing: error))") } - + // MARK: - Peripheral Manager Characteristic Subscriptions - public func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didSubscribeTo characteristic: CBCharacteristic) { - Log(v: "didSubscribeTo \(characteristic.uuid.uuidString) \(central.maximumUpdateValueLength)") + public func peripheralManager( + _ peripheral: CBPeripheralManager, central: CBCentral, + didSubscribeTo characteristic: CBCharacteristic + ) { + Log( + v: + "didSubscribeTo \(characteristic.uuid.uuidString) \(central.maximumUpdateValueLength)" + ) } - - public func peripheralManager(_ peripheral: CBPeripheralManager, central: CBCentral, didUnsubscribeFrom characteristic: CBCharacteristic) { + + public func peripheralManager( + _ peripheral: CBPeripheralManager, central: CBCentral, + didUnsubscribeFrom characteristic: CBCharacteristic + ) { Log(v: "didUnsubscribeFrom \(characteristic.uuid.uuidString)") } - + public func peripheralManagerIsReady(toUpdateSubscribers peripheral: CBPeripheralManager) { Log(v: "peripheralManagerIsReady: Currently \(notificationQueue.count) items to transmit") emitQueuedItems() } - + // MARK: - Peripheral Manager Read/Write requests - public func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest) { + public func peripheralManager( + _ peripheral: CBPeripheralManager, didReceiveRead request: CBATTRequest + ) { guard let uuid = UUID(cbuuid: request.characteristic.uuid) else { - Log(w: "didReceiveRead: CBATTRequest contained invalid UUID (\(request.characteristic.uuid))") + Log( + w: + "didReceiveRead: CBATTRequest contained invalid UUID (\(request.characteristic.uuid))" + ) peripheralManager.respond(to: request, withResult: .unlikelyError) return } - + guard let handler = readHandlers[uuid] else { Log(w: "didReceiveRead: No associated read handler with UUID \(uuid)") peripheralManager.respond(to: request, withResult: .unlikelyError) return } - + handler { (result) in if let value = try? result.get() { request.value = value @@ -252,35 +275,43 @@ extension SwiftyTooth: CBPeripheralManagerDelegate { } } } - + // TODO: Only works for 1 request right now // TODO: Do we need a "respond" if this is an unacknowledged write? What if it's both ack and unack'd? // Note: If you have acknowledged and unacknowledged writes on the same characteristic, they will BOTH be called (need to review the BLE spec to see how this should be handled) - public func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) { + public func peripheralManager( + _ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest] + ) { guard requests.isEmpty != true else { Log(e: "didReceiveWrite: There are no requests somehow") return } - + if requests.count > 1 { - Log(i: "didReceiveWrite: TODO: Received multiple requests - but only handling first one...") + Log( + i: + "didReceiveWrite: TODO: Received multiple requests - but only handling first one..." + ) } - + // TODO: Handle multiple requests let firstRequest = requests[0] - + guard let uuid = UUID(cbuuid: firstRequest.characteristic.uuid) else { - Log(w: "didReceiveWrite: CBATTRequest contained invalid UUID (\(firstRequest.characteristic.uuid))") + Log( + w: + "didReceiveWrite: CBATTRequest contained invalid UUID (\(firstRequest.characteristic.uuid))" + ) peripheralManager.respond(to: firstRequest, withResult: .unlikelyError) return } - + let noResponseHandler = writeNoResponseHandlers[uuid] noResponseHandler?(firstRequest.value) let handler = writeHandlers[uuid] handler?(firstRequest.value) { (result) in - if ((try? result.get()) != nil) { + if (try? result.get()) != nil { peripheralManager.respond(to: firstRequest, withResult: .success) } else { // TODO: Until there is a better idea, should errors be returned? Or an empty success? diff --git a/Sources/SwiftyTooth/SwiftyToothLogger.swift b/Sources/SwiftyTooth/SwiftyToothLogger.swift index 0a39456..8888f97 100644 --- a/Sources/SwiftyTooth/SwiftyToothLogger.swift +++ b/Sources/SwiftyTooth/SwiftyToothLogger.swift @@ -6,22 +6,37 @@ // // -public func Log(v message: String, tag: String = "SwiftyTooth", path: String = #file, function: String = #function, line: Int = #line) { +public func Log( + v message: String, tag: String = "SwiftyTooth", path: String = #file, + function: String = #function, line: Int = #line +) { swiftyToothLogger?.verbose("\(tag): \(message)") } -public func Log(d message: String, tag: String = "SwiftyTooth", path: String = #file, function: String = #function, line: Int = #line) { +public func Log( + d message: String, tag: String = "SwiftyTooth", path: String = #file, + function: String = #function, line: Int = #line +) { swiftyToothLogger?.debug("\(tag): \(message)") } -public func Log(i message: String, tag: String = "SwiftyTooth", path: String = #file, function: String = #function, line: Int = #line) { +public func Log( + i message: String, tag: String = "SwiftyTooth", path: String = #file, + function: String = #function, line: Int = #line +) { swiftyToothLogger?.info("\(tag): \(message)") } -public func Log(w message: String, tag: String = "SwiftyTooth", path: String = #file, function: String = #function, line: Int = #line) { +public func Log( + w message: String, tag: String = "SwiftyTooth", path: String = #file, + function: String = #function, line: Int = #line +) { swiftyToothLogger?.warning("\(tag): \(message)") } -public func Log(e message: String, tag: String = "SwiftyTooth", path: String = #file, function: String = #function, line: Int = #line) { +public func Log( + e message: String, tag: String = "SwiftyTooth", path: String = #file, + function: String = #function, line: Int = #line +) { swiftyToothLogger?.error("\(tag): \(message)") } diff --git a/SwiftyTeeth-Sample/Extension/Device+Identifiable.swift b/SwiftyTeeth-Sample/Extension/Device+Identifiable.swift index 7e04acd..87dea3e 100644 --- a/SwiftyTeeth-Sample/Extension/Device+Identifiable.swift +++ b/SwiftyTeeth-Sample/Extension/Device+Identifiable.swift @@ -7,8 +7,6 @@ import SwiftyTeeth -import SwiftyTeeth - extension Device: Identifiable { } diff --git a/SwiftyTeeth-Sample/PeripheralView.swift b/SwiftyTeeth-Sample/PeripheralView.swift index 30641fd..a283d0f 100644 --- a/SwiftyTeeth-Sample/PeripheralView.swift +++ b/SwiftyTeeth-Sample/PeripheralView.swift @@ -5,8 +5,8 @@ // Created by SJ on 2020-03-27. // -import SwiftyTeeth import SwiftUI +import SwiftyTeeth final class PeripheralViewModel: ObservableObject { let peripheral: Device @@ -43,11 +43,14 @@ final class PeripheralViewModel: ObservableObject { self.peripheral.discoverServices { (result) in let services = (try? result.get()) ?? [] services.forEach { (service) in - self.log("App: Discovering characteristics for service: \(service.uuid.uuidString)") + self.log( + "App: Discovering characteristics for service: \(service.uuid.uuidString)") self.peripheral.discoverCharacteristics(for: service) { (result) in let characteristics = (try? result.get().characteristics) ?? [] characteristics.forEach { (characteristuc) in - self.log("App: Discovered characteristic: \(characteristuc.uuid.uuidString) in \(String(describing: service.uuid.uuidString))") + self.log( + "App: Discovered characteristic: \(characteristuc.uuid.uuidString) in \(String(describing: service.uuid.uuidString))" + ) } if service.uuid == services.last?.uuid { diff --git a/SwiftyTeeth.xcodeproj/project.pbxproj b/SwiftyTeeth.xcodeproj/project.pbxproj index 9f3ebc9..02d01a2 100644 --- a/SwiftyTeeth.xcodeproj/project.pbxproj +++ b/SwiftyTeeth.xcodeproj/project.pbxproj @@ -518,7 +518,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1430; - LastUpgradeCheck = 1430; + LastUpgradeCheck = 1500; TargetAttributes = { 3324E6422A51D4F600F2C908 = { CreatedOnToolsVersion = 14.3.1; @@ -706,6 +706,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -746,6 +747,7 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -785,6 +787,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 13.3; MARKETING_VERSION = 1.0; @@ -802,6 +805,7 @@ ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; GENERATE_INFOPLIST_FILE = YES; MACOSX_DEPLOYMENT_TARGET = 13.3; MARKETING_VERSION = 1.0; @@ -849,6 +853,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -909,6 +914,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -997,8 +1003,10 @@ isa = XCBuildConfiguration; buildSettings = { ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = VM48BXJYVB; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1034,8 +1042,10 @@ isa = XCBuildConfiguration; buildSettings = { ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = VM48BXJYVB; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/SwiftyTeeth.xcodeproj/xcshareddata/xcschemes/SwiftyTeeth-Sample.xcscheme b/SwiftyTeeth.xcodeproj/xcshareddata/xcschemes/SwiftyTeeth-Sample.xcscheme index 832ab9f..23a476c 100644 --- a/SwiftyTeeth.xcodeproj/xcshareddata/xcschemes/SwiftyTeeth-Sample.xcscheme +++ b/SwiftyTeeth.xcodeproj/xcshareddata/xcschemes/SwiftyTeeth-Sample.xcscheme @@ -1,6 +1,6 @@