|
| 1 | +# Firebase API Guidelines |
| 2 | + |
| 3 | +This document builds upon the design guidelines provided by [Swift's official |
| 4 | +API design guidelines][1] and the [Google Swift Style Guide][2]. It is essential |
| 5 | +to be familiar with these resources before proceeding with the API proposal |
| 6 | +process. |
| 7 | + |
| 8 | +If Objective-C is required, additionally refer to the [Google Objective-C Style |
| 9 | +Guide][3]. |
| 10 | + |
| 11 | +## Guidance {:#guidance} |
| 12 | + |
| 13 | +### New APIs should be Swift-only {:#new-apis} |
| 14 | + |
| 15 | +Swift is the preferred language for Apple development, so new Firebase APIs |
| 16 | +should be fully optimized for Swift. When designing new APIs, consider whether |
| 17 | +they can be implemented in Swift only. If an Objective-C API is required, it |
| 18 | +should be carefully considered and justified. |
| 19 | + |
| 20 | +Note that Swift and Objective-C are interoperable, and a module's public |
| 21 | +Objective-C API will be exposed to Swift via a generated Swift interface. In |
| 22 | +some cases where the public Objective-C API surface is added to, the |
| 23 | +corresponding generated Swift interface should be manually refined to be more |
| 24 | +idiomatic. Apple provides a [guide][4] for improving such Objective-C API |
| 25 | +declarations for Swift. |
| 26 | + |
| 27 | +### Include Swift code samples {:#include-swift} |
| 28 | + |
| 29 | +It is important for new APIs to be as easy to use in Swift as possible. When |
| 30 | +creating a proposal, prioritize Swift code samples to demonstrate the new API's |
| 31 | +usage. |
| 32 | + |
| 33 | +### Async API should be written in async/await form {:#async-api} |
| 34 | + |
| 35 | +Swift has built-in [support][5] for writing asynchronous code. If a |
| 36 | +callback-based API is required, it should be carefully considered and justified. |
| 37 | +Callbacks are still useful for capturing work to be done later, like for |
| 38 | +example, an event handler like SwiftUI's [`onSubmit`][6] view modifier. |
| 39 | + |
| 40 | +```swift |
| 41 | +// ✔ Preferred async/await form. |
| 42 | +public func fetchData() async throws -> Data { ... } |
| 43 | + |
| 44 | +// x Pre Swift Structured Concurrency. No longer preferred. |
| 45 | +public func fetchData(completion: (Data, any Error) -> Void) { ... } |
| 46 | +``` |
| 47 | + |
| 48 | +### New APIs should be Sendable {:#new-apis} |
| 49 | + |
| 50 | +A [Sendable][7] type is one that is thread-safe and can be shared safely across |
| 51 | +multiple concurrency contexts. The requirements for conforming to the Sendable |
| 52 | +protocol vary depending on the type (class, actor, enum, struct, etc.). Swift 6 |
| 53 | +introduces strict concurrency checking and enforces Sendable types in |
| 54 | +asynchronous code. If applicable, new APIs should be Sendable and designed to be |
| 55 | +used in an async context (e.g. `Task`). |
| 56 | + |
| 57 | +### API Availability {:#api-availability} |
| 58 | + |
| 59 | +By design, an API may not be available on a given Apple platform. Swift supports |
| 60 | +platform-specific compilation and runtime checks. |
| 61 | + |
| 62 | +Code can be conditionally compiled for only iOS by wrapping it with `#if |
| 63 | +os(iOS)and #endif`. For more info, refer to [NSHipster's guide][8]. |
| 64 | + |
| 65 | +#### Platform Versioning Specification |
| 66 | + |
| 67 | +For code that builds on all platforms but should only be available on select |
| 68 | +platforms, use the `@available` declaration attribute discussed in [NSHipster's |
| 69 | +guide][9]. |
| 70 | + |
| 71 | +Firebase's main distribution channel, Swift Package Manager, only supports a |
| 72 | +single minimum supported version for all products. This minimum supported |
| 73 | +version will correspond to Crashlytics and Analytics, which historically share a |
| 74 | +minimum supported version that is lower than the rest of Firebase. When adding a |
| 75 | +new API to the rest of Firebase, refer to the minimum supported versions in the |
| 76 | +product's CocoaPods podspec, and declare it on the API's signature via the |
| 77 | +[`@available`][9] declaration attribute. |
| 78 | + |
| 79 | +```swift |
| 80 | +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) |
| 81 | +func myNewAPI() { ... } |
| 82 | +``` |
| 83 | + |
| 84 | +### Constants {:#constants} |
| 85 | + |
| 86 | +In Objective-C, constants were usually defined at the global level. In Swift, |
| 87 | +constants can instead be grouped under a case-less enum. The benefit of a |
| 88 | +case-less enum over a struct or class is that a case-less enum cannot be |
| 89 | +initialized. Additionally, Swift constants do not contain a "k" prefix. |
| 90 | + |
| 91 | +```swift |
| 92 | +public enum NetworkConstants { |
| 93 | + public static let httpPostMethod = "POST" |
| 94 | + public static let httpGetMethod = "GET" |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +### Minimizing optionals {:#minimizing-optionals} |
| 99 | + |
| 100 | +Unlike Objective-C, Swift handles nullability in a type safe way using |
| 101 | +[optionals][10]. While useful, avoid overusing them as they can complicate |
| 102 | +the callsite. |
| 103 | + |
| 104 | +### Structs over enums for backwards compatibility {:#structs-enums} |
| 105 | + |
| 106 | +Adding a case to a Swift enum is a breaking change for clients switching |
| 107 | +over the enum's cases. Enums should be chosen when they are considered |
| 108 | +frozen or very unlikely to be changed (as major version releases occur |
| 109 | +about once a year). |
| 110 | + |
| 111 | +An alternative to using enums is to use structs. The following PRs use |
| 112 | +structs to model cases similar to how an enum does. One downside to this |
| 113 | +approach is that clients switching over a struct do not get a compile |
| 114 | +time check ensuring each case is handled. |
| 115 | + |
| 116 | +1. [firebase-ios-sdk/13728][11] |
| 117 | +1. [firebase-ios-sdk/13976][12] |
| 118 | + |
| 119 | +### Avoid Any, AnyObject, and NS-prefixed types {:#avoid-any,} |
| 120 | + |
| 121 | +In Swift, `Any` represents an instance of a reference or value type, |
| 122 | +while `AnyObject` represents reference types exclusively (making it |
| 123 | +similar to `id` in Objective-C). NS-prefixed types like `NSString` or |
| 124 | +`NSNumber` are reference types bridged from Objective-C. None of these |
| 125 | +should be used in the public Swift API. Using `Any` and `AnyObject` can |
| 126 | +make APIs harder to work with due to their lack of type safety and |
| 127 | +ambiguousness. NS-prefixed types on the other hand should be swapped |
| 128 | +\out for their corresponding Swift value type like `String` for |
| 129 | +`NSString`. |
| 130 | + |
| 131 | +For example, if a public API's signature uses a dictionary whose values |
| 132 | +should be either a `String` or `Int`, consider modeling this with an |
| 133 | +enum or struct rather than `Any`. |
| 134 | + |
| 135 | +```swift |
| 136 | +public struct CustomValue { |
| 137 | + public static func string(_ string: String) -> Self { ... } |
| 138 | + public static func integer(_ integer: Int) -> Self { ... } |
| 139 | +} |
| 140 | + |
| 141 | +func setValues(_ values: [String: CustomValue]) async throws { ... } |
| 142 | +``` |
| 143 | + |
| 144 | +### Documentation {:#documentation} |
| 145 | + |
| 146 | +New APIs should have corresponding documentation using [Swift-flavored |
| 147 | +Markdown][13]. Xcode can generate a documentation block's structure when the |
| 148 | +cursor is in the method signature and ⌥ ⌘ / is pressed. |
| 149 | + |
| 150 | +### Naming Conventions {:#naming-conventions} |
| 151 | + |
| 152 | +[Swift's official API design guidelines][1] provide an overview to creating |
| 153 | +idiomatic Swift API. |
| 154 | + |
| 155 | +#### Optimize for clarity and expressiveness |
| 156 | + |
| 157 | +Choose names that leverage clarity over conciseness. For example, choose |
| 158 | +`fetchMetadata()` over `fetch()`. |
| 159 | + |
| 160 | +Avoid using _get_ as a prefix. For example, in Apple's WeatherKit, Apple |
| 161 | +[uses][14] `weather(for location: CLLocation)` instead of `getWeather(for |
| 162 | +location: CLLocation)`. |
| 163 | + |
| 164 | +#### Consistency with existing Firebase APIs |
| 165 | + |
| 166 | +Consider the precedent set by the existing API that is adjacent to the new API |
| 167 | +being added. New APIs should be consistent with the existing Firebase API |
| 168 | +surface, and diverge only when justified. |
| 169 | + |
| 170 | +### Errors {:#errors} |
| 171 | + |
| 172 | +If the new API can fail, mark the API as `throws` and create (or add onto) an |
| 173 | +existing public [error][15] for the API to throw. Swift provides an `Error` |
| 174 | +protocol that can be used to create descriptive errors that can be easily |
| 175 | +handled by clients. |
| 176 | + |
| 177 | +### Don't define typedefs {:#don't-define} |
| 178 | + |
| 179 | +_This guideline only applies when changing public Objective-C API_. |
| 180 | + |
| 181 | +Don't define typedefs as they hide the underlying type in the corresponding |
| 182 | +generated Swift API. Instead, use the full type, regardless of how long the |
| 183 | +Objective-C API signature becomes. |
| 184 | + |
| 185 | +[1]: https://www.swift.org/documentation/api-design-guidelines/ |
| 186 | +[2]: https://google.github.io/swift/ |
| 187 | +[3]: https://google.github.io/styleguide/objcguide.html |
| 188 | +[4]: https://developer.apple.com/documentation/swift/improving-objective-c-api-declarations-for-swift |
| 189 | +[5]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/concurrency/ |
| 190 | +[6]: https://developer.apple.com/documentation/swiftui/view/onsubmit(of:_:) |
| 191 | +[7]: https://developer.apple.com/documentation/swift/sendable |
| 192 | +[8]: https://nshipster.com/swift-system-version-checking/ |
| 193 | +[9]: https://nshipster.com/available/ |
| 194 | +[10]: https://developer.apple.com/documentation/swift/optional |
| 195 | +[11]: https://github.com/firebase/firebase-ios-sdk/pull/13728 |
| 196 | +[12]: https://github.com/firebase/firebase-ios-sdk/pull/13976 |
| 197 | +[13]: https://nshipster.com/swift-documentation/ |
| 198 | +[14]: https://developer.apple.com/documentation/weatherkit/weatherservice/weather(for:) |
| 199 | +[15]: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/errorhandling/ |
0 commit comments