账号创建偶现EXC_BAD_ACCESS崩溃及Swift并发合规问题求助
创建账号时的偶发崩溃诊断与修复建议
崩溃现象
- 创建账号时约20%概率触发崩溃,仅在Firebase App Distribution分发的应用中出现,模拟器运行无问题
- 崩溃核心信息:
Crashed: com.apple.main-thread EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x0000000000000010
崩溃调用栈
Crashed: com.apple.main-thread 0 libobjc.A.dylib 0x2820 objc_msgSend + 32 1 UIKitCore 0x2004a4 -[UIViewController dealloc] + 772 2 UIKitCore 0x1fe0cc -[UINavigationController dealloc] + 308 3 UIKitCore 0x486bbc -[_UISplitViewControllerColumnContents .cxx_destruct] + 44 4 libobjc.A.dylib 0x20a4 object_cxxDestructFromClass(objc_object*, objc_class*) + 116 5 libobjc.A.dylib 0x6e00 objc_destructInstance + 80 6 libobjc.A.dylib 0x104fc _objc_rootDealloc + 80 7 CoreFoundation 0x2a26c cow_cleanup + 168 8 CoreFoundation 0x62538 -[__NSDictionaryM dealloc] + 148 9 libobjc.A.dylib 0x20a4 object_cxxDestructFromClass(objc_object*, objc_class*) + 116 10 libobjc.A.dylib 0x6e00 objc_destructInstance + 80 11 libobjc.A.dylib 0x104fc _objc_rootDealloc + 80 12 libobjc.A.dylib 0x20a4 object_cxxDestructFromClass(objc_object*, objc_class*) + 116 13 libobjc.A.dylib 0x6e00 objc_destructInstance + 80 14 libobjc.A.dylib 0x104fc _objc_rootDealloc + 80 15 UIKitCore 0x2008d8 -[UIResponder dealloc] + 124 16 UIKitCore 0x2005dc -[UIViewController dealloc] + 1084 17 libobjc.A.dylib 0x21d4 AutoreleasePoolPage::releaseUntil(objc_object**) + 196 18 libobjc.A.dylib 0x5bdc objc_autoreleasePoolPop + 256 19 UIKitCore 0x1a0444 -[_UIAfterCACommitBlock run] + 92 20 UIKitCore 0x1a0364 -[_UIAfterCACommitQueue flush] + 168 21 UIKitCore 0x1a0278 _runAfterCACommitDeferredBlocks + 496 22 UIKitCore 0x3eee4 _cleanUpAfterCAFlushAndRunDeferredBlocks + 108 23 UIKitCore 0x4fd608 _UIApplicationFlushCATransaction + 72 24 UIKitCore 0x64de00 _UIUpdateSequenceRun + 84 25 UIKitCore 0xcb1944 schedulerStepScheduledMainSection + 144 26 UIKitCore 0xcb0ea0 runloopSourceCallback + 92 27 CoreFoundation 0xd3208 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28 28 CoreFoundation 0xdf864 __CFRunLoopDoSource0 + 176 29 CoreFoundation 0x646c8 __CFRunLoopDoSources0 + 244 30 CoreFoundation 0x7a1c4 __CFRunLoopRun + 828 31 CoreFoundation 0x7f4dc CFRunLoopRunSpecific + 612 32 GraphicsServices 0x135c GSEventRunModal + 164 33 UIKitCore 0x39d37c -[UIApplication _run] + 888 34 UIKitCore 0x39cfe0 UIApplicationMain + 340 35 SwiftUI 0x1bc3d8 OUTLINED_FUNCTION_895 + 2472 36 SwiftUI 0x1229b0 block_copy_helper.1 + 496 37 SwiftUI 0x10ca54 OUTLINED_FUNCTION_901 + 2752 38 AppName 0x7844 main + 23 (AppName.swift:23) 39 ??? 0x1ab1b0dec (Missing)
触发代码与相关逻辑
触发按钮与ViewModel声明
@StateObject var viewModel: AuthViewModel.CreateAccountViewModel ... Button("Create Account", action: viewModel.submit)
AuthViewModel代码
func makeCreateAccountViewModel() -> CreateAccountViewModel { return CreateAccountViewModel(action: authService.createAccount(name:email:password:)) } class CreateAccountViewModel: FormViewModel<(name: String, email: String, password: String)> { convenience init(action: @escaping Action) { self.init(initialValue: (name: "", email: "", password: ""), action: action) } }
FormViewModel代码
@MainActor @dynamicMemberLookup class FormViewModel<Value>: ObservableObject { typealias Action = (Value) async throws -> Void @Published var value: Value @Published var error: Error? @Published var isWorking = false subscript<T>(dynamicMember keyPath: WritableKeyPath<Value, T>) -> T { get { value[keyPath: keyPath] } set { value[keyPath: keyPath] = newValue } } private let action: Action init(initialValue: Value, action: @escaping Action) { self.value = initialValue self.action = action } nonisolated func submit() { Task { await handleSubmit() } } private func handleSubmit() async { isWorking = true do { try await action(value) } catch { print("[FormViewModel] Cannot submit: \(error)") self.error = error } isWorking = false } }
AuthService代码
private let auth = Auth.auth() func createAccount(email: String, password: String) async throws { let result = try await auth.createUser(withEmail: email, password: password) try await result.user.updateProfile(\.displayName, to: email) } private extension FirebaseAuth.User { func updateProfile<T>(_ keyPath: WritableKeyPath<UserProfileChangeRequest, T>, to newValue: T) async throws { var profileChangeRequest = createProfileChangeRequest() profileChangeRequest[keyPath: keyPath] = newValue try await profileChangeRequest.commitChanges() } }
并发检查警告
开启严格/完整并发检查后,createAccount方法出现两个警告:
func createAccount(email: String, password: String) async throws { let result = try await auth.createUser(withEmail: email, password: password) try await result.user.updateProfile(\.displayName, to: email) }
警告内容:
let result行:Non-sendable type 'AuthDataResult' returned by call from main actor-isolated context to non-isolated instance method 'createUser(withEmail:password:)' cannot cross actor boundarytry await行:Non-sendable type 'WritableKeyPath<UserProfileChangeRequest, String?>' exiting main actor-isolated context in call to non-isolated instance method 'updateProfile(_:to:)' cannot cross actor boundary
修复建议
1. 解决并发安全警告
处理AuthDataResult跨actor问题
Firebase的AuthDataResult未标记为Sendable,需将Firebase操作移至非主线程上下文:
- 方案一:给AuthService添加自定义actor隔离
actor AuthService { private let auth = Auth.auth() func createAccount(email: String, password: String) async throws { let result = try await auth.createUser(withEmail: email, password: password) try await result.user.updateProfile(\.displayName, to: email) } }
- 方案二:使用
Task.detached在后台线程执行Firebase操作
func createAccount(email: String, password: String) async throws { try await Task.detached { let result = try await Auth.auth().createUser(withEmail: email, password: password) try await result.user.updateProfile(\.displayName, to: email) }.value }
处理WritableKeyPath警告
给updateProfile方法添加@Sendable标记,确保参数符合并发安全要求:
private extension FirebaseAuth.User { func updateProfile<T: Sendable>(_ keyPath: @Sendable WritableKeyPath<UserProfileChangeRequest, T>, to newValue: T) async throws { var profileChangeRequest = createProfileChangeRequest() profileChangeRequest[keyPath: keyPath] = newValue try await profileChangeRequest.commitChanges() } }
2. 修复偶发崩溃问题
从调用栈看,崩溃发生在UIViewController释放阶段,大概率是并发导致的野指针或对象过度释放:
- 确认ViewModel生命周期:
@StateObject已保证ViewModel与视图同生命周期,需避免账号创建成功后导航时非法持有或提前释放ViewModel引用 - 减轻主线程压力:通过上述actor或detached Task将Firebase操作移至后台,减少主线程在自动释放池弹出时的负载
- 更新Firebase版本:升级Firebase Auth到最新版,官方可能已修复相关内存管理或并发兼容问题
- 启用内存调试:在Release模式下开启Zombies工具(Xcode Scheme设置),捕获具体被过度释放的对象,定位根因
内容的提问来源于stack exchange,提问作者Mech
相关产品推荐
相关产品推荐

