Skip to content

Commit

Permalink
Remove Protobuf from the ABT SDK (#5890)
Browse files Browse the repository at this point in the history
* Remove Protobuf dependency, add struct-ish class that mimics ABTExperimentPayload

* Remove a lot of the compiler errors, but not all of them. We need to figure out how RC serializes the NSData objects passed to FIRExperimentController in updateExperimentsWithServiceOrigin:

* Add ABTExperimentPayload class that replaces generated proto, refactor classes that depend on it

* Add unit testing class for ABTExperimentPayload

* Updates to ABTExperimentPayload API, tests for ABTConditionalUserPropertyTest, add tests for ABTExperimentPayload

* Update podspec to pull in testing resources

* Add utility class for parsing test files. Use it in ABTExperimentPayloadTests

* Fix FIRExperimentController tests

* Better documentation

* Run scripts/styles.sh

* Fix whitespace

* Remove protobuf imports

* Bump pod spec version for ABT SDK, depend on new ABT SDK in RC. Fix up RemoteConfigExperiment to use new ABTExperimentPayload object

* scripts/style.sh

* Update In-app messaging to use new ABTExperimentPayload class, which involves extending the ABTExperimentPayload initializer to accept different values for overflowPolicy and experimentStartTime

* Delete test payload

* Revert scripts/check_imports.swift

* Better initialization tags for ABTExperimentLite

* Update CHANGELOGs

* Remove private headers in podspec, use integerValue to parse integers

* Refactor ABTExperimentPayload header into Private headers. Fix integer types for millisecond metadata

* scripts/styles.sh

* Fix integer parsing

* Fix copyrights

* Fix CHANGELOG typo:

* Add private_header_files to public_header_files

* Try just using NSInteger for experimentStartTimeMillis

* Bump FIAM dependency on ABTesting

* Use long long for milli parameters (32-bit apps)

* Use longlong in test timestamps

* Don't cast to nsnumber in tests

* Use in64_t

* Fix Test Overflow

* Bump major version on ABT SDK

* Bump ABTesting version in Firebase.podspec

Co-authored-by: Paul Beusterien <paulbeusterien@google.com>
  • Loading branch information
christibbs and paulb777 committed Jul 6, 2020
1 parent a9a0a10 commit 9b48451
Show file tree
Hide file tree
Showing 33 changed files with 901 additions and 895 deletions.
2 changes: 1 addition & 1 deletion Firebase.podspec
Expand Up @@ -61,7 +61,7 @@ Simplify your app development, grow your user base, and monetize more effectivel

s.subspec 'ABTesting' do |ss|
ss.dependency 'Firebase/CoreOnly'
ss.dependency 'FirebaseABTesting', '~> 3.3.0'
ss.dependency 'FirebaseABTesting', '~> 4.0.0'
end

s.subspec 'AdMob' do |ss|
Expand Down
11 changes: 5 additions & 6 deletions FirebaseABTesting.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'FirebaseABTesting'
s.version = '3.3.0'
s.version = '4.0.0'
s.summary = 'Firebase ABTesting'

s.description = <<-DESC
Expand Down Expand Up @@ -35,20 +35,19 @@ Firebase Cloud Messaging and Firebase Remote Config in your app.
'FirebaseCore/Sources/Private/*.h',
]
s.requires_arc = base_dir + '*.m'
s.public_header_files = base_dir + 'Public/*.h', base_dir + 'Protos/developers/mobile/abt/proto/*.h'
s.private_header_files = base_dir + 'Protos/developers/mobile/abt/proto/*.h'
s.public_header_files = base_dir + 'Public/*.h', base_dir + 'Private/*.h'
s.private_header_files = base_dir + 'Private/*.h'
s.pod_target_xcconfig = {
'GCC_C_LANGUAGE_STANDARD' => 'c99',
'GCC_PREPROCESSOR_DEFINITIONS' =>
'GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1 ' +
'FIRABTesting_VERSION=' + String(s.version),
'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"'
}
s.dependency 'FirebaseCore', '~> 6.8'
s.dependency 'Protobuf', '~> 3.9', '>= 3.9.2'

s.test_spec 'unit' do |unit_tests|
unit_tests.source_files = 'FirebaseABTesting/Tests/Unit/*.[mh]'
unit_tests.source_files = 'FirebaseABTesting/Tests/Unit/**/*.[mh]'
unit_tests.resources = 'FirebaseABTesting/Tests/Unit/Resources/*.txt'
unit_tests.requires_app_host = true
unit_tests.dependency 'OCMock'
end
Expand Down
3 changes: 3 additions & 0 deletions FirebaseABTesting/CHANGELOG.md
@@ -1,3 +1,6 @@
# v4.0.0
- [changed] Removed Protobuf dependency (#5890).

# v3.2.0
- [added] Added completion handler for FIRExperimentController's updateExperimentsWithServiceOrigin method.
- [deprecated] Deprecated `FIRExperimentController.updateExperiments(serviceOrigin:events:policy:lastStartTime:payloads:)`.
Expand Down
Expand Up @@ -12,10 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.

#import <FirebaseABTesting/ABTExperimentPayload.h>
#import <Foundation/Foundation.h>

#import "FirebaseABTesting/Sources/Protos/developers/mobile/abt/proto/ExperimentPayload.pbobjc.h"

#import "Interop/Analytics/Public/FIRAnalyticsInterop.h"

NS_ASSUME_NONNULL_BEGIN
Expand Down Expand Up @@ -69,7 +68,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)setExperimentWithOrigin:(NSString *)origin
payload:(ABTExperimentPayload *)payload
events:(FIRLifecycleEvents *)events
policy:(ABTExperimentPayload_ExperimentOverflowPolicy)policy;
policy:(ABTExperimentPayloadExperimentOverflowPolicy)policy;

/**
* Unavailable. Use sharedInstanceWithAnalytics: instead.
Expand Down
18 changes: 9 additions & 9 deletions FirebaseABTesting/Sources/ABTConditionalUserPropertyController.m
Expand Up @@ -73,7 +73,7 @@ - (void)clearExperiment:(NSString *)experimentID
- (void)setExperimentWithOrigin:(NSString *)origin
payload:(ABTExperimentPayload *)payload
events:(FIRLifecycleEvents *)events
policy:(ABTExperimentPayload_ExperimentOverflowPolicy)policy {
policy:(ABTExperimentPayloadExperimentOverflowPolicy)policy {
NSInteger maxNumOfExperiments = [self maxNumberOfExperimentsOfOrigin:origin];
if (maxNumOfExperiments < 0) {
return;
Expand All @@ -88,10 +88,10 @@ - (void)setExperimentWithOrigin:(NSString *)origin
}

if (maxNumOfExperiments <= experiments.count) {
ABTExperimentPayload_ExperimentOverflowPolicy overflowPolicy =
ABTExperimentPayloadExperimentOverflowPolicy overflowPolicy =
[self overflowPolicyWithPayload:payload originalPolicy:policy];
id experimentToClear = experiments.firstObject;
if (overflowPolicy == ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest &&
if (overflowPolicy == ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest &&
experimentToClear) {
NSString *expID = [self experimentIDOfExperiment:experimentToClear];
NSString *varID = [self variantIDOfExperiment:experimentToClear];
Expand Down Expand Up @@ -265,17 +265,17 @@ - (BOOL)isExperiment:(id)experiment theSameAsPayload:(ABTExperimentPayload *)pay
[variantID isEqualToString:payload.variantId];
}

- (ABTExperimentPayload_ExperimentOverflowPolicy)
- (ABTExperimentPayloadExperimentOverflowPolicy)
overflowPolicyWithPayload:(ABTExperimentPayload *)payload
originalPolicy:(ABTExperimentPayload_ExperimentOverflowPolicy)originalPolicy {
if (payload.overflowPolicy != ABTExperimentPayload_ExperimentOverflowPolicy_PolicyUnspecified) {
originalPolicy:(ABTExperimentPayloadExperimentOverflowPolicy)originalPolicy {
if ([payload overflowPolicyIsValid]) {
return payload.overflowPolicy;
}
if (originalPolicy != ABTExperimentPayload_ExperimentOverflowPolicy_PolicyUnspecified &&
ABTExperimentPayload_ExperimentOverflowPolicy_IsValidValue(originalPolicy)) {
if (originalPolicy == ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest ||
originalPolicy == ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest) {
return originalPolicy;
}
return ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest;
return ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest;
}

@end
151 changes: 151 additions & 0 deletions FirebaseABTesting/Sources/ABTExperimentPayload.m
@@ -0,0 +1,151 @@
// Copyright 2020 Google LLC
//
// 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 "ABTExperimentPayload.h"

static NSString *const kExperimentPayloadKeyExperimentID = @"experimentId";
static NSString *const kExperimentPayloadKeyVariantID = @"variantId";

// Start time can either be a date string or integer (milliseconds since 1970).
static NSString *const kExperimentPayloadKeyExperimentStartTime = @"experimentStartTime";
static NSString *const kExperimentPayloadKeyExperimentStartTimeMillis =
@"experimentStartTimeMillis";
static NSString *const kExperimentPayloadKeyTriggerEvent = @"triggerEvent";
static NSString *const kExperimentPayloadKeyTriggerTimeoutMillis = @"triggerTimeoutMillis";
static NSString *const kExperimentPayloadKeyTimeToLiveMillis = @"timeToLiveMillis";
static NSString *const kExperimentPayloadKeySetEventToLog = @"setEventToLog";
static NSString *const kExperimentPayloadKeyActivateEventToLog = @"activateEventToLog";
static NSString *const kExperimentPayloadKeyClearEventToLog = @"clearEventToLog";
static NSString *const kExperimentPayloadKeyTimeoutEventToLog = @"timeoutEventToLog";
static NSString *const kExperimentPayloadKeyTTLExpiryEventToLog = @"ttlExpiryEventToLog";

static NSString *const kExperimentPayloadKeyOverflowPolicy = @"overflowPolicy";
static NSString *const kExperimentPayloadValueDiscardOldestOverflowPolicy = @"DISCARD_OLDEST";
static NSString *const kExperimentPayloadValueIgnoreNewestOverflowPolicy = @"IGNORE_NEWEST";

static NSString *const kExperimentPayloadKeyOngoingExperiments = @"ongoingExperiments";

@implementation ABTExperimentLite

- (instancetype)initWithExperimentId:(NSString *)experimentId {
if (self = [super init]) {
_experimentId = experimentId;
}
return self;
}

@end

@implementation ABTExperimentPayload

+ (NSDateFormatter *)experimentStartTimeFormatter {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
// Locale needs to be hardcoded. See
// https://developer.apple.com/library/ios/#qa/qa1480/_index.html for more details.
[dateFormatter setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]];
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
return dateFormatter;
}

+ (instancetype)parseFromData:(NSData *)data {
NSError *error;
NSDictionary *experimentDictionary =
[NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingAllowFragments
error:&error];
if (error != nil) {
return nil;
} else {
return [[ABTExperimentPayload alloc] initWithDictionary:experimentDictionary];
}
}

- (instancetype)initWithDictionary:(NSDictionary<NSString *, id> *)dictionary {
if (self = [super init]) {
_experimentId = dictionary[kExperimentPayloadKeyExperimentID];
_variantId = dictionary[kExperimentPayloadKeyVariantID];
_triggerEvent = dictionary[kExperimentPayloadKeyTriggerEvent];
_setEventToLog = dictionary[kExperimentPayloadKeySetEventToLog];
_activateEventToLog = dictionary[kExperimentPayloadKeyActivateEventToLog];
_clearEventToLog = dictionary[kExperimentPayloadKeyClearEventToLog];
_timeoutEventToLog = dictionary[kExperimentPayloadKeyTimeoutEventToLog];
_ttlExpiryEventToLog = dictionary[kExperimentPayloadKeyTTLExpiryEventToLog];

// Experiment start time can either be in the form of a date string or milliseconds since 1970.
if (dictionary[kExperimentPayloadKeyExperimentStartTime]) {
// Convert from date string.
NSDate *experimentStartTime = [[[self class] experimentStartTimeFormatter]
dateFromString:dictionary[kExperimentPayloadKeyExperimentStartTime]];
_experimentStartTimeMillis =
[@([experimentStartTime timeIntervalSince1970] * 1000) longLongValue];
} else if (dictionary[kExperimentPayloadKeyExperimentStartTimeMillis]) {
// Simply store milliseconds.
_experimentStartTimeMillis =
[dictionary[kExperimentPayloadKeyExperimentStartTimeMillis] longLongValue];
;
}

_triggerTimeoutMillis = [dictionary[kExperimentPayloadKeyTriggerTimeoutMillis] longLongValue];
_timeToLiveMillis = [dictionary[kExperimentPayloadKeyTimeToLiveMillis] longLongValue];

// Overflow policy can be an integer, or string e.g. "DISCARD_OLDEST" or "IGNORE_NEWEST".
if ([dictionary[kExperimentPayloadKeyOverflowPolicy] isKindOfClass:[NSString class]]) {
// If it's a string, pick against the preset string values.
NSString *policy = dictionary[kExperimentPayloadKeyOverflowPolicy];
if ([policy isEqualToString:kExperimentPayloadValueDiscardOldestOverflowPolicy]) {
_overflowPolicy = ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest;
} else if ([policy isEqualToString:kExperimentPayloadValueIgnoreNewestOverflowPolicy]) {
_overflowPolicy = ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest;
} else {
_overflowPolicy = ABTExperimentPayloadExperimentOverflowPolicyUnrecognizedValue;
}
} else {
_overflowPolicy = [dictionary[kExperimentPayloadKeyOverflowPolicy] intValue];
}

NSMutableArray<ABTExperimentLite *> *ongoingExperiments = [[NSMutableArray alloc] init];

NSArray<NSDictionary<NSString *, NSString *> *> *ongoingExperimentsArray =
dictionary[kExperimentPayloadKeyOngoingExperiments];

for (NSDictionary<NSString *, NSString *> *experimentDictionary in ongoingExperimentsArray) {
NSString *experimentId = experimentDictionary[kExperimentPayloadKeyExperimentID];
if (experimentId) {
ABTExperimentLite *liteExperiment =
[[ABTExperimentLite alloc] initWithExperimentId:experimentId];
[ongoingExperiments addObject:liteExperiment];
}
}

_ongoingExperiments = [ongoingExperiments copy];
}
return self;
}

- (void)clearTriggerEvent {
_triggerEvent = nil;
}

- (BOOL)overflowPolicyIsValid {
return self.overflowPolicy == ABTExperimentPayloadExperimentOverflowPolicyIgnoreNewest ||
self.overflowPolicy == ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest;
}

- (void)setOverflowPolicy:(ABTExperimentPayloadExperimentOverflowPolicy)overflowPolicy {
_overflowPolicy = overflowPolicy;
}

@end
21 changes: 11 additions & 10 deletions FirebaseABTesting/Sources/FIRExperimentController.m
Expand Up @@ -14,6 +14,7 @@

#import <FirebaseABTesting/FIRExperimentController.h>

#import <FirebaseABTesting/ABTExperimentPayload.h>
#import <FirebaseABTesting/FIRLifecycleEvents.h>
#import "FirebaseABTesting/Sources/ABTConditionalUserPropertyController.h"
#import "FirebaseABTesting/Sources/ABTConstants.h"
Expand All @@ -34,19 +35,19 @@
#define STR_EXPAND(x) #x
/// Default experiment overflow policy.
const ABTExperimentPayload_ExperimentOverflowPolicy FIRDefaultExperimentOverflowPolicy =
ABTExperimentPayload_ExperimentOverflowPolicy_DiscardOldest;
const ABTExperimentPayloadExperimentOverflowPolicy FIRDefaultExperimentOverflowPolicy =
ABTExperimentPayloadExperimentOverflowPolicyDiscardOldest;
/// Deserialize the experiment payloads.
ABTExperimentPayload *ABTDeserializeExperimentPayload(NSData *payload) {
// Verify that we have a JSON object.
NSError *error;
ABTExperimentPayload *experimentPayload = [ABTExperimentPayload parseFromData:payload
error:&error];
if (error) {
id JSONObject = [NSJSONSerialization JSONObjectWithData:payload options:kNilOptions error:&error];
if (JSONObject == nil) {
FIRLogError(kFIRLoggerABTesting, @"I-ABT000001", @"Failed to parse experiment payload: %@",
error.debugDescription);
}
return experimentPayload;
return [ABTExperimentPayload parseFromData:payload];
}
/// Returns a list of experiments to be set given the payloads and current list of experiments from
Expand Down Expand Up @@ -173,7 +174,7 @@ + (FIRExperimentController *)sharedInstance {
- (void)updateExperimentsWithServiceOrigin:(NSString *)origin
events:(FIRLifecycleEvents *)events
policy:(ABTExperimentPayload_ExperimentOverflowPolicy)policy
policy:(ABTExperimentPayloadExperimentOverflowPolicy)policy
lastStartTime:(NSTimeInterval)lastStartTime
payloads:(NSArray<NSData *> *)payloads
completionHandler:
Expand All @@ -192,7 +193,7 @@ - (void)updateExperimentsWithServiceOrigin:(NSString *)origin
- (void)updateExperimentsWithServiceOrigin:(NSString *)origin
events:(FIRLifecycleEvents *)events
policy:(ABTExperimentPayload_ExperimentOverflowPolicy)policy
policy:(ABTExperimentPayloadExperimentOverflowPolicy)policy
lastStartTime:(NSTimeInterval)lastStartTime
payloads:(NSArray<NSData *> *)payloads {
[self updateExperimentsWithServiceOrigin:origin
Expand All @@ -207,7 +208,7 @@ - (void)updateExperimentsWithServiceOrigin:(NSString *)origin
updateExperimentConditionalUserPropertiesWithServiceOrigin:(NSString *)origin
events:(FIRLifecycleEvents *)events
policy:
(ABTExperimentPayload_ExperimentOverflowPolicy)
(ABTExperimentPayloadExperimentOverflowPolicy)
policy
lastStartTime:(NSTimeInterval)lastStartTime
payloads:(NSArray<NSData *> *)payloads
Expand Down Expand Up @@ -325,7 +326,7 @@ - (void)activateExperiment:(ABTExperimentPayload *)experimentPayload
FIRLifecycleEvents *lifecycleEvents = [[FIRLifecycleEvents alloc] init];
// Ensure that trigger event is nil, which will immediately set the experiment to active.
experimentPayload.triggerEvent = nil;
[experimentPayload clearTriggerEvent];
[controller setExperimentWithOrigin:origin
payload:experimentPayload
Expand Down

0 comments on commit 9b48451

Please sign in to comment.