Skip to content
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

Implement Query (part 1) and add improved support for Futures #21

Merged
merged 16 commits into from
Feb 15, 2024
2 changes: 1 addition & 1 deletion Examples/FireBaseUI/FireBaseUIViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ internal final class FireBaseUIViewController: ViewController {
let document = firestore
.collection("users")
.document(user.uid)
let snapshot = try await document.get()
let snapshot = try await document.getDocument()
await MainActor.run { [weak self] in
guard let self else { return }
userDetailsLabel.text = snapshot.debugDescription
Expand Down
4 changes: 2 additions & 2 deletions Examples/FireBaseUI/FirestoreTestingViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ internal final class FirestoreTestingViewController: ViewController {
await buttonsEnabled(false)
let document = Firestore.firestore().document(path)
do {
let snapshot = try await document.get()
await displayData(data: snapshot?.debugDescription ?? "No snapshot")
let snapshot = try await document.getDocument()
await displayData(data: snapshot.debugDescription)
} catch {
await displayError(error: error)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/FirebaseAuth/FirebaseAuth+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

@_exported
import firebase
@_spi(Error)
@_spi(Internal)
darinf marked this conversation as resolved.
Show resolved Hide resolved
import FirebaseCore

import CxxShim
Expand Down
2 changes: 1 addition & 1 deletion Sources/FirebaseAuth/FirebaseUser+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Foundation

@_exported
import firebase
@_spi(Error)
@_spi(Internal)
import FirebaseCore

public typealias User = firebase.auth.User
Expand Down
2 changes: 1 addition & 1 deletion Sources/FirebaseCore/FirebaseError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ public struct FirebaseError: Error {
public let code: CInt
public let message: String

@_spi(Error)
@_spi(Internal)
public init(code: CInt, message: String) {
self.code = code
self.message = message
Expand Down
58 changes: 58 additions & 0 deletions Sources/FirebaseCore/FutureProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: BSD-3-Clause

@_exported
import firebase

import Foundation

@_spi(Internal)
public typealias FutureCompletionType = swift_firebase.swift_cxx_shims.firebase.FutureCompletionType
darinf marked this conversation as resolved.
Show resolved Hide resolved

// This protocol enables extracting common code for Future handling. Because
// C++ interop is limited for templated class types, we need to invent a
// protocol to reflect the features of a Future<R> that should be generically
// available. This works by having a C++ annotation (see ConformingFuture<R>)
// that specifies this protocol conformance.
@_spi(Internal)
public protocol FutureProtocol {
associatedtype ResultType
func error() -> Int32
func __error_messageUnsafe() -> UnsafePointer<CChar>?
func __resultUnsafe() -> UnsafePointer<ResultType>?
darinf marked this conversation as resolved.
Show resolved Hide resolved
func CallOnCompletion(_ completion: FutureCompletionType, _ user_data: UnsafeMutableRawPointer?)
darinf marked this conversation as resolved.
Show resolved Hide resolved
}

// A home for various helper functions that make it easier to work with
// Firebase Future objects from Swift.
darinf marked this conversation as resolved.
Show resolved Hide resolved
@_spi(Internal)
public extension FutureProtocol {
// Callsites retain their own reference to the Future<R>, but they still need
// a way to know when the Future completes. This provides that mechanism.
// While the underlying Firebase `OnCompletion` method can provide a reference
// back to the Future, we don't need to expose that here.
func setCompletion(_ completion: @escaping () -> Void) {
CallOnCompletion({ ptr in
Unmanaged<CompletionWrapper>.fromOpaque(ptr!).takeRetainedValue().completion()
darinf marked this conversation as resolved.
Show resolved Hide resolved
darinf marked this conversation as resolved.
Show resolved Hide resolved
}, Unmanaged.passRetained(CompletionWrapper(completion)).toOpaque())
}

// A convenient way to access the result or error of a Future. Handles the
// messy details.
var resultAndError: (ResultType?, Error?) {
darinf marked this conversation as resolved.
Show resolved Hide resolved
let error = error()
guard error == 0 else {
let message = String(cString: __error_messageUnsafe()!)
return (nil, FirebaseError(code: error, message: message))
}
return (__resultUnsafe().pointee, nil)
}
}

// The Unmanaged type only works with classes, so we need a wrapper for the
// completion callback.
private class CompletionWrapper {
darinf marked this conversation as resolved.
Show resolved Hide resolved
let completion: () -> Void
init(_ completion: @escaping () -> Void) {
self.completion = completion
}
}
24 changes: 12 additions & 12 deletions Sources/FirebaseFirestore/DocumentChange+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,19 @@ public typealias DocumentChange = firebase.firestore.DocumentChange
public typealias DocumentChangeType = firebase.firestore.DocumentChange.`Type`

extension DocumentChange {
public var type: DocumentChangeType {
swift_firebase.swift_cxx_shims.firebase.firestore.document_change_type(self)
}
public var type: DocumentChangeType {
swift_firebase.swift_cxx_shims.firebase.firestore.document_change_type(self)
}

public var document: DocumentSnapshot {
swift_firebase.swift_cxx_shims.firebase.firestore.document_change_document(self)
}
public var document: DocumentSnapshot {
swift_firebase.swift_cxx_shims.firebase.firestore.document_change_document(self)
}

public var oldIndex: UInt {
UInt(swift_firebase.swift_cxx_shims.firebase.firestore.document_change_old_index(self))
}
public var oldIndex: UInt {
UInt(swift_firebase.swift_cxx_shims.firebase.firestore.document_change_old_index(self))
}

public var newIndex: UInt {
UInt(swift_firebase.swift_cxx_shims.firebase.firestore.document_change_new_index(self))
}
public var newIndex: UInt {
UInt(swift_firebase.swift_cxx_shims.firebase.firestore.document_change_new_index(self))
}
}
39 changes: 18 additions & 21 deletions Sources/FirebaseFirestore/DocumentReference+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

@_exported
import firebase
@_spi(Error)
@_spi(Internal)
import FirebaseCore

import CxxShim
Expand Down Expand Up @@ -33,27 +33,24 @@ extension DocumentReference {
String(swift_firebase.swift_cxx_shims.firebase.firestore.document_path(self))
}

public func get() async throws -> DocumentSnapshot? {
typealias Promise = CheckedContinuation<UnsafeMutablePointer<firebase.firestore.DocumentSnapshot>, any Error>
let snapshot = try await withCheckedThrowingContinuation { (continuation: Promise) in
let future = swift_firebase.swift_cxx_shims.firebase.firestore.document_get(self, .default)
withUnsafePointer(to: continuation) { continuation in
future.OnCompletion_SwiftWorkaround({ future, pvContinuation in
let pContinuation = pvContinuation?.assumingMemoryBound(to: Promise.self)
if future.pointee.error() == 0 {
pContinuation.pointee.resume(returning: .init(mutating: future.pointee.__resultUnsafe()))
} else {
let code = future.pointee.error()
let message = String(cString: future.pointee.__error_messageUnsafe()!)
pContinuation.pointee.resume(throwing: FirebaseError(code: code, message: message))
}
}, UnsafeMutableRawPointer(mutating: continuation))
}
future.Wait(firebase.FutureBase.kWaitTimeoutInfinite)
}
public func getDocument(completion: @escaping (DocumentSnapshot?, Error?) -> Void) {
darinf marked this conversation as resolved.
Show resolved Hide resolved
let future = swift_firebase.swift_cxx_shims.firebase.firestore.document_get(self, .default)
future.setCompletion({
let (snapshot, error) = future.resultAndError
completion(snapshot, error)
})
}

guard snapshot.pointee.exists else { return nil }
return snapshot.pointee.is_valid() ? snapshot.pointee : nil
public func getDocument() async throws -> DocumentSnapshot {
try await withCheckedThrowingContinuation { continuation in
getDocument(completion: { snapshot, error in
if let error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: snapshot ?? .init())
}
})
}
}

public func addSnapshotListener(_ listener: @escaping SnapshotListenerCallback) -> ListenerRegistration {
Expand Down
2 changes: 1 addition & 1 deletion Sources/FirebaseFirestore/DocumentSnapshot+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

@_exported
import firebase
@_spi(Error)
@_spi(Internal)
import FirebaseCore

import CxxShim
Expand Down
2 changes: 1 addition & 1 deletion Sources/FirebaseFirestore/Firestore+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

@_exported
import firebase
@_spi(Error)
@_spi(Internal)
import FirebaseCore

import CxxShim
Expand Down
2 changes: 1 addition & 1 deletion Sources/FirebaseFirestore/ListenerRegistration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

@_exported
import firebase
@_spi(Error)
@_spi(Internal)
import FirebaseCore

import CxxShim
Expand Down
36 changes: 36 additions & 0 deletions Sources/FirebaseFirestore/Query+Swift.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: BSD-3-Clause

@_exported
import firebase
@_spi(Internal)
import FirebaseCore

import CxxShim

public typealias Query = firebase.firestore.Query

extension Query {
var firestore: Firestore {
darinf marked this conversation as resolved.
Show resolved Hide resolved
swift_firebase.swift_cxx_shims.firebase.firestore.query_firestore(self)
}

func getDocuments(completion: @escaping (QuerySnapshot?, Error?) -> Void) {
darinf marked this conversation as resolved.
Show resolved Hide resolved
let future = swift_firebase.swift_cxx_shims.firebase.firestore.query_get(self, .default)
darinf marked this conversation as resolved.
Show resolved Hide resolved
future.setCompletion({
let (snapshot, error) = future.resultAndError
completion(snapshot, error)
})
}

func getDocuments() async throws -> QuerySnapshot {
try await withCheckedThrowingContinuation { continuation in
getDocuments() { snapshot, error in
if let error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: snapshot ?? .init())
}
}
}
}
}
8 changes: 8 additions & 0 deletions Sources/FirebaseFirestore/QuerySnapshot+Swift.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: BSD-3-Clause

@_exported
import firebase

import CxxShim

public typealias QuerySnapshot = firebase.firestore.QuerySnapshot
2 changes: 1 addition & 1 deletion Sources/FirebaseFirestore/Settings+Swift.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

@_exported
import firebase
@_spi(Error)
@_spi(Internal)
import FirebaseCore

import CxxShim
Expand Down
29 changes: 28 additions & 1 deletion Sources/firebase/include/FirebaseCore.hh
Original file line number Diff line number Diff line change
@@ -1,7 +1,34 @@

#ifndef firebase_include_FirebaseCore_hh
#define firebase_include_FirebaseCore_hh

#include <firebase/util.h>

#include <stdio.h>

namespace swift_firebase::swift_cxx_shims::firebase {

typedef void (*FutureCompletionType)(void*);

// This class exists to provide protocol conformance to FutureProtocol. It
// also provides a method to invoke `OnCompletion` in a way that works from
// Swift. We can ignore the `FutureBase` param as the Swift caller can just
// retain the Future as part of its closure.
template <class R> class ConformingFuture: public ::firebase::Future<R> {
darinf marked this conversation as resolved.
Show resolved Hide resolved
public:
typedef R ResultType;
typedef ::firebase::Future<R> FutureType;

ConformingFuture(const FutureType& rhs) : FutureType(rhs) {}

void CallOnCompletion(
_Nonnull FutureCompletionType completion,
_Nullable void* user_data) const {
OnCompletion([completion, user_data](const FutureBase&) {
completion(user_data);
});
}
} __attribute__((swift_attr("conforms_to:FirebaseCore.FutureProtocol")));
darinf marked this conversation as resolved.
Show resolved Hide resolved

} // namespace swift_firebase::swift_cxx_shims::firebase
darinf marked this conversation as resolved.
Show resolved Hide resolved

#endif
22 changes: 20 additions & 2 deletions Sources/firebase/include/FirebaseFirestore.hh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include <firebase/firestore.h>

#include "FirebaseCore.hh"

// Functions defined in this namespace are used to get around the lack of
// virtual function support currently in Swift. As that support changes
// these functions will go away whenever possible.
Expand Down Expand Up @@ -43,14 +45,15 @@ document_path(const ::firebase::firestore::DocumentReference document) {
return document.path();
}

inline ::firebase::Future<::firebase::firestore::DocumentSnapshot>
inline ::swift_firebase::swift_cxx_shims::firebase::ConformingFuture<
::firebase::firestore::DocumentSnapshot>
document_get(const ::firebase::firestore::DocumentReference document,
::firebase::firestore::Source source =
::firebase::firestore::Source::kDefault) {
return document.Get(source);
}

inline ::firebase::Future<void>
inline ::swift_firebase::swift_cxx_shims::firebase::ConformingFuture<void>
document_set_data(::firebase::firestore::DocumentReference document,
const ::firebase::firestore::MapFieldValue data,
const ::firebase::firestore::SetOptions options) {
Expand Down Expand Up @@ -166,6 +169,21 @@ document_change_new_index(const ::firebase::firestore::DocumentChange change) {
return change.new_index();
}

// MARK: Query

inline ::firebase::firestore::Firestore *
query_firestore(::firebase::firestore::Query query) {
return query.firestore();
}

inline ::swift_firebase::swift_cxx_shims::firebase::ConformingFuture<
::firebase::firestore::QuerySnapshot>
query_get(const ::firebase::firestore::Query query,
::firebase::firestore::Source source =
::firebase::firestore::Source::kDefault) {
return query.Get(source);
}

} // namespace swift_firebase::swift_cxx_shims::firebase::firestore

#endif