Skip to content

Use ParseLiveQuery Subscription as a SwiftUI view model #65

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 25, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
# Parse-Swift Changelog

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

### 1.1.0
[Full Changelog](https://github.com/parse-community/Parse-Swift/compare/1.0.2...1.1.0)

__New features__
- Enable `ParseFile` for Linux ([#64](https://github.com/parse-community/Parse-Swift/pull/64)), thanks to [jt9897253](https://github.com/jt9897253).
- 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).
- Idempotency support ([#62](https://github.com/parse-community/Parse-Swift/pull/62)), thanks to [Corey Baker](https://github.com/cbaker6).

### 1.0.2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,10 @@ struct GameScore: ParseObject {
//: Create a query just as you normally would.
var query = GameScore.query("score" > 9)

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

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

//: Subscribe to your new query.
let subscription2 = query2.subscribe!
let subscription2 = query2.subscribeCallback!

//: As before, setup your subscription and event handlers.
subscription2.handleSubscribe { subscribedQuery, isNew in
Expand Down
2 changes: 1 addition & 1 deletion ParseSwift.playground/contents.xcplayground
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@
<page name='13 - Operations'/>
<page name='14 - Config'/>
</pages>
</playground>
</playground>
2 changes: 1 addition & 1 deletion ParseSwift.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "ParseSwift"
s.version = "1.0.2"
s.version = "1.1.0"
s.summary = "Parse Pure Swift SDK"
s.homepage = "https://github.com/parse-community/Parse-Swift"
s.authors = {
Expand Down
16 changes: 8 additions & 8 deletions ParseSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2045,7 +2045,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 1.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift;
PRODUCT_NAME = ParseSwift;
SKIP_INSTALL = YES;
Expand All @@ -2067,7 +2067,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 1.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift;
PRODUCT_NAME = ParseSwift;
SKIP_INSTALL = YES;
Expand Down Expand Up @@ -2131,7 +2131,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.13;
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 1.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift;
PRODUCT_NAME = ParseSwift;
SDKROOT = macosx;
Expand All @@ -2155,7 +2155,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks";
MACOSX_DEPLOYMENT_TARGET = 10.13;
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 1.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwift;
PRODUCT_NAME = ParseSwift;
SDKROOT = macosx;
Expand Down Expand Up @@ -2300,7 +2300,7 @@
INFOPLIST_FILE = "ParseSwift-watchOS/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 1.1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-watchOS";
Expand Down Expand Up @@ -2328,7 +2328,7 @@
INFOPLIST_FILE = "ParseSwift-watchOS/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 1.1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-watchOS";
PRODUCT_NAME = ParseSwift;
Expand All @@ -2354,7 +2354,7 @@
INFOPLIST_FILE = "ParseSwift-tvOS/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 1.1.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-tvOS";
Expand All @@ -2381,7 +2381,7 @@
INFOPLIST_FILE = "ParseSwift-tvOS/Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MARKETING_VERSION = 1.0.2;
MARKETING_VERSION = 1.1.0;
MTL_FAST_MATH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "com.parse.ParseSwift-tvOS";
PRODUCT_NAME = ParseSwift;
Expand Down
2 changes: 1 addition & 1 deletion Scripts/jazzy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ bundle exec jazzy \
--author_url http://parseplatform.org \
--github_url https://github.com/parse-community/Parse-Swift \
--root-url http://parseplatform.org/Parse-Swift/api/ \
--module-version 1.0.2 \
--module-version 1.1.0 \
--theme fullwidth \
--skip-undocumented \
--output ./docs/api \
Expand Down
34 changes: 31 additions & 3 deletions Sources/ParseSwift/LiveQuery/ParseLiveQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,10 @@ extension ParseLiveQuery {
try subscribe(Subscription(query: query))
}

func subscribe<T>(_ query: Query<T>) throws -> SubscriptionCallback<T> {
try subscribe(SubscriptionCallback(query: query))
}

func subscribe<T>(_ handler: T) throws -> T where T: ParseSubscription {

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

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

/**
Registers a query for live updates, using a custom subscription handler.
- parameter handler: A custom subscription handler.
- parameter handler: A custom subscription handler.
- returns: Your subscription handler, for easy chaining.
*/
static func subscribe<T: ParseSubscription>(_ handler: T) throws -> T {
Expand All @@ -729,6 +739,24 @@ public extension Query {
static func subscribe<T: ParseSubscription>(_ handler: T, client: ParseLiveQuery) throws -> T {
try client.subscribe(handler)
}

/**
Registers the query for live updates, using the default subscription handler,
and the default `ParseLiveQuery` client.
*/
var subscribeCallback: SubscriptionCallback<ResultType>? {
try? ParseLiveQuery.client?.subscribe(self)
}

/**
Registers the query for live updates, using the default subscription handler,
and a specific `ParseLiveQuery` client.
- parameter client: A specific client.
- returns: The subscription that has just been registered.
*/
func subscribeCallback(_ client: ParseLiveQuery) throws -> SubscriptionCallback<ResultType> {
try client.subscribe(SubscriptionCallback(query: self))
}
}

// MARK: ParseLiveQuery - Unsubscribe
Expand Down
91 changes: 84 additions & 7 deletions Sources/ParseSwift/LiveQuery/Subscription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,85 @@ private func == <T>(lhs: Event<T>, rhs: Event<T>) -> Bool {
}
}

#if !os(Linux)
/**
A default implementation of the `ParseSubscription` protocol, using closures for callbacks.
A default implementation of the `ParseSubscription` protocol. Suitable for `ObjectObserved`
as the subscription can be used as a SwiftUI publisher. Meaning it can serve
indepedently as a ViewModel in MVVM.
*/
open class Subscription<T: ParseObject>: ParseSubscription {
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *)
open class Subscription<T: ParseObject>: ParseSubscription, ObservableObject {
//The query subscribed to.
public var query: Query<T>
//The ParseObject
public typealias Object = T

/// Notifies there's a new event related to a specific query.
private (set) var event: (query: Query<T>, event: Event<T>)? {
willSet {
if newValue != nil {
subscribed = nil
unsubscribed = nil
objectWillChange.send()
}
}
}

/// Notifies when a subscription request has been fulfilled and if it is new.
private (set) var subscribed: (query: Query<T>, isNew: Bool)? {
willSet {
if newValue != nil {
unsubscribed = nil
event = nil
objectWillChange.send()
}
}
}

/// Notifies when an unsubscribe request has been fulfilled.
private (set) var unsubscribed: Query<T>? {
willSet {
if newValue != nil {
subscribed = nil
event = nil
objectWillChange.send()
}
}
}

/**
Creates a new subscription that can be used to handle updates.
*/
public init(query: Query<T>) {
self.query = query
self.subscribed = nil
self.event = nil
self.unsubscribed = nil
}

open func didReceive(_ eventData: Data) throws {
// Need to decode the event with respect to the `ParseObject`.
let eventMessage = try ParseCoding.jsonDecoder().decode(EventResponse<T>.self, from: eventData)
guard let event = Event(event: eventMessage) else {
throw ParseError(code: .unknownError, message: "ParseLiveQuery Error: couldn't create event.")
}
self.event = (query, event)
}

open func didSubscribe(_ new: Bool) {
self.subscribed = (query, new)
}

open func didUnsubscribe() {
self.unsubscribed = query
}
}
#endif

/**
A default implementation of the `ParseSubscription` protocol using closures for callbacks.
*/
open class SubscriptionCallback<T: ParseObject>: ParseSubscription {
//The query subscribed to.
public var query: Query<T>
//The ParseObject
Expand All @@ -80,7 +155,8 @@ open class Subscription<T: ParseObject>: ParseSubscription {
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@discardableResult open func handleEvent(_ handler: @escaping (Query<T>, Event<T>) -> Void) -> Subscription {
@discardableResult open func handleEvent(_ handler: @escaping (Query<T>,
Event<T>) -> Void) -> SubscriptionCallback {
eventHandlers.append(handler)
return self
}
Expand All @@ -90,7 +166,8 @@ open class Subscription<T: ParseObject>: ParseSubscription {
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@discardableResult open func handleSubscribe(_ handler: @escaping (Query<T>, Bool) -> Void) -> Subscription {
@discardableResult open func handleSubscribe(_ handler: @escaping (Query<T>,
Bool) -> Void) -> SubscriptionCallback {
subscribeHandlers.append(handler)
return self
}
Expand All @@ -100,7 +177,7 @@ open class Subscription<T: ParseObject>: ParseSubscription {
- parameter handler: The callback to register.
- returns: The same subscription, for easy chaining.
*/
@discardableResult open func handleUnsubscribe(_ handler: @escaping (Query<T>) -> Void) -> Subscription {
@discardableResult open func handleUnsubscribe(_ handler: @escaping (Query<T>) -> Void) -> SubscriptionCallback {
unsubscribeHandlers.append(handler)
return self
}
Expand All @@ -123,7 +200,7 @@ open class Subscription<T: ParseObject>: ParseSubscription {
}
}

extension Subscription {
extension SubscriptionCallback {

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