@@ -65,7 +65,7 @@ public class RemoteConfigSettings: NSObject, NSCopying {
6565
6666/// Indicates whether updated data was successfully fetched.
6767@objc ( FIRRemoteConfigFetchStatus)
68- public enum RemoteConfigFetchStatus : Int {
68+ public enum RemoteConfigFetchStatus : Int , Sendable {
6969 /// Config has never been fetched.
7070 case noFetchYet
7171 /// Config fetch succeeded.
@@ -134,6 +134,17 @@ public enum RemoteConfigUpdateError: Int, LocalizedError, CustomNSError {
134134 }
135135}
136136
137+ /// Firebase Remote Config custom signals error.
138+ @objc ( FIRRemoteConfigCustomSignalsError)
139+ public enum RemoteConfigCustomSignalsError : Int , CustomNSError {
140+ /// Unknown error.
141+ case unknown = 8101
142+ /// Invalid value type in the custom signals dictionary.
143+ case invalidValueType = 8102
144+ /// Limit exceeded for key length, value length, or number of signals.
145+ case limitExceeded = 8103
146+ }
147+
137148/// Enumerated value that indicates the source of Remote Config data. Data can come from
138149/// the Remote Config service, the DefaultConfig that is available when the app is first
139150/// installed, or a static initialized value if data is not available from the service or
@@ -468,7 +479,10 @@ open class RemoteConfig: NSObject, NSFastEnumeration {
468479 /// and avoid calling this method again.
469480 ///
470481 /// - Parameter completionHandler Fetch operation callback with status and error parameters.
471- @objc public func fetch( completionHandler: ( ( RemoteConfigFetchStatus , Error ? ) -> Void ) ? = nil ) {
482+ @objc public func fetch( completionHandler: (
483+ @Sendable ( RemoteConfigFetchStatus , Error ? ) -> Void
484+ ) ? =
485+ nil ) {
472486 queue. async {
473487 self . fetch ( withExpirationDuration: self . settings. minimumFetchInterval,
474488 completionHandler: completionHandler)
@@ -515,7 +529,10 @@ open class RemoteConfig: NSObject, NSFastEnumeration {
515529 /// To stop the periodic sync, call `Installations.delete(completion:)`
516530 /// and avoid calling this method again.
517531 @objc public func fetch( withExpirationDuration expirationDuration: TimeInterval ,
518- completionHandler: ( ( RemoteConfigFetchStatus , Error ? ) -> Void ) ? = nil ) {
532+ completionHandler: (
533+ @Sendable ( RemoteConfigFetchStatus , Error ? ) -> Void
534+ ) ? =
535+ nil ) {
519536 configFetch. fetchConfig ( withExpirationDuration: expirationDuration,
520537 completionHandler: completionHandler)
521538 }
@@ -554,8 +571,7 @@ open class RemoteConfig: NSObject, NSFastEnumeration {
554571 ///
555572 /// - Parameter completionHandler Fetch operation callback with status and error parameters.
556573 @objc public func fetchAndActivate( completionHandler:
557- ( ( RemoteConfigFetchAndActivateStatus , Error ? ) -> Void ) ? =
558- nil ) {
574+ ( @Sendable ( RemoteConfigFetchAndActivateStatus , Error ? ) -> Void ) ? = nil ) {
559575 fetch { [ weak self] fetchStatus, error in
560576 guard let self else { return }
561577 // Fetch completed. We are being called on the main queue.
@@ -602,7 +618,7 @@ open class RemoteConfig: NSObject, NSFastEnumeration {
602618 /// Applies Fetched Config data to the Active Config, causing updates to the behavior and
603619 /// appearance of the app to take effect (depending on how config data is used in the app).
604620 /// - Parameter completion Activate operation callback with changed and error parameters.
605- @objc public func activate( completion: ( ( Bool , Error ? ) -> Void ) ? = nil ) {
621+ @objc public func activate( completion: ( @ Sendable ( Bool , Error ? ) -> Void ) ? = nil ) {
606622 queue. async { [ weak self] in
607623 guard let self else {
608624 let error = NSError (
@@ -882,8 +898,9 @@ open class RemoteConfig: NSObject, NSFastEnumeration {
882898 /// contains a remove method, which can be used to stop receiving updates for the provided
883899 /// listener.
884900 @discardableResult
885- @objc ( addOnConfigUpdateListener: ) public func addOnConfigUpdateListener( remoteConfigUpdateCompletion listener: @Sendable @escaping ( RemoteConfigUpdate ? ,
886- Error ? )
901+ @objc ( addOnConfigUpdateListener: )
902+ public func addOnConfigUpdateListener( remoteConfigUpdateCompletion listener: @Sendable @escaping ( RemoteConfigUpdate ? ,
903+ Error ? )
887904 -> Void )
888905 -> ConfigUpdateListenerRegistration {
889906 return configRealtime. addConfigUpdateListener ( listener)
@@ -951,6 +968,145 @@ open class RemoteConfig: NSObject, NSFastEnumeration {
951968 }
952969 return rolloutsAssignments
953970 }
971+
972+ let customSignalsMaxKeyLength = 250
973+ let customSignalsMaxStringValueLength = 500
974+ let customSignalsMaxCount = 100
975+
976+ // MARK: - Custom Signals
977+
978+ /// Sets custom signals for this Remote Config instance.
979+ /// - Parameter customSignals: A dictionary mapping string keys to custom
980+ /// signals to be set for the app instance.
981+ ///
982+ /// When a new key is provided, a new key-value pair is added to the custom signals.
983+ /// If an existing key is provided with a new value, the corresponding signal is updated.
984+ /// If the value for a key is `nil`, the signal associated with that key is removed.
985+ @available ( iOS 13 , tvOS 13 , macOS 10 . 15 , macCatalyst 13 , watchOS 7 , * )
986+ public
987+ func setCustomSignals( _ customSignals: [ String : CustomSignalValue ? ] ) async throws {
988+ return try await withUnsafeThrowingContinuation { continuation in
989+ let customSignals = customSignals. mapValues { $0? . toNSObject ( ) ?? NSNull ( ) }
990+ self . setCustomSignalsImpl ( customSignals) { error in
991+ if let error {
992+ continuation. resume ( throwing: error)
993+ } else {
994+ continuation. resume ( )
995+ }
996+ }
997+ }
998+ }
999+
1000+ @available ( swift 1000 . 0 ) // Objective-C only API
1001+ @objc ( setCustomSignals: withCompletion: ) public func __setCustomSignals( _ customSignals: [
1002+ String : Any
1003+ ] ? ,
1004+ withCompletion completionHandler: (
1005+ @Sendable ( Error ? ) -> Void
1006+ ) ? ) {
1007+ setCustomSignalsImpl ( customSignals, withCompletion: completionHandler)
1008+ }
1009+
1010+ private func setCustomSignalsImpl( _ customSignals: [ String : Any ] ? ,
1011+ withCompletion completionHandler: (
1012+ @Sendable ( Error ? ) -> Void
1013+ ) ? ) {
1014+ queue. async { [ weak self] in
1015+ guard let self else { return }
1016+ guard let customSignals = customSignals else {
1017+ if let completionHandler {
1018+ DispatchQueue . main. async {
1019+ completionHandler ( nil )
1020+ }
1021+ }
1022+ return
1023+ }
1024+
1025+ // Validate value type, and key and value length
1026+ for (key, value) in customSignals {
1027+ if !( value is NSNull || value is NSString || value is NSNumber ) {
1028+ let error = NSError (
1029+ domain: ConfigConstants . remoteConfigCustomSignalsErrorDomain,
1030+ code: RemoteConfigCustomSignalsError . invalidValueType. rawValue,
1031+ userInfo: [
1032+ NSLocalizedDescriptionKey: " Invalid value type. Must be NSString, NSNumber, or NSNull. " ,
1033+ ]
1034+ )
1035+ if let completionHandler {
1036+ DispatchQueue . main. async {
1037+ completionHandler ( error)
1038+ }
1039+ }
1040+ return
1041+ }
1042+
1043+ if key. count > customSignalsMaxKeyLength ||
1044+ ( value is NSString && ( value as! NSString ) . length > customSignalsMaxStringValueLength) {
1045+ if let completionHandler {
1046+ let error = NSError (
1047+ domain: ConfigConstants . remoteConfigCustomSignalsErrorDomain,
1048+ code: RemoteConfigCustomSignalsError . limitExceeded. rawValue,
1049+ userInfo: [
1050+ NSLocalizedDescriptionKey:
1051+ " Custom signal keys and string values must be " +
1052+ " \( customSignalsMaxKeyLength) and " +
1053+ " \( customSignalsMaxStringValueLength) " +
1054+ " characters or less respectively. " ,
1055+ ]
1056+ )
1057+ DispatchQueue . main. async {
1058+ completionHandler ( error)
1059+ }
1060+ }
1061+ return
1062+ }
1063+ }
1064+
1065+ // Merge new signals with existing ones, overwriting existing keys.
1066+ // Also, remove entries where the new value is null.
1067+ var newCustomSignals = self . settings. customSignals
1068+
1069+ for (key, value) in customSignals {
1070+ if !( value is NSNull ) {
1071+ let stringValue = value is NSNumber ? ( value as! NSNumber ) . stringValue : value as! String
1072+ newCustomSignals [ key] = stringValue
1073+ } else {
1074+ newCustomSignals. removeValue ( forKey: key)
1075+ }
1076+ }
1077+
1078+ // Check the size limit.
1079+ if newCustomSignals. count > customSignalsMaxCount {
1080+ if let completionHandler {
1081+ let error = NSError (
1082+ domain: ConfigConstants . remoteConfigCustomSignalsErrorDomain,
1083+ code: RemoteConfigCustomSignalsError . limitExceeded. rawValue,
1084+ userInfo: [
1085+ NSLocalizedDescriptionKey:
1086+ " Custom signals count exceeds the limit of \( customSignalsMaxCount) . " ,
1087+ ]
1088+ )
1089+ DispatchQueue . main. async {
1090+ completionHandler ( error)
1091+ }
1092+ }
1093+ return
1094+ }
1095+
1096+ // Update only if there are changes.
1097+ if newCustomSignals != self . settings. customSignals {
1098+ self . settings. customSignals = newCustomSignals
1099+ }
1100+
1101+ // Log the keys of the updated custom signals using RCLog.debug
1102+ RCLog . debug ( " I-RCN000078 " ,
1103+ " Keys of updated custom signals: \( newCustomSignals. keys. sorted ( ) ) " )
1104+
1105+ DispatchQueue . main. async {
1106+ completionHandler ? ( nil )
1107+ }
1108+ }
1109+ }
9541110}
9551111
9561112// MARK: - Rollout Notification
0 commit comments