Skip to content

Commit 39f20d2

Browse files
cbaker6TomWFox
andauthored
Use ParseLiveQuery Subscription as a SwiftUI view model (#65)
* Enable LiveQuery Subscription as a SwiftUI VM * Add changes to changelog * update changelog * Don't use Subscription on Linux * fix jazzy script * Update Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com> Co-authored-by: Tom Fox <13188249+TomWFox@users.noreply.github.com>
1 parent 7474bcb commit 39f20d2

File tree

9 files changed

+656
-45
lines changed

9 files changed

+656
-45
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
# Parse-Swift Changelog
22

33
### main
4-
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.0.2...main)
4+
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.1.0...main)
55
* _Contributing to this repo? Add info about your change here to be included in next release_
66

7+
### 1.1.0
8+
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.0.2...1.1.0)
9+
710
__New features__
11+
- Enable `ParseFile` for Linux ([#64](https://github.com/parse-community/Parse-Swift/pull/64)), thanks to [jt9897253](https://github.com/jt9897253).
12+
- Use a `ParseLiveQuery` subscription as a SwiftUI view model ([#65](https://github.com/parse-community/Parse-Swift/pull/65)), thanks to [Corey Baker](https://github.com/cbaker6).
813
- Idempotency support ([#62](https://github.com/parse-community/Parse-Swift/pull/62)), thanks to [Corey Baker](https://github.com/cbaker6).
914

1015
### 1.0.2

ParseSwift.playground/Pages/11 - LiveQuery.xcplaygroundpage/Contents.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ struct GameScore: ParseObject {
3232
//: Create a query just as you normally would.
3333
var query = GameScore.query("score" > 9)
3434

35-
//: This is how you subscribe your created query
36-
let subscription = query.subscribe!
35+
//: This is how you subscribe to your created query using callbacks.
36+
//: Note that if you want to use subscriptions with SwiftUI, you should
37+
//: use `let subscription = query.subscribe` instead.
38+
let subscription = query.subscribeCallback!
3739

3840
//: This is how you receive notifications about the success
3941
//: of your subscription.
@@ -86,7 +88,7 @@ var query2 = GameScore.query("score" > 50)
8688
query2.fields("score")
8789

8890
//: Subscribe to your new query.
89-
let subscription2 = query2.subscribe!
91+
let subscription2 = query2.subscribeCallback!
9092

9193
//: As before, setup your subscription and event handlers.
9294
subscription2.handleSubscribe { subscribedQuery, isNew in

ParseSwift.playground/contents.xcplayground

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@
1717
<page name='13 - Operations'/>
1818
<page name='14 - Config'/>
1919
</pages>
20-
</playground>
20+
</playground>

ParseSwift.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "ParseSwift"
3-
s.version = "1.0.2"
3+
s.version = "1.1.0"
44
s.summary = "Parse Pure Swift SDK"
55
s.homepage = "https://github.com/parse-community/Parse-Swift"
66
s.authors = {

ParseSwift.xcodeproj/project.pbxproj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2045,7 +2045,7 @@
20452045
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
20462046
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
20472047
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
2048-
MARKETING_VERSION = 1.0.2;
2048+
MARKETING_VERSION = 1.1.0;
20492049
PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift;
20502050
PRODUCT_NAME = ParseSwift;
20512051
SKIP_INSTALL = YES;
@@ -2067,7 +2067,7 @@
20672067
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
20682068
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
20692069
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
2070-
MARKETING_VERSION = 1.0.2;
2070+
MARKETING_VERSION = 1.1.0;
20712071
PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift;
20722072
PRODUCT_NAME = ParseSwift;
20732073
SKIP_INSTALL = YES;
@@ -2131,7 +2131,7 @@
21312131
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
21322132
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
21332133
MACOSX_DEPLOYMENT_TARGET = 10.13;
2134-
MARKETING_VERSION = 1.0.2;
2134+
MARKETING_VERSION = 1.1.0;
21352135
PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift;
21362136
PRODUCT_NAME = ParseSwift;
21372137
SDKROOT = macosx;
@@ -2155,7 +2155,7 @@
21552155
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
21562156
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
21572157
MACOSX_DEPLOYMENT_TARGET = 10.13;
2158-
MARKETING_VERSION = 1.0.2;
2158+
MARKETING_VERSION = 1.1.0;
21592159
PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift;
21602160
PRODUCT_NAME = ParseSwift;
21612161
SDKROOT = macosx;
@@ -2300,7 +2300,7 @@
23002300
INFOPLIST_FILE = "ParseSwift-watchOS/Info.plist";
23012301
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
23022302
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
2303-
MARKETING_VERSION = 1.0.2;
2303+
MARKETING_VERSION = 1.1.0;
23042304
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
23052305
MTL_FAST_MATH = YES;
23062306
PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-watchOS";
@@ -2328,7 +2328,7 @@
23282328
INFOPLIST_FILE = "ParseSwift-watchOS/Info.plist";
23292329
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
23302330
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
2331-
MARKETING_VERSION = 1.0.2;
2331+
MARKETING_VERSION = 1.1.0;
23322332
MTL_FAST_MATH = YES;
23332333
PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-watchOS";
23342334
PRODUCT_NAME = ParseSwift;
@@ -2354,7 +2354,7 @@
23542354
INFOPLIST_FILE = "ParseSwift-tvOS/Info.plist";
23552355
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
23562356
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
2357-
MARKETING_VERSION = 1.0.2;
2357+
MARKETING_VERSION = 1.1.0;
23582358
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
23592359
MTL_FAST_MATH = YES;
23602360
PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-tvOS";
@@ -2381,7 +2381,7 @@
23812381
INFOPLIST_FILE = "ParseSwift-tvOS/Info.plist";
23822382
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
23832383
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
2384-
MARKETING_VERSION = 1.0.2;
2384+
MARKETING_VERSION = 1.1.0;
23852385
MTL_FAST_MATH = YES;
23862386
PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-tvOS";
23872387
PRODUCT_NAME = ParseSwift;

Scripts/jazzy.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ bundle exec jazzy \
55
--author_url http://parseplatform.org \
66
--github_url https://github.com/parse-community/Parse-Swift \
77
--root-url http://parseplatform.org/Parse-Swift/api/ \
8-
--module-version 1.0.2 \
8+
--module-version 1.1.0 \
99
--theme fullwidth \
1010
--skip-undocumented \
1111
--output ./docs/api \

Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,10 @@ extension ParseLiveQuery {
619619
try subscribe(Subscription(query: query))
620620
}
621621

622+
func subscribe<T>(_ query: Query<T>) throws -> SubscriptionCallback<T> {
623+
try subscribe(SubscriptionCallback(query: query))
624+
}
625+
622626
func subscribe<T>(_ handler: T) throws -> T where T: ParseSubscription {
623627

624628
let requestId = requestIdGenerator()
@@ -689,27 +693,33 @@ extension ParseLiveQuery {
689693
// MARK: ParseLiveQuery - Subscribe
690694
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
691695
public extension Query {
696+
#if !os(Linux)
692697
/**
693698
Registers the query for live updates, using the default subscription handler,
694-
and the default `ParseLiveQuery` client.
699+
and the default `ParseLiveQuery` client. Suitable for `ObjectObserved`
700+
as the subscription can be used as a SwiftUI publisher. Meaning it can serve
701+
indepedently as a ViewModel in MVVM.
695702
*/
696703
var subscribe: Subscription<ResultType>? {
697704
try? ParseLiveQuery.client?.subscribe(self)
698705
}
699706

700707
/**
701708
Registers the query for live updates, using the default subscription handler,
702-
and a specific `ParseLiveQuery` client.
709+
and a specific `ParseLiveQuery` client. Suitable for `ObjectObserved`
710+
as the subscription can be used as a SwiftUI publisher. Meaning it can serve
711+
indepedently as a ViewModel in MVVM.
703712
- parameter client: A specific client.
704713
- returns: The subscription that has just been registered
705714
*/
706715
func subscribe(_ client: ParseLiveQuery) throws -> Subscription<ResultType> {
707716
try client.subscribe(Subscription(query: self))
708717
}
718+
#endif
709719

710720
/**
711721
Registers a query for live updates, using a custom subscription handler.
712-
- parameter handler: A custom subscription handler.
722+
- parameter handler: A custom subscription handler.
713723
- returns: Your subscription handler, for easy chaining.
714724
*/
715725
static func subscribe<T: ParseSubscription>(_ handler: T) throws -> T {
@@ -729,6 +739,24 @@ public extension Query {
729739
static func subscribe<T: ParseSubscription>(_ handler: T, client: ParseLiveQuery) throws -> T {
730740
try client.subscribe(handler)
731741
}
742+
743+
/**
744+
Registers the query for live updates, using the default subscription handler,
745+
and the default `ParseLiveQuery` client.
746+
*/
747+
var subscribeCallback: SubscriptionCallback<ResultType>? {
748+
try? ParseLiveQuery.client?.subscribe(self)
749+
}
750+
751+
/**
752+
Registers the query for live updates, using the default subscription handler,
753+
and a specific `ParseLiveQuery` client.
754+
- parameter client: A specific client.
755+
- returns: The subscription that has just been registered.
756+
*/
757+
func subscribeCallback(_ client: ParseLiveQuery) throws -> SubscriptionCallback<ResultType> {
758+
try client.subscribe(SubscriptionCallback(query: self))
759+
}
732760
}
733761

734762
// MARK: ParseLiveQuery - Unsubscribe

Sources/ParseSwift/LiveQuery/Subscription.swift

Lines changed: 84 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,85 @@ private func == <T>(lhs: Event<T>, rhs: Event<T>) -> Bool {
5656
}
5757
}
5858

59+
#if !os(Linux)
5960
/**
60-
A default implementation of the `ParseSubscription` protocol, using closures for callbacks.
61+
A default implementation of the `ParseSubscription` protocol. Suitable for `ObjectObserved`
62+
as the subscription can be used as a SwiftUI publisher. Meaning it can serve
63+
indepedently as a ViewModel in MVVM.
6164
*/
62-
open class Subscription<T: ParseObject>: ParseSubscription {
65+
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
66+
open class Subscription<T: ParseObject>: ParseSubscription, ObservableObject {
67+
//The query subscribed to.
68+
public var query: Query<T>
69+
//The ParseObject
70+
public typealias Object = T
71+
72+
/// Notifies there's a new event related to a specific query.
73+
private (set) var event: (query: Query<T>, event: Event<T>)? {
74+
willSet {
75+
if newValue != nil {
76+
subscribed = nil
77+
unsubscribed = nil
78+
objectWillChange.send()
79+
}
80+
}
81+
}
82+
83+
/// Notifies when a subscription request has been fulfilled and if it is new.
84+
private (set) var subscribed: (query: Query<T>, isNew: Bool)? {
85+
willSet {
86+
if newValue != nil {
87+
unsubscribed = nil
88+
event = nil
89+
objectWillChange.send()
90+
}
91+
}
92+
}
93+
94+
/// Notifies when an unsubscribe request has been fulfilled.
95+
private (set) var unsubscribed: Query<T>? {
96+
willSet {
97+
if newValue != nil {
98+
subscribed = nil
99+
event = nil
100+
objectWillChange.send()
101+
}
102+
}
103+
}
104+
105+
/**
106+
Creates a new subscription that can be used to handle updates.
107+
*/
108+
public init(query: Query<T>) {
109+
self.query = query
110+
self.subscribed = nil
111+
self.event = nil
112+
self.unsubscribed = nil
113+
}
114+
115+
open func didReceive(_ eventData: Data) throws {
116+
// Need to decode the event with respect to the `ParseObject`.
117+
let eventMessage = try ParseCoding.jsonDecoder().decode(EventResponse<T>.self, from: eventData)
118+
guard let event = Event(event: eventMessage) else {
119+
throw ParseError(code: .unknownError, message: "ParseLiveQuery Error: couldn't create event.")
120+
}
121+
self.event = (query, event)
122+
}
123+
124+
open func didSubscribe(_ new: Bool) {
125+
self.subscribed = (query, new)
126+
}
127+
128+
open func didUnsubscribe() {
129+
self.unsubscribed = query
130+
}
131+
}
132+
#endif
133+
134+
/**
135+
A default implementation of the `ParseSubscription` protocol using closures for callbacks.
136+
*/
137+
open class SubscriptionCallback<T: ParseObject>: ParseSubscription {
63138
//The query subscribed to.
64139
public var query: Query<T>
65140
//The ParseObject
@@ -80,7 +155,8 @@ open class Subscription<T: ParseObject>: ParseSubscription {
80155
- parameter handler: The callback to register.
81156
- returns: The same subscription, for easy chaining.
82157
*/
83-
@discardableResult open func handleEvent(_ handler: @escaping (Query<T>, Event<T>) -> Void) -> Subscription {
158+
@discardableResult open func handleEvent(_ handler: @escaping (Query<T>,
159+
Event<T>) -> Void) -> SubscriptionCallback {
84160
eventHandlers.append(handler)
85161
return self
86162
}
@@ -90,7 +166,8 @@ open class Subscription<T: ParseObject>: ParseSubscription {
90166
- parameter handler: The callback to register.
91167
- returns: The same subscription, for easy chaining.
92168
*/
93-
@discardableResult open func handleSubscribe(_ handler: @escaping (Query<T>, Bool) -> Void) -> Subscription {
169+
@discardableResult open func handleSubscribe(_ handler: @escaping (Query<T>,
170+
Bool) -> Void) -> SubscriptionCallback {
94171
subscribeHandlers.append(handler)
95172
return self
96173
}
@@ -100,7 +177,7 @@ open class Subscription<T: ParseObject>: ParseSubscription {
100177
- parameter handler: The callback to register.
101178
- returns: The same subscription, for easy chaining.
102179
*/
103-
@discardableResult open func handleUnsubscribe(_ handler: @escaping (Query<T>) -> Void) -> Subscription {
180+
@discardableResult open func handleUnsubscribe(_ handler: @escaping (Query<T>) -> Void) -> SubscriptionCallback {
104181
unsubscribeHandlers.append(handler)
105182
return self
106183
}
@@ -123,7 +200,7 @@ open class Subscription<T: ParseObject>: ParseSubscription {
123200
}
124201
}
125202

126-
extension Subscription {
203+
extension SubscriptionCallback {
127204

128205
/**
129206
Register a callback for when an event occurs of a specific type
@@ -136,7 +213,7 @@ extension Subscription {
136213
- returns: The same subscription, for easy chaining.
137214
*/
138215
@discardableResult public func handle(_ eventType: @escaping (T) -> Event<T>,
139-
_ handler: @escaping (Query<T>, T) -> Void) -> Subscription {
216+
_ handler: @escaping (Query<T>, T) -> Void) -> SubscriptionCallback {
140217
return handleEvent { query, event in
141218
switch event {
142219
case .entered(let obj) where eventType(obj) == event: handler(query, obj)

0 commit comments

Comments
 (0)