-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Docs] Create firebase-api-guidelines.md (#14325)
- Loading branch information
Showing
1 changed file
with
199 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
# Firebase API Guidelines | ||
|
||
This document builds upon the design guidelines provided by [Swift's official | ||
API design guidelines][1] and the [Google Swift Style Guide][2]. It is essential | ||
to be familiar with these resources before proceeding with the API proposal | ||
process. | ||
|
||
If Objective-C is required, additionally refer to the [Google Objective-C Style | ||
Guide][3]. | ||
|
||
## Guidance {:#guidance} | ||
|
||
### New APIs should be Swift-only {:#new-apis} | ||
|
||
Swift is the preferred language for Apple development, so new Firebase APIs | ||
should be fully optimized for Swift. When designing new APIs, consider whether | ||
they can be implemented in Swift only. If an Objective-C API is required, it | ||
should be carefully considered and justified. | ||
|
||
Note that Swift and Objective-C are interoperable, and a module's public | ||
Objective-C API will be exposed to Swift via a generated Swift interface. In | ||
some cases where the public Objective-C API surface is added to, the | ||
corresponding generated Swift interface should be manually refined to be more | ||
idiomatic. Apple provides a [guide][4] for improving such Objective-C API | ||
declarations for Swift. | ||
|
||
### Include Swift code samples {:#include-swift} | ||
|
||
It is important for new APIs to be as easy to use in Swift as possible. When | ||
creating a proposal, prioritize Swift code samples to demonstrate the new API's | ||
usage. | ||
|
||
### Async API should be written in async/await form {:#async-api} | ||
|
||
Swift has built-in [support][5] for writing asynchronous code. If a | ||
callback-based API is required, it should be carefully considered and justified. | ||
Callbacks are still useful for capturing work to be done later, like for | ||
example, an event handler like SwiftUI's [`onSubmit`][6] view modifier. | ||
|
||
```swift | ||
// ✔ Preferred async/await form. | ||
public func fetchData() async throws -> Data { ... } | ||
|
||
// x Pre Swift Structured Concurrency. No longer preferred. | ||
public func fetchData(completion: (Data, any Error) -> Void) { ... } | ||
``` | ||
|
||
### New APIs should be Sendable {:#new-apis} | ||
|
||
A [Sendable][7] type is one that is thread-safe and can be shared safely across | ||
multiple concurrency contexts. The requirements for conforming to the Sendable | ||
protocol vary depending on the type (class, actor, enum, struct, etc.). Swift 6 | ||
introduces strict concurrency checking and enforces Sendable types in | ||
asynchronous code. If applicable, new APIs should be Sendable and designed to be | ||
used in an async context (e.g. `Task`). | ||
|
||
### API Availability {:#api-availability} | ||
|
||
By design, an API may not be available on a given Apple platform. Swift supports | ||
platform-specific compilation and runtime checks. | ||
|
||
Code can be conditionally compiled for only iOS by wrapping it with `#if | ||
os(iOS)and #endif`. For more info, refer to [NSHipster's guide][8]. | ||
|
||
#### Platform Versioning Specification | ||
|
||
For code that builds on all platforms but should only be available on select | ||
platforms, use the `@available` declaration attribute discussed in [NSHipster's | ||
guide][9]. | ||
|
||
Firebase's main distribution channel, Swift Package Manager, only supports a | ||
single minimum supported version for all products. This minimum supported | ||
version will correspond to Crashlytics and Analytics, which historically share a | ||
minimum supported version that is lower than the rest of Firebase. When adding a | ||
new API to the rest of Firebase, refer to the minimum supported versions in the | ||
product's CocoaPods podspec, and declare it on the API's signature via the | ||
[`@available`][9] declaration attribute. | ||
|
||
```swift | ||
@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) | ||
func myNewAPI() { ... } | ||
``` | ||
|
||
### Constants {:#constants} | ||
|
||
In Objective-C, constants were usually defined at the global level. In Swift, | ||
constants can instead be grouped under a case-less enum. The benefit of a | ||
case-less enum over a struct or class is that a case-less enum cannot be | ||
initialized. Additionally, Swift constants do not contain a "k" prefix. | ||
|
||
```swift | ||
public enum NetworkConstants { | ||
public static let httpPostMethod = "POST" | ||
public static let httpGetMethod = "GET" | ||
} | ||
``` | ||
|
||
### Minimizing optionals {:#minimizing-optionals} | ||
|
||
Unlike Objective-C, Swift handles nullability in a type safe way using | ||
[optionals][10]. While useful, avoid overusing them as they can complicate | ||
the callsite. | ||
|
||
### Structs over enums for backwards compatibility {:#structs-enums} | ||
|
||
Adding a case to a Swift enum is a breaking change for clients switching | ||
over the enum's cases. Enums should be chosen when they are considered | ||
frozen or very unlikely to be changed (as major version releases occur | ||
about once a year). | ||
|
||
An alternative to using enums is to use structs. The following PRs use | ||
structs to model cases similar to how an enum does. One downside to this | ||
approach is that clients switching over a struct do not get a compile | ||
time check ensuring each case is handled. | ||
|
||
1. [firebase-ios-sdk/13728][11] | ||
1. [firebase-ios-sdk/13976][12] | ||
|
||
### Avoid Any, AnyObject, and NS-prefixed types {:#avoid-any,} | ||
|
||
In Swift, `Any` represents an instance of a reference or value type, | ||
while `AnyObject` represents reference types exclusively (making it | ||
similar to `id` in Objective-C). NS-prefixed types like `NSString` or | ||
`NSNumber` are reference types bridged from Objective-C. None of these | ||
should be used in the public Swift API. Using `Any` and `AnyObject` can | ||
make APIs harder to work with due to their lack of type safety and | ||
ambiguousness. NS-prefixed types on the other hand should be swapped | ||
\out for their corresponding Swift value type like `String` for | ||
`NSString`. | ||
|
||
For example, if a public API's signature uses a dictionary whose values | ||
should be either a `String` or `Int`, consider modeling this with an | ||
enum or struct rather than `Any`. | ||
|
||
```swift | ||
public struct CustomValue { | ||
public static func string(_ string: String) -> Self { ... } | ||
public static func integer(_ integer: Int) -> Self { ... } | ||
} | ||
|
||
func setValues(_ values: [String: CustomValue]) async throws { ... } | ||
``` | ||
|
||
### Documentation {:#documentation} | ||
|
||
New APIs should have corresponding documentation using [Swift-flavored | ||
Markdown][13]. Xcode can generate a documentation block's structure when the | ||
cursor is in the method signature and ⌥ ⌘ / is pressed. | ||
|
||
### Naming Conventions {:#naming-conventions} | ||
|
||
[Swift's official API design guidelines][1] provide an overview to creating | ||
idiomatic Swift API. | ||
|
||
#### Optimize for clarity and expressiveness | ||
|
||
Choose names that leverage clarity over conciseness. For example, choose | ||
`fetchMetadata()` over `fetch()`. | ||
|
||
Avoid using _get_ as a prefix. For example, in Apple's WeatherKit, Apple | ||
[uses][14] `weather(for location: CLLocation)` instead of `getWeather(for | ||
location: CLLocation)`. | ||
|
||
#### Consistency with existing Firebase APIs | ||
|
||
Consider the precedent set by the existing API that is adjacent to the new API | ||
being added. New APIs should be consistent with the existing Firebase API | ||
surface, and diverge only when justified. | ||
|
||
### Errors {:#errors} | ||
|
||
If the new API can fail, mark the API as `throws` and create (or add onto) an | ||
existing public [error][15] for the API to throw. Swift provides an `Error` | ||
protocol that can be used to create descriptive errors that can be easily | ||
handled by clients. | ||
|
||
### Don't define typedefs {:#don't-define} | ||
|
||
_This guideline only applies when changing public Objective-C API_. | ||
|
||
Don't define typedefs as they hide the underlying type in the corresponding | ||
generated Swift API. Instead, use the full type, regardless of how long the | ||
Objective-C API signature becomes. | ||
|
||
[1]: https://www.swift.org/documentation/api-design-guidelines/ | ||
[2]: https://google.github.io/swift/ | ||
[3]: https://google.github.io/styleguide/objcguide.html | ||
[4]: https://developer.apple.com/documentation/swift/improving-objective-c-api-declarations-for-swift | ||
[5]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/ | ||
[6]: https://developer.apple.com/documentation/swiftui/view/onsubmit(of:_:) | ||
[7]: https://developer.apple.com/documentation/swift/sendable | ||
[8]: https://nshipster.com/swift-system-version-checking/ | ||
[9]: https://nshipster.com/available/ | ||
[10]: https://developer.apple.com/documentation/swift/optional | ||
[11]: https://github.com/firebase/firebase-ios-sdk/pull/13728 | ||
[12]: https://github.com/firebase/firebase-ios-sdk/pull/13976 | ||
[13]: https://nshipster.com/swift-documentation/ | ||
[14]: https://developer.apple.com/documentation/weatherkit/weatherservice/weather(for:) | ||
[15]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/errorhandling/ |