Переглянути джерело

Add development documentation

Signed-off-by: Peter Friese <peter@peterfriese.de>
Peter Friese 5 роки тому
батько
коміт
ffa115847c

+ 62 - 0
FirebaseCombineSwift/DECISIONS.md

@@ -0,0 +1,62 @@
+# Decisions
+
+This file documents some of the decisions we made when developing Combine support for Firebase.
+
+# Module structure
+
+## Discussion
+The general idea is to keep all Combine-related code in a separate module (`FirebaseCombineSwift`, to match the naming scheme used for `FirebaseFirestoreSwift` and `FirebaseStorageSwift`).
+
+By using the `#if canImport(moduleName)` directive, we can make sure to only enable the publishers for a module that developers have imported into a build target.
+
+
+# Implementing Publishers
+
+## Custom Publishers vs. wrapping in Futures / using PassthroughSubject
+
+Instead of implementing [custom  publishers](https://thoughtbot.com/blog/lets-build-a-custom-publisher-in-combine), which [Apple discourages developers from doing](https://developer.apple.com/documentation/combine/publisher), we make use of [`PassthroughSubject`](https://developer.apple.com/documentation/combine/passthroughsubject) (for publishers that emit a stream of events), and [`Future`](https://developer.apple.com/documentation/combine/future) for one-shot calls that produce a single value.
+
+## Using capture lists
+
+After discussing internally, we came to the conclusion that the outer closure in the following piece of code is non-escaping, hence there is no benefit to weakly capture `self`. As the inner closure does't refer to `self`, the reference does not outlive the current call stack. 
+
+It is thus safe to not use `[weak self]` in this instance.
+
+```swift
+extension Auth {
+    public func createUser(withEmail email: String,
+                           password: String) -> Future<AuthDataResult, Error> {
+      Future<AuthDataResult, Error> { /* [weak self]  <-- not required */ promise in
+        self?.createUser(withEmail: email, password: password) { authDataResult, error in
+          if let error = error {
+            promise(.failure(error))
+          } else if let authDataResult = authDataResult {
+            promise(.success(authDataResult))
+          }
+        }
+      }
+    }
+}
+```
+
+# Method naming
+
+## Discussion
+* Methods that might send a **stream of events** over time will receive a `Publisher` suffix, in line with Apple's own APIs. Any `add` prefix will be removed. This helps to clarify that the user is not _adding_ something that they will have to remove later on ([as is required](https://firebase.google.com/docs/auth/ios/start#listen_for_authentication_state) in most of Firebase's existing APIs). Instead, the result of the publisher needs to be handled just like any other publisher (i.e. be kept in a set of `Cancellable`s).
+
+    Examples:
+    * `addStateDidChangeListener` -> `authStateDidChangePublisher`
+    * `addSnapshotListener` -> `snapshotPublisher`
+
+* Methods that **return a result once** will not receive a suffix. This effectively means that these methods are overloads to their existing counterparts that take a closure. To silence any `Result of call to xzy is unused` warnings, these methods need to be prefixed with `@discardableresult`. This shouldn't be a problem, since the Future that is created inside those functions is called immediately and will be disposed of by the runtime upon returning from the inner closure.
+
+    Examples:
+    * `signIn` -> `signIn`
+    * `createUser` -> `createUser`
+
+## Options considered
+Using the same method and parameter names for one-shot asynchronous methods results in both methods to be shown in close proximity when invoking code completion
+
+![image](https://user-images.githubusercontent.com/232107/99672274-76f05680-2a73-11eb-880a-3563f293de7d.png)
+
+To achieve the same for methods that return a stream of events, we'd have to name those `addXzyListener`. This would be in contrast to Apple's naming scheme (e.g. `dataTask(with:completionHandler)` -> `dataTaskPublisher(for:)`

+ 37 - 0
FirebaseCombineSwift/DEVELOPING.md

@@ -0,0 +1,37 @@
+# Developing
+
+This is a quick overview to help you get started contributing to Firebase Combine.
+
+## Prerequisites
+
+* Xcode 12.x (or later)
+* CocoaPods 1.10.x (or later)
+* [CocoaPods Generate](https://github.com/square/cocoapods-generate)
+
+## Setting up your development environment
+
+* Check out firebase-ios-sdk
+* Install utilities
+
+```bash
+$ ./scripts/setup_check.sh
+$ ./scripts/setup_bundler.sh
+```
+
+## Generating the development project
+
+For developing _Firebase Combine_, you'll need a development project that imports the relevant pods.
+
+Run the following command to generate and open the development project:
+
+```bash
+$ pod gen FirebaseCombineSwift.podspec --local-sources=./ --auto-open --platforms=ios
+```
+
+## Checking in code
+
+Before checking in your code, make sure to check your code against the coding styleguide by running the following command:
+
+```bash
+$ ./scripts/check.sh --allow-dirty
+```

+ 61 - 0
FirebaseCombineSwift/README.md

@@ -0,0 +1,61 @@
+# Combine Support for Firebase
+
+This module contains Combine support for Firebase APIs. 
+
+## Installation
+
+<details><summary>CocoaPods</summary>
+
+* Add `pod 'Firebase/FirebaseCombineSwift'` to your podfile:
+
+```Ruby
+platform :ios, '14.0'
+
+target 'YourApp' do
+  use_frameworks!
+
+  pod 'Firebase/Auth'
+  pod 'Firebase/Analytics'
+  pod 'Firebase/FirebaseCombineSwift'
+end
+```
+
+</details>
+
+<details><summary>Swift Package Manager</summary>
+
+* Follow the instructions in [Swift Package Manager for Firebase Beta
+](../SwiftPackageManager.md)
+* Make sure to import the package `FirebaseCombineSwift-Beta`
+
+</details>
+
+## Usage
+
+### Auth
+
+#### Sign in anonymously
+
+```swift
+  Auth.auth().signInAnonymously()
+    .sink { completion in
+      switch completion {
+      case .finished:
+        print("Finished")
+      case let .failure(error):
+        print("\(error.localizedDescription)")
+      }
+    } receiveValue: { authDataResult in
+    }
+    .store(in: &cancellables)
+```
+
+```swift
+  Auth.auth().signInAnonymously()
+    .map { result in
+      result.user.uid
+    }
+    .replaceError(with: "(unable to sign in anonymously)")
+    .assign(to: \.uid, on: self)
+    .store(in: &cancellables)
+```