AutorÃa | Ultima modificación | Ver Log |
// Copyright 2019 Google//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// http://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License.#import "FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h"#import "FirebaseABTesting/Sources/ABTConstants.h"#import "FirebaseABTesting/Sources/Public/FirebaseABTesting/FIRLifecycleEvents.h"#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"@implementation ABTConditionalUserPropertyController {dispatch_queue_t _analyticOperationQueue;id<FIRAnalyticsInterop> _Nullable _analytics;}/// Returns the ABTConditionalUserPropertyController singleton.+ (instancetype)sharedInstanceWithAnalytics:(id<FIRAnalyticsInterop> _Nullable)analytics {static ABTConditionalUserPropertyController *sharedInstance = nil;static dispatch_once_t onceToken = 0;dispatch_once(&onceToken, ^{sharedInstance = [[ABTConditionalUserPropertyController alloc] initWithAnalytics:analytics];});return sharedInstance;}- (instancetype)initWithAnalytics:(id<FIRAnalyticsInterop> _Nullable)analytics {self = [super init];if (self) {_analyticOperationQueue =dispatch_queue_create("com.google.FirebaseABTesting.analytics", DISPATCH_QUEUE_SERIAL);_analytics = analytics;}return self;}#pragma mark - experiments proxy methods on Firebase Analytics- (NSArray *)experimentsWithOrigin:(NSString *)origin {return [_analytics conditionalUserProperties:origin propertyNamePrefix:@""];}- (void)clearExperiment:(NSString *)experimentIDvariantID:(NSString *)variantIDwithOrigin:(NSString *)originpayload:(ABTExperimentPayload *)payloadevents:(FIRLifecycleEvents *)events {// Payload always overwrite event names.NSString *clearExperimentEventName = events.clearExperimentEventName;if (payload && payload.clearEventToLog && payload.clearEventToLog.length) {clearExperimentEventName = payload.clearEventToLog;}[_analytics clearConditionalUserProperty:experimentIDforOrigin:originclearEventName:clearExperimentEventNameclearEventParameters:@{experimentID : variantID}];FIRLogDebug(kFIRLoggerABTesting, @"I-ABT000015", @"Clear Experiment ID %@, variant ID %@.",experimentID, variantID);}- (void)setExperimentWithOrigin:(NSString *)originpayload:(ABTExperimentPayload *)payloadevents:(FIRLifecycleEvents *)eventspolicy:(ABTExperimentPayloadExperimentOverflowPolicy)policy {NSInteger maxNumOfExperiments = [self maxNumberOfExperimentsOfOrigin:origin];if (maxNumOfExperiments < 0) {return;}// Clear experiments if overflowNSArray *experiments = [self experimentsWithOrigin:origin];if (!experiments) {FIRLogInfo(kFIRLoggerABTesting, @"I-ABT000003",@"Failed to get conditional user properties from Firebase Analytics.");return;}if (maxNumOfExperiments <= experiments.count) {ABTExperimentPayloadExperimentOverflowPolicy overflowPolicy =[self overflowPolicyWithPayload:payload originalPolicy:policy];id experimentToClear = experiments.firstObject;if (overflowPolicy == ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest &&experimentToClear) {NSString *expID = [self experimentIDOfExperiment:experimentToClear];NSString *varID = [self variantIDOfExperiment:experimentToClear];[self clearExperiment:expID variantID:varID withOrigin:origin payload:payload events:events];FIRLogDebug(kFIRLoggerABTesting, @"I-ABT000016",@"Clear experiment ID %@ variant ID %@ due to "@"overflow policy.",expID, varID);} else {FIRLogDebug(kFIRLoggerABTesting, @"I-ABT000017",@"Experiment ID %@ variant ID %@ won't be set due to "@"overflow policy.",payload.experimentId, payload.variantId);return;}}// Clear experiment if other variant ID exists.NSString *experimentID = payload.experimentId;NSString *variantID = payload.variantId;for (id experiment in experiments) {NSString *expID = [self experimentIDOfExperiment:experiment];NSString *varID = [self variantIDOfExperiment:experiment];if ([expID isEqualToString:experimentID] && ![varID isEqualToString:variantID]) {FIRLogDebug(kFIRLoggerABTesting, @"I-ABT000018",@"Clear experiment ID %@ with variant ID %@ because "@"only one variant ID can be existed "@"at any time.",expID, varID);[self clearExperiment:expID variantID:varID withOrigin:origin payload:payload events:events];}}// Set experimentNSDictionary<NSString *, id> *experiment = [self createExperimentFromOrigin:originpayload:payloadevents:events];[_analytics setConditionalUserProperty:experiment];FIRLogDebug(kFIRLoggerABTesting, @"I-ABT000019",@"Set conditional user property, experiment ID %@ with "@"variant ID %@ triggered event %@.",experimentID, variantID, payload.triggerEvent);// Log setEvent (experiment lifecycle event to be set when an experiment is set)[self logEventWithOrigin:origin payload:payload events:events];}- (NSMutableDictionary<NSString *, id> *)createExperimentFromOrigin:(NSString *)originpayload:(ABTExperimentPayload *)payloadevents:(FIRLifecycleEvents *)events {NSMutableDictionary<NSString *, id> *experiment = [[NSMutableDictionary alloc] init];NSString *experimentID = payload.experimentId;NSString *variantID = payload.variantId;NSDictionary *eventParams = @{experimentID : variantID};[experiment setValue:origin forKey:kABTExperimentDictionaryOriginKey];NSTimeInterval creationTimestamp = (double)(payload.experimentStartTimeMillis / ABT_MSEC_PER_SEC);[experiment setValue:@(creationTimestamp) forKey:kABTExperimentDictionaryCreationTimestampKey];[experiment setValue:experimentID forKey:kABTExperimentDictionaryExperimentIDKey];[experiment setValue:variantID forKey:kABTExperimentDictionaryVariantIDKey];// For the experiment to be immediately activated/triggered, its trigger event must be null.// Double check if payload's trigger event is empty string, it must be set to null to trigger.if (payload && payload.triggerEvent && payload.triggerEvent.length) {[experiment setValue:payload.triggerEvent forKey:kABTExperimentDictionaryTriggeredEventNameKey];} else {[experiment setValue:nil forKey:kABTExperimentDictionaryTriggeredEventNameKey];}// Set timeout event name and params.NSString *timeoutEventName = events.timeoutExperimentEventName;if (payload && payload.timeoutEventToLog && payload.timeoutEventToLog.length) {timeoutEventName = payload.timeoutEventToLog;}NSDictionary<NSString *, id> *timeoutEvent = [self eventDictionaryWithOrigin:origineventName:timeoutEventNameparams:eventParams];[experiment setValue:timeoutEvent forKey:kABTExperimentDictionaryTimedOutEventKey];// Set trigger timeout information on how long to wait for trigger event.NSTimeInterval triggerTimeout = (double)(payload.triggerTimeoutMillis / ABT_MSEC_PER_SEC);[experiment setValue:@(triggerTimeout) forKey:kABTExperimentDictionaryTriggerTimeoutKey];// Set activate event name and params.NSString *activateEventName = events.activateExperimentEventName;if (payload && payload.activateEventToLog && payload.activateEventToLog.length) {activateEventName = payload.activateEventToLog;}NSDictionary<NSString *, id> *triggeredEvent = [self eventDictionaryWithOrigin:origineventName:activateEventNameparams:eventParams];[experiment setValue:triggeredEvent forKey:kABTExperimentDictionaryTriggeredEventKey];// Set time to live information for how long the experiment lasts.NSTimeInterval timeToLive = (double)(payload.timeToLiveMillis / ABT_MSEC_PER_SEC);[experiment setValue:@(timeToLive) forKey:kABTExperimentDictionaryTimeToLiveKey];// Set expired event name and params.NSString *expiredEventName = events.expireExperimentEventName;if (payload && payload.ttlExpiryEventToLog && payload.ttlExpiryEventToLog.length) {expiredEventName = payload.ttlExpiryEventToLog;}NSDictionary<NSString *, id> *expiredEvent = [self eventDictionaryWithOrigin:origineventName:expiredEventNameparams:eventParams];[experiment setValue:expiredEvent forKey:kABTExperimentDictionaryExpiredEventKey];return experiment;}- (NSDictionary<NSString *, id> *)eventDictionaryWithOrigin:(nonnull NSString *)origineventName:(nonnull NSString *)eventNameparams:(nonnull NSDictionary<NSString *, NSString *> *)params {return @{kABTEventDictionaryOriginKey : origin,kABTEventDictionaryNameKey : eventName,kABTEventDictionaryTimestampKey : @([NSDate date].timeIntervalSince1970),kABTEventDictionaryParametersKey : params};}#pragma mark - experiment properties- (NSString *)experimentIDOfExperiment:(id)experiment {if (!experiment) {return @"";}return [experiment valueForKey:kABTExperimentDictionaryExperimentIDKey];}- (NSString *)variantIDOfExperiment:(id)experiment {if (!experiment) {return @"";}return [experiment valueForKey:kABTExperimentDictionaryVariantIDKey];}- (NSInteger)maxNumberOfExperimentsOfOrigin:(NSString *)origin {if (!_analytics) {return 0;}return [_analytics maxUserProperties:origin];}#pragma mark - analytics internal methods- (void)logEventWithOrigin:(NSString *)originpayload:(ABTExperimentPayload *)payloadevents:(FIRLifecycleEvents *)events {NSString *setExperimentEventName = events.setExperimentEventName;if (payload && payload.setEventToLog && payload.setEventToLog.length) {setExperimentEventName = payload.setEventToLog;}NSDictionary<NSString *, NSString *> *params;params = payload.experimentId ? @{payload.experimentId : payload.variantId} : @{};[_analytics logEventWithOrigin:origin name:setExperimentEventName parameters:params];}#pragma mark - helper- (BOOL)isExperiment:(id)experiment theSameAsPayload:(ABTExperimentPayload *)payload {NSString *experimentID = [self experimentIDOfExperiment:experiment];NSString *variantID = [self variantIDOfExperiment:experiment];return [experimentID isEqualToString:payload.experimentId] &&[variantID isEqualToString:payload.variantId];}- (ABTExperimentPayloadExperimentOverflowPolicy)overflowPolicyWithPayload:(ABTExperimentPayload *)payloadoriginalPolicy:(ABTExperimentPayloadExperimentOverflowPolicy)originalPolicy {if ([payload overflowPolicyIsValid]) {return payload.overflowPolicy;}if (originalPolicy == ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest ||originalPolicy == ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest) {return originalPolicy;}return ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest;}@end