Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// Copyright 2019 Google
2
//
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
//
7
//      http://www.apache.org/licenses/LICENSE-2.0
8
//
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14
 
15
#import "FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h"
16
 
17
#import "FirebaseABTesting/Sources/ABTConstants.h"
18
#import "FirebaseABTesting/Sources/Public/FirebaseABTesting/FIRLifecycleEvents.h"
19
#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
20
#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
21
 
22
@implementation ABTConditionalUserPropertyController {
23
  dispatch_queue_t _analyticOperationQueue;
24
  id<FIRAnalyticsInterop> _Nullable _analytics;
25
}
26
 
27
/// Returns the ABTConditionalUserPropertyController singleton.
28
+ (instancetype)sharedInstanceWithAnalytics:(id<FIRAnalyticsInterop> _Nullable)analytics {
29
  static ABTConditionalUserPropertyController *sharedInstance = nil;
30
  static dispatch_once_t onceToken = 0;
31
  dispatch_once(&onceToken, ^{
32
    sharedInstance = [[ABTConditionalUserPropertyController alloc] initWithAnalytics:analytics];
33
  });
34
  return sharedInstance;
35
}
36
 
37
- (instancetype)initWithAnalytics:(id<FIRAnalyticsInterop> _Nullable)analytics {
38
  self = [super init];
39
  if (self) {
40
    _analyticOperationQueue =
41
        dispatch_queue_create("com.google.FirebaseABTesting.analytics", DISPATCH_QUEUE_SERIAL);
42
    _analytics = analytics;
43
  }
44
  return self;
45
}
46
 
47
#pragma mark - experiments proxy methods on Firebase Analytics
48
 
49
- (NSArray *)experimentsWithOrigin:(NSString *)origin {
50
  return [_analytics conditionalUserProperties:origin propertyNamePrefix:@""];
51
}
52
 
53
- (void)clearExperiment:(NSString *)experimentID
54
              variantID:(NSString *)variantID
55
             withOrigin:(NSString *)origin
56
                payload:(ABTExperimentPayload *)payload
57
                 events:(FIRLifecycleEvents *)events {
58
  // Payload always overwrite event names.
59
  NSString *clearExperimentEventName = events.clearExperimentEventName;
60
  if (payload && payload.clearEventToLog && payload.clearEventToLog.length) {
61
    clearExperimentEventName = payload.clearEventToLog;
62
  }
63
 
64
  [_analytics clearConditionalUserProperty:experimentID
65
                                 forOrigin:origin
66
                            clearEventName:clearExperimentEventName
67
                      clearEventParameters:@{experimentID : variantID}];
68
 
69
  FIRLogDebug(kFIRLoggerABTesting, @"I-ABT000015", @"Clear Experiment ID %@, variant ID %@.",
70
              experimentID, variantID);
71
}
72
 
73
- (void)setExperimentWithOrigin:(NSString *)origin
74
                        payload:(ABTExperimentPayload *)payload
75
                         events:(FIRLifecycleEvents *)events
76
                         policy:(ABTExperimentPayloadExperimentOverflowPolicy)policy {
77
  NSInteger maxNumOfExperiments = [self maxNumberOfExperimentsOfOrigin:origin];
78
  if (maxNumOfExperiments < 0) {
79
    return;
80
  }
81
 
82
  // Clear experiments if overflow
83
  NSArray *experiments = [self experimentsWithOrigin:origin];
84
  if (!experiments) {
85
    FIRLogInfo(kFIRLoggerABTesting, @"I-ABT000003",
86
               @"Failed to get conditional user properties from Firebase Analytics.");
87
    return;
88
  }
89
 
90
  if (maxNumOfExperiments <= experiments.count) {
91
    ABTExperimentPayloadExperimentOverflowPolicy overflowPolicy =
92
        [self overflowPolicyWithPayload:payload originalPolicy:policy];
93
    id experimentToClear = experiments.firstObject;
94
    if (overflowPolicy == ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest &&
95
        experimentToClear) {
96
      NSString *expID = [self experimentIDOfExperiment:experimentToClear];
97
      NSString *varID = [self variantIDOfExperiment:experimentToClear];
98
 
99
      [self clearExperiment:expID variantID:varID withOrigin:origin payload:payload events:events];
100
      FIRLogDebug(kFIRLoggerABTesting, @"I-ABT000016",
101
                  @"Clear experiment ID %@ variant ID %@ due to "
102
                  @"overflow policy.",
103
                  expID, varID);
104
 
105
    } else {
106
      FIRLogDebug(kFIRLoggerABTesting, @"I-ABT000017",
107
                  @"Experiment ID %@ variant ID %@ won't be set due to "
108
                  @"overflow policy.",
109
                  payload.experimentId, payload.variantId);
110
 
111
      return;
112
    }
113
  }
114
 
115
  // Clear experiment if other variant ID exists.
116
  NSString *experimentID = payload.experimentId;
117
  NSString *variantID = payload.variantId;
118
  for (id experiment in experiments) {
119
    NSString *expID = [self experimentIDOfExperiment:experiment];
120
    NSString *varID = [self variantIDOfExperiment:experiment];
121
    if ([expID isEqualToString:experimentID] && ![varID isEqualToString:variantID]) {
122
      FIRLogDebug(kFIRLoggerABTesting, @"I-ABT000018",
123
                  @"Clear experiment ID %@ with variant ID %@ because "
124
                  @"only one variant ID can be existed "
125
                  @"at any time.",
126
                  expID, varID);
127
      [self clearExperiment:expID variantID:varID withOrigin:origin payload:payload events:events];
128
    }
129
  }
130
 
131
  // Set experiment
132
  NSDictionary<NSString *, id> *experiment = [self createExperimentFromOrigin:origin
133
                                                                      payload:payload
134
                                                                       events:events];
135
 
136
  [_analytics setConditionalUserProperty:experiment];
137
 
138
  FIRLogDebug(kFIRLoggerABTesting, @"I-ABT000019",
139
              @"Set conditional user property, experiment ID %@ with "
140
              @"variant ID %@ triggered event %@.",
141
              experimentID, variantID, payload.triggerEvent);
142
 
143
  // Log setEvent (experiment lifecycle event to be set when an experiment is set)
144
  [self logEventWithOrigin:origin payload:payload events:events];
145
}
146
 
147
- (NSMutableDictionary<NSString *, id> *)createExperimentFromOrigin:(NSString *)origin
148
                                                            payload:(ABTExperimentPayload *)payload
149
                                                             events:(FIRLifecycleEvents *)events {
150
  NSMutableDictionary<NSString *, id> *experiment = [[NSMutableDictionary alloc] init];
151
  NSString *experimentID = payload.experimentId;
152
  NSString *variantID = payload.variantId;
153
 
154
  NSDictionary *eventParams = @{experimentID : variantID};
155
 
156
  [experiment setValue:origin forKey:kABTExperimentDictionaryOriginKey];
157
 
158
  NSTimeInterval creationTimestamp = (double)(payload.experimentStartTimeMillis / ABT_MSEC_PER_SEC);
159
  [experiment setValue:@(creationTimestamp) forKey:kABTExperimentDictionaryCreationTimestampKey];
160
  [experiment setValue:experimentID forKey:kABTExperimentDictionaryExperimentIDKey];
161
  [experiment setValue:variantID forKey:kABTExperimentDictionaryVariantIDKey];
162
 
163
  // For the experiment to be immediately activated/triggered, its trigger event must be null.
164
  // Double check if payload's trigger event is empty string, it must be set to null to trigger.
165
  if (payload && payload.triggerEvent && payload.triggerEvent.length) {
166
    [experiment setValue:payload.triggerEvent forKey:kABTExperimentDictionaryTriggeredEventNameKey];
167
  } else {
168
    [experiment setValue:nil forKey:kABTExperimentDictionaryTriggeredEventNameKey];
169
  }
170
 
171
  // Set timeout event name and params.
172
  NSString *timeoutEventName = events.timeoutExperimentEventName;
173
  if (payload && payload.timeoutEventToLog && payload.timeoutEventToLog.length) {
174
    timeoutEventName = payload.timeoutEventToLog;
175
  }
176
  NSDictionary<NSString *, id> *timeoutEvent = [self eventDictionaryWithOrigin:origin
177
                                                                     eventName:timeoutEventName
178
                                                                        params:eventParams];
179
  [experiment setValue:timeoutEvent forKey:kABTExperimentDictionaryTimedOutEventKey];
180
 
181
  // Set trigger timeout information on how long to wait for trigger event.
182
  NSTimeInterval triggerTimeout = (double)(payload.triggerTimeoutMillis / ABT_MSEC_PER_SEC);
183
  [experiment setValue:@(triggerTimeout) forKey:kABTExperimentDictionaryTriggerTimeoutKey];
184
 
185
  // Set activate event name and params.
186
  NSString *activateEventName = events.activateExperimentEventName;
187
  if (payload && payload.activateEventToLog && payload.activateEventToLog.length) {
188
    activateEventName = payload.activateEventToLog;
189
  }
190
  NSDictionary<NSString *, id> *triggeredEvent = [self eventDictionaryWithOrigin:origin
191
                                                                       eventName:activateEventName
192
                                                                          params:eventParams];
193
  [experiment setValue:triggeredEvent forKey:kABTExperimentDictionaryTriggeredEventKey];
194
 
195
  // Set time to live information for how long the experiment lasts.
196
  NSTimeInterval timeToLive = (double)(payload.timeToLiveMillis / ABT_MSEC_PER_SEC);
197
  [experiment setValue:@(timeToLive) forKey:kABTExperimentDictionaryTimeToLiveKey];
198
 
199
  // Set expired event name and params.
200
  NSString *expiredEventName = events.expireExperimentEventName;
201
  if (payload && payload.ttlExpiryEventToLog && payload.ttlExpiryEventToLog.length) {
202
    expiredEventName = payload.ttlExpiryEventToLog;
203
  }
204
  NSDictionary<NSString *, id> *expiredEvent = [self eventDictionaryWithOrigin:origin
205
                                                                     eventName:expiredEventName
206
                                                                        params:eventParams];
207
  [experiment setValue:expiredEvent forKey:kABTExperimentDictionaryExpiredEventKey];
208
  return experiment;
209
}
210
 
211
- (NSDictionary<NSString *, id> *)
212
    eventDictionaryWithOrigin:(nonnull NSString *)origin
213
                    eventName:(nonnull NSString *)eventName
214
                       params:(nonnull NSDictionary<NSString *, NSString *> *)params {
215
  return @{
216
    kABTEventDictionaryOriginKey : origin,
217
    kABTEventDictionaryNameKey : eventName,
218
    kABTEventDictionaryTimestampKey : @([NSDate date].timeIntervalSince1970),
219
    kABTEventDictionaryParametersKey : params
220
  };
221
}
222
 
223
#pragma mark - experiment properties
224
- (NSString *)experimentIDOfExperiment:(id)experiment {
225
  if (!experiment) {
226
    return @"";
227
  }
228
  return [experiment valueForKey:kABTExperimentDictionaryExperimentIDKey];
229
}
230
 
231
- (NSString *)variantIDOfExperiment:(id)experiment {
232
  if (!experiment) {
233
    return @"";
234
  }
235
  return [experiment valueForKey:kABTExperimentDictionaryVariantIDKey];
236
}
237
 
238
- (NSInteger)maxNumberOfExperimentsOfOrigin:(NSString *)origin {
239
  if (!_analytics) {
240
    return 0;
241
  }
242
  return [_analytics maxUserProperties:origin];
243
}
244
 
245
#pragma mark - analytics internal methods
246
 
247
- (void)logEventWithOrigin:(NSString *)origin
248
                   payload:(ABTExperimentPayload *)payload
249
                    events:(FIRLifecycleEvents *)events {
250
  NSString *setExperimentEventName = events.setExperimentEventName;
251
  if (payload && payload.setEventToLog && payload.setEventToLog.length) {
252
    setExperimentEventName = payload.setEventToLog;
253
  }
254
  NSDictionary<NSString *, NSString *> *params;
255
  params = payload.experimentId ? @{payload.experimentId : payload.variantId} : @{};
256
  [_analytics logEventWithOrigin:origin name:setExperimentEventName parameters:params];
257
}
258
 
259
#pragma mark - helper
260
 
261
- (BOOL)isExperiment:(id)experiment theSameAsPayload:(ABTExperimentPayload *)payload {
262
  NSString *experimentID = [self experimentIDOfExperiment:experiment];
263
  NSString *variantID = [self variantIDOfExperiment:experiment];
264
  return [experimentID isEqualToString:payload.experimentId] &&
265
         [variantID isEqualToString:payload.variantId];
266
}
267
 
268
- (ABTExperimentPayloadExperimentOverflowPolicy)
269
    overflowPolicyWithPayload:(ABTExperimentPayload *)payload
270
               originalPolicy:(ABTExperimentPayloadExperimentOverflowPolicy)originalPolicy {
271
  if ([payload overflowPolicyIsValid]) {
272
    return payload.overflowPolicy;
273
  }
274
  if (originalPolicy == ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest ||
275
      originalPolicy == ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest) {
276
    return originalPolicy;
277
  }
278
  return ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest;
279
}
280
 
281
@end