Skip to content

Commit

Permalink
refactor: improve layouts of live query subscription files (#273)
Browse files Browse the repository at this point in the history
* WIP

* improve live query subscription files

* add more publisher docs

* lint
  • Loading branch information
cbaker6 authored Nov 3, 2021
1 parent 72eb7c5 commit fa1e07f
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 84 deletions.
2 changes: 1 addition & 1 deletion .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ coverage:
status:
patch:
default:
target: auto
target: 36
changes: false
project:
default:
Expand Down
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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__
Expand Down
10 changes: 10 additions & 0 deletions ParseSwift.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -878,6 +882,7 @@
705A99F8259807F900B3547F /* ParseFileManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseFileManagerTests.swift; sourceTree = "<group>"; };
705A9A2E25991C1400B3547F /* Fileable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Fileable.swift; sourceTree = "<group>"; };
705D950725BE4C08003EF6F8 /* SubscriptionCallback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionCallback.swift; sourceTree = "<group>"; };
7064369A273313D5007C6461 /* LiveQueryConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveQueryConstants.swift; sourceTree = "<group>"; };
70647E9B259E3A9A004C1004 /* ParseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseType.swift; sourceTree = "<group>"; };
70732C592606CCAD000CAB81 /* ParseObjectCustomObjectIdTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseObjectCustomObjectIdTests.swift; sourceTree = "<group>"; };
707A3BF025B0A4F0000D215C /* ParseAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParseAuthentication.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1379,6 +1384,7 @@
70510AAA259EE23700FEA700 /* LiveQuery */ = {
isa = PBXGroup;
children = (
7064369A273313D5007C6461 /* LiveQueryConstants.swift */,
70510AAB259EE25E00FEA700 /* LiveQuerySocket.swift */,
7003959425A10DFC0052CB31 /* Messages.swift */,
700395A225A119430052CB31 /* Operations.swift */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
56 changes: 56 additions & 0 deletions Sources/ParseSwift/LiveQuery/LiveQueryConstants.swift
Original file line number Diff line number Diff line change
@@ -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<T: ParseObject> {
/// 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<T>) {
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 == <T>(lhs: Event<T>, rhs: Event<T>) -> 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
}
}
84 changes: 8 additions & 76 deletions Sources/ParseSwift/LiveQuery/Subscription.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: ParseObject> {
/// 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<T>) {
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 == <T>(lhs: Event<T>, rhs: Event<T>) -> 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<T: ParseObject>: QueryViewModel<T>, QuerySubscribable {

Expand Down Expand Up @@ -112,7 +67,11 @@ open class Subscription<T: ParseObject>: QueryViewModel<T>, 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<T>.self, from: eventData)
Expand All @@ -131,30 +90,3 @@ open class Subscription<T: ParseObject>: QueryViewModel<T>, 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<T>,
_ 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)
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
}
}
}
}
37 changes: 33 additions & 4 deletions Sources/ParseSwift/LiveQuery/SubscriptionCallback.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ open class SubscriptionCallback<T: ParseObject>: QuerySubscribable {

public var query: Query<T>
public typealias Object = T
fileprivate var eventHandlers: [(Query<T>, Event<T>) -> Void] = []
fileprivate var subscribeHandlers: [(Query<T>, Bool) -> Void] = []
fileprivate var unsubscribeHandlers: [(Query<T>) -> Void] = []
fileprivate var eventHandlers = [(Query<T>, Event<T>) -> Void]()
fileprivate var subscribeHandlers = [(Query<T>, Bool) -> Void]()
fileprivate var unsubscribeHandlers = [(Query<T>) -> Void]()

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

Expand Down Expand Up @@ -58,6 +58,35 @@ open class SubscriptionCallback<T: ParseObject>: 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<T>,
_ 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)
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<T>.self, from: eventData)
Expand Down
4 changes: 3 additions & 1 deletion Sources/ParseSwift/Types/CloudViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: ParseCloud>: CloudObservable {

Expand Down
4 changes: 3 additions & 1 deletion Sources/ParseSwift/Types/QueryViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: ParseObject>: QueryObservable {

Expand Down

0 comments on commit fa1e07f

Please sign in to comment.