-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[rc-swift] RemoteConfig.swift #14309
Conversation
Generated by 🚫 Danger |
Apple API Diff ReportCommit: 54267c8 FirebaseRemoteConfigType Definitions[REMOVED] FIRRemoteConfigFetchAndActivateCompletion[REMOVED] FIRRemoteConfigFetchAndActivateCompletionObjective-C:
- typedef void ( ^ FIRRemoteConfigFetchAndActivateCompletion )( FIRRemoteConfigFetchAndActivateStatus , NSError * _Nullable ) [REMOVED] FIRRemoteConfigFetchCompletion[REMOVED] FIRRemoteConfigFetchCompletionObjective-C:
- typedef void ( ^ FIRRemoteConfigFetchCompletion )( FIRRemoteConfigFetchStatus , NSError * _Nullable ) [REMOVED] FIRRemoteConfigUpdateCompletion[REMOVED] FIRRemoteConfigUpdateCompletion[REMOVED] EnumerationsSwift:
- typealias RemoteConfigError . Code . _ErrorType = RemoteConfigError
- case unknown = 8001
- case throttled = 8002
- case internalError = 8003
- enum RemoteConfigFetchAndActivateStatus : Int , @unchecked Sendable
- case successFetchedFromRemote = 0
- case successUsingPreFetchedData = 1
- case error = 2
- enum RemoteConfigFetchStatus : Int , @unchecked Sendable
- case noFetchYet = 0
- case success = 1
- case failure = 2
- case throttled = 3
- enum RemoteConfigSource : Int , @unchecked Sendable
- case remote = 0
- case ` default ` = 1
- case ` static ` = 2
- typealias RemoteConfigUpdateError . Code . _ErrorType = RemoteConfigUpdateError
- case streamError = 8001
- case notFetched = 8002
- case messageInvalid = 8003
- case unavailable = 8004
Objective-C:
- enum FIRRemoteConfigError : NSInteger {}
- FIRRemoteConfigErrorUnknown = 8001
- FIRRemoteConfigErrorThrottled = 8002
- FIRRemoteConfigErrorInternalError = 8003
- enum FIRRemoteConfigFetchAndActivateStatus : NSInteger {}
- FIRRemoteConfigFetchAndActivateStatusSuccessFetchedFromRemote
- FIRRemoteConfigFetchAndActivateStatusSuccessUsingPreFetchedData
- FIRRemoteConfigFetchAndActivateStatusError
- enum FIRRemoteConfigFetchStatus : NSInteger {}
- FIRRemoteConfigFetchStatusNoFetchYet
- FIRRemoteConfigFetchStatusSuccess
- FIRRemoteConfigFetchStatusFailure
- FIRRemoteConfigFetchStatusThrottled
- enum FIRRemoteConfigSource : NSInteger {}
- FIRRemoteConfigSourceRemote
- FIRRemoteConfigSourceDefault
- FIRRemoteConfigSourceStatic
- enum FIRRemoteConfigUpdateError : NSInteger {}
- FIRRemoteConfigUpdateErrorStreamError = 8001
- FIRRemoteConfigUpdateErrorNotFetched = 8002
- FIRRemoteConfigUpdateErrorMessageInvalid = 8003
- FIRRemoteConfigUpdateErrorUnavailable = 8004 Constants[REMOVED] FIRRemoteConfigErrorDomain[REMOVED] FIRRemoteConfigErrorDomainSwift:
- let RemoteConfigErrorDomain : String
Objective-C:
- extern NS_SWIFT_NAME ( RemoteConfigErrorDomain ) NSString * const FIRRemoteConfigErrorDomain [REMOVED] FIRRemoteConfigUpdateErrorDomain[REMOVED] FIRRemoteConfigUpdateErrorDomainSwift:
- let RemoteConfigUpdateErrorDomain : String
Objective-C:
- extern NS_SWIFT_NAME ( RemoteConfigUpdateErrorDomain ) NSString * const FIRRemoteConfigUpdateErrorDomain [REMOVED] ClassesSwift:
- var lastFetchTime : Date ? { get }
- var lastFetchStatus : RemoteConfigFetchStatus { get }
- var configSettings : RemoteConfigSettings { get set }
- class func remoteConfig () -> RemoteConfig
- class func remoteConfig ( app : FIRApp ) -> RemoteConfig
- func ensureInitialized () async throws
- func fetch () async throws -> RemoteConfigFetchStatus
- func fetch ( withExpirationDuration expirationDuration : TimeInterval ) async throws -> RemoteConfigFetchStatus
- func fetchAndActivate () async throws -> RemoteConfigFetchAndActivateStatus
- func activate () async throws -> Bool
- subscript ( key : String ) -> FIRRemoteConfigValue { get }
- func configValue ( forKey key : String ?) -> FIRRemoteConfigValue
- func configValue ( forKey key : String ?, source : RemoteConfigSource ) -> FIRRemoteConfigValue
- func allKeys ( from source : RemoteConfigSource ) -> [ String ]
- func keys ( withPrefix prefix : String ?) -> Set < String >
- func setDefaults ( _ defaults : [ String : NSObject ]?)
- func setDefaults ( fromPlist fileName : String ?)
- func defaultValue ( forKey key : String ?) -> FIRRemoteConfigValue ?
- func addOnConfigUpdateListener ( remoteConfigUpdateCompletion listener : @escaping ( FIRRemoteConfigUpdate ?, ( any Error )?) -> Void ) -> FIRConfigUpdateListenerRegistration
- var configRealtime : RCNConfigRealtime { get set }
- var settings : RCNConfigSettings { get }
- init ( appName : String , firOptions options : FIROptions , namespace FIRNamespace : String , dbManager DBManager : RCNConfigDBManager , configContent : RCNConfigContent , analytics : ( any FIRAnalyticsInterop )?)
- init ( appName : String , firOptions options : FIROptions , namespace FIRNamespace : String , dbManager DBManager : RCNConfigDBManager , configContent : RCNConfigContent , userDefaults : UserDefaults ?, analytics : ( any FIRAnalyticsInterop )?, configFetch : RCNConfigFetch ?, configRealtime : RCNConfigRealtime ?)
- func addInteropSubscriber ( _ subscriber : any FIRRolloutsStateSubscriber )
- class RemoteConfigSettings : NSObject
- var minimumFetchInterval : TimeInterval { get set }
- var fetchTimeout : TimeInterval { get set }
Objective-C:
- @property ( nonatomic , strong , readonly , nullable ) NSDate * lastFetchTime ;
- @property ( nonatomic , readonly ) FIRRemoteConfigFetchStatus lastFetchStatus ;
- @property ( nonatomic , strong , nonnull ) FIRRemoteConfigSettings * configSettings ;
- + ( nonnull FIRRemoteConfig * ) remoteConfig ;
- + ( nonnull FIRRemoteConfig * ) remoteConfigWithApp :( nonnull FIRApp * ) app ;
- - ( nonnull instancetype ) init ;
- - ( void ) ensureInitializedWithCompletionHandler : ( void ( ^ _Nonnull )( NSError * _Nullable )) completionHandler ;
- - ( void ) fetchWithCompletionHandler : ( void ( ^ _Nullable )( FIRRemoteConfigFetchStatus , NSError * _Nullable )) completionHandler ;
- - ( void ) fetchWithExpirationDuration :( NSTimeInterval ) expirationDuration completionHandler : ( void ( ^ _Nullable )( FIRRemoteConfigFetchStatus , NSError * _Nullable )) completionHandler ;
- - ( void ) fetchAndActivateWithCompletionHandler : ( void ( ^ _Nullable )( FIRRemoteConfigFetchAndActivateStatus , NSError * _Nullable )) completionHandler ;
- - ( void ) activateWithCompletion : ( void ( ^ _Nullable )( BOOL , NSError * _Nullable )) completion ;
- - ( nonnull FIRRemoteConfigValue * ) objectForKeyedSubscript : ( nonnull NSString * ) key ;
- - ( nonnull FIRRemoteConfigValue * ) configValueForKey :( nullable NSString * ) key ;
- - ( nonnull FIRRemoteConfigValue * ) configValueForKey :( nullable NSString * ) key source : ( FIRRemoteConfigSource ) source ;
- - ( nonnull NSArray < NSString *> * ) allKeysFromSource : ( FIRRemoteConfigSource ) source ;
- - ( nonnull NSSet < NSString *> * ) keysWithPrefix :( nullable NSString * ) prefix ;
- - ( void ) setDefaults :( nullable NSDictionary < NSString * , NSObject *> * ) defaults ;
- - ( void ) setDefaultsFromPlistFileName :( nullable NSString * ) fileName ;
- - ( nullable FIRRemoteConfigValue * ) defaultValueForKey :( nullable NSString * ) key ;
- - ( FIRConfigUpdateListenerRegistration * _Nonnull ) addOnConfigUpdateListener : ( FIRRemoteConfigUpdateCompletion _Nonnull ) listener ;
- @property ( nonatomic , readwrite , strong , nonnull ) RCNConfigRealtime * configRealtime
- @property ( nonatomic , readonly , strong ) RCNConfigSettings * settings
- - ( nonnull instancetype ) initWithAppName :( nonnull NSString * ) appName FIROptions :( nonnull FIROptions * ) options namespace :( nonnull NSString * ) FIRNamespace DBManager :( nonnull RCNConfigDBManager * ) DBManager configContent :( nonnull RCNConfigContent * ) configContent analytics :( nullable id < FIRAnalyticsInterop > ) analytics ;
- - ( instancetype ) initWithAppName :( NSString * ) appName FIROptions :( FIROptions * ) options namespace :( NSString * ) FIRNamespace DBManager :( RCNConfigDBManager * ) DBManager configContent :( RCNConfigContent * ) configContent userDefaults :( nullable NSUserDefaults * ) userDefaults analytics :( nullable id < FIRAnalyticsInterop > ) analytics configFetch :( nullable RCNConfigFetch * ) configFetch configRealtime :( nullable RCNConfigRealtime * ) configRealtime ;
- - ( void ) addRemoteConfigInteropSubscriber : ( id < FIRRolloutsStateSubscriber > _Nonnull ) subscriber ;
- @interface FIRRemoteConfigSettings : NSObject
- @property ( nonatomic ) NSTimeInterval minimumFetchInterval ;
- @property ( nonatomic ) NSTimeInterval fetchTimeout ; |
FirebaseRemoteConfig/Tests/Swift/SwiftAPI/FirebaseRemoteConfigSwift_APIBuildTests.swift
Outdated
Show resolved
Hide resolved
configRealtime: nil | ||
) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
init()
was made unavailable in ObjC. Can clients use the inherited init()
? If so, we may need to add:
/// Unavailable. Use `remoteConfig()` instead.
@available(*, unavailable)
@objc public init() {}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RCLog.debug("I-RCN987366", | ||
"Successfully read configSettings. Minimum Fetch Interval: " + | ||
"\(minimumFetchInterval), Fetch timeout: \(fetchTimeout)") | ||
return settings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ObjC implementation would recreate a network session before returning the settings.
/// The NSURLSession needs to be recreated whenever the fetch timeout may be updated.
[_configFetch recreateNetworkSession];
return settings;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why the get
needs to do this, but will add it back for now to be consistent.
NSError( | ||
domain: ConfigConstants.RemoteConfigErrorDomain, | ||
code: RemoteConfigError.internalError.rawValue, | ||
userInfo: [NSLocalizedDescriptionKey: "Timed out waiting for database load."] | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be an RC-typed error ?
@objc public func addListener(_ listener: @escaping RemoteConfigListener) { | ||
queue.async { | ||
self.listeners.append(listener) | ||
} | ||
} | ||
|
||
private func callListeners(key: String, config: [String: RemoteConfigValue]) { | ||
queue.async { [weak self] in | ||
guard let self = self else { return } | ||
for listener in self.listeners { | ||
listener(key, config) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No changes required, but the ObjC impl. synchronized with a lock on the listeners property rather than the serial queue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I can't think of advantages of a separate dispatch_async for each listener
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the thorough review so far
public let FIRNamespaceGoogleMobilePlatform = "firebase" | ||
|
||
public let FIRRemoteConfigThrottledEndTimeInSecondsKey = "error_throttled_end_time_seconds" | ||
|
||
public let FIRRemoteConfigActivateNotification = | ||
Notification.Name("FIRRemoteConfigActivateNotification") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes for some. Restored objc version of FIRRemoteConfigThrottledEndTimeInSecondsKey and FIRNamespaceGoogleMobilePlatform
@ncooke3 I've only processed your first batch so far - will start on second batch now. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should now have responded to all review comments
RCLog.debug("I-RCN987366", | ||
"Successfully read configSettings. Minimum Fetch Interval: " + | ||
"\(minimumFetchInterval), Fetch timeout: \(fetchTimeout)") | ||
return settings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why the get
needs to do this, but will add it back for now to be consistent.
@objc public func addListener(_ listener: @escaping RemoteConfigListener) { | ||
queue.async { | ||
self.listeners.append(listener) | ||
} | ||
} | ||
|
||
private func callListeners(key: String, config: [String: RemoteConfigValue]) { | ||
queue.async { [weak self] in | ||
guard let self = self else { return } | ||
for listener in self.listeners { | ||
listener(key, config) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I can't think of advantages of a separate dispatch_async for each listener
I'm going to merge here and rebase with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for resolving everything! I'm leaving one final batch, and I can open a PR to address these points. Nothing critical or problematic found.
((RemoteConfigFetchAndActivateStatus, Error?) -> Void)? = | ||
nil) { | ||
fetch { [weak self] fetchStatus, error in | ||
guard let self = self else { return } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
guard let self = self else { return } | |
guard let self else { return } |
_ = try await fetch() | ||
do { | ||
try await activate() | ||
return .successFetchedFromRemote | ||
} catch { | ||
return .successUsingPreFetchedData | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This impl. looks good to me, but it may be worth it to wrap a continuation around a call to the completion handler API below. So both API's share the same impl.
/// - Parameter completion Activate operation callback with changed and error parameters. | ||
@objc public func activate(completion: ((Bool, Error?) -> Void)? = nil) { | ||
queue.async { [weak self] in | ||
guard let self = self else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
guard let self = self else { | |
guard let self else { |
RCLog.error("I-RCN000068", "Internal error activating config.") | ||
if let completion { | ||
DispatchQueue.main.async { | ||
completion(false, error) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably doesn't matter, but ObjC logged after the async dispatch
if let completion {
DispatchQueue.main.async {
completion(false, error)
}
}
RCLog.error("I-RCN000068", "Internal error activating config.")
) | ||
} | ||
|
||
// MARK: helpers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// MARK: - Helpers
var keys = Set<String>() | ||
queue.sync { | ||
let fullyQualifiedNamespace = self.fullyQualifiedNamespace(FIRNamespace) | ||
|
||
if let config = configContent.activeConfig()[fullyQualifiedNamespace] { | ||
if let prefix = prefix, !prefix.isEmpty { | ||
keys = Set(config.keys.filter { $0.hasPrefix(prefix) }) | ||
} else { | ||
keys = Set(config.keys) | ||
} | ||
} | ||
} | ||
return keys |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably could return the value from the sync block, rather than passing it through the context.
var count = 0 | ||
queue.sync { | ||
let fullyQualifiedNamespace = self.fullyQualifiedNamespace(FIRNamespace) | ||
|
||
if let config = configContent.activeConfig()[fullyQualifiedNamespace] as? NSDictionary { | ||
count = config.countByEnumerating(with: state, objects: buffer, count: len) | ||
} | ||
} | ||
return count |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably could return the value from the sync block, rather than passing it through the context.
/// Sets config defaults for parameter keys and values in the default namespace config. | ||
/// - Parameter defaults A dictionary mapping a NSString * key to a NSObject * value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spacing
let fullyQualifiedNamespace = self.fullyQualifiedNamespace(FIRNamespace) | ||
var value: RemoteConfigValue? | ||
queue.sync { | ||
if let config = configContent.defaultConfig()[fullyQualifiedNamespace] { | ||
value = config[key] | ||
if let value, value.source != .default { | ||
RCLog.error("I-RCN000002", | ||
"Key \(key) should come from source: \(RemoteConfigSource.default.rawValue)" + | ||
"instead coming from source: \(value.source.rawValue)") | ||
} | ||
} | ||
} | ||
return value |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably could return the value from the sync block, rather than passing it through the context.
/// `RemoteConfig.remoteConfig().setDefaults(fromPlist: "defaultSamples")` | ||
@objc(setDefaultsFromPlistFileName:) | ||
public func setDefaults(fromPlist fileName: String?) { | ||
guard let fileName = fileName, !fileName.isEmpty else { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
guard let fileName, !fileName.isEmpty else {
Agree with all comments. |
FIRRemoteConfig.m -> RemoteConfig.swift
This is a first pass sufficient to pass the fake console integration tests. Open questions include: