ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Firebase iOS SDK 的 Combine 支持:用响应式编程简化 Auth、Firestore、Functions 与 Storage 集成

Firebase iOS SDK 的 Combine 支持:用响应式编程简化 Auth、Firestore、Functions 与 Storage 集成 Firebase iOS SDK 的 Combine 支持用响应式编程简化 Auth、Firestore、Functions 与 Storage 集成【免费下载链接】firebase-ios-sdkFirebase SDK for Apple App Development项目地址: https://gitcode.com/GitHub_Trending/fi/firebase-ios-sdk导读FirebaseCombineSwift 是 firebase-ios-sdk 仓库中为 Firebase 各 API 提供 Combine 响应式封装的社区模块它把 Firebase Auth、Cloud Firestore、Cloud Functions 和 Cloud Storage 原本基于回调completion handler的异步接口包装成标准的Future/AnyPublisher让开发者可以用.sink、.map、.flatMap、.assign等 Combine 操作符以声明式方式组织异步逻辑。读完本文你将掌握该模块的安装方式、四个子模块的公开 API 清单以及 Auth 登录、MFA 二次验证、Cloud Functions 调用等典型实战写法并结合仓库源码理解其底层实现机制。一、模块概览四个 Combine 社区子模块FirebaseCombineSwift 不是一个单一模块而是按业务域拆分的四个 Swift 库的集合对应关系如下见 Package.swift 中FirebaseAuthCombine-Community、FirebaseFirestoreCombine-Community、FirebaseFunctionsCombine-Community、FirebaseStorageCombine-Community四个 product 定义Combine 库模块名底层 Firebase 产品源文件目录FirebaseAuthCombineSwiftFirebase AuthFirebaseCombineSwift/Sources/AuthFirebaseFirestoreCombineSwiftCloud FirestoreFirebaseCombineSwift/Sources/FirestoreFirebaseFunctionsCombineSwiftCloud Functions for FirebaseFirebaseCombineSwift/Sources/FunctionsFirebaseStorageCombineSwiftCloud StorageFirebaseCombineSwift/Sources/Storage从 CHANGELOG.md 可以看到Auth、Storage、Firestore、Functions 四个方向的 Combine 支持是在 8.9.0 版本集中引入的此后持续演进例如 10.8.0 修复了写入文档数据时使用调用方 encoder 的问题12.0.0 移除了fetchSignInMethods的 Combine 包装因为底层 API 已废弃。需要特别注意的是模块的维护状态README 明确说明该特性仍处于开发中under development仅提供社区级支持community basis相关进展可关注其项目 tracker。因此生产项目中引入时应评估其稳定性并做好 API 变更的跟进。二、安装CocoaPods 与 Swift Package Manager 双方案2.1 通过 CocoaPods 安装在 Podfile 中添加Firebase/FirebaseCombineSwift子规范即可官方示例要求 iOS 14.0并使用use_frameworks!platform :ios, 14.0 target YourApp do use_frameworks! pod Firebase/Auth pod Firebase/Analytics pod Firebase/FirebaseCombineSwift end2.2 通过 Swift Package Manager 安装按仓库根目录 SwiftPackageManager.md 的说明将 Firebase 以 SwiftPM 方式集成到工程后需要显式导入本次要用到的 Combine 子包FirebaseAuthCombine-CommunityFirebaseFirestoreCombine-CommunityFirebaseFunctionsCombine-CommunityFirebaseStorageCombine-Community然后在代码中按需import对应模块import FirebaseAuthCombineSwiftimport FirebaseFirestoreCombineSwiftimport FirebaseFunctionsCombineSwiftimport FirebaseStorageCombineSwift2.3 底层实现形态extension 与 Future 包装从源码看这套 Combine 支持并非重写 Firebase 接口而是通过public extension在原生类型Auth、User、HTTPSCallable、StorageReference、Query、DocumentReference等上扩展出返回Future或AnyPublisher的新方法内部仍调用原有带回调的 API。典型实现模式见 AuthCombine.swift 的signInAnonymously()discardableResult func signInAnonymously() - FutureAuthDataResult, Error { FutureAuthDataResult, Error { promise in self.signInAnonymously { authDataResult, error in if let error { promise(.failure(error)) } else if let authDataResult { promise(.success(authDataResult)) } } } }即用Future的promise桥接原回调的(result, error)二元结果把要么成功要么失败的一次性异步操作转化为标准的 CombineFuture。除此之外Core.swift 中还通过_exported import FirebaseAuthCombineSwift对外转出 Auth 子模块并带有#warning(This is experimental - use at your own risk.)的编译期提示再次印证其实验性质。三、Auth响应式处理登录与账号状态3.1 匿名登录的两种写法匿名登录是 Combine 包装最直观的用例README 给出了两种风格风格一显式处理完成与失败通过sink的completion分支捕获错误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)风格二链式转换并绑定到属性用map提取 UID、replaceError兜底、assign直接绑定到Published或普通属性Auth.auth().signInAnonymously() .map { result in result.user.uid } .replaceError(with: (unable to sign in anonymously)) .assign(to: \.uid, on: self) .store(in: cancellables)注意两个示例末尾的.store(in: cancellables)sink返回的AnyCancellable必须被持有否则订阅一旦释放发布者即被取消回调不会触发。这是 Combine 使用中最容易踩的坑。3.2 第三方凭据登录与 MFA 二次验证对于 Google 等第三方登录在GIDSignInDelegate的sign(_:didSignInFor:withError:)回调中把GIDAuthentication的 ID Token 与 Access Token 交换成 FirebaseAuthCredential再走 Combine 链路并通过tryCatch对错误进行二次分流处理见 README 完整示例func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error?) { // ... if let error { // ... return } guard let authentication user.authentication else { return } let credential GoogleAuthProvider.credential(withIDToken: authentication.idToken, accessToken: authentication.accessToken) Auth.auth() .signIn(withCredential: credential) .mapError { $0 as NSError } .tryCatch(handleError) .sink { /* ... */ } receiveValue: { /* ... */ } .store(in: subscriptions) }其中handleError返回AnyPublisherAuthDataResult, Error当用户启用了多因素认证MFA且错误码为AuthErrorCode.secondFactorRequired时触发二次验证流程从error.userInfo[AuthErrorUserInfoMultiFactorResolverKey]取出MultiFactorResolver用PhoneAuthProvider的verifyPhoneNumber(withMultiFactorInfo:multiFactorSession:)发送验证码再用zip同时等待用户输入验证码最终通过PhoneMultiFactorGenerator.assertion(with:)与resolver.resolveSignIn(withAssertion:)完成 MFA 挑战private func handleError(_ error: NSError) throws - AnyPublisherAuthDataResult, Error { guard isMFAEnabled error.code AuthErrorCode.secondFactorRequired.rawValue else { throw error } let resolver error.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver let displayNameString resolver.hints.compactMap(\.displayName).joined(separator: ) return showTextInputPrompt(withMessage: Select factor to sign in\n\(displayNameString)) .compactMap { displayName in resolver.hints.first(where: { displayName $0.displayName }) as? PhoneMultiFactorInfo } .flatMap { [unowned self] factorInfo in PhoneAuthProvider.provider() .verifyPhoneNumber(withMultiFactorInfo: factorInfo, multiFactorSession: resolver.session) .zip(self.showTextInputPrompt(withMessage: Verification code for \(factorInfo.displayName ?? ))) .map { (verificationID, verificationCode) in let credential PhoneAuthProvider.provider().credential(withVerificationID: verificationID, verificationCode: verificationCode) return PhoneMultiFactorGenerator.assertion(with: credential) } } .flatMap { assertion in resolver.resolveSignIn(withAssertion: assertion) } .eraseToAnyPublisher() }这个示例集中展示了 Combine 的进阶组合能力tryCatch做错误恢复、compactMap过滤可选值、flatMap串行拼接异步操作、zip并行等待两个发布者值得作为响应式登录流的范本研读。3.3 Auth 的完整公开 API 面从 AuthCombine.swift 及同目录下的 UserCombine.swift、MultiFactorCombine.swift、PhoneAuthProviderCombine.swift 等文件可以梳理出完整的响应式接口登录类signInAnonymously()、signIn(withEmail:password:)、signIn(withEmail:link:)、signIn(withCustomToken:)、signIn(with:)凭据、signIn(with:uiDelegate:)联邦登录仅 iOS/macCatalyst账号创建与密码管理createUser(withEmail:password:)、sendPasswordReset(withEmail:)、sendPasswordReset(withEmail:actionCodeSettings:)、confirmPasswordReset(withCode:newPassword:)、verifyPasswordResetCode(_:)、checkActionCode(code:)、applyActionCode(code:)状态监听authStateDidChangePublisher()与idTokenDidChangePublisher()返回AnyPublisherUser?, Never分别监听登录状态变化与 ID Token 刷新并在receiveCancel时自动移除底层监听器当前用户操作updateCurrentUser(_:)、User.link(with:)、User.reauthenticate(with:)、User.unlink(fromProvider:)、User.sendEmailVerification()含带ActionCodeSettings的重载。这些方法统一在主线程发射事件且都带有discardableResult标注允许不接收返回值。源码中每个方法都注明了可能的AuthErrorCode如InvalidEmail、WeakPassword、WrongPassword、OperationNotAllowed等便于在sink的失败分支里做针对性处理。仓库对应的单元测试如 AnonymousAuthTests.swift通过 MockAuthBackend拦截网络层验证了signInAnonymously()发出的请求参数apiKey、returnSecureToken等以及成功/失败两条路径的Future语义可作为理解底层调用链的参考。四、Cloud Functions声明式调用可调用函数HTTPSCallable的 Combine 扩展见 HTTPSCallableCombine.swift提供两个方法call()无参数调用call(_ data: Any?)携带参数调用data支持nil、String、Number、Array、Dictionary等 JSON 可序列化类型。无参数调用示例let helloWorld Functions.functions().httpsCallable(helloWorld) helloWorld.call() .sink { completion in switch completion { case .finished: print(Finished) case let .failure(error): print(\(error.localizedDescription)) } } receiveValue: { functionResult in if let result functionResult.data as? String { print(The function returned: \(result)) } } .store(in: cancellables)带参数调用示例把Peter传给后端函数let helloWorld Functions.functions().httpsCallable(helloWorld) helloWorld.call(Peter) .sink { completion in switch completion { case .finished: print(Finished) case let .failure(error): print(\(error.localizedDescription)) } } receiveValue: { functionResult in if let result functionResult.data as? String { print(The function returned: \(result)) } } .store(in: cancellables)成功时发布者发射HTTPSCallableResult其data属性携带后端返回值可向下转型为String、字典、数组等失败时发射Error。值得一提的细节是调用 Cloud Functions 的请求会自动附带 Firebase Installations 实例 ID Token若当前有已登录用户还会附带 Auth ID Token见 HTTPSCallableCombine.swift 中call()的文档注释因此无需手动拼接鉴权信息。五、Cloud Firestore读、写与实时快照Firestore 子模块覆盖了 FirestoreCombine.swift、DocumentReferenceCombine.swift、QueryCombine.swift、CollectionReferenceCombine.swift、TransactionCombine.swift、WriteBatchCombine.swift 六个文件。5.1 写入文档setData 系列DocumentReferenceCombine.swift 提供三档写入粒度全部返回FutureVoid, Error// 整体覆盖不存在则创建已存在则覆盖 ref.setData([name: Ada]) // merge: true 时仅合并传入字段保留文档中未涉及的字段 ref.setData([age: 36], merge: true) // mergeFields 精确指定要合并的字段支持点号路径定位嵌套字段 ref.setData([age: 36, city: London], mergeFields: [age])从源码看setData家族还有mergeFields接受String/FieldPath混合数组的重载。需要留意的是这些发布者仅在写入被服务端确认后才发射成功值离线状态下本地会立即反映变更但发布者不会发射符合 Firestore 离线能力的既有语义。5.2 读取查询与实时监听QueryCombine.swift 提供两类接口一次性读取getDocuments(source:)source参数支持FirestoreSource.default先服务端、失败回退缓存、.server仅服务端、.cache仅缓存返回FutureQuerySnapshot, Errordb.collection(users).whereField(age, isGreaterThan: 18) .getDocuments() .sink(receiveCompletion: { _ in }, receiveValue: { snapshot in // 遍历 snapshot.documents 处理结果 }) .store(in: cancellables)实时监听snapshotPublisher(includeMetadataChanges:)底层调用addSnapshotListener把连续的快照事件通过PassthroughSubject转发为AnyPublisherQuerySnapshot, Error并在订阅取消时自动调用listenerHandle.remove()注销监听见 QueryCombine.swift。这是响应式 实时数据结合的关键接口适合驱动 SwiftUI 界面自动刷新query.snapshotPublisher() .map { $0.documents } .sink(receiveCompletion: { _ in }, receiveValue: { docs in // 每次数据变更自动触发 }) .store(in: cancellables)六、Cloud Storage上传、下载与元数据StorageReferenceCombine.swift 把 Storage 的上传/下载任务包装成Future一个值得注意的工程细节是上传、下载类方法通过.handleEvents(receiveCancel:)在订阅取消时调用task?.cancel()取消底层StorageUploadTask/StorageDownloadTask避免取消订阅后任务仍在后台执行。上传putData(_:metadata:)上传内存中的Data适合小文件putFile(from:metadata:)上传磁盘文件大文件推荐。返回FutureStorageMetadata, Error下载getData(maxSize:)在内存中下载maxSize为最大字节数超限则取消任务并报错write(toFile:)下载到本地文件并返回文件URL链接与列表downloadURL()获取可撤销的长效下载链接listAll()递归列出全部条目list(maxResults:)/list(maxResults:pageToken:)分页列出maxResults需大于 0 且不超过 1000后两者仅适用于 Firebase Rules 版本 2 的项目元数据与删除getMetadata()、updateMetadata(_:)、delete()成功发射true。例如下载并展示头像可以写成storageRef.child(avatars/\(uid).jpg) .getData(maxSize: 2 * 1024 * 1024) // 2MB 上限 .sink(receiveCompletion: { completion in if case .failure(let error) completion { print(下载失败: \(error.localizedDescription)) } }, receiveValue: { data in imageView.image UIImage(data: data) }) .store(in: cancellables)七、错误处理与生命周期管理的要点综合四个子模块使用 FirebaseCombineSwift 时有几个贯穿始终的准则订阅必须被持有所有sink产生的AnyCancellable都要.store(in: cancellables)或subscriptions否则立即失效统一在completion分支处理错误Future的失败语义通过completion .failure(error)体现receiveValue只收到成功值两分支职责清晰Future是单值发布者适合登录、写入、上传等一次性操作需要持续事件流登录状态变化、实时快照时使用AnyPublisher变体authStateDidChangePublisher、snapshotPublisher二者在使用上要区分可组合性带来更干净的流程tryCatch错误恢复、zip并行等待、flatMap串行衔接可以替代嵌套回调这正是引入 Combine 的价值所在MFA 示例即是完整示范主线程发射各方法文档均注明 publisher 在主线程发射事件UI 更新无需再手动切线程。八、源码地图从文档到实现如果想深入阅读实现或参与社区贡献可按以下路径定位模块入口与声明FirebaseCombineSwift/Sources/Core/Core.swiftAuth 实现FirebaseCombineSwift/Sources/AuthAuth、User、MultiFactor、PhoneAuthProvider、OAuthProvider、GameCenterAuthProviderFirestore 实现FirebaseCombineSwift/Sources/FirestoreQuery、DocumentReference、CollectionReference、Transaction、WriteBatchFunctions 实现FirebaseCombineSwift/Sources/FunctionsFunctions、HTTPSCallableStorage 实现FirebaseCombineSwift/Sources/StorageStorage、StorageReference单元测试FirebaseCombineSwift/Tests/UnitAuth 各登录路径、Firestore 的 GetDocuments、Storage 的 StorageReferenceTests集成测试FirebaseCombineSwift/Tests/IntegrationStorage 上传下载端到端验证SwiftPM 包定义Package.swift四个-Communityproduct版本演进记录FirebaseCombineSwift/CHANGELOG.md综上FirebaseCombineSwift 以极小的包装成本纯extensionFuture桥接为 Firebase 四大核心产品提供了完整的 Combine 适配让响应式架构的应用可以用统一、可组合的方式消费 Firebase 能力。对开发者而言在评估其社区维护状态后可放心将其用于个人项目或内部工具体验 Combine 与 Firebase 结合带来的代码组织方式升级。【免费下载链接】firebase-ios-sdkFirebase SDK for Apple App Development项目地址: https://gitcode.com/GitHub_Trending/fi/firebase-ios-sdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表