Jelajahi Sumber

Notification services

Ivan Valkou 5 tahun lalu
induk
melakukan
dfc3981f53

+ 36 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -124,6 +124,11 @@
 		38B17BEE25DD987B005CAE3D /* NightscoutUploadKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 38B17B1A25DD6BBE005CAE3D /* NightscoutUploadKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
 		38B4F3AF25E2979F00E76A18 /* IndexedCollection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38B4F3AE25E2979F00E76A18 /* IndexedCollection.swift */; };
 		38B4F3C325E2A20B00E76A18 /* PumpSetupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38B4F3C225E2A20B00E76A18 /* PumpSetupView.swift */; };
+		38B4F3C625E5017E00E76A18 /* NotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38B4F3C525E5017E00E76A18 /* NotificationCenter.swift */; };
+		38B4F3CA25E502E200E76A18 /* SwiftNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38B4F3C825E502E100E76A18 /* SwiftNotificationCenter.swift */; };
+		38B4F3CB25E502E200E76A18 /* WeakObjectSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38B4F3C925E502E100E76A18 /* WeakObjectSet.swift */; };
+		38B4F3CD25E5031100E76A18 /* Broadcaster.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38B4F3CC25E5031100E76A18 /* Broadcaster.swift */; };
+		38B4F3CF25E5041600E76A18 /* APSContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38B4F3CE25E5041600E76A18 /* APSContainer.swift */; };
 		38FE826A25CC82DB001FF17A /* NetworkService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FE826925CC82DB001FF17A /* NetworkService.swift */; };
 		38FE826D25CC8461001FF17A /* NightscoutAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FE826C25CC8461001FF17A /* NightscoutAPI.swift */; };
 		45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */; };
@@ -591,6 +596,11 @@
 		38B17B6225DD8B5B005CAE3D /* DeviceDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceDataManager.swift; sourceTree = "<group>"; };
 		38B4F3AE25E2979F00E76A18 /* IndexedCollection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IndexedCollection.swift; sourceTree = "<group>"; };
 		38B4F3C225E2A20B00E76A18 /* PumpSetupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpSetupView.swift; sourceTree = "<group>"; };
+		38B4F3C525E5017E00E76A18 /* NotificationCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationCenter.swift; sourceTree = "<group>"; };
+		38B4F3C825E502E100E76A18 /* SwiftNotificationCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftNotificationCenter.swift; sourceTree = "<group>"; };
+		38B4F3C925E502E100E76A18 /* WeakObjectSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakObjectSet.swift; sourceTree = "<group>"; };
+		38B4F3CC25E5031100E76A18 /* Broadcaster.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Broadcaster.swift; sourceTree = "<group>"; };
+		38B4F3CE25E5041600E76A18 /* APSContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APSContainer.swift; sourceTree = "<group>"; };
 		38FE826925CC82DB001FF17A /* NetworkService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkService.swift; sourceTree = "<group>"; };
 		38FE826C25CC8461001FF17A /* NightscoutAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutAPI.swift; sourceTree = "<group>"; };
 		3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigProvider.swift; sourceTree = "<group>"; };
@@ -842,6 +852,7 @@
 		3811DE9125C9D88200A708ED /* Services */ = {
 			isa = PBXGroup;
 			children = (
+				38B4F3C425E5016800E76A18 /* Notifications */,
 				3811DE9225C9D88200A708ED /* Appearance */,
 				3811DE9425C9D88200A708ED /* Network */,
 				3811DE9825C9D88300A708ED /* Storage */,
@@ -925,6 +936,7 @@
 				3811DEC025C9D99900A708ED /* NetworkContainer.swift */,
 				3811DEC125C9D99900A708ED /* StorageContainer.swift */,
 				3811DF0725CAAA4700A708ED /* ServiceContainer.swift */,
+				38B4F3CE25E5041600E76A18 /* APSContainer.swift */,
 			);
 			path = Containers;
 			sourceTree = "<group>";
@@ -1129,6 +1141,25 @@
 			name = Frameworks;
 			sourceTree = "<group>";
 		};
+		38B4F3C425E5016800E76A18 /* Notifications */ = {
+			isa = PBXGroup;
+			children = (
+				38B4F3CC25E5031100E76A18 /* Broadcaster.swift */,
+				38B4F3C525E5017E00E76A18 /* NotificationCenter.swift */,
+				38B4F3C725E502C000E76A18 /* SwiftNotificationCenter */,
+			);
+			path = Notifications;
+			sourceTree = "<group>";
+		};
+		38B4F3C725E502C000E76A18 /* SwiftNotificationCenter */ = {
+			isa = PBXGroup;
+			children = (
+				38B4F3C825E502E100E76A18 /* SwiftNotificationCenter.swift */,
+				38B4F3C925E502E100E76A18 /* WeakObjectSet.swift */,
+			);
+			path = SwiftNotificationCenter;
+			sourceTree = "<group>";
+		};
 		4E8C7B59F8065047ECE20965 /* View */ = {
 			isa = PBXGroup;
 			children = (
@@ -1544,9 +1575,12 @@
 				3811DEB225C9D88300A708ED /* KeychainItemAccessibility.swift in Sources */,
 				3811DE6B25C9D62600A708ED /* OnboardingProvider.swift in Sources */,
 				38B4F3C325E2A20B00E76A18 /* PumpSetupView.swift in Sources */,
+				38B4F3CA25E502E200E76A18 /* SwiftNotificationCenter.swift in Sources */,
 				3811DEC225C9D99900A708ED /* SecurityContainer.swift in Sources */,
+				38B4F3CF25E5041600E76A18 /* APSContainer.swift in Sources */,
 				3895E4C625B9E00D00214B37 /* Preferences.swift in Sources */,
 				3811DE6E25C9D62600A708ED /* OnboardingViewModel.swift in Sources */,
+				38B4F3CD25E5031100E76A18 /* Broadcaster.swift in Sources */,
 				3811DE6D25C9D62600A708ED /* OnboardingRootView.swift in Sources */,
 				3811DE3025C9D49500A708ED /* HomeViewModel.swift in Sources */,
 				3811DF0525CAA62600A708ED /* DependeciesContainer.swift in Sources */,
@@ -1571,6 +1605,7 @@
 				3811DE3125C9D49500A708ED /* HomeProvider.swift in Sources */,
 				388E5A5C25B6F0770019842D /* JSON.swift in Sources */,
 				3811DF0225CA9FEA00A708ED /* Credentials.swift in Sources */,
+				38B4F3C625E5017E00E76A18 /* NotificationCenter.swift in Sources */,
 				3811DEB625C9D88300A708ED /* UnlockManager.swift in Sources */,
 				38A13D3225E28B4B00EAA382 /* PumpHystoryEvent.swift in Sources */,
 				3811DE1825C9D40400A708ED /* Router.swift in Sources */,
@@ -1613,6 +1648,7 @@
 				3811DE6C25C9D62600A708ED /* OnboardingDataFlow.swift in Sources */,
 				3811DE2425C9D48300A708ED /* MainViewModel.swift in Sources */,
 				3811DE3F25C9D4A100A708ED /* SettingsViewModel.swift in Sources */,
+				38B4F3CB25E502E200E76A18 /* WeakObjectSet.swift in Sources */,
 				3811DEB725C9D88300A708ED /* AuthorizationManager.swift in Sources */,
 				3811DF0825CAAA4700A708ED /* ServiceContainer.swift in Sources */,
 				3811DEB025C9D88300A708ED /* BaseKeychain.swift in Sources */,

+ 4 - 3
FreeAPS/Sources/APS/BaseAPSManager.swift

@@ -7,7 +7,8 @@ import Swinject
 
 final class BaseAPSManager: APSManager, Injectable {
     private var openAPS: OpenAPS!
-    @Injected() var deviceDataManager: DeviceDataManager!
+    @Injected() private var deviceDataManager: DeviceDataManager!
+    @Injected() private var notificationCenter: NotificationCenter!
 
     let rileyDisplayStates = CurrentValueSubject<[RileyDisplayState], Never>([])
 
@@ -77,7 +78,7 @@ final class BaseAPSManager: APSManager, Injectable {
 
     private func registerNotifications() {
         // Register for manager notifications
-        NotificationCenter.default.addObserver(
+        notificationCenter.addObserver(
             self,
             selector: #selector(reloadDevices),
             name: .ManagerDevicesDidChange,
@@ -86,7 +87,7 @@ final class BaseAPSManager: APSManager, Injectable {
 
         // Register for device notifications
         for name in [.DeviceConnectionStateDidChange, .DeviceRSSIDidChange, .DeviceNameDidChange] as [Notification.Name] {
-            NotificationCenter.default.addObserver(self, selector: #selector(deviceDidUpdate(_:)), name: name, object: nil)
+            notificationCenter.addObserver(self, selector: #selector(deviceDidUpdate(_:)), name: name, object: nil)
         }
     }
 

+ 1 - 0
FreeAPS/Sources/Application/FreeAPSApp.swift

@@ -3,6 +3,7 @@ import Swinject
 
 private let dependencies: [DependeciesContainer.Type] = [
     ServiceContainer.self,
+    APSContainer.self,
     UIContainer.self,
     StorageContainer.self,
     NetworkContainer.self,

+ 11 - 0
FreeAPS/Sources/Containers/APSContainer.swift

@@ -0,0 +1,11 @@
+import Foundation
+import Swinject
+
+private let resolver = FreeAPSApp.resolver
+
+enum APSContainer: DependeciesContainer {
+    static func register(container: Container) {
+        container.register(DeviceDataManager.self) { _ in BaseDeviceManager() }
+        container.register(APSManager.self) { _ in BaseAPSManager(resolver: resolver) }
+    }
+}

+ 2 - 2
FreeAPS/Sources/Containers/ServiceContainer.swift

@@ -5,7 +5,7 @@ private let resolver = FreeAPSApp.resolver
 
 enum ServiceContainer: DependeciesContainer {
     static func register(container: Container) {
-        container.register(DeviceDataManager.self) { _ in BaseDeviceManager() }
-        container.register(APSManager.self) { _ in BaseAPSManager(resolver: resolver) }
+        container.register(NotificationCenter.self) { _ in Foundation.NotificationCenter.default }
+        container.register(Broadcaster.self) { _ in BaseBroadcaster() }
     }
 }

+ 2 - 2
FreeAPS/Sources/Helpers/ViewModifiers.swift

@@ -63,12 +63,12 @@ struct AdaptsToSoftwareKeyboard: ViewModifier {
             .onAppear(perform: subscribeToKeyboardChanges)
     }
 
-    private let keyboardHeightOnOpening = NotificationCenter.default
+    private let keyboardHeightOnOpening = Foundation.NotificationCenter.default
         .publisher(for: UIResponder.keyboardWillShowNotification)
         .map { $0.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as! CGRect }
         .map(\.height)
 
-    private let keyboardHeightOnHiding = NotificationCenter.default
+    private let keyboardHeightOnHiding = Foundation.NotificationCenter.default
         .publisher(for: UIResponder.keyboardWillHideNotification)
         .map { _ in CGFloat(0) }
 

+ 1 - 1
FreeAPS/Sources/Services/Network/NightscoutAPI.swift

@@ -13,7 +13,7 @@ extension NightscoutAPI {
         struct Check: Codable, Equatable {
             var eventType = "Note"
             var enteredBy = "feeaps-x://"
-            var notes = "FreeAPS connected"
+            var notes = "FreeAPS X connected"
         }
         let check = Check()
         var request = URLRequest(url: url.appendingPathComponent("api/v1/treatments.json"))

+ 27 - 0
FreeAPS/Sources/Services/Notifications/Broadcaster.swift

@@ -0,0 +1,27 @@
+import Foundation
+
+protocol Broadcaster {
+    func register<T>(_ protocolType: T.Type, observer: T)
+    func unregister<T>(_ protocolType: T.Type, observer: T)
+    func unregister<T>(_ protocolType: T.Type)
+    func notify<T>(_ protocolType: T.Type, on queue: DispatchQueue, block: @escaping (T) -> Void)
+}
+
+final class BaseBroadcaster: Broadcaster {
+    func register<T>(_ protocolType: T.Type, observer: T) {
+        SwiftNotificationCenter.register(protocolType, observer: observer)
+    }
+
+    func unregister<T>(_ protocolType: T.Type, observer: T) {
+        SwiftNotificationCenter.unregister(protocolType, observer: observer)
+    }
+
+    func unregister<T>(_ protocolType: T.Type) {
+        SwiftNotificationCenter.unregister(protocolType)
+    }
+
+    func notify<T>(_ protocolType: T.Type, on queue: DispatchQueue, block: @escaping (T) -> Void) {
+        dispatchPrecondition(condition: .onQueue(queue))
+        SwiftNotificationCenter.notify(protocolType, block: block)
+    }
+}

+ 19 - 0
FreeAPS/Sources/Services/Notifications/NotificationCenter.swift

@@ -0,0 +1,19 @@
+import Foundation
+
+protocol NotificationCenter {
+    func addObserver(_ observer: Any, selector aSelector: Selector, name aName: NSNotification.Name?, object anObject: Any?)
+    func post(_ notification: Notification)
+    func post(name aName: NSNotification.Name, object anObject: Any?)
+    func post(name aName: NSNotification.Name, object anObject: Any?, userInfo aUserInfo: [AnyHashable: Any]?)
+    func removeObserver(_ observer: Any)
+    func removeObserver(_ observer: Any, name aName: NSNotification.Name?, object anObject: Any?)
+
+    @discardableResult func addObserver(
+        forName name: NSNotification.Name?,
+        object obj: Any?,
+        queue: OperationQueue?,
+        using block: @escaping (Notification) -> Void
+    ) -> NSObjectProtocol
+}
+
+extension Foundation.NotificationCenter: NotificationCenter {}

+ 75 - 0
FreeAPS/Sources/Services/Notifications/SwiftNotificationCenter/SwiftNotificationCenter.swift

@@ -0,0 +1,75 @@
+import Foundation
+
+public enum SwiftNotificationCenter {
+    fileprivate static var observersDic = [String: Any]()
+
+    fileprivate static let notificationQueue = DispatchQueue(
+        label: "com.swift.notification.center.dispatch.queue",
+        attributes: .concurrent
+    )
+
+    public static func register<T>(_ protocolType: T.Type, observer: T) {
+        let key = "\(protocolType)"
+        safeSet(key: key, object: observer as AnyObject)
+    }
+
+    public static func unregister<T>(_ protocolType: T.Type, observer: T) {
+        let key = "\(protocolType)"
+        safeRemove(key: key, object: observer as AnyObject)
+    }
+
+    /// Remove all observers which comform to the protocol
+    public static func unregister<T>(_ protocolType: T.Type) {
+        let key = "\(protocolType)"
+        safeRemove(key: key)
+    }
+
+    public static func notify<T>(_ protocolType: T.Type, block: (T) -> Void) {
+        let key = "\(protocolType)"
+        guard let objectSet = safeGetObjectSet(key: key) else {
+            return
+        }
+
+        for observer in objectSet {
+            if let observer = observer as? T {
+                block(observer)
+            }
+        }
+    }
+}
+
+private extension SwiftNotificationCenter {
+    static func safeSet(key: String, object: AnyObject) {
+        notificationQueue.async(flags: .barrier) {
+            if var set = observersDic[key] as? WeakObjectSet<AnyObject> {
+                set.add(object)
+                observersDic[key] = set
+            } else {
+                observersDic[key] = WeakObjectSet(object)
+            }
+        }
+    }
+
+    static func safeRemove(key: String, object: AnyObject) {
+        notificationQueue.async(flags: .barrier) {
+            if var set = observersDic[key] as? WeakObjectSet<AnyObject> {
+                set.remove(object)
+                observersDic[key] = set
+            }
+        }
+    }
+
+    static func safeRemove(key: String) {
+        notificationQueue.async(flags: .barrier) {
+            observersDic.removeValue(forKey: key)
+        }
+    }
+
+    static func safeGetObjectSet(key: String) -> WeakObjectSet<AnyObject>? {
+        var objectSet: WeakObjectSet<AnyObject>?
+        notificationQueue.sync {
+            objectSet = observersDic[key] as? WeakObjectSet<AnyObject>
+        }
+        return objectSet
+    }
+}

+ 71 - 0
FreeAPS/Sources/Services/Notifications/SwiftNotificationCenter/WeakObjectSet.swift

@@ -0,0 +1,71 @@
+import Foundation
+
+struct WeakObject<T: AnyObject>: Equatable, Hashable {
+    private let identifier: ObjectIdentifier
+    weak var object: T?
+    init(_ object: T) {
+        self.object = object
+        identifier = ObjectIdentifier(object)
+    }
+
+    func hash(into hasher: inout Hasher) {
+        hasher.combine(identifier)
+    }
+
+    static func == (lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool {
+        lhs.identifier == rhs.identifier
+    }
+}
+
+struct WeakObjectSet<T: AnyObject>: Sequence {
+    var objects: Set<WeakObject<T>>
+
+    init() {
+        objects = Set<WeakObject<T>>([])
+    }
+
+    init(_ object: T) {
+        objects = Set<WeakObject<T>>([WeakObject(object)])
+    }
+
+    init(_ objects: [T]) {
+        self.objects = Set<WeakObject<T>>(objects.map { WeakObject($0) })
+    }
+
+    var allObjects: [T] {
+        objects.compactMap(\.object)
+    }
+
+    func contains(_ object: T) -> Bool {
+        objects.contains(WeakObject(object))
+    }
+
+    mutating func add(_ object: T) {
+        // prevent ObjectIdentifier be reused
+        if contains(object) {
+            remove(object)
+        }
+        objects.insert(WeakObject(object))
+    }
+
+    mutating func add(_ objects: [T]) {
+        objects.forEach { self.add($0) }
+    }
+
+    mutating func remove(_ object: T) {
+        objects.remove(WeakObject<T>(object))
+    }
+
+    mutating func remove(_ objects: [T]) {
+        objects.forEach { self.remove($0) }
+    }
+
+    func makeIterator() -> AnyIterator<T> {
+        let objects = allObjects
+        var index = 0
+        return AnyIterator {
+            defer { index += 1 }
+            return index < objects.count ? objects[index] : nil
+        }
+    }
+}