Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

All notable changes to the LaunchDarkly iOS SDK will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org).

## [2.14.1] - 2018-12-21
### Changed
- Added copy methods to several objects involved in creating a summary event.
- Added additional synchronization to creating a summary event in order to potentially prevent some crash scenarios.

## [2.14.0] - 2018-12-05
### Added
- Added `allFlags` property to `LDClient` that provides a dictionary of feature flag keys and values. Accessing feature flags via `allFlags` does not record any analytics events.
Expand Down
2 changes: 1 addition & 1 deletion Darkly/DataModels/DarklyConstants.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

#import "DarklyConstants.h"

NSString * const kClientVersion = @"2.14.0";
NSString * const kClientVersion = @"2.14.1";
NSString * const kLDPrimaryEnvironmentName = @"LaunchDarkly.EnvironmentName.Primary";
NSString * const kBaseUrl = @"https://app.launchdarkly.com";
NSString * const kEventsUrl = @"https://mobile.launchdarkly.com";
Expand Down
3 changes: 2 additions & 1 deletion Darkly/DataModels/LDFlagConfig/LDEventTrackingContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

#import <Foundation/Foundation.h>

@interface LDEventTrackingContext : NSObject<NSCoding>
@interface LDEventTrackingContext : NSObject<NSCoding, NSCopying>
@property (nonatomic, assign) BOOL trackEvents;
@property (nullable, nonatomic, strong) NSDate *debugEventsUntilDate;

Expand All @@ -18,4 +18,5 @@
-(void)encodeWithCoder:(nonnull NSCoder*)aCoder;
-(nullable instancetype)initWithCoder:(nonnull NSCoder*)aDecoder;
-(nonnull NSString*)description;
-(id)copyWithZone:(nullable NSZone*)zone;
@end
7 changes: 7 additions & 0 deletions Darkly/DataModels/LDFlagConfig/LDEventTrackingContext.m
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,11 @@ -(instancetype)initWithCoder:(NSCoder*)decoder {
-(NSString*)description {
return [NSString stringWithFormat:@"<LDEventTrackingContext: %p, trackEvents: %@, debugEventsUntilDate: %@>", self, self.trackEvents ? @"YES" : @"NO", self.debugEventsUntilDate ?: @"nil"];
}

-(id)copyWithZone:(NSZone*)zone {
LDEventTrackingContext *copiedContext = [[self class] new];
copiedContext.trackEvents = self.trackEvents;
copiedContext.debugEventsUntilDate = [self.debugEventsUntilDate copy]; //This may not return a different object, NSDate may be treated more like a value-type
return copiedContext;
}
@end
3 changes: 2 additions & 1 deletion Darkly/DataModels/LDFlagConfig/LDFlagConfigTracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
@class LDFlagCounter;
@class LDFlagConfigValue;

@interface LDFlagConfigTracker : NSObject
@interface LDFlagConfigTracker : NSObject <NSCopying>
@property (nonatomic, assign, readonly) LDMillisecond startDateMillis;
@property (nonatomic, assign, readonly) BOOL hasTrackedEvents;

Expand All @@ -25,4 +25,5 @@
defaultValue:(nullable id)defaultValue;
-(nonnull NSDictionary<NSString*, NSDictionary*>*)flagRequestSummary;
-(nonnull NSString*)description;
-(id)copyWithZone:(nullable NSZone*)zone;
@end
12 changes: 12 additions & 0 deletions Darkly/DataModels/LDFlagConfig/LDFlagConfigTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import "LDFlagConfigValue.h"
#import "NSDate+ReferencedDate.h"
#import "LDUtil.h"
#import "NSDictionary+LaunchDarkly.h"

@interface LDFlagConfigTracker()
@property (nonatomic, assign) LDMillisecond startDateMillis;
Expand Down Expand Up @@ -71,4 +72,15 @@ -(void)logRequestForFlagKey:(NSString*)flagKey reportedFlagValue:(id)reportedFla
-(NSString*)description {
return [NSString stringWithFormat:@"<LDFlagConfigTracker: %p, flagCounters: %@, startDateMillis: %ld>", self, [self.mutableFlagCounters description], (long)self.startDateMillis];
}

-(id)copyWithZone:(NSZone*)zone {
LDFlagConfigTracker *copy = [[self class] new];
copy.startDateMillis = self.startDateMillis;
copy.mutableFlagCounters = [NSMutableDictionary dictionary];
NSDictionary<NSString*, LDFlagCounter*> *flagCountersCopy = [self.mutableFlagCounters copy];
for (NSString *flagKey in flagCountersCopy.allKeys) {
copy.mutableFlagCounters[flagKey] = [flagCountersCopy[flagKey] copy];
}
return copy;
}
@end
4 changes: 3 additions & 1 deletion Darkly/DataModels/LDFlagConfig/LDFlagConfigValue.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ extern NSString * _Nonnull const kLDFlagConfigValueKeyVariation;

extern NSInteger const kLDFlagConfigValueItemDoesNotExist;

@interface LDFlagConfigValue: NSObject
@interface LDFlagConfigValue: NSObject <NSCopying>
//Core Items
@property (nullable, nonatomic, strong) id value;
@property (nonatomic, assign) NSInteger modelVersion;
Expand All @@ -43,4 +43,6 @@ extern NSInteger const kLDFlagConfigValueItemDoesNotExist;
-(BOOL)hasPropertiesMatchingDictionary:(nullable NSDictionary*)dictionary;

-(nonnull NSString*)description;
-(id)copyWithZone:(nullable NSZone*)zone;

@end
11 changes: 11 additions & 0 deletions Darkly/DataModels/LDFlagConfig/LDFlagConfigValue.m
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,15 @@ -(BOOL)hasPropertiesMatchingDictionary:(NSDictionary*)dictionary {
-(NSString*)description {
return [NSString stringWithFormat:@"<LDFlagConfigValue: %p, value: %@, modelVersion: %ld, variation: %ld, flagVersion: %@, eventTrackingContext: %@>", self, [self.value description], (long)self.modelVersion, (long)self.variation, self.flagVersion != nil ? [self.flagVersion description] : @"nil", self.eventTrackingContext ?: @"nil"];
}

-(id)copyWithZone:(NSZone*)zone {
LDFlagConfigValue *copy = [[self class] new];
copy.value = [self.value copy];
copy.modelVersion = self.modelVersion;
copy.variation = self.variation;
copy.flagVersion = [self.flagVersion copy];
copy.eventTrackingContext = [self.eventTrackingContext copy];

return copy;
}
@end
4 changes: 3 additions & 1 deletion Darkly/DataModels/LDFlagConfig/LDFlagCounter.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
@class LDFlagValueCounter;
@class LDFlagConfigValue;

@interface LDFlagCounter : NSObject
@interface LDFlagCounter : NSObject <NSCopying>
@property (nonatomic, strong, nonnull, readonly) NSString *flagKey;
@property (nonatomic, strong, nonnull) id defaultValue;
@property (nonatomic, assign, readonly) BOOL hasLoggedRequests;
Expand All @@ -24,4 +24,6 @@
-(nonnull NSDictionary*)dictionaryValue;

-(nonnull NSString*)description;
-(id)copyWithZone:(nullable NSZone*)zone;

@end
13 changes: 13 additions & 0 deletions Darkly/DataModels/LDFlagConfig/LDFlagCounter.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#import "LDFlagCounter.h"
#import "LDFlagValueCounter.h"
#import "LDFlagConfigValue.h"
#import "NSDictionary+LaunchDarkly.h"

NSString * const kLDFlagCounterKeyDefaultValue = @"default";
NSString * const kLDFlagCounterKeyCounters = @"counters";
Expand Down Expand Up @@ -98,4 +99,16 @@ -(NSString*)description {
[self.flagValueCounters description]];
}

-(id)copyWithZone:(nullable NSZone*)zone {
LDFlagCounter *copy = [[self class] new];
copy.flagKey = self.flagKey;
copy.defaultValue = [self.defaultValue copy];
NSMutableArray *copiedFlagValueCounters = [NSMutableArray arrayWithCapacity:self.flagValueCounters.count];
[self.flagValueCounters enumerateObjectsUsingBlock:^(LDFlagValueCounter * _Nonnull flagValueCounter, NSUInteger idx, BOOL * _Nonnull stop) {
[copiedFlagValueCounters addObject:[flagValueCounter copy]];
}];
copy.flagValueCounters = copiedFlagValueCounters;

return copy;
}
@end
4 changes: 3 additions & 1 deletion Darkly/DataModels/LDFlagConfig/LDFlagValueCounter.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

@class LDFlagConfigValue;

@interface LDFlagValueCounter : NSObject
@interface LDFlagValueCounter : NSObject <NSCopying>
@property (nonnull, nonatomic, strong) id reportedFlagValue;
@property (nullable, nonatomic, strong, readonly) LDFlagConfigValue *flagConfigValue;
@property (nonatomic, assign, readonly, getter=isKnown) BOOL known;
Expand All @@ -22,4 +22,6 @@
-(nonnull NSDictionary*)dictionaryValue;

-(nonnull NSString*)description;
-(id)copyWithZone:(nullable NSZone*)zone;

@end
10 changes: 10 additions & 0 deletions Darkly/DataModels/LDFlagConfig/LDFlagValueCounter.m
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,14 @@ -(NSString*)description {
self, [self.reportedFlagValue description], [self.flagConfigValue description], (long)self.count, self.known ? @"YES" : @"NO"];
}

-(id)copyWithZone:(NSZone*)zone {
LDFlagValueCounter *copy = [[self class] new];
copy.flagConfigValue = [self.flagConfigValue copy];
copy.reportedFlagValue = [self.reportedFlagValue copy];
copy.known = self.known;
copy.count = self.count;

return copy;
}

@end
2 changes: 1 addition & 1 deletion Darkly/Services/LDDataManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
user:(nonnull LDUserModel*)user;
-(void)recordCustomEventWithKey:(nonnull NSString*)eventKey customData:(nullable NSDictionary*)customData user:(nonnull LDUserModel*)user;
-(void)recordIdentifyEventWithUser:(nonnull LDUserModel*)user;
-(void)recordSummaryEventWithTracker:(nullable LDFlagConfigTracker*)tracker;
-(void)recordSummaryEventAndResetTrackerForUser:(nonnull LDUserModel*)user;
-(void)recordDebugEventWithFlagKey:(nonnull NSString*)flagKey
reportedFlagValue:(nonnull id)reportedFlagValue
flagConfigValue:(nullable LDFlagConfigValue*)flagConfigValue
Expand Down
27 changes: 16 additions & 11 deletions Darkly/Services/LDDataManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -376,18 +376,23 @@ -(void)recordIdentifyEventWithUser:(LDUserModel*)user {
[self addEventDictionary:[[LDEventModel identifyEventWithUser:user] dictionaryValueUsingConfig:self.config]];
}

-(void)recordSummaryEventWithTracker:(LDFlagConfigTracker*)tracker {
if (!tracker.hasTrackedEvents) {
DEBUG_LOGX(@"Tracker has no events to report. Discarding summary event.");
return;
}
LDEventModel *summaryEvent = [LDEventModel summaryEventWithTracker:tracker];
if (summaryEvent == nil) {
DEBUG_LOGX(@"Failed to create summary event. Aborting.");
return;
-(void)recordSummaryEventAndResetTrackerForUser:(LDUserModel*)user {
@synchronized (self) {
LDFlagConfigTracker *trackerCopy = [user.flagConfigTracker copy];
if (!trackerCopy.hasTrackedEvents) {
DEBUG_LOGX(@"Tracker has no events to report. Discarding summary event.");
return;
}
[user resetTracker];

LDEventModel *summaryEvent = [LDEventModel summaryEventWithTracker:trackerCopy];
if (summaryEvent == nil) {
DEBUG_LOGX(@"Failed to create summary event. Aborting.");
return;
}
DEBUG_LOGX(@"Creating summary event");
[self addEventDictionary:[summaryEvent dictionaryValueUsingConfig:self.config]];
}
DEBUG_LOGX(@"Creating summary event");
[self addEventDictionary:[summaryEvent dictionaryValueUsingConfig:self.config]];
}

-(void)recordDebugEventWithFlagKey:(NSString *)flagKey
Expand Down
2 changes: 1 addition & 1 deletion Darkly/Services/LDEnvironment.m
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ -(void)updateUser:(LDUserModel*)newUser {
return;
}

[self.dataManager recordSummaryEventWithTracker:self.user.flagConfigTracker];
[self.dataManager recordSummaryEventAndResetTrackerForUser:self.user];

DEBUG_LOG(@"LDEnvironment updating user key: %@, was online: %@", newUser.key, self.isOnline ? @"YES" : @"NO");
BOOL wasOnline = self.isOnline;
Expand Down
3 changes: 1 addition & 2 deletions Darkly/Services/LDEnvironmentController.m
Original file line number Diff line number Diff line change
Expand Up @@ -359,12 +359,11 @@ -(void)syncWithServerForEvents {

DEBUG_LOGX(@"EnvironmentController syncing events with server");

[self.dataManager recordSummaryEventWithTracker:self.user.flagConfigTracker];
[self.dataManager recordSummaryEventAndResetTrackerForUser:self.user];

__weak typeof(self) weakSelf = self;
[self.dataManager allEventDictionaries:^(NSArray *eventDictionaries) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf.user resetTracker];
if (eventDictionaries.count == 0) {
DEBUG_LOGX(@"EnvironmentController has no events so won't sync events with server");
return;
Expand Down
22 changes: 9 additions & 13 deletions DarklyTests/LDEnvironmentControllerTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ - (void)testStopPolling_streaming {
self.environmentController.online = YES; //triggers startPolling
[[self.pollingManagerMock expect] stopEventPolling];
[[self.eventSourceMock expect] close];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:self.user.flagConfigTracker];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];
[[self.dataManagerMock expect] allEventDictionaries:[OCMArg checkWithBlock:^BOOL(id obj) {
((void (^)(NSArray *))obj)(self.events);
return YES;
Expand All @@ -250,7 +250,7 @@ - (void)testStopPolling_polling {
self.environmentController.online = YES; //triggers startPolling
[[self.pollingManagerMock expect] stopEventPolling];
[[self.pollingManagerMock expect] stopFlagConfigPolling];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:self.user.flagConfigTracker];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];
[[self.dataManagerMock expect] allEventDictionaries:[OCMArg checkWithBlock:^BOOL(id obj) {
((void (^)(NSArray *))obj)(self.events);
return YES;
Expand All @@ -270,7 +270,7 @@ - (void)testWillEnterBackground_streaming {
[[self.pollingManagerMock expect] suspendEventPolling];
[[self.eventSourceMock expect] close];
[[self.pollingManagerMock reject] suspendFlagConfigPolling];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:self.user.flagConfigTracker];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];
[[self.dataManagerMock expect] allEventDictionaries:[OCMArg checkWithBlock:^BOOL(id obj) {
((void (^)(NSArray *))obj)(self.events);
return YES;
Expand All @@ -292,7 +292,7 @@ - (void)testWillEnterBackground_polling {
[[self.pollingManagerMock expect] suspendEventPolling];
[[self.eventSourceMock reject] close];
[[self.pollingManagerMock expect] suspendFlagConfigPolling];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:self.user.flagConfigTracker];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];
[[self.dataManagerMock expect] allEventDictionaries:[OCMArg checkWithBlock:^BOOL(id obj) {
((void (^)(NSArray *))obj)(self.events);
return YES;
Expand Down Expand Up @@ -1357,7 +1357,7 @@ - (void)testEventTimerFiredNotification {
completion(self.events);
return YES;
}]];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:[OCMArg any]];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];
self.environmentController.online = YES;
[[self.requestManagerMock expect] performEventRequest:self.events isOnline:self.environmentController.isOnline];

Expand All @@ -1375,21 +1375,17 @@ - (void)testSyncWithServerForEvents_eventsExist {
}]];
self.environmentController.online = YES;
[[self.requestManagerMock expect] performEventRequest:self.events isOnline:YES];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:[OCMArg any]];
LDMillisecond startDateMillis = [[NSDate date] millisSince1970];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];

[self.environmentController syncWithServerForEvents];

[self.requestManagerMock verify];
[self.dataManagerMock verify];
XCTAssertNotNil(self.user.flagConfigTracker);
XCTAssertFalse(self.user.flagConfigTracker.hasTrackedEvents);
XCTAssertTrue(Approximately(self.user.flagConfigTracker.startDateMillis, startDateMillis, 10));
}

- (void)testSyncWithServerForEvents_eventsEmpty {
self.environmentController.online = YES;
[[self.dataManagerMock expect] recordSummaryEventWithTracker:[OCMArg any]];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];
[[self.dataManagerMock stub] allEventDictionaries:[OCMArg checkWithBlock:^BOOL(id obj) {
void (^completion)(NSArray *) = obj;
completion(@[]);
Expand All @@ -1405,7 +1401,7 @@ - (void)testSyncWithServerForEvents_eventsEmpty {

- (void)testSyncWithServerForEvents_eventsNil {
self.environmentController.online = YES;
[[self.dataManagerMock expect] recordSummaryEventWithTracker:[OCMArg any]];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];
[[self.dataManagerMock stub] allEventDictionaries:[OCMArg checkWithBlock:^BOOL(id obj) {
void (^completion)(NSArray *) = obj;
completion(nil);
Expand Down Expand Up @@ -1490,7 +1486,7 @@ - (void)testFlushEventsWhenOnline {
completion(self.events);
return YES;
}]];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:[OCMArg any]];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.user];

[self.environmentController flushEvents];

Expand Down
10 changes: 5 additions & 5 deletions DarklyTests/LDEnvironmentTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ -(void)testUpdateUser {
LDUserModel *newUser = [LDUserModel stubWithKey:[[NSUUID UUID] UUIDString]];
id newUserMock = [OCMockObject niceMockForClass:[LDUserModel class]];
[[[newUserMock expect] andReturn:newUser] copy];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:self.environment.user.flagConfigTracker];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.environment.user];
[[self.environmentControllerMock expect] setOnline:NO];
[[self.dataManagerMock expect] convertToEnvironmentBasedCacheForUser:newUser config:self.config];
[[self.dataManagerMock expect] retrieveFlagConfigForUser:newUser];
Expand Down Expand Up @@ -719,7 +719,7 @@ -(void)testUpdateUser_secondaryEnvironment {

[self.environment start];
self.environment.online = YES;
[[self.dataManagerMock expect] recordSummaryEventWithTracker:self.environment.user.flagConfigTracker];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.environment.user];
[[originalEnvironmentControllerMock expect] setOnline:NO];
[[self.dataManagerMock reject] convertToEnvironmentBasedCacheForUser:[OCMArg any] config:[OCMArg any]];
[[self.dataManagerMock expect] retrieveFlagConfigForUser:newUser];
Expand All @@ -741,7 +741,7 @@ -(void)testUpdateUser_offline {
LDUserModel *newUser = [LDUserModel stubWithKey:[[NSUUID UUID] UUIDString]];
id newUserMock = [OCMockObject niceMockForClass:[LDUserModel class]];
[[[newUserMock expect] andReturn:newUser] copy];
[[self.dataManagerMock expect] recordSummaryEventWithTracker:self.environment.user.flagConfigTracker];
[[self.dataManagerMock expect] recordSummaryEventAndResetTrackerForUser:self.environment.user];
[[self.environmentControllerMock reject] setOnline:NO];
[[self.dataManagerMock expect] convertToEnvironmentBasedCacheForUser:newUser config:self.config];
[[self.dataManagerMock expect] retrieveFlagConfigForUser:newUser];
Expand All @@ -763,7 +763,7 @@ -(void)testUpdateUser_missingNewUser {
LDUserModel *originalEnvironmentUser = self.environment.user;
[self.environment start];
self.environment.online = YES;
[[self.dataManagerMock reject] recordSummaryEventWithTracker:[OCMArg any]];
[[self.dataManagerMock reject] recordSummaryEventAndResetTrackerForUser:[OCMArg any]];
[[self.environmentControllerMock reject] setOnline:[OCMArg any]];
[[self.dataManagerMock reject] convertToEnvironmentBasedCacheForUser:[OCMArg any] config:[OCMArg any]];
[[self.dataManagerMock reject] retrieveFlagConfigForUser:[OCMArg any]];
Expand All @@ -781,7 +781,7 @@ -(void)testUpdateUser_missingNewUser {
-(void)testUpdateUser_notStarted {
LDUserModel *originalEnvironmentUser = self.environment.user;
LDUserModel *newUser = [LDUserModel stubWithKey:[[NSUUID UUID] UUIDString]];
[[self.dataManagerMock reject] recordSummaryEventWithTracker:[OCMArg any]];
[[self.dataManagerMock reject] recordSummaryEventAndResetTrackerForUser:[OCMArg any]];
[[self.environmentControllerMock reject] setOnline:[OCMArg any]];
[[self.dataManagerMock reject] convertToEnvironmentBasedCacheForUser:[OCMArg any] config:[OCMArg any]];
[[self.dataManagerMock reject] retrieveFlagConfigForUser:[OCMArg any]];
Expand Down
Loading