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(FirebaseInternal)
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(FirebaseInternal)
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(FirebaseInternal)
public init(code: CInt, message: String) {
self.code = code
self.message = message
Expand Down
66 changes: 66 additions & 0 deletions Sources/FirebaseCore/FutureProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: BSD-3-Clause

@_exported
import firebase

import Foundation

@_spi(FirebaseInternal)
public typealias FutureCompletionType =
swift_firebase.swift_cxx_shims.firebase.FutureCompletionType

// 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 swift_cxx_shims'
// Future<R>) that specifies this protocol conformance.
@_spi(FirebaseInternal)
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 OnCompletion(
_ completion: FutureCompletionType,
_ user_data: UnsafeMutableRawPointer?
)
}

@_spi(FirebaseInternal)
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) {
OnCompletion({ ptr in
Unmanaged<CompletionReference>.fromOpaque(ptr!).takeRetainedValue().completion()
}, Unmanaged.passRetained(CompletionReference(completion)).toOpaque())
}

var result: ResultType? {
__resultUnsafe().pointee
}

var errorMessage: String? {
guard let errorMessageUnsafe = __error_messageUnsafe() else { return nil }
return String(cString: errorMessageUnsafe)
}

var resultAndError: (ResultType?, Error?) {
darinf marked this conversation as resolved.
Show resolved Hide resolved
let error = error()
guard error == 0 else {
return (nil, FirebaseError(code: error, message: errorMessage!))
}
return (result, nil)
}
}

// The Unmanaged type only works with classes, so we need a wrapper for the
// completion callback.
private class CompletionReference {
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))
}
}
42 changes: 22 additions & 20 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(FirebaseInternal)
import FirebaseCore

import CxxShim
Expand Down Expand Up @@ -33,27 +33,29 @@ 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))
// This variant is provided for compatibility with the ObjC API.
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
DispatchQueue.main.async {
completion(snapshot, error)
}
future.Wait(firebase.FutureBase.kWaitTimeoutInfinite)
}
})
}

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
let future = swift_firebase.swift_cxx_shims.firebase.firestore.document_get(self, .default)
future.setCompletion({
let (snapshot, error) = future.resultAndError
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(FirebaseInternal)
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(FirebaseInternal)
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(FirebaseInternal)
import FirebaseCore

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

@_exported
import firebase
@_spi(FirebaseInternal)
import FirebaseCore

import CxxShim
import Foundation

public typealias Query = firebase.firestore.Query

extension Query {
public var firestore: Firestore {
swift_firebase.swift_cxx_shims.firebase.firestore.query_firestore(self)
}

// This variant is provided for compatibility with the ObjC API.
public func getDocuments(completion: @escaping (QuerySnapshot?, Error?) -> Void) {
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
DispatchQueue.main.async {
completion(snapshot, error)
}
})
}

public func getDocuments() async throws -> QuerySnapshot {
try await withCheckedThrowingContinuation { continuation in
let future = swift_firebase.swift_cxx_shims.firebase.firestore.query_get(self, .default)
future.setCompletion({
let (snapshot, error) = future.resultAndError
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(FirebaseInternal)
import FirebaseCore

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

#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 __attribute__((swift_attr("conforms_to:FirebaseCore.FutureProtocol")))
Future : public ::firebase::Future<R> {
public:
typedef R ResultType;

Future(const ::firebase::Future<R>& rhs) : ::firebase::Future<R>(rhs) {}

void OnCompletion(
_Nonnull FutureCompletionType completion,
_Nullable void* user_data) const {
::firebase::FutureBase::OnCompletion(
[completion, user_data](const FutureBase&) {
completion(user_data);
});
}
};

} // namespace swift_firebase::swift_cxx_shims::firebase

#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::Future<
::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::Future<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::Future<
::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