Skip to content

Commit

Permalink
fix(firestore): Prevent iOS crash caused by concurrency issue (#772)
Browse files Browse the repository at this point in the history
  • Loading branch information
TomCarey authored Dec 16, 2024
1 parent ab66995 commit f1f07aa
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/eleven-hornets-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@capacitor-firebase/firestore': patch
---

fix(ios): prevent crash caused by concurrency issue
51 changes: 37 additions & 14 deletions packages/firestore/ios/Plugin/FirebaseFirestore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,32 @@ import Foundation
import FirebaseCore
import FirebaseFirestore

@objc public class FirebaseFirestore: NSObject {
private let plugin: FirebaseFirestorePlugin
private actor ListenerRegistrationMap {
private var listenerRegistrationMap: [String: ListenerRegistration] = [:]

func addRegistration(_ listenerRegistration: ListenerRegistration, listenerId: String) async {
listenerRegistrationMap[listenerId] = listenerRegistration
}

func removeRegistration(listenerId: String) async {
if let listenerRegistration = listenerRegistrationMap[listenerId] {
listenerRegistration.remove()
}
listenerRegistrationMap.removeValue(forKey: listenerId)
}

func removeAll() async {
listenerRegistrationMap.forEach { _, value in
value.remove()
}
listenerRegistrationMap.removeAll()
}
}

@objc public class FirebaseFirestore: NSObject {
private let plugin: FirebaseFirestorePlugin
private var listenerRegistrationMap = ListenerRegistrationMap()

init(plugin: FirebaseFirestorePlugin) {
self.plugin = plugin
super.init()
Expand Down Expand Up @@ -208,7 +230,9 @@ import FirebaseFirestore
completion(result, nil)
}
}
self.listenerRegistrationMap[callbackId] = listenerRegistration
Task {
await self.listenerRegistrationMap.addRegistration(listenerRegistration, listenerId: callbackId)
}
}

@objc public func addCollectionSnapshotListener(_ options: AddCollectionSnapshotListenerOptions, completion: @escaping (Result?, Error?) -> Void) {
Expand Down Expand Up @@ -241,7 +265,7 @@ import FirebaseFirestore
completion(result, nil)
}
}
self.listenerRegistrationMap[callbackId] = listenerRegistration
await listenerRegistrationMap.addRegistration(listenerRegistration, listenerId: callbackId)
} catch {
completion(nil, error)
}
Expand Down Expand Up @@ -278,26 +302,25 @@ import FirebaseFirestore
completion(result, nil)
}
}
self.listenerRegistrationMap[callbackId] = listenerRegistration
await listenerRegistrationMap.addRegistration(listenerRegistration, listenerId: callbackId)
} catch {
completion(nil, error)
}
}
}

@objc public func removeSnapshotListener(_ options: RemoveSnapshotListenerOptions) {
@objc public func removeSnapshotListener(_ options: RemoveSnapshotListenerOptions, completion: @escaping () -> Void) {
let callbackId = options.getCallbackId()

if let listenerRegistration = self.listenerRegistrationMap[callbackId] {
listenerRegistration.remove()
Task {
await listenerRegistrationMap.removeRegistration(listenerId: callbackId)
completion()
}
self.listenerRegistrationMap.removeValue(forKey: callbackId)
}

@objc public func removeAllListeners() {
for listenerRegistration in self.listenerRegistrationMap.values {
listenerRegistration.remove()
@objc public func removeAllListeners(completion: @escaping () -> Void) {
Task {
await listenerRegistrationMap.removeAll()
completion()
}
self.listenerRegistrationMap.removeAll()
}
}
13 changes: 8 additions & 5 deletions packages/firestore/ios/Plugin/FirebaseFirestorePlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -348,17 +348,20 @@ public class FirebaseFirestorePlugin: CAPPlugin {

let options = RemoveSnapshotListenerOptions(callbackId: callbackId)

implementation?.removeSnapshotListener(options)
call.resolve()
implementation?.removeSnapshotListener(options) {
call.resolve()
}
}

@objc override public func removeAllListeners(_ call: CAPPluginCall) {
for (_, savedCall) in self.pluginCallMap {
bridge?.releaseCall(savedCall)
}
self.pluginCallMap.removeAll()

implementation?.removeAllListeners()
super.removeAllListeners(call)
self.eventListeners?.removeAllObjects()

implementation?.removeAllListeners {
call.resolve()
}
}
}

0 comments on commit f1f07aa

Please sign in to comment.