@@ -27,6 +27,36 @@ import Foundation
27
27
// Avoids exposing internal FirebaseCore APIs to Swift users.
28
28
@_implementationOnly import FirebaseCoreExtension
29
29
30
+ final class AtomicBox < T> {
31
+ private var _value : T
32
+ private let lock = NSLock ( )
33
+
34
+ public init ( _ value: T ) {
35
+ _value = value
36
+ }
37
+
38
+ public func value( ) -> T {
39
+ lock. withLock {
40
+ _value
41
+ }
42
+ }
43
+
44
+ @discardableResult
45
+ public func withLock( _ mutatingBody: ( _ value: inout T ) -> Void ) -> T {
46
+ lock. withLock {
47
+ mutatingBody ( & _value)
48
+ return _value
49
+ }
50
+ }
51
+
52
+ @discardableResult
53
+ public func withLock< R> ( _ mutatingBody: ( _ value: inout T ) throws -> R ) rethrows -> R {
54
+ try lock. withLock {
55
+ try mutatingBody ( & _value)
56
+ }
57
+ }
58
+ }
59
+
30
60
/// File specific constants.
31
61
private enum Constants {
32
62
static let appCheckTokenHeader = " X-Firebase-AppCheck "
@@ -53,10 +83,12 @@ enum FunctionsConstants {
53
83
54
84
/// A map of active instances, grouped by app. Keys are FirebaseApp names and values are arrays
55
85
/// containing all instances of Functions associated with the given app.
56
- private static var instances : [ String : [ Functions ] ] = [ : ]
57
-
58
- /// Lock to manage access to the instances array to avoid race conditions.
59
- private static var instancesLock : os_unfair_lock = . init( )
86
+ #if compiler(>=6.0)
87
+ private nonisolated ( unsafe) static var instances : AtomicBox < [ String : [ Functions ] ] > =
88
+ AtomicBox ( [ : ] )
89
+ #else
90
+ private static var instances : AtomicBox < [ String : [ Functions ] ] > = AtomicBox ( [ : ] )
91
+ #endif
60
92
61
93
/// The custom domain to use for all functions references (optional).
62
94
let customDomain : String ?
@@ -304,30 +336,28 @@ enum FunctionsConstants {
304
336
guard let app else {
305
337
fatalError ( " `FirebaseApp.configure()` needs to be called before using Functions. " )
306
338
}
307
- os_unfair_lock_lock ( & instancesLock)
308
-
309
- // Unlock before the function returns.
310
- defer { os_unfair_lock_unlock ( & instancesLock) }
311
-
312
- if let associatedInstances = instances [ app. name] {
313
- for instance in associatedInstances {
314
- // Domains may be nil, so handle with care.
315
- var equalDomains = false
316
- if let instanceCustomDomain = instance. customDomain {
317
- equalDomains = instanceCustomDomain == customDomain
318
- } else {
319
- equalDomains = customDomain == nil
320
- }
321
- // Check if it's a match.
322
- if instance. region == region, equalDomains {
323
- return instance
339
+
340
+ return instances. withLock { instances in
341
+ if let associatedInstances = instances [ app. name] {
342
+ for instance in associatedInstances {
343
+ // Domains may be nil, so handle with care.
344
+ var equalDomains = false
345
+ if let instanceCustomDomain = instance. customDomain {
346
+ equalDomains = instanceCustomDomain == customDomain
347
+ } else {
348
+ equalDomains = customDomain == nil
349
+ }
350
+ // Check if it's a match.
351
+ if instance. region == region, equalDomains {
352
+ return instance
353
+ }
324
354
}
325
355
}
356
+ let newInstance = Functions ( app: app, region: region, customDomain: customDomain)
357
+ let existingInstances = instances [ app. name, default: [ ] ]
358
+ instances [ app. name] = existingInstances + [ newInstance]
359
+ return newInstance
326
360
}
327
- let newInstance = Functions ( app: app, region: region, customDomain: customDomain)
328
- let existingInstances = instances [ app. name, default: [ ] ]
329
- instances [ app. name] = existingInstances + [ newInstance]
330
- return newInstance
331
361
}
332
362
333
363
@objc init ( projectID: String ,
@@ -576,34 +606,65 @@ enum FunctionsConstants {
576
606
}
577
607
}
578
608
579
- @available ( macOS 12 . 0 , iOS 15 . 0 , watchOS 8 . 0 , tvOS 15 . 0 , * )
580
- private func callableStreamResult( fromResponseData data: Data ,
581
- endpointURL url: URL ) throws -> JSONStreamResponse {
582
- let data = try processedData ( fromResponseData: data, endpointURL: url)
609
+ #if compiler(>=6.0)
610
+ @available ( macOS 12 . 0 , iOS 15 . 0 , watchOS 8 . 0 , tvOS 15 . 0 , * )
611
+ private func callableStreamResult( fromResponseData data: Data ,
612
+ endpointURL url: URL ) throws -> sending JSONStreamResponse {
613
+ let data = try processedData ( fromResponseData: data, endpointURL: url)
614
+
615
+ let responseJSONObject : Any
616
+ do {
617
+ responseJSONObject = try JSONSerialization . jsonObject ( with: data)
618
+ } catch {
619
+ throw FunctionsError ( . dataLoss, userInfo: [ NSUnderlyingErrorKey: error] )
620
+ }
583
621
584
- let responseJSONObject : Any
585
- do {
586
- responseJSONObject = try JSONSerialization . jsonObject ( with: data)
587
- } catch {
588
- throw FunctionsError ( . dataLoss, userInfo: [ NSUnderlyingErrorKey: error] )
589
- }
622
+ guard let responseJSON = responseJSONObject as? [ String : Any ] else {
623
+ let userInfo = [ NSLocalizedDescriptionKey: " Response was not a dictionary. " ]
624
+ throw FunctionsError ( . dataLoss, userInfo: userInfo)
625
+ }
590
626
591
- guard let responseJSON = responseJSONObject as? [ String : Any ] else {
592
- let userInfo = [ NSLocalizedDescriptionKey: " Response was not a dictionary. " ]
593
- throw FunctionsError ( . dataLoss, userInfo: userInfo)
627
+ if let _ = responseJSON [ " result " ] {
628
+ return . result( responseJSON)
629
+ } else if let _ = responseJSON [ " message " ] {
630
+ return . message( responseJSON)
631
+ } else {
632
+ throw FunctionsError (
633
+ . dataLoss,
634
+ userInfo: [ NSLocalizedDescriptionKey: " Response is missing result or message field. " ]
635
+ )
636
+ }
594
637
}
638
+ #else
639
+ @available ( macOS 12 . 0 , iOS 15 . 0 , watchOS 8 . 0 , tvOS 15 . 0 , * )
640
+ private func callableStreamResult( fromResponseData data: Data ,
641
+ endpointURL url: URL ) throws -> JSONStreamResponse {
642
+ let data = try processedData ( fromResponseData: data, endpointURL: url)
643
+
644
+ let responseJSONObject : Any
645
+ do {
646
+ responseJSONObject = try JSONSerialization . jsonObject ( with: data)
647
+ } catch {
648
+ throw FunctionsError ( . dataLoss, userInfo: [ NSUnderlyingErrorKey: error] )
649
+ }
595
650
596
- if let _ = responseJSON [ " result " ] {
597
- return . result( responseJSON)
598
- } else if let _ = responseJSON [ " message " ] {
599
- return . message( responseJSON)
600
- } else {
601
- throw FunctionsError (
602
- . dataLoss,
603
- userInfo: [ NSLocalizedDescriptionKey: " Response is missing result or message field. " ]
604
- )
651
+ guard let responseJSON = responseJSONObject as? [ String : Any ] else {
652
+ let userInfo = [ NSLocalizedDescriptionKey: " Response was not a dictionary. " ]
653
+ throw FunctionsError ( . dataLoss, userInfo: userInfo)
654
+ }
655
+
656
+ if let _ = responseJSON [ " result " ] {
657
+ return . result( responseJSON)
658
+ } else if let _ = responseJSON [ " message " ] {
659
+ return . message( responseJSON)
660
+ } else {
661
+ throw FunctionsError (
662
+ . dataLoss,
663
+ userInfo: [ NSLocalizedDescriptionKey: " Response is missing result or message field. " ]
664
+ )
665
+ }
605
666
}
606
- }
667
+ #endif // compiler(>=6.0)
607
668
608
669
private func jsonData( jsonText: String ) throws -> Data {
609
670
guard let data = jsonText. data ( using: . utf8) else {
0 commit comments