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/Public/FirebaseABTesting/FIRExperimentController.h"
16
 
17
#import "FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h"
18
#import "FirebaseABTesting/Sources/ABTConstants.h"
19
#import "FirebaseABTesting/Sources/Private/ABTExperimentPayload.h"
20
#import "FirebaseABTesting/Sources/Public/FirebaseABTesting/FIRLifecycleEvents.h"
21
#import "FirebaseCore/Sources/Private/FirebaseCoreInternal.h"
22
 
23
#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"
24
 
25
/// Logger Service String.
26
FIRLoggerService kFIRLoggerABTesting = @"[Firebase/ABTesting]";
27
 
28
/// Default experiment overflow policy.
29
const ABTExperimentPayloadExperimentOverflowPolicy FIRDefaultExperimentOverflowPolicy =
30
    ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest;
31
 
32
/// Deserialize the experiment payloads.
33
ABTExperimentPayload *ABTDeserializeExperimentPayload(NSData *payload) {
34
  // Verify that we have a JSON object.
35
  NSError *error;
36
  id JSONObject = [NSJSONSerialization JSONObjectWithData:payload options:kNilOptions error:&error];
37
  if (JSONObject == nil) {
38
    FIRLogError(kFIRLoggerABTesting, @"I-ABT000001", @"Failed to parse experiment payload: %@",
39
                error.debugDescription);
40
  }
41
  return [ABTExperimentPayload parseFromData:payload];
42
}
43
 
44
/// Returns a list of experiments to be set given the payloads and current list of experiments from
45
/// Firebase Analytics. If an experiment is in payloads but not in experiments, it should be set to
46
/// Firebase Analytics.
47
NSArray<ABTExperimentPayload *> *ABTExperimentsToSetFromPayloads(
48
    NSArray<NSData *> *payloads,
49
    NSArray<NSDictionary<NSString *, NSString *> *> *experiments,
50
    id<FIRAnalyticsInterop> _Nullable analytics) {
51
  NSArray<NSData *> *payloadsCopy = [payloads copy];
52
  NSArray *experimentsCopy = [experiments copy];
53
  NSMutableArray *experimentsToSet = [[NSMutableArray alloc] init];
54
  ABTConditionalUserPropertyController *controller =
55
      [ABTConditionalUserPropertyController sharedInstanceWithAnalytics:analytics];
56
 
57
  // Check if the experiment is in payloads but not in experiments.
58
  for (NSData *payload in payloadsCopy) {
59
    ABTExperimentPayload *experimentPayload = ABTDeserializeExperimentPayload(payload);
60
    if (!experimentPayload) {
61
      FIRLogInfo(kFIRLoggerABTesting, @"I-ABT000002",
62
                 @"Either payload is not set or it cannot be deserialized.");
63
      continue;
64
    }
65
 
66
    BOOL isExperimentSet = NO;
67
    for (id experiment in experimentsCopy) {
68
      if ([controller isExperiment:experiment theSameAsPayload:experimentPayload]) {
69
        isExperimentSet = YES;
70
        break;
71
      }
72
    }
73
 
74
    if (!isExperimentSet) {
75
      [experimentsToSet addObject:experimentPayload];
76
    }
77
  }
78
  return [experimentsToSet copy];
79
}
80
 
81
/// Returns a list of experiments to be cleared given the payloads and current list of
82
/// experiments from Firebase Analytics. If an experiment is in experiments but not in payloads, it
83
/// should be cleared in Firebase Analytics.
84
NSArray *ABTExperimentsToClearFromPayloads(
85
    NSArray<NSData *> *payloads,
86
    NSArray<NSDictionary<NSString *, NSString *> *> *experiments,
87
    id<FIRAnalyticsInterop> _Nullable analytics) {
88
  NSMutableArray *experimentsToClear = [[NSMutableArray alloc] init];
89
  ABTConditionalUserPropertyController *controller =
90
      [ABTConditionalUserPropertyController sharedInstanceWithAnalytics:analytics];
91
 
92
  // Check if the experiment is in experiments but not payloads.
93
  for (id experiment in experiments) {
94
    BOOL doesExperimentNoLongerExist = YES;
95
    for (NSData *payload in payloads) {
96
      ABTExperimentPayload *experimentPayload = ABTDeserializeExperimentPayload(payload);
97
      if (!experimentPayload) {
98
        FIRLogInfo(kFIRLoggerABTesting, @"I-ABT000002",
99
                   @"Either payload is not set or it cannot be deserialized.");
100
        continue;
101
      }
102
 
103
      if ([controller isExperiment:experiment theSameAsPayload:experimentPayload]) {
104
        doesExperimentNoLongerExist = NO;
105
      }
106
    }
107
    if (doesExperimentNoLongerExist) {
108
      [experimentsToClear addObject:experiment];
109
    }
110
  }
111
  return experimentsToClear;
112
}
113
 
114
// ABT doesn't provide any functionality to other components,
115
// so it provides a private, empty protocol that it conforms to and use it for registration.
116
 
117
@protocol FIRABTInstanceProvider
118
@end
119
 
120
@interface FIRExperimentController () <FIRABTInstanceProvider, FIRLibrary>
121
@property(nonatomic, readwrite, strong) id<FIRAnalyticsInterop> _Nullable analytics;
122
@end
123
 
124
@implementation FIRExperimentController
125
 
126
+ (void)load {
127
  [FIRApp registerInternalLibrary:(Class<FIRLibrary>)self withName:@"fire-abt"];
128
}
129
 
130
+ (nonnull NSArray<FIRComponent *> *)componentsToRegister {
131
  FIRDependency *analyticsDep = [FIRDependency dependencyWithProtocol:@protocol(FIRAnalyticsInterop)
132
                                                           isRequired:NO];
133
  FIRComponentCreationBlock creationBlock =
134
      ^id _Nullable(FIRComponentContainer *container, BOOL *isCacheable) {
135
    // Ensure it's cached so it returns the same instance every time ABTesting is called.
136
    *isCacheable = YES;
137
    id<FIRAnalyticsInterop> analytics = FIR_COMPONENT(FIRAnalyticsInterop, container);
138
    return [[FIRExperimentController alloc] initWithAnalytics:analytics];
139
  };
140
  FIRComponent *abtProvider = [FIRComponent componentWithProtocol:@protocol(FIRABTInstanceProvider)
141
                                              instantiationTiming:FIRInstantiationTimingLazy
142
                                                     dependencies:@[ analyticsDep ]
143
                                                    creationBlock:creationBlock];
144
 
145
  return @[ abtProvider ];
146
}
147
 
148
- (instancetype)initWithAnalytics:(nullable id<FIRAnalyticsInterop>)analytics {
149
  self = [super init];
150
  if (self != nil) {
151
    _analytics = analytics;
152
  }
153
  return self;
154
}
155
 
156
+ (FIRExperimentController *)sharedInstance {
157
  FIRApp *defaultApp = [FIRApp defaultApp];  // Missing configure will be logged here.
158
  id<FIRABTInstanceProvider> instance = FIR_COMPONENT(FIRABTInstanceProvider, defaultApp.container);
159
 
160
  // We know the instance coming from the container is a FIRExperimentController instance, cast it.
161
  return (FIRExperimentController *)instance;
162
}
163
 
164
- (void)updateExperimentsWithServiceOrigin:(NSString *)origin
165
                                    events:(FIRLifecycleEvents *)events
166
                                    policy:(ABTExperimentPayloadExperimentOverflowPolicy)policy
167
                             lastStartTime:(NSTimeInterval)lastStartTime
168
                                  payloads:(NSArray<NSData *> *)payloads
169
                         completionHandler:
170
                             (nullable void (^)(NSError *_Nullable error))completionHandler {
171
  FIRExperimentController *__weak weakSelf = self;
172
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
173
    FIRExperimentController *strongSelf = weakSelf;
174
    [strongSelf updateExperimentConditionalUserPropertiesWithServiceOrigin:origin
175
                                                                    events:events
176
                                                                    policy:policy
177
                                                             lastStartTime:lastStartTime
178
                                                                  payloads:payloads
179
                                                         completionHandler:completionHandler];
180
  });
181
}
182
 
183
- (void)
184
    updateExperimentConditionalUserPropertiesWithServiceOrigin:(NSString *)origin
185
                                                        events:(FIRLifecycleEvents *)events
186
                                                        policy:
187
                                                            (ABTExperimentPayloadExperimentOverflowPolicy)
188
                                                                policy
189
                                                 lastStartTime:(NSTimeInterval)lastStartTime
190
                                                      payloads:(NSArray<NSData *> *)payloads
191
                                             completionHandler:
192
                                                 (nullable void (^)(NSError *_Nullable error))
193
                                                     completionHandler {
194
  ABTConditionalUserPropertyController *controller =
195
      [ABTConditionalUserPropertyController sharedInstanceWithAnalytics:_analytics];
196
 
197
  // Get the list of expriments from Firebase Analytics.
198
  NSArray *experiments = [controller experimentsWithOrigin:origin];
199
  if (!experiments) {
200
    NSString *errorDescription =
201
        @"Failed to get conditional user properties from Firebase Analytics.";
202
    FIRLogInfo(kFIRLoggerABTesting, @"I-ABT000003", @"%@", errorDescription);
203
 
204
    if (completionHandler) {
205
      completionHandler([NSError
206
          errorWithDomain:kABTErrorDomain
207
                     code:kABTInternalErrorFailedToFetchConditionalUserProperties
208
                 userInfo:@{NSLocalizedDescriptionKey : errorDescription}]);
209
    }
210
 
211
    return;
212
  }
213
  NSArray<ABTExperimentPayload *> *experimentsToSet =
214
      ABTExperimentsToSetFromPayloads(payloads, experiments, _analytics);
215
  NSArray<NSDictionary<NSString *, NSString *> *> *experimentsToClear =
216
      ABTExperimentsToClearFromPayloads(payloads, experiments, _analytics);
217
 
218
  for (id experiment in experimentsToClear) {
219
    NSString *experimentID = [controller experimentIDOfExperiment:experiment];
220
    NSString *variantID = [controller variantIDOfExperiment:experiment];
221
    [controller clearExperiment:experimentID
222
                      variantID:variantID
223
                     withOrigin:origin
224
                        payload:nil
225
                         events:events];
226
  }
227
 
228
  for (ABTExperimentPayload *experimentPayload in experimentsToSet) {
229
    if (experimentPayload.experimentStartTimeMillis > lastStartTime * ABT_MSEC_PER_SEC) {
230
      [controller setExperimentWithOrigin:origin
231
                                  payload:experimentPayload
232
                                   events:events
233
                                   policy:policy];
234
      FIRLogInfo(kFIRLoggerABTesting, @"I-ABT000008",
235
                 @"Set Experiment ID %@, variant ID %@ to Firebase Analytics.",
236
                 experimentPayload.experimentId, experimentPayload.variantId);
237
 
238
    } else {
239
      FIRLogInfo(kFIRLoggerABTesting, @"I-ABT000009",
240
                 @"Not setting experiment ID %@, variant ID %@ due to the last update time %lld.",
241
                 experimentPayload.experimentId, experimentPayload.variantId,
242
                 (long)lastStartTime * ABT_MSEC_PER_SEC);
243
    }
244
  }
245
 
246
  if (completionHandler) {
247
    completionHandler(nil);
248
  }
249
}
250
 
251
- (NSTimeInterval)latestExperimentStartTimestampBetweenTimestamp:(NSTimeInterval)timestamp
252
                                                     andPayloads:(NSArray<NSData *> *)payloads {
253
  for (NSData *payload in [payloads copy]) {
254
    ABTExperimentPayload *experimentPayload = ABTDeserializeExperimentPayload(payload);
255
    if (!experimentPayload) {
256
      FIRLogInfo(kFIRLoggerABTesting, @"I-ABT000002",
257
                 @"Either payload is not set or it cannot be deserialized.");
258
      continue;
259
    }
260
    if (experimentPayload.experimentStartTimeMillis > timestamp * ABT_MSEC_PER_SEC) {
261
      timestamp = (double)(experimentPayload.experimentStartTimeMillis / ABT_MSEC_PER_SEC);
262
    }
263
  }
264
  return timestamp;
265
}
266
 
267
- (void)validateRunningExperimentsForServiceOrigin:(NSString *)origin
268
                         runningExperimentPayloads:(NSArray<ABTExperimentPayload *> *)payloads {
269
  ABTConditionalUserPropertyController *controller =
270
      [ABTConditionalUserPropertyController sharedInstanceWithAnalytics:_analytics];
271
 
272
  FIRLifecycleEvents *lifecycleEvents = [[FIRLifecycleEvents alloc] init];
273
 
274
  // Get the list of experiments from Firebase Analytics.
275
  NSArray<NSDictionary<NSString *, NSString *> *> *activeExperiments =
276
      [controller experimentsWithOrigin:origin];
277
 
278
  NSMutableSet *runningExperimentIDs = [NSMutableSet setWithCapacity:payloads.count];
279
  for (ABTExperimentPayload *payload in payloads) {
280
    [runningExperimentIDs addObject:payload.experimentId];
281
  }
282
 
283
  for (NSDictionary<NSString *, NSString *> *activeExperimentDictionary in activeExperiments) {
284
    NSString *experimentID = activeExperimentDictionary[@"name"];
285
    if (![runningExperimentIDs containsObject:experimentID]) {
286
      NSString *variantID = activeExperimentDictionary[@"value"];
287
 
288
      [controller clearExperiment:experimentID
289
                        variantID:variantID
290
                       withOrigin:origin
291
                          payload:nil
292
                           events:lifecycleEvents];
293
    }
294
  }
295
}
296
 
297
- (void)activateExperiment:(ABTExperimentPayload *)experimentPayload
298
          forServiceOrigin:(NSString *)origin {
299
  ABTConditionalUserPropertyController *controller =
300
      [ABTConditionalUserPropertyController sharedInstanceWithAnalytics:_analytics];
301
 
302
  FIRLifecycleEvents *lifecycleEvents = [[FIRLifecycleEvents alloc] init];
303
 
304
  // Ensure that trigger event is nil, which will immediately set the experiment to active.
305
  [experimentPayload clearTriggerEvent];
306
 
307
  [controller setExperimentWithOrigin:origin
308
                              payload:experimentPayload
309
                               events:lifecycleEvents
310
                               policy:experimentPayload.overflowPolicy];
311
}
312
 
313
@end