From 1f7d18cc31d637657070dd35229e991281248abc Mon Sep 17 00:00:00 2001 From: Nick Romano Date: Wed, 26 Apr 2023 19:56:57 -0700 Subject: [PATCH] Swiftformat --- .swift-version | 1 + .swiftformat | 1 + Package.swift | 38 +- .../ExtensionDelegate.swift | 26 +- .../InterfaceController.swift | 43 +- .../NotificationController.swift | 39 +- .../WatchSync Example/AppDelegate.swift | 24 +- .../WatchSync Example/MyMessage.swift | 4 +- .../WatchSync Example/ViewController.swift | 81 +- .../WatchSync_ExampleTests.swift | 36 +- .../WatchSync/Extensions/Codable+JSON.swift | 28 +- .../Results/FileTransferResult.swift | 18 +- Sources/WatchSync/Results/SendResult.swift | 30 +- .../Results/UpdateContextResult.swift | 18 +- .../ApplicationContextSubscription.swift | 20 +- .../FileTransferSubscription.swift | 20 +- .../Subscriptions/MessageSubscription.swift | 20 +- .../Subscriptions/SubscriptionToken.swift | 8 +- .../SyncableMessageSubscription.swift | 30 +- Sources/WatchSync/SyncableMessage.swift | 9 +- Sources/WatchSync/WatchSync.swift | 861 +++++++++--------- 21 files changed, 676 insertions(+), 679 deletions(-) create mode 100644 .swift-version create mode 100644 .swiftformat diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..3659ea2 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +5.8 diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..322474e --- /dev/null +++ b/.swiftformat @@ -0,0 +1 @@ +--indent 2 diff --git a/Package.swift b/Package.swift index be3c8fa..0ae8f1e 100644 --- a/Package.swift +++ b/Package.swift @@ -2,23 +2,23 @@ import PackageDescription let package = Package( - name: "WatchSync", - platforms: [ - .iOS(.v13), - .watchOS(.v4), - ], - products: [ - .library(name: "WatchSync", targets: ["WatchSync"]), - ], - dependencies: [ - .package(url: "https://github.com/1024jp/GzipSwift", from: "5.0.0"), - ], - targets: [ - .target( - name: "WatchSync", - dependencies: [ - .product(name: "Gzip", package: "GzipSwift"), - ] - ), - ] + name: "WatchSync", + platforms: [ + .iOS(.v13), + .watchOS(.v4), + ], + products: [ + .library(name: "WatchSync", targets: ["WatchSync"]), + ], + dependencies: [ + .package(url: "https://github.com/1024jp/GzipSwift", from: "5.0.0"), + ], + targets: [ + .target( + name: "WatchSync", + dependencies: [ + .product(name: "Gzip", package: "GzipSwift"), + ] + ), + ] ) diff --git a/Sources/WatchSync Example/WatchSync Example WatchKit Extension/ExtensionDelegate.swift b/Sources/WatchSync Example/WatchSync Example WatchKit Extension/ExtensionDelegate.swift index 9dbb8c1..82d4e57 100644 --- a/Sources/WatchSync Example/WatchSync Example WatchKit Extension/ExtensionDelegate.swift +++ b/Sources/WatchSync Example/WatchSync Example WatchKit Extension/ExtensionDelegate.swift @@ -10,21 +10,19 @@ import WatchKit import WatchSync class ExtensionDelegate: NSObject, WKExtensionDelegate { + var subscriptionToken: SubscriptionToken? - var subscriptionToken: SubscriptionToken? - - func applicationDidFinishLaunching() { - - WatchSync.shared.activateSession { error in - if let error = error { - print("Error activating session \(error.localizedDescription)") - return - } - print("Activated") - } + func applicationDidFinishLaunching() { + WatchSync.shared.activateSession { error in + if let error = error { + print("Error activating session \(error.localizedDescription)") + return + } + print("Activated") + } - subscriptionToken = WatchSync.shared.subscribeToMessages(ofType: MyMessage.self) { myMessage in - print(myMessage) - } + subscriptionToken = WatchSync.shared.subscribeToMessages(ofType: MyMessage.self) { myMessage in + print(myMessage) } + } } diff --git a/Sources/WatchSync Example/WatchSync Example WatchKit Extension/InterfaceController.swift b/Sources/WatchSync Example/WatchSync Example WatchKit Extension/InterfaceController.swift index 3d85dc9..747c5da 100644 --- a/Sources/WatchSync Example/WatchSync Example WatchKit Extension/InterfaceController.swift +++ b/Sources/WatchSync Example/WatchSync Example WatchKit Extension/InterfaceController.swift @@ -6,40 +6,39 @@ // Copyright © 2018 Ten Minute Wait. All rights reserved. // -import WatchKit import Foundation +import WatchKit import WatchSync class InterfaceController: WKInterfaceController { + var subscriptionToken: SubscriptionToken? - var subscriptionToken: SubscriptionToken? - - @IBOutlet var receivedLabel: WKInterfaceLabel! + @IBOutlet var receivedLabel: WKInterfaceLabel! - override func awake(withContext context: Any?) { - super.awake(withContext: context) + override func awake(withContext context: Any?) { + super.awake(withContext: context) - receivedLabel.setHidden(true) + receivedLabel.setHidden(true) - subscriptionToken = WatchSync.shared.subscribeToMessages(ofType: MyMessage.self) { [weak self] myMessage in - self?.receivedLabel.setHidden(false) + subscriptionToken = WatchSync.shared.subscribeToMessages(ofType: MyMessage.self) { [weak self] myMessage in + self?.receivedLabel.setHidden(false) - DispatchQueue.main.asyncAfter(deadline: .now() + 5.0, execute: { - self?.receivedLabel.setHidden(true) - }) + DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) { + self?.receivedLabel.setHidden(true) + } - print(String(describing: myMessage.myString), String(describing: myMessage.myDate)) - } + print(String(describing: myMessage.myString), String(describing: myMessage.myDate)) } + } - override func willActivate() { - // This method is called when watch view controller is about to be visible to user - super.willActivate() - } + override func willActivate() { + // This method is called when watch view controller is about to be visible to user + super.willActivate() + } - override func didDeactivate() { - // This method is called when watch view controller is no longer visible - super.didDeactivate() - } + override func didDeactivate() { + // This method is called when watch view controller is no longer visible + super.didDeactivate() + } } diff --git a/Sources/WatchSync Example/WatchSync Example WatchKit Extension/NotificationController.swift b/Sources/WatchSync Example/WatchSync Example WatchKit Extension/NotificationController.swift index 0582b75..41d2d4a 100644 --- a/Sources/WatchSync Example/WatchSync Example WatchKit Extension/NotificationController.swift +++ b/Sources/WatchSync Example/WatchSync Example WatchKit Extension/NotificationController.swift @@ -6,30 +6,29 @@ // Copyright © 2018 Ten Minute Wait. All rights reserved. // -import WatchKit import Foundation import UserNotifications +import WatchKit class NotificationController: WKUserNotificationInterfaceController { + override func willActivate() { + // This method is called when watch view controller is about to be visible to user + super.willActivate() + } - override func willActivate() { - // This method is called when watch view controller is about to be visible to user - super.willActivate() - } - - override func didDeactivate() { - // This method is called when watch view controller is no longer visible - super.didDeactivate() - } + override func didDeactivate() { + // This method is called when watch view controller is no longer visible + super.didDeactivate() + } - /* - override func didReceive(_ notification: UNNotification, withCompletion completionHandler: @escaping (WKUserNotificationInterfaceType) -> Swift.Void) { - // This method is called when a notification needs to be presented. - // Implement it if you use a dynamic notification interface. - // Populate your dynamic notification interface as quickly as possible. - // - // After populating your dynamic notification interface call the completion block. - completionHandler(.custom) - } - */ + /* + override func didReceive(_ notification: UNNotification, withCompletion completionHandler: @escaping (WKUserNotificationInterfaceType) -> Swift.Void) { + // This method is called when a notification needs to be presented. + // Implement it if you use a dynamic notification interface. + // Populate your dynamic notification interface as quickly as possible. + // + // After populating your dynamic notification interface call the completion block. + completionHandler(.custom) + } + */ } diff --git a/Sources/WatchSync Example/WatchSync Example/AppDelegate.swift b/Sources/WatchSync Example/WatchSync Example/AppDelegate.swift index 63386e0..d6b0473 100644 --- a/Sources/WatchSync Example/WatchSync Example/AppDelegate.swift +++ b/Sources/WatchSync Example/WatchSync Example/AppDelegate.swift @@ -11,19 +11,17 @@ import WatchSync @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? - var window: UIWindow? - - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { - - WatchSync.shared.activateSession { error in - if let error = error { - print("Error activating session \(error.localizedDescription)") - return - } - print("Activated") - } - - return true + func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { + WatchSync.shared.activateSession { error in + if let error = error { + print("Error activating session \(error.localizedDescription)") + return + } + print("Activated") } + + return true + } } diff --git a/Sources/WatchSync Example/WatchSync Example/MyMessage.swift b/Sources/WatchSync Example/WatchSync Example/MyMessage.swift index 7d1cb7f..fe42093 100644 --- a/Sources/WatchSync Example/WatchSync Example/MyMessage.swift +++ b/Sources/WatchSync Example/WatchSync Example/MyMessage.swift @@ -13,6 +13,6 @@ import WatchSync Example message */ struct MyMessage: SyncableMessage { - var myString: String? - var myDate: Date? + var myString: String? + var myDate: Date? } diff --git a/Sources/WatchSync Example/WatchSync Example/ViewController.swift b/Sources/WatchSync Example/WatchSync Example/ViewController.swift index 8c3362c..6e6da55 100644 --- a/Sources/WatchSync Example/WatchSync Example/ViewController.swift +++ b/Sources/WatchSync Example/WatchSync Example/ViewController.swift @@ -10,52 +10,51 @@ import UIKit import WatchSync class ViewController: UIViewController { + var subscriptionToken: SubscriptionToken? - var subscriptionToken: SubscriptionToken? + override func viewDidLoad() { + super.viewDidLoad() - override func viewDidLoad() { - super.viewDidLoad() - - subscriptionToken = WatchSync.shared.subscribeToMessages(ofType: MyMessage.self) { myMessage in - print(String(describing: myMessage.myString), String(describing: myMessage.myDate)) - } + subscriptionToken = WatchSync.shared.subscribeToMessages(ofType: MyMessage.self) { myMessage in + print(String(describing: myMessage.myString), String(describing: myMessage.myDate)) } + } - @IBAction func sendMessageButtonPressed(_ sender: Any) { - let myMessage = MyMessage(myString: "Test", myDate: Date()) + @IBAction func sendMessageButtonPressed(_: Any) { + let myMessage = MyMessage(myString: "Test", myDate: Date()) - WatchSync.shared.sendMessage(myMessage) { result in - switch result { - case .failure(let failure): - switch failure { - case .sessionNotActivated: - break - case .watchConnectivityNotAvailable: - break - case .unableToSerializeMessageAsJSON(let error), .unableToCompressMessage(let error): - print(error.localizedDescription) - case .watchAppNotPaired: - break - case .watchAppNotInstalled: - break - case .unhandledError(let error): - print(error.localizedDescription) - case .badPayloadError(let error): - print(error.localizedDescription) - case .failedToDeliver(let error): - let alertController = UIAlertController(title: "✅", message: "Failed to Deliver \(error.localizedDescription)", preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - self.present(alertController, animated: true, completion: nil) - } - case .sent: - let alertController = UIAlertController(title: "✅", message: "Sent!", preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - self.present(alertController, animated: true, completion: nil) - case .delivered: - let alertController = UIAlertController(title: "✅", message: "Delivery Confirmed", preferredStyle: .alert) - alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) - self.present(alertController, animated: true, completion: nil) - } + WatchSync.shared.sendMessage(myMessage) { result in + switch result { + case let .failure(failure): + switch failure { + case .sessionNotActivated: + break + case .watchConnectivityNotAvailable: + break + case let .unableToSerializeMessageAsJSON(error), let .unableToCompressMessage(error): + print(error.localizedDescription) + case .watchAppNotPaired: + break + case .watchAppNotInstalled: + break + case let .unhandledError(error): + print(error.localizedDescription) + case let .badPayloadError(error): + print(error.localizedDescription) + case let .failedToDeliver(error): + let alertController = UIAlertController(title: "✅", message: "Failed to Deliver \(error.localizedDescription)", preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self.present(alertController, animated: true, completion: nil) } + case .sent: + let alertController = UIAlertController(title: "✅", message: "Sent!", preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self.present(alertController, animated: true, completion: nil) + case .delivered: + let alertController = UIAlertController(title: "✅", message: "Delivery Confirmed", preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + self.present(alertController, animated: true, completion: nil) + } } + } } diff --git a/Sources/WatchSync Example/WatchSync ExampleTests/WatchSync_ExampleTests.swift b/Sources/WatchSync Example/WatchSync ExampleTests/WatchSync_ExampleTests.swift index 9fb876d..802c3e4 100644 --- a/Sources/WatchSync Example/WatchSync ExampleTests/WatchSync_ExampleTests.swift +++ b/Sources/WatchSync Example/WatchSync ExampleTests/WatchSync_ExampleTests.swift @@ -6,29 +6,29 @@ // Copyright © 2018 Ten Minute Wait. All rights reserved. // -import XCTest @testable import WatchSync_Example +import XCTest class WatchSyncExampleTests: XCTestCase { - override func setUp() { - super.setUp() - // Put setup code here. This method is called before the invocation of each test method in the class. - } + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } - override func tearDown() { - // Put teardown code here. This method is called after the invocation of each test method in the class. - super.tearDown() - } + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } - func testExample() { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - } + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } - func testPerformanceExample() { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } + func testPerformanceExample() { + // This is an example of a performance test case. + measure { + // Put the code you want to measure the time of here. } + } } diff --git a/Sources/WatchSync/Extensions/Codable+JSON.swift b/Sources/WatchSync/Extensions/Codable+JSON.swift index bc44fb9..95c2076 100644 --- a/Sources/WatchSync/Extensions/Codable+JSON.swift +++ b/Sources/WatchSync/Extensions/Codable+JSON.swift @@ -9,23 +9,23 @@ import Foundation extension Encodable { - func toJSONData() throws -> Data { - let encoder = JSONEncoder() - return try encoder.encode(self) - } + func toJSONData() throws -> Data { + let encoder = JSONEncoder() + return try encoder.encode(self) + } - func toJSONString() throws -> String { - return String(data: try toJSONData(), encoding: .utf8)! - } + func toJSONString() throws -> String { + String(data: try toJSONData(), encoding: .utf8)! + } } extension Decodable { - static func fromJSONData(_ data: Data) throws -> Self { - let decoder = JSONDecoder() - return try decoder.decode(Self.self, from: data) - } + static func fromJSONData(_ data: Data) throws -> Self { + let decoder = JSONDecoder() + return try decoder.decode(Self.self, from: data) + } - static func fromJSONString(_ string: String) throws -> Self { - return try fromJSONData(string.data(using: .utf8)!) - } + static func fromJSONString(_ string: String) throws -> Self { + try fromJSONData(string.data(using: .utf8)!) + } } diff --git a/Sources/WatchSync/Results/FileTransferResult.swift b/Sources/WatchSync/Results/FileTransferResult.swift index 5e385bd..0035e54 100644 --- a/Sources/WatchSync/Results/FileTransferResult.swift +++ b/Sources/WatchSync/Results/FileTransferResult.swift @@ -10,20 +10,20 @@ import Foundation public typealias FileTransferCallback = (FileTransferResult) -> Void public enum FileTransferFailure { - /// `WatchSync.shared.activateSession()` must finish before transfering files - case sessionNotActivated - case watchConnectivityNotAvailable + /// `WatchSync.shared.activateSession()` must finish before transfering files + case sessionNotActivated + case watchConnectivityNotAvailable - #if os(iOS) + #if os(iOS) case watchAppNotPaired case watchAppNotInstalled - #endif + #endif - case failedToSend(Error) + case failedToSend(Error) } public enum FileTransferResult { - case failure(FileTransferFailure) - case sent - case delivered + case failure(FileTransferFailure) + case sent + case delivered } diff --git a/Sources/WatchSync/Results/SendResult.swift b/Sources/WatchSync/Results/SendResult.swift index 4867392..e8b9620 100644 --- a/Sources/WatchSync/Results/SendResult.swift +++ b/Sources/WatchSync/Results/SendResult.swift @@ -10,28 +10,28 @@ import Foundation public typealias SendResultCallback = (SendResult) -> Void public enum SendResultFailure { - /// `WatchSync.shared.activateSession()` must finish before sending messages - case sessionNotActivated - case watchConnectivityNotAvailable - /// The `WatchSyncable` message could not be encoded as JSON - case unableToSerializeMessageAsJSON(Error) - case unableToCompressMessage(Error) + /// `WatchSync.shared.activateSession()` must finish before sending messages + case sessionNotActivated + case watchConnectivityNotAvailable + /// The `WatchSyncable` message could not be encoded as JSON + case unableToSerializeMessageAsJSON(Error) + case unableToCompressMessage(Error) - #if os(iOS) + #if os(iOS) case watchAppNotPaired case watchAppNotInstalled - #endif + #endif - /// Can be timeouts or general connectivity failures, could retry - case failedToDeliver(Error) + /// Can be timeouts or general connectivity failures, could retry + case failedToDeliver(Error) - case unhandledError(Error) - case badPayloadError(Error) + case unhandledError(Error) + case badPayloadError(Error) } /// Return codes for sending a message public enum SendResult { - case failure(SendResultFailure) - case sent - case delivered + case failure(SendResultFailure) + case sent + case delivered } diff --git a/Sources/WatchSync/Results/UpdateContextResult.swift b/Sources/WatchSync/Results/UpdateContextResult.swift index e3b2de9..611498e 100644 --- a/Sources/WatchSync/Results/UpdateContextResult.swift +++ b/Sources/WatchSync/Results/UpdateContextResult.swift @@ -10,20 +10,20 @@ import Foundation public typealias UpdateContextCallback = (UpdateContextResult) -> Void public enum UpdateContextFailure { - /// `WatchSync.shared.activateSession()` must finish before updating application context - case sessionNotActivated - case watchConnectivityNotAvailable + /// `WatchSync.shared.activateSession()` must finish before updating application context + case sessionNotActivated + case watchConnectivityNotAvailable - #if os(iOS) + #if os(iOS) case watchAppNotPaired case watchAppNotInstalled - #endif + #endif - case unhandledError(Error) - case badPayloadError(Error) + case unhandledError(Error) + case badPayloadError(Error) } public enum UpdateContextResult { - case failure(UpdateContextFailure) - case success + case failure(UpdateContextFailure) + case success } diff --git a/Sources/WatchSync/Subscriptions/ApplicationContextSubscription.swift b/Sources/WatchSync/Subscriptions/ApplicationContextSubscription.swift index a84c75e..fc81ade 100644 --- a/Sources/WatchSync/Subscriptions/ApplicationContextSubscription.swift +++ b/Sources/WatchSync/Subscriptions/ApplicationContextSubscription.swift @@ -10,17 +10,17 @@ import Foundation public typealias ApplicationContextListener = ([String: Any]) -> Void class ApplicationContextSubscription { - private var callback: ApplicationContextListener? - private var dispatchQueue: DispatchQueue + private var callback: ApplicationContextListener? + private var dispatchQueue: DispatchQueue - func callCallback(_ message: [String: Any]) { - dispatchQueue.async { [weak self] in - self?.callback?(message) - } + func callCallback(_ message: [String: Any]) { + dispatchQueue.async { [weak self] in + self?.callback?(message) } + } - init(callback: ApplicationContextListener?, dispatchQueue: DispatchQueue) { - self.callback = callback - self.dispatchQueue = dispatchQueue - } + init(callback: ApplicationContextListener?, dispatchQueue: DispatchQueue) { + self.callback = callback + self.dispatchQueue = dispatchQueue + } } diff --git a/Sources/WatchSync/Subscriptions/FileTransferSubscription.swift b/Sources/WatchSync/Subscriptions/FileTransferSubscription.swift index 784f3a6..e379ec2 100644 --- a/Sources/WatchSync/Subscriptions/FileTransferSubscription.swift +++ b/Sources/WatchSync/Subscriptions/FileTransferSubscription.swift @@ -11,17 +11,17 @@ import WatchConnectivity public typealias FileTransferListener = (WCSessionFile) -> Void class FileTransferSubscription { - private var callback: FileTransferListener? - private var dispatchQueue: DispatchQueue + private var callback: FileTransferListener? + private var dispatchQueue: DispatchQueue - func callCallback(_ message: WCSessionFile) { - dispatchQueue.async { [weak self] in - self?.callback?(message) - } + func callCallback(_ message: WCSessionFile) { + dispatchQueue.async { [weak self] in + self?.callback?(message) } + } - init(callback: FileTransferListener?, dispatchQueue: DispatchQueue) { - self.callback = callback - self.dispatchQueue = dispatchQueue - } + init(callback: FileTransferListener?, dispatchQueue: DispatchQueue) { + self.callback = callback + self.dispatchQueue = dispatchQueue + } } diff --git a/Sources/WatchSync/Subscriptions/MessageSubscription.swift b/Sources/WatchSync/Subscriptions/MessageSubscription.swift index 543c7d9..d7f4522 100644 --- a/Sources/WatchSync/Subscriptions/MessageSubscription.swift +++ b/Sources/WatchSync/Subscriptions/MessageSubscription.swift @@ -10,17 +10,17 @@ import Foundation public typealias MessageListener = ([String: Any]) -> Void class MessageSubscription { - private var callback: MessageListener? - private var dispatchQueue: DispatchQueue + private var callback: MessageListener? + private var dispatchQueue: DispatchQueue - func callCallback(_ message: [String: Any]) { - dispatchQueue.async { [weak self] in - self?.callback?(message) - } + func callCallback(_ message: [String: Any]) { + dispatchQueue.async { [weak self] in + self?.callback?(message) } + } - init(callback: MessageListener?, dispatchQueue: DispatchQueue) { - self.callback = callback - self.dispatchQueue = dispatchQueue - } + init(callback: MessageListener?, dispatchQueue: DispatchQueue) { + self.callback = callback + self.dispatchQueue = dispatchQueue + } } diff --git a/Sources/WatchSync/Subscriptions/SubscriptionToken.swift b/Sources/WatchSync/Subscriptions/SubscriptionToken.swift index 21ee64f..3cb6a7b 100644 --- a/Sources/WatchSync/Subscriptions/SubscriptionToken.swift +++ b/Sources/WatchSync/Subscriptions/SubscriptionToken.swift @@ -9,8 +9,8 @@ import Foundation /// Keep a strong reference to this when you want to continue receiving messages public class SubscriptionToken { - private var object: Any? - init(object: Any) { - self.object = object - } + private var object: Any? + init(object: Any) { + self.object = object + } } diff --git a/Sources/WatchSync/Subscriptions/SyncableMessageSubscription.swift b/Sources/WatchSync/Subscriptions/SyncableMessageSubscription.swift index 77c70c3..bb9c93a 100644 --- a/Sources/WatchSync/Subscriptions/SyncableMessageSubscription.swift +++ b/Sources/WatchSync/Subscriptions/SyncableMessageSubscription.swift @@ -10,25 +10,25 @@ import Foundation public typealias SyncableMessageListener = (T) -> Void protocol SubscriptionCallable: AnyObject { - func callCallback(_ message: SyncableMessage) + func callCallback(_ message: SyncableMessage) } class SyncableMessageSunscription: SubscriptionCallable { - private var callback: SyncableMessageListener? - private var dispatchQueue: DispatchQueue + private var callback: SyncableMessageListener? + private var dispatchQueue: DispatchQueue - func callCallback(_ message: SyncableMessage) { - guard let message = message as? T else { - // Drop message of other types - return - } - dispatchQueue.async { [weak self] in - self?.callback?(message) - } + func callCallback(_ message: SyncableMessage) { + guard let message = message as? T else { + // Drop message of other types + return } - - init(callback: SyncableMessageListener?, dispatchQueue: DispatchQueue) { - self.callback = callback - self.dispatchQueue = dispatchQueue + dispatchQueue.async { [weak self] in + self?.callback?(message) } + } + + init(callback: SyncableMessageListener?, dispatchQueue: DispatchQueue) { + self.callback = callback + self.dispatchQueue = dispatchQueue + } } diff --git a/Sources/WatchSync/SyncableMessage.swift b/Sources/WatchSync/SyncableMessage.swift index f4b53cc..5a6c523 100644 --- a/Sources/WatchSync/SyncableMessage.swift +++ b/Sources/WatchSync/SyncableMessage.swift @@ -18,11 +18,10 @@ import Foundation optional. Otherwise Decodable will throw this error when new fields are added: `"No value associated with key"` from old message data. */ -public protocol SyncableMessage: Codable { -} +public protocol SyncableMessage: Codable {} extension SyncableMessage { - static var messageKey: String { - return String(describing: self) - } + static var messageKey: String { + String(describing: self) + } } diff --git a/Sources/WatchSync/WatchSync.swift b/Sources/WatchSync/WatchSync.swift index 1ab96e7..690b03d 100644 --- a/Sources/WatchSync/WatchSync.swift +++ b/Sources/WatchSync/WatchSync.swift @@ -7,11 +7,10 @@ // import Foundation -import WatchConnectivity import Gzip +import WatchConnectivity -public struct CouldNotActivateError: Error { -} +public struct CouldNotActivateError: Error {} public protocol ErrorLoggingDelegateWatchSync: AnyObject { func logError(_ error: Error) @@ -19,474 +18,478 @@ public protocol ErrorLoggingDelegateWatchSync: AnyObject { /// Singleton to manage phone and watch communication open class WatchSync: NSObject { - public static let shared = WatchSync() - - public weak var errorLoggingDelegate: ErrorLoggingDelegateWatchSync? - - public let session: WCSession? = WCSession.isSupported() ? WCSession.default : nil - - private var activationCallback: ((Error?) -> Void)? - - /// Loop through these when processing an incoming message - /// - /// I would prefer to use a Set here but not sure not sure how - /// to implement the `Hashable` protocol on metatype `Type` - private var registeredMessageTypes: [SyncableMessage.Type] = [] - - /// Weak references to subscriptions for `SyncableMessage` messages - private var syncableMessageSubscriptions = NSPointerArray.weakObjects() - - /// Weak references to subscriptions for `[String: Any]` messages - private var messageSubscriptions = NSPointerArray.weakObjects() - - /// Weak references to subscriptions for applicationContext - private var applicationContextSubscriptions = NSPointerArray.weakObjects() - - /// Weak references to subscriptions for file transfers - private var fileTransferSubscriptions = NSPointerArray.weakObjects() - - /// Store callbacks until we receive a response from didFinish userInfoTransfer - private var userInfoCallbacks: [WCSessionUserInfoTransfer: SendResultCallback?] = [:] - - /// Store callbacks until we receive a response from didFinish fileTransfer - private var fileTransferCallbacks: [WCSessionFileTransfer: FileTransferCallback?] = [:] - - /// Called when launching the app for the first time to setup Watch Connectivity - /// - /// - Parameter activationCallback: Closure called when activation has finished. - public func activateSession(activationCallback: @escaping (Error?) -> Void) { - self.activationCallback = activationCallback - session?.delegate = self - session?.activate() + public static let shared = WatchSync() + + public weak var errorLoggingDelegate: ErrorLoggingDelegateWatchSync? + + public let session: WCSession? = WCSession.isSupported() ? WCSession.default : nil + + private var activationCallback: ((Error?) -> Void)? + + /// Loop through these when processing an incoming message + /// + /// I would prefer to use a Set here but not sure not sure how + /// to implement the `Hashable` protocol on metatype `Type` + private var registeredMessageTypes: [SyncableMessage.Type] = [] + + /// Weak references to subscriptions for `SyncableMessage` messages + private var syncableMessageSubscriptions = NSPointerArray.weakObjects() + + /// Weak references to subscriptions for `[String: Any]` messages + private var messageSubscriptions = NSPointerArray.weakObjects() + + /// Weak references to subscriptions for applicationContext + private var applicationContextSubscriptions = NSPointerArray.weakObjects() + + /// Weak references to subscriptions for file transfers + private var fileTransferSubscriptions = NSPointerArray.weakObjects() + + /// Store callbacks until we receive a response from didFinish userInfoTransfer + private var userInfoCallbacks: [WCSessionUserInfoTransfer: SendResultCallback?] = [:] + + /// Store callbacks until we receive a response from didFinish fileTransfer + private var fileTransferCallbacks: [WCSessionFileTransfer: FileTransferCallback?] = [:] + + /// Called when launching the app for the first time to setup Watch Connectivity + /// + /// - Parameter activationCallback: Closure called when activation has finished. + public func activateSession(activationCallback: @escaping (Error?) -> Void) { + self.activationCallback = activationCallback + session?.delegate = self + session?.activate() + } + + /// Observe messages of Type (Recommended) + /// + /// - Parameters: + /// - ofType: Message that conforms to `WatchSyncable` protocol + /// - queue: Queue to call the callback on. Defaults to `.main` + /// - callback: Closure to be called when receiving a message + /// - Returns: `SubscriptionToken` store this for as long as you would like to receive messages + public func subscribeToMessages(ofType: T.Type, on queue: DispatchQueue = DispatchQueue.main, callback: @escaping SyncableMessageListener) -> SubscriptionToken { + let subscription = SyncableMessageSunscription(callback: callback, dispatchQueue: queue) + + let pointer = Unmanaged.passUnretained(subscription).toOpaque() + syncableMessageSubscriptions.addPointer(pointer) + + if !registeredMessageTypes.contains(where: { watchSyncableType -> Bool in + if watchSyncableType == T.self { + return true + } + return false + }) { + registeredMessageTypes.append(ofType) } - /// Observe messages of Type (Recommended) - /// - /// - Parameters: - /// - ofType: Message that conforms to `WatchSyncable` protocol - /// - queue: Queue to call the callback on. Defaults to `.main` - /// - callback: Closure to be called when receiving a message - /// - Returns: `SubscriptionToken` store this for as long as you would like to receive messages - public func subscribeToMessages(ofType: T.Type, on queue: DispatchQueue = DispatchQueue.main, callback: @escaping SyncableMessageListener) -> SubscriptionToken { - let subscription = SyncableMessageSunscription(callback: callback, dispatchQueue: queue) - - let pointer = Unmanaged.passUnretained(subscription).toOpaque() - syncableMessageSubscriptions.addPointer(pointer) - - if !registeredMessageTypes.contains(where: { watchSyncableType -> Bool in - if watchSyncableType == T.self { - return true - } - return false - }) { - registeredMessageTypes.append(ofType) - } - - return SubscriptionToken(object: subscription) + return SubscriptionToken(object: subscription) + } + + /// Observe messages for all data that is not a `WatchSyncable` message + /// + /// - Parameters: + /// - queue: Queue to call the callback on. Defaults to `.main` + /// - callback: Closure to be called when receiving a message + /// - Returns: `SubscriptionToken` store this for as long as you would like to receive messages + public func subscribeToMessages(on queue: DispatchQueue = DispatchQueue.main, callback: @escaping MessageListener) -> SubscriptionToken { + let rawSubscription = MessageSubscription(callback: callback, dispatchQueue: queue) + + let pointer = Unmanaged.passUnretained(rawSubscription).toOpaque() + messageSubscriptions.addPointer(pointer) + + return SubscriptionToken(object: rawSubscription) + } + + /// Observer application context, also called immediately with the most recently received context + /// + /// - Parameters: + /// - queue: Queue to call the callback on. Defaults to `.main` + /// - callback: Closure to be called when receiving an application context + /// - Returns: `SubscriptionToken` store this for as long as you would like to application contexts + public func subscribeToApplicationContext(on queue: DispatchQueue = DispatchQueue.main, callback: @escaping ApplicationContextListener) -> SubscriptionToken { + let rawSubscription = ApplicationContextSubscription(callback: callback, dispatchQueue: queue) + + let pointer = Unmanaged.passUnretained(rawSubscription).toOpaque() + applicationContextSubscriptions.addPointer(pointer) + + // Call immediately on the most recently received app context + if let session = session { + callback(session.receivedApplicationContext) } - /// Observe messages for all data that is not a `WatchSyncable` message - /// - /// - Parameters: - /// - queue: Queue to call the callback on. Defaults to `.main` - /// - callback: Closure to be called when receiving a message - /// - Returns: `SubscriptionToken` store this for as long as you would like to receive messages - public func subscribeToMessages(on queue: DispatchQueue = DispatchQueue.main, callback: @escaping MessageListener) -> SubscriptionToken { - let rawSubscription = MessageSubscription(callback: callback, dispatchQueue: queue) - - let pointer = Unmanaged.passUnretained(rawSubscription).toOpaque() - messageSubscriptions.addPointer(pointer) - - return SubscriptionToken(object: rawSubscription) + return SubscriptionToken(object: rawSubscription) + } + + public func subscribeToFileTransfers(on queue: DispatchQueue = DispatchQueue.main, callback: @escaping FileTransferListener) -> SubscriptionToken { + let rawSubscription = FileTransferSubscription(callback: callback, dispatchQueue: queue) + + let pointer = Unmanaged.passUnretained(rawSubscription).toOpaque() + fileTransferSubscriptions.addPointer(pointer) + + return SubscriptionToken(object: rawSubscription) + } + + /// Send a `WatchSyncable` message to the paired device (Recommended) + /// + /// The data is JSON serialized to reduce the size of the payload. + /// If the device is reachable it will send it in realtime. + /// If the device is not reachable, it will store it in a queue to be received later. + /// + /// ``` + /// WatchSync.shared.sendMessage(myMessage) { result in + /// // switch on result + /// } + /// ``` + /// + /// - Parameters: + /// - message: object that conforms to `WatchSyncable` + /// - completion: Closure that provides a `SendResult` describing the status of the message + public func sendMessage(_ message: SyncableMessage, completion: SendResultCallback?) { + // Package message for sending + let messageData: Data + do { + messageData = try message.toJSONData() + } catch { + completion?(.failure(.unableToSerializeMessageAsJSON(error))) + return } - /// Observer application context, also called immediately with the most recently received context - /// - /// - Parameters: - /// - queue: Queue to call the callback on. Defaults to `.main` - /// - callback: Closure to be called when receiving an application context - /// - Returns: `SubscriptionToken` store this for as long as you would like to application contexts - public func subscribeToApplicationContext(on queue: DispatchQueue = DispatchQueue.main, callback: @escaping ApplicationContextListener) -> SubscriptionToken { - let rawSubscription = ApplicationContextSubscription(callback: callback, dispatchQueue: queue) - - let pointer = Unmanaged.passUnretained(rawSubscription).toOpaque() - applicationContextSubscriptions.addPointer(pointer) - - // Call immediately on the most recently received app context - if let session = session { - callback(session.receivedApplicationContext) - } - - return SubscriptionToken(object: rawSubscription) + let optimizedData: Data + do { + optimizedData = try messageData.gzipped(level: .bestCompression) + } catch { + completion?(.failure(.unableToCompressMessage(error))) + return } - public func subscribeToFileTransfers(on queue: DispatchQueue = DispatchQueue.main, callback: @escaping FileTransferListener) -> SubscriptionToken { - let rawSubscription = FileTransferSubscription(callback: callback, dispatchQueue: queue) - - let pointer = Unmanaged.passUnretained(rawSubscription).toOpaque() - fileTransferSubscriptions.addPointer(pointer) - - return SubscriptionToken(object: rawSubscription) + let data: [String: String] = [ + type(of: message).messageKey: optimizedData.base64EncodedString(), + ] + sendMessage(data, completion: completion) + } + + private func transferUserInfo(_ message: [String: Any], in session: WCSession, completion: SendResultCallback?) { + let transfer = session.transferUserInfo(message) + userInfoCallbacks[transfer] = completion + completion?(.sent) + } + + /// Send a dictionary message to the paired device + /// + /// If the device is reachable it will send it in realtime. + /// If the device is not reachable, it will store it in a queue to be received later. + /// + /// ``` + /// WatchSync.shared.sendMessage(["test": "hello"]) { result in + /// // switch on result + /// } + /// ``` + /// + /// - Parameters: + /// - message: object that conforms to `WatchSyncable` + /// - completion: Closure that provides a `SendResult` describing the status of the message + public func sendMessage(_ message: [String: Any], completion: SendResultCallback?) { + guard let session = session else { + completion?(.failure(.watchConnectivityNotAvailable)) + return + } + guard session.activationState == .activated else { + completion?(.failure(.sessionNotActivated)) + return } - /// Send a `WatchSyncable` message to the paired device (Recommended) - /// - /// The data is JSON serialized to reduce the size of the payload. - /// If the device is reachable it will send it in realtime. - /// If the device is not reachable, it will store it in a queue to be received later. - /// - /// ``` - /// WatchSync.shared.sendMessage(myMessage) { result in - /// // switch on result - /// } - /// ``` - /// - /// - Parameters: - /// - message: object that conforms to `WatchSyncable` - /// - completion: Closure that provides a `SendResult` describing the status of the message - public func sendMessage(_ message: SyncableMessage, completion: SendResultCallback?) { - // Package message for sending - let messageData: Data - do { - messageData = try message.toJSONData() - } catch let error { - completion?(.failure(.unableToSerializeMessageAsJSON(error))) - return - } - - let optimizedData: Data - do { - optimizedData = try messageData.gzipped(level: .bestCompression) - } catch let error { - completion?(.failure(.unableToCompressMessage(error))) - return - } + #if os(iOS) + guard session.isPaired else { + completion?(.failure(.watchAppNotPaired)) + return + } + guard session.isWatchAppInstalled else { + completion?(.failure(.watchAppNotInstalled)) + return + } + #endif - let data: [String: String] = [ - type(of: message).messageKey: optimizedData.base64EncodedString() - ] - sendMessage(data, completion: completion) + guard session.isReachable else { + transferUserInfo(message, in: session, completion: completion) + return } - private func transferUserInfo(_ message: [String: Any], in session: WCSession, completion: SendResultCallback?) { - let transfer = session.transferUserInfo(message) - userInfoCallbacks[transfer] = completion - completion?(.sent) + session.sendMessage(message, replyHandler: { _ in + completion?(.delivered) + }, errorHandler: { [weak self] error in + guard let watchError = error as? WCError else { + completion?(.failure(.unhandledError(error))) + return + } + + switch watchError.code { + case .sessionNotSupported, .sessionMissingDelegate, .sessionNotActivated, + .sessionInactive, .deviceNotPaired, .watchAppNotInstalled, .notReachable, .companionAppNotInstalled, .watchOnlyApp: + // Shouldn't reach this state since we handle these above + completion?(.failure(.unhandledError(watchError))) + + case .fileAccessDenied, .insufficientSpace: + // Only relevant for file transfers + completion?(.failure(.unhandledError(watchError))) + + case .genericError: + // Not sure what can throw these + completion?(.failure(.unhandledError(watchError))) + + case .invalidParameter, .payloadTooLarge, .payloadUnsupportedTypes: + // Should be handled before sending again. + completion?(.failure(.badPayloadError(watchError))) + + case .deliveryFailed, .transferTimedOut, .messageReplyTimedOut, .messageReplyFailed: + // Retry sending in the background + self?.transferUserInfo(message, in: session, completion: completion) + @unknown default: + completion?(.failure(.unhandledError(watchError))) + } + }) + } + + /// Update the application context on the paired device. + /// + /// The paired device doesn't need to be reachable but the session must be activated + /// + /// - Parameters: + /// - applicationContext: Dictionary for the paired device + /// - completion: Closure that provides a `UpdateContextResult` describing the status of the update + public func update(applicationContext: [String: Any], completion: UpdateContextCallback?) { + guard let session = session else { + completion?(.failure(.watchConnectivityNotAvailable)) + return } - - /// Send a dictionary message to the paired device - /// - /// If the device is reachable it will send it in realtime. - /// If the device is not reachable, it will store it in a queue to be received later. - /// - /// ``` - /// WatchSync.shared.sendMessage(["test": "hello"]) { result in - /// // switch on result - /// } - /// ``` - /// - /// - Parameters: - /// - message: object that conforms to `WatchSyncable` - /// - completion: Closure that provides a `SendResult` describing the status of the message - public func sendMessage(_ message: [String: Any], completion: SendResultCallback?) { - guard let session = session else { - completion?(.failure(.watchConnectivityNotAvailable)) - return - } - guard session.activationState == .activated else { - completion?(.failure(.sessionNotActivated)) - return - } - - #if os(iOS) - guard session.isPaired else { - completion?(.failure(.watchAppNotPaired)) - return - } - guard session.isWatchAppInstalled else { - completion?(.failure(.watchAppNotInstalled)) - return - } - #endif - - guard session.isReachable else { - transferUserInfo(message, in: session, completion: completion) - return - } - - session.sendMessage(message, replyHandler: { _ in - completion?(.delivered) - }, errorHandler: { [weak self] error in - guard let watchError = error as? WCError else { - completion?(.failure(.unhandledError(error))) - return - } - - switch watchError.code { - case .sessionNotSupported, .sessionMissingDelegate, .sessionNotActivated, - .sessionInactive, .deviceNotPaired, .watchAppNotInstalled, .notReachable, .companionAppNotInstalled, .watchOnlyApp: - // Shouldn't reach this state since we handle these above - completion?(.failure(.unhandledError(watchError))) - - case .fileAccessDenied, .insufficientSpace: - // Only relevant for file transfers - completion?(.failure(.unhandledError(watchError))) - - case .genericError: - // Not sure what can throw these - completion?(.failure(.unhandledError(watchError))) - - case .invalidParameter, .payloadTooLarge, .payloadUnsupportedTypes: - // Should be handled before sending again. - completion?(.failure(.badPayloadError(watchError))) - - case .deliveryFailed, .transferTimedOut, .messageReplyTimedOut, .messageReplyFailed: - // Retry sending in the background - self?.transferUserInfo(message, in: session, completion: completion) - @unknown default: - completion?(.failure(.unhandledError(watchError))) - } - }) + guard session.activationState == .activated else { + completion?(.failure(.sessionNotActivated)) + return } - /// Update the application context on the paired device. - /// - /// The paired device doesn't need to be reachable but the session must be activated - /// - /// - Parameters: - /// - applicationContext: Dictionary for the paired device - /// - completion: Closure that provides a `UpdateContextResult` describing the status of the update - public func update(applicationContext: [String: Any], completion: UpdateContextCallback?) { - guard let session = session else { - completion?(.failure(.watchConnectivityNotAvailable)) - return - } - guard session.activationState == .activated else { - completion?(.failure(.sessionNotActivated)) - return - } - - #if os(iOS) - guard session.isPaired else { - completion?(.failure(.watchAppNotPaired)) - return - } - guard session.isWatchAppInstalled else { - completion?(.failure(.watchAppNotInstalled)) - return - } - #endif + #if os(iOS) + guard session.isPaired else { + completion?(.failure(.watchAppNotPaired)) + return + } + guard session.isWatchAppInstalled else { + completion?(.failure(.watchAppNotInstalled)) + return + } + #endif - do { - try session.updateApplicationContext(applicationContext) - } catch let error { - guard let watchError = error as? WCError else { - completion?(.failure(.unhandledError(error))) - return - } - - switch watchError.code { - case .sessionNotSupported, .sessionMissingDelegate, .sessionNotActivated, .sessionInactive, .deviceNotPaired, .watchAppNotInstalled, .notReachable, .companionAppNotInstalled, .watchOnlyApp: - // Shouldn't reach this state since we handle these above - completion?(.failure(.unhandledError(watchError))) - - case .fileAccessDenied, .insufficientSpace: - // Only relevant for file transfers - completion?(.failure(.unhandledError(watchError))) - - case .deliveryFailed, .transferTimedOut, .messageReplyTimedOut, .messageReplyFailed: - // Only relevant for messages and transfers - completion?(.failure(.unhandledError(watchError))) - - case .genericError: - // Not sure what can throw these - completion?(.failure(.unhandledError(watchError))) - - case .invalidParameter, .payloadTooLarge, .payloadUnsupportedTypes: - // Should be handled before sending again. - completion?(.failure(.badPayloadError(watchError))) - @unknown default: - completion?(.failure(.unhandledError(watchError))) - } - } + do { + try session.updateApplicationContext(applicationContext) + } catch { + guard let watchError = error as? WCError else { + completion?(.failure(.unhandledError(error))) + return + } + + switch watchError.code { + case .sessionNotSupported, .sessionMissingDelegate, .sessionNotActivated, .sessionInactive, .deviceNotPaired, .watchAppNotInstalled, .notReachable, .companionAppNotInstalled, .watchOnlyApp: + // Shouldn't reach this state since we handle these above + completion?(.failure(.unhandledError(watchError))) + + case .fileAccessDenied, .insufficientSpace: + // Only relevant for file transfers + completion?(.failure(.unhandledError(watchError))) + + case .deliveryFailed, .transferTimedOut, .messageReplyTimedOut, .messageReplyFailed: + // Only relevant for messages and transfers + completion?(.failure(.unhandledError(watchError))) + + case .genericError: + // Not sure what can throw these + completion?(.failure(.unhandledError(watchError))) + + case .invalidParameter, .payloadTooLarge, .payloadUnsupportedTypes: + // Should be handled before sending again. + completion?(.failure(.badPayloadError(watchError))) + @unknown default: + completion?(.failure(.unhandledError(watchError))) + } } + } - public func transferFile(file: URL, metadata: [String: Any]?, completion: FileTransferCallback?) { - guard let session = session else { - completion?(.failure(.watchConnectivityNotAvailable)) - return + public func transferFile(file: URL, metadata: [String: Any]?, completion: FileTransferCallback?) { + guard let session = session else { + completion?(.failure(.watchConnectivityNotAvailable)) + return + } + guard session.activationState == .activated else { + completion?(.failure(.sessionNotActivated)) + return + } + let transfer = session.transferFile(file, metadata: metadata) + fileTransferCallbacks[transfer] = completion + completion?(.sent) + } + + /// Entrypoint for all received messages from the paired device. + /// + /// - Parameter message: Paired device message + private func receivedMessage(_ message: [String: Any]) { + var foundMessage = false + // Call all observers based on message types + for messageType in registeredMessageTypes { + guard let messageBase64String = message[messageType.messageKey] as? String else { + continue + } + guard let messageData = Data(base64Encoded: messageBase64String) else { + continue + } + + let decompressedData: Data + if messageData.isGzipped { + do { + decompressedData = try messageData.gunzipped() + } catch { + continue } - guard session.activationState == .activated else { - completion?(.failure(.sessionNotActivated)) - return + } else { + decompressedData = messageData + } + + let watchSyncableMessage: SyncableMessage + do { + watchSyncableMessage = try messageType.fromJSONData(decompressedData) + foundMessage = true + } catch { + errorLoggingDelegate?.logError(error) + continue + } + + // Cleanup the subscriptions from time to time + syncableMessageSubscriptions.compact() + + syncableMessageSubscriptions.allObjects.forEach { subscription in + guard let subscription = subscription as? SubscriptionCallable else { + return } - let transfer = session.transferFile(file, metadata: metadata) - fileTransferCallbacks[transfer] = completion - completion?(.sent) + subscription.callCallback(watchSyncableMessage) + } } - - /// Entrypoint for all received messages from the paired device. - /// - /// - Parameter message: Paired device message - private func receivedMessage(_ message: [String: Any]) { - var foundMessage = false - // Call all observers based on message types - for messageType in registeredMessageTypes { - guard let messageBase64String = message[messageType.messageKey] as? String else { - continue - } - guard let messageData = Data.init(base64Encoded: messageBase64String) else { - continue - } - - let decompressedData: Data - if messageData.isGzipped { - do { - decompressedData = try messageData.gunzipped() - } catch { - continue - } - } else { - decompressedData = messageData - } - - let watchSyncableMessage: SyncableMessage - do { - watchSyncableMessage = try messageType.fromJSONData(decompressedData) - foundMessage = true - } catch let error { - errorLoggingDelegate?.logError(error) - continue - } - - // Cleanup the subscriptions from time to time - syncableMessageSubscriptions.compact() - - syncableMessageSubscriptions.allObjects.forEach { subscription in - guard let subscription = subscription as? SubscriptionCallable else { - return - } - subscription.callCallback(watchSyncableMessage) - } - } - // If there are no message types found, just give the raw payload back - if !foundMessage { - messageSubscriptions.compact() - messageSubscriptions.allObjects.forEach { subscription in - guard let subscription = subscription as? MessageSubscription else { - return - } - subscription.callCallback(message) - } + // If there are no message types found, just give the raw payload back + if !foundMessage { + messageSubscriptions.compact() + messageSubscriptions.allObjects.forEach { subscription in + guard let subscription = subscription as? MessageSubscription else { + return } + subscription.callCallback(message) + } } + } } extension WatchSync: WCSessionDelegate { - // MARK: Watch Activation, multiple devices can be paired and swapped with the phone - public func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { - var error = error - if error == nil && activationState != .activated { - // We should hopefully never end up in this state, if activationState - // isn't activated there should be an error with reason from Apple - error = CouldNotActivateError() - } - activationCallback?(error) + // MARK: Watch Activation, multiple devices can be paired and swapped with the phone + + public func session(_: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) { + var error = error + if error == nil, activationState != .activated { + // We should hopefully never end up in this state, if activationState + // isn't activated there should be an error with reason from Apple + error = CouldNotActivateError() } + activationCallback?(error) + } - #if os(iOS) - public func sessionDidBecomeInactive(_ session: WCSession) {} + #if os(iOS) + public func sessionDidBecomeInactive(_: WCSession) {} // Apple recommends trying to reactivate if the session has switched between devices public func sessionDidDeactivate(_ session: WCSession) { - session.activate() + session.activate() } - #endif + #endif - // MARK: Reachability - public func sessionReachabilityDidChange(_ session: WCSession) {} + // MARK: Reachability - // MARK: Realtime messaging (must be reachable) - public func session(_ session: WCSession, didReceiveMessage message: [String: Any]) { - // All session delegate methods are called on a background thread. - receivedMessage(message) - } + public func sessionReachabilityDidChange(_: WCSession) {} - public func session(_ session: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) { - // All session delegate methods are called on a background thread. - receivedMessage(message) - // Reply handler is always called so the other device get's a confirmation the message was delivered in realtime - replyHandler([:]) - } + // MARK: Realtime messaging (must be reachable) - // MARK: FIFO messaging (queue's with delivery guarantees) - public func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) { - // All session delegate methods are called on a background thread. - receivedMessage(userInfo) - } + public func session(_: WCSession, didReceiveMessage message: [String: Any]) { + // All session delegate methods are called on a background thread. + receivedMessage(message) + } - public func session(_ session: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) { - if let completion = userInfoCallbacks[userInfoTransfer] { - if let error = error { - guard let watchError = error as? WCError else { - completion?(.failure(.unhandledError(error))) - return - } - - switch watchError.code { - case .sessionNotSupported, .sessionMissingDelegate, .sessionNotActivated, .sessionInactive, .deviceNotPaired, .watchAppNotInstalled, .notReachable, .messageReplyTimedOut, .messageReplyFailed, .fileAccessDenied, .insufficientSpace, .companionAppNotInstalled, .watchOnlyApp: - // Not applicable for transfers - completion?(.failure(.unhandledError(watchError))) - - case .deliveryFailed, .transferTimedOut: - completion?(.failure(.failedToDeliver(watchError))) - - case .genericError: - // Not sure what can throw these - completion?(.failure(.unhandledError(watchError))) - - case .invalidParameter, .payloadTooLarge, .payloadUnsupportedTypes: - // Should be handled before sending again. - completion?(.failure(.badPayloadError(watchError))) - @unknown default: - completion?(.failure(.unhandledError(watchError))) - } - } else { - completion?(.delivered) - } - userInfoCallbacks[userInfoTransfer] = nil - } - } + public func session(_: WCSession, didReceiveMessage message: [String: Any], replyHandler: @escaping ([String: Any]) -> Void) { + // All session delegate methods are called on a background thread. + receivedMessage(message) + // Reply handler is always called so the other device get's a confirmation the message was delivered in realtime + replyHandler([:]) + } - /// Entrypoint for application contexts, use `subscribeToApplicationContext` to receive this data - public func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) { - applicationContextSubscriptions.compact() - applicationContextSubscriptions.allObjects.forEach { subscription in - guard let subscription = subscription as? ApplicationContextSubscription else { - return - } - subscription.callCallback(applicationContext) - } - } + // MARK: FIFO messaging (queue's with delivery guarantees) - /// Entrypoint for received file transfers, use `subscribeToFileTransfer` to receive these - public func session(_ session: WCSession, didReceive file: WCSessionFile) { - fileTransferSubscriptions.compact() - fileTransferSubscriptions.allObjects.forEach { subscription in - guard let subscription = subscription as? FileTransferSubscription else { - return - } - subscription.callCallback(file) + public func session(_: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) { + // All session delegate methods are called on a background thread. + receivedMessage(userInfo) + } + + public func session(_: WCSession, didFinish userInfoTransfer: WCSessionUserInfoTransfer, error: Error?) { + if let completion = userInfoCallbacks[userInfoTransfer] { + if let error = error { + guard let watchError = error as? WCError else { + completion?(.failure(.unhandledError(error))) + return } - } - public func session(_ session: WCSession, didFinish fileTransfer: WCSessionFileTransfer, error: Error?) { - if let callback = fileTransferCallbacks[fileTransfer] { - if let error = error { - callback?(.failure(.failedToSend(error))) - } else { - callback?(.delivered) - } + switch watchError.code { + case .sessionNotSupported, .sessionMissingDelegate, .sessionNotActivated, .sessionInactive, .deviceNotPaired, .watchAppNotInstalled, .notReachable, .messageReplyTimedOut, .messageReplyFailed, .fileAccessDenied, .insufficientSpace, .companionAppNotInstalled, .watchOnlyApp: + // Not applicable for transfers + completion?(.failure(.unhandledError(watchError))) + + case .deliveryFailed, .transferTimedOut: + completion?(.failure(.failedToDeliver(watchError))) + + case .genericError: + // Not sure what can throw these + completion?(.failure(.unhandledError(watchError))) + + case .invalidParameter, .payloadTooLarge, .payloadUnsupportedTypes: + // Should be handled before sending again. + completion?(.failure(.badPayloadError(watchError))) + @unknown default: + completion?(.failure(.unhandledError(watchError))) } + } else { + completion?(.delivered) + } + userInfoCallbacks[userInfoTransfer] = nil + } + } + + /// Entrypoint for application contexts, use `subscribeToApplicationContext` to receive this data + public func session(_: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) { + applicationContextSubscriptions.compact() + applicationContextSubscriptions.allObjects.forEach { subscription in + guard let subscription = subscription as? ApplicationContextSubscription else { + return + } + subscription.callCallback(applicationContext) + } + } + + /// Entrypoint for received file transfers, use `subscribeToFileTransfer` to receive these + public func session(_: WCSession, didReceive file: WCSessionFile) { + fileTransferSubscriptions.compact() + fileTransferSubscriptions.allObjects.forEach { subscription in + guard let subscription = subscription as? FileTransferSubscription else { + return + } + subscription.callCallback(file) + } + } + + public func session(_: WCSession, didFinish fileTransfer: WCSessionFileTransfer, error: Error?) { + if let callback = fileTransferCallbacks[fileTransfer] { + if let error = error { + callback?(.failure(.failedToSend(error))) + } else { + callback?(.delivered) + } } + } }