diff --git a/.codecov.yml b/.codecov.yml index 7009a7cb2..104773319 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,7 +6,7 @@ coverage: status: patch: default: - target: auto + target: 36 changes: false project: default: diff --git a/CHANGELOG.md b/CHANGELOG.md index 6abd0579b..161e741e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.2.0...main) * _Contributing to this repo? Add info about your change here to be included in the next release_ -### 2.1.0 +### 2.2.0 [Full Changelog](https://github.com/parse-community/Parse-Swift/compare/2.1.0...2.2.0) __Improvements__ diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index cd2b603ab..5db1dc85b 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -293,6 +293,10 @@ 705D950925BE4C08003EF6F8 /* SubscriptionCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705D950725BE4C08003EF6F8 /* SubscriptionCallback.swift */; }; 705D950A25BE4C08003EF6F8 /* SubscriptionCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705D950725BE4C08003EF6F8 /* SubscriptionCallback.swift */; }; 705D950B25BE4C08003EF6F8 /* SubscriptionCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = 705D950725BE4C08003EF6F8 /* SubscriptionCallback.swift */; }; + 7064369B273313D5007C6461 /* LiveQueryConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7064369A273313D5007C6461 /* LiveQueryConstants.swift */; }; + 7064369C273313D5007C6461 /* LiveQueryConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7064369A273313D5007C6461 /* LiveQueryConstants.swift */; }; + 7064369D273313D5007C6461 /* LiveQueryConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7064369A273313D5007C6461 /* LiveQueryConstants.swift */; }; + 7064369E273313D5007C6461 /* LiveQueryConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7064369A273313D5007C6461 /* LiveQueryConstants.swift */; }; 70647E9C259E3A9A004C1004 /* ParseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70647E9B259E3A9A004C1004 /* ParseType.swift */; }; 70647E9D259E3A9A004C1004 /* ParseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70647E9B259E3A9A004C1004 /* ParseType.swift */; }; 70647E9E259E3A9A004C1004 /* ParseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70647E9B259E3A9A004C1004 /* ParseType.swift */; }; @@ -878,6 +882,7 @@ 705A99F8259807F900B3547F /* ParseFileManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileManagerTests.swift; sourceTree = ""; }; 705A9A2E25991C1400B3547F /* Fileable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fileable.swift; sourceTree = ""; }; 705D950725BE4C08003EF6F8 /* SubscriptionCallback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionCallback.swift; sourceTree = ""; }; + 7064369A273313D5007C6461 /* LiveQueryConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveQueryConstants.swift; sourceTree = ""; }; 70647E9B259E3A9A004C1004 /* ParseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseType.swift; sourceTree = ""; }; 70732C592606CCAD000CAB81 /* ParseObjectCustomObjectIdTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseObjectCustomObjectIdTests.swift; sourceTree = ""; }; 707A3BF025B0A4F0000D215C /* ParseAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAuthentication.swift; sourceTree = ""; }; @@ -1379,6 +1384,7 @@ 70510AAA259EE23700FEA700 /* LiveQuery */ = { isa = PBXGroup; children = ( + 7064369A273313D5007C6461 /* LiveQueryConstants.swift */, 70510AAB259EE25E00FEA700 /* LiveQuerySocket.swift */, 7003959425A10DFC0052CB31 /* Messages.swift */, 700395A225A119430052CB31 /* Operations.swift */, @@ -2087,6 +2093,7 @@ 70C550A025B4A9F600B5DBC2 /* RemoveRelation.swift in Sources */, F97B463B24D9C74400F4A88B /* API+Command.swift in Sources */, F97B464624D9C78B00F4A88B /* ParseOperation.swift in Sources */, + 7064369B273313D5007C6461 /* LiveQueryConstants.swift in Sources */, 89899CCF2603CE3A002E2043 /* ParseFacebook.swift in Sources */, 7028373926BD8C89007688C9 /* ParseUser+async.swift in Sources */, 705A9A2F25991C1400B3547F /* Fileable.swift in Sources */, @@ -2300,6 +2307,7 @@ 70C550A125B4A9F600B5DBC2 /* RemoveRelation.swift in Sources */, F97B463C24D9C74400F4A88B /* API+Command.swift in Sources */, F97B464724D9C78B00F4A88B /* ParseOperation.swift in Sources */, + 7064369C273313D5007C6461 /* LiveQueryConstants.swift in Sources */, 89899CD02603CE3A002E2043 /* ParseFacebook.swift in Sources */, 7028373A26BD8C89007688C9 /* ParseUser+async.swift in Sources */, 705A9A3025991C1400B3547F /* Fileable.swift in Sources */, @@ -2609,6 +2617,7 @@ 70C550A325B4A9F600B5DBC2 /* RemoveRelation.swift in Sources */, F97B460D24D9C6F200F4A88B /* Fetchable.swift in Sources */, F97B45ED24D9C6F200F4A88B /* ParseGeoPoint.swift in Sources */, + 7064369E273313D5007C6461 /* LiveQueryConstants.swift in Sources */, 89899CD22603CE3A002E2043 /* ParseFacebook.swift in Sources */, 7028373C26BD8C89007688C9 /* ParseUser+async.swift in Sources */, 705A9A3225991C1400B3547F /* Fileable.swift in Sources */, @@ -2735,6 +2744,7 @@ 70C550A225B4A9F600B5DBC2 /* RemoveRelation.swift in Sources */, F97B460C24D9C6F200F4A88B /* Fetchable.swift in Sources */, F97B45EC24D9C6F200F4A88B /* ParseGeoPoint.swift in Sources */, + 7064369D273313D5007C6461 /* LiveQueryConstants.swift in Sources */, 89899CD12603CE3A002E2043 /* ParseFacebook.swift in Sources */, 7028373B26BD8C89007688C9 /* ParseUser+async.swift in Sources */, 705A9A3125991C1400B3547F /* Fileable.swift in Sources */, diff --git a/Sources/ParseSwift/LiveQuery/LiveQueryConstants.swift b/Sources/ParseSwift/LiveQuery/LiveQueryConstants.swift new file mode 100644 index 000000000..f51777419 --- /dev/null +++ b/Sources/ParseSwift/LiveQuery/LiveQueryConstants.swift @@ -0,0 +1,56 @@ +// +// LiveQueryConstants.swift +// ParseSwift +// +// Created by Corey Baker on 11/3/21. +// Copyright © 2021 Parse Community. All rights reserved. +// + +import Foundation + +/** + Represents an update on a specific object from the `ParseLiveQuery` Server. + - Entered: The object has been updated, and is now included in the query. + - Left: The object has been updated, and is no longer included in the query. + - Created: The object has been created, and is a part of the query. + - Updated: The object has been updated, and is still a part of the query. + - Deleted: The object has been deleted, and is no longer included in the query. + */ +public enum Event { + /// The object has been updated, and is now included in the query. + case entered(T) + + /// The object has been updated, and is no longer included in the query. + case left(T) + + /// The object has been created, and is a part of the query. + case created(T) + + /// The object has been updated, and is still a part of the query. + case updated(T) + + /// The object has been deleted, and is no longer included in the query. + case deleted(T) + + init?(event: EventResponse) { + switch event.op { + case .enter: self = .entered(event.object) + case .leave: self = .left(event.object) + case .create: self = .created(event.object) + case .update: self = .updated(event.object) + case .delete: self = .deleted(event.object) + default: fatalError() + } + } +} + +internal func == (lhs: Event, rhs: Event) -> Bool { + switch (lhs, rhs) { + case (.entered(let obj1), .entered(let obj2)): return obj1 == obj2 + case (.left(let obj1), .left(let obj2)): return obj1 == obj2 + case (.created(let obj1), .created(let obj2)): return obj1 == obj2 + case (.updated(let obj1), .updated(let obj2)): return obj1 == obj2 + case (.deleted(let obj1), .deleted(let obj2)): return obj1 == obj2 + default: return false + } +} diff --git a/Sources/ParseSwift/LiveQuery/Subscription.swift b/Sources/ParseSwift/LiveQuery/Subscription.swift index 958615feb..88789da82 100644 --- a/Sources/ParseSwift/LiveQuery/Subscription.swift +++ b/Sources/ParseSwift/LiveQuery/Subscription.swift @@ -7,60 +7,15 @@ // // +#if canImport(Combine) import Foundation -/** - Represents an update on a specific object from the `ParseLiveQuery` Server. - - Entered: The object has been updated, and is now included in the query. - - Left: The object has been updated, and is no longer included in the query. - - Created: The object has been created, and is a part of the query. - - Updated: The object has been updated, and is still a part of the query. - - Deleted: The object has been deleted, and is no longer included in the query. - */ -public enum Event { - /// The object has been updated, and is now included in the query. - case entered(T) - - /// The object has been updated, and is no longer included in the query. - case left(T) - - /// The object has been created, and is a part of the query. - case created(T) - - /// The object has been updated, and is still a part of the query. - case updated(T) - - /// The object has been deleted, and is no longer included in the query. - case deleted(T) - - init?(event: EventResponse) { - switch event.op { - case .enter: self = .entered(event.object) - case .leave: self = .left(event.object) - case .create: self = .created(event.object) - case .update: self = .updated(event.object) - case .delete: self = .deleted(event.object) - default: fatalError() - } - } -} - -private func == (lhs: Event, rhs: Event) -> Bool { - switch (lhs, rhs) { - case (.entered(let obj1), .entered(let obj2)): return obj1 == obj2 - case (.left(let obj1), .left(let obj2)): return obj1 == obj2 - case (.created(let obj1), .created(let obj2)): return obj1 == obj2 - case (.updated(let obj1), .updated(let obj2)): return obj1 == obj2 - case (.deleted(let obj1), .deleted(let obj2)): return obj1 == obj2 - default: return false - } -} - -#if canImport(Combine) /** A default implementation of the `QuerySubscribable` protocol. Suitable for `ObjectObserved` as the subscription can be used as a SwiftUI publisher. Meaning it can serve - indepedently as a ViewModel in MVVM. + indepedently as a ViewModel in MVVM. Also can be used as a Combine publisher. See Apple's + [documentation](https://developer.apple.com/documentation/combine/observableobject) + for more details. */ open class Subscription: QueryViewModel, QuerySubscribable { @@ -112,7 +67,11 @@ open class Subscription: QueryViewModel, QuerySubscribable { self.event = nil self.unsubscribed = nil } +} +// MARK: QuerySubscribable + +extension Subscription { open func didReceive(_ eventData: Data) throws { // Need to decode the event with respect to the `ParseObject`. let eventMessage = try ParseCoding.jsonDecoder().decode(EventResponse.self, from: eventData) @@ -131,30 +90,3 @@ open class Subscription: QueryViewModel, QuerySubscribable { } } #endif - -extension SubscriptionCallback { - - /** - Register a callback for when an event occurs of a specific type - Example: - subscription.handle(Event.Created) { query, object in - // Called whenever an object is creaated - } - - parameter eventType: The event type to handle. You should pass one of the enum cases in `Event`. - - parameter handler: The callback to register. - - returns: The same subscription, for easy chaining. - */ - @discardableResult public func handle(_ eventType: @escaping (T) -> Event, - _ handler: @escaping (Query, T) -> Void) -> SubscriptionCallback { - return handleEvent { query, event in - switch event { - case .entered(let obj) where eventType(obj) == event: handler(query, obj) - case .left(let obj) where eventType(obj) == event: handler(query, obj) - case .created(let obj) where eventType(obj) == event: handler(query, obj) - case .updated(let obj) where eventType(obj) == event: handler(query, obj) - case .deleted(let obj) where eventType(obj) == event: handler(query, obj) - default: return - } - } - } -} diff --git a/Sources/ParseSwift/LiveQuery/SubscriptionCallback.swift b/Sources/ParseSwift/LiveQuery/SubscriptionCallback.swift index 5c9af3a8c..45349e7ee 100644 --- a/Sources/ParseSwift/LiveQuery/SubscriptionCallback.swift +++ b/Sources/ParseSwift/LiveQuery/SubscriptionCallback.swift @@ -15,14 +15,14 @@ open class SubscriptionCallback: QuerySubscribable { public var query: Query public typealias Object = T - fileprivate var eventHandlers: [(Query, Event) -> Void] = [] - fileprivate var subscribeHandlers: [(Query, Bool) -> Void] = [] - fileprivate var unsubscribeHandlers: [(Query) -> Void] = [] + fileprivate var eventHandlers = [(Query, Event) -> Void]() + fileprivate var subscribeHandlers = [(Query, Bool) -> Void]() + fileprivate var unsubscribeHandlers = [(Query) -> Void]() /** Creates a new subscription that can be used to handle updates. */ - required public init(query: Query) { + public required init(query: Query) { self.query = query } @@ -58,6 +58,35 @@ open class SubscriptionCallback: QuerySubscribable { return self } + /** + Register a callback for when an event occurs of a specific type + Example: + subscription.handle(Event.Created) { query, object in + // Called whenever an object is creaated + } + - parameter eventType: The event type to handle. You should pass one of the enum cases in `Event`. + - parameter handler: The callback to register. + - returns: The same subscription, for easy chaining. + */ + @discardableResult public func handle(_ eventType: @escaping (T) -> Event, + _ handler: @escaping (Query, T) -> Void) -> SubscriptionCallback { + return handleEvent { query, event in + switch event { + case .entered(let obj) where eventType(obj) == event: handler(query, obj) + case .left(let obj) where eventType(obj) == event: handler(query, obj) + case .created(let obj) where eventType(obj) == event: handler(query, obj) + case .updated(let obj) where eventType(obj) == event: handler(query, obj) + case .deleted(let obj) where eventType(obj) == event: handler(query, obj) + default: return + } + } + } + +} + +// MARK: QuerySubscribable + +extension SubscriptionCallback { open func didReceive(_ eventData: Data) throws { // Need to decode the event with respect to the `ParseObject`. let eventMessage = try ParseCoding.jsonDecoder().decode(EventResponse.self, from: eventData) diff --git a/Sources/ParseSwift/Types/CloudViewModel.swift b/Sources/ParseSwift/Types/CloudViewModel.swift index 5607c7d19..2967d1eab 100644 --- a/Sources/ParseSwift/Types/CloudViewModel.swift +++ b/Sources/ParseSwift/Types/CloudViewModel.swift @@ -10,7 +10,9 @@ import Foundation /** A default implementation of the `CloudCodeObservable` protocol. Suitable for `ObjectObserved` - and can be used as a SwiftUI view model. + and can be used as a SwiftUI view model. Also can be used as a Combine publisher. See Apple's + [documentation](https://developer.apple.com/documentation/combine/observableobject) + for more details. */ open class CloudViewModel: CloudObservable { diff --git a/Sources/ParseSwift/Types/QueryViewModel.swift b/Sources/ParseSwift/Types/QueryViewModel.swift index c2b57acb1..b28d7f764 100644 --- a/Sources/ParseSwift/Types/QueryViewModel.swift +++ b/Sources/ParseSwift/Types/QueryViewModel.swift @@ -11,7 +11,9 @@ import Foundation /** A default implementation of the `QueryObservable` protocol. Suitable for `ObjectObserved` - and can be used as a SwiftUI view model. + and can be used as a SwiftUI view model. Also can be used as a Combine publisher. See Apple's + [documentation](https://developer.apple.com/documentation/combine/observableobject) + for more details. */ open class QueryViewModel: QueryObservable {