aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustin R. Miller <incanus@users.noreply.github.com>2015-05-12 18:36:59 -0700
committerJustin R. Miller <incanus@codesorcery.net>2015-05-12 18:48:10 -0700
commit6519d23611755a3cb1d17cbf6068941673af9e11 (patch)
treee6ffb6688b354fdd2ae71c3b783000d76e94f431
parent13405479b7fc1ce43ae262e30be3eacb7c707c92 (diff)
Merge pull request #1491 from mapbox/1ec5-metrics-opt-way-out
refactor metrics system to immediately jettison data & tracking when users opt out
-rw-r--r--gyp/platform-ios.gypi3
-rw-r--r--include/mbgl/ios/MGLAccountManager.h31
-rw-r--r--include/mbgl/ios/MGLMapboxEvents.h7
-rw-r--r--include/mbgl/ios/MGLMetricsLocationManager.h13
-rw-r--r--include/mbgl/ios/MapboxGL.h2
-rw-r--r--platform/ios/MGLAccountManager.m54
-rw-r--r--platform/ios/MGLMapView.mm13
-rw-r--r--platform/ios/MGLMapboxEvents.m437
-rw-r--r--platform/ios/MGLMapboxEvents_Private.h7
-rw-r--r--platform/ios/MGLMetricsLocationManager.m107
-rw-r--r--test/ios/ios-tests.xcodeproj/project.pbxproj2
-rw-r--r--test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/ios-tests.xccheckout12
12 files changed, 347 insertions, 341 deletions
diff --git a/gyp/platform-ios.gypi b/gyp/platform-ios.gypi
index 1f96f9470..7e91648c4 100644
--- a/gyp/platform-ios.gypi
+++ b/gyp/platform-ios.gypi
@@ -19,6 +19,7 @@
'../platform/darwin/reachability.m',
'../include/mbgl/ios/MapboxGL.h',
'../include/mbgl/ios/MGLMapboxEvents.h',
+ '../platform/ios/MGLMapboxEvents_Private.h',
'../platform/ios/MGLMapboxEvents.m',
'../include/mbgl/ios/MGLMapView.h',
'../include/mbgl/ios/MGLMapView+IBAdditions.h',
@@ -33,8 +34,6 @@
'../platform/ios/MGLUserLocation.m',
'../platform/ios/MGLUserLocationAnnotationView.h',
'../platform/ios/MGLUserLocationAnnotationView.m',
- '../include/mbgl/ios/MGLMetricsLocationManager.h',
- '../platform/ios/MGLMetricsLocationManager.m',
'../include/mbgl/ios/MGLTypes.h',
'../platform/ios/NSBundle+MGLAdditions.h',
'../platform/ios/NSBundle+MGLAdditions.m',
diff --git a/include/mbgl/ios/MGLAccountManager.h b/include/mbgl/ios/MGLAccountManager.h
index f8bb2bf9a..7ec4135f1 100644
--- a/include/mbgl/ios/MGLAccountManager.h
+++ b/include/mbgl/ios/MGLAccountManager.h
@@ -1,8 +1,29 @@
+#import <Foundation/Foundation.h>
+
+/** The MGLAccountManager object provides a global way to set a Mapbox API access token, as well as other settings used framework-wide. */
@interface MGLAccountManager : NSObject
-+ (void) setMapboxMetricsEnabledSettingShownInApp:(BOOL)showsOptOut;
-+ (BOOL) mapboxMetricsEnabledSettingShownInApp;
-+ (void) setAccessToken:(NSString *) accessToken;
-+ (NSString *) accessToken;
+/** @name Authorizing Access */
+
+/** Set the Mapbox API access token for the framework.
+*
+* You can set an access token on MGLAccountManager or on an individual map view. The same token is used throughout the framework.
+* @param accessToken The Mapbox API access token. */
++ (void)setAccessToken:(NSString *)accessToken;
+
+/** Retreive the Mapbox API access token for the framework.
+*
+* You can set an access token on MGLAccountManager or on an individual map view. The same token is used throughout the framework.
+* @return accessToken The Mapbox API access token. */
++ (NSString *)accessToken;
+
+/** @name Providing User Metrics Opt-Out */
+
+/** Certain Mapbox plans require the collection of user metrics. If you aren't using a preference switch in an existing or new `Settings.bundle` in your application, set this value to `YES` to indicate that you are providing a metrics opt-out for users within your app's interface directly.
+* @param showsOptOut Whether your application's interface provides a user opt-out preference. The default value is `NO`, meaning a `Settings.bundle` is expected for providing a user opt-out preference. */
++ (void)setMapboxMetricsEnabledSettingShownInApp:(BOOL)showsOptOut;
+
+/** Whether in-app user metrics opt-out is configured. If set to the default value of `NO`, a user opt-out preference is expected in a `Settings.bundle` that shows in the application's section within the system Settings app. */
++ (BOOL)mapboxMetricsEnabledSettingShownInApp;
-@end \ No newline at end of file
+@end
diff --git a/include/mbgl/ios/MGLMapboxEvents.h b/include/mbgl/ios/MGLMapboxEvents.h
index c70c7cb33..5c5d8a924 100644
--- a/include/mbgl/ios/MGLMapboxEvents.h
+++ b/include/mbgl/ios/MGLMapboxEvents.h
@@ -32,10 +32,6 @@ extern NSString *const MGLEventGestureRotateStart;
// You must call these methods from the main thread.
//
-+ (void) setToken:(NSString *)token;
-+ (void) setAppName:(NSString *)appName;
-+ (void) setAppVersion:(NSString *)appVersion;
-+ (void) setAppBuildNumber:(NSString *)appBuildNumber;
+ (void) pauseMetricsCollection;
+ (void) resumeMetricsCollection;
@@ -58,4 +54,7 @@ extern NSString *const MGLEventGestureRotateStart;
//
+ (void) flush;
+// Main thread only
++ (void)validate;
+
@end
diff --git a/include/mbgl/ios/MGLMetricsLocationManager.h b/include/mbgl/ios/MGLMetricsLocationManager.h
deleted file mode 100644
index f02c76f53..000000000
--- a/include/mbgl/ios/MGLMetricsLocationManager.h
+++ /dev/null
@@ -1,13 +0,0 @@
-#import <Foundation/Foundation.h>
-
-@interface MGLMetricsLocationManager : NSObject
-
-// These methods can be called from any thread.
-//
-+ (instancetype)sharedManager;
-- (void) startUpdatingLocation;
-- (void) stopUpdatingLocation;
-- (void) startMonitoringVisits;
-- (void) stopMonitoringVisits;
-
-@end
diff --git a/include/mbgl/ios/MapboxGL.h b/include/mbgl/ios/MapboxGL.h
index 34e080a51..a6d23d328 100644
--- a/include/mbgl/ios/MapboxGL.h
+++ b/include/mbgl/ios/MapboxGL.h
@@ -1,5 +1,5 @@
+#import "MGLAccountManager.h"
#import "MGLAnnotation.h"
#import "MGLMapView.h"
#import "MGLTypes.h"
#import "MGLUserLocation.h"
-#import "MGLAccountManager.h" \ No newline at end of file
diff --git a/platform/ios/MGLAccountManager.m b/platform/ios/MGLAccountManager.m
index 09bfb3db2..32540e4e4 100644
--- a/platform/ios/MGLAccountManager.m
+++ b/platform/ios/MGLAccountManager.m
@@ -1,12 +1,12 @@
#import <Foundation/Foundation.h>
#import "MGLAccountManager.h"
+#import "MGLMapboxEvents_Private.h"
#import "NSProcessInfo+MGLAdditions.h"
-#import "MGLMapboxEvents.h"
@interface MGLAccountManager()
-@property (atomic) BOOL showsOptOutInApp;
+@property (atomic) BOOL mapboxMetricsEnabledSettingShownInApp;
@property (atomic) NSString *accessToken;
@end
@@ -14,49 +14,49 @@
@implementation MGLAccountManager
-static MGLAccountManager *_sharedManager;
-
// Can be called from any thread.
//
-+ (instancetype) sharedInstance {
++ (instancetype) sharedManager {
+ if (NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent) {
+ return;
+ }
static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- if ( ! NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent) {
- void (^setupBlock)() = ^{
- _sharedManager = [[self alloc] init];
- _sharedManager.showsOptOutInApp = NO;
- };
- if ( ! [[NSThread currentThread] isMainThread]) {
- dispatch_sync(dispatch_get_main_queue(), ^{
- setupBlock();
- });
- }
- else {
- setupBlock();
- }
- }
- });
+ static MGLAccountManager *_sharedManager;
+ void (^setupBlock)() = ^{
+ dispatch_once(&onceToken, ^{
+ _sharedManager = [[self alloc] init];
+ _sharedManager.mapboxMetricsEnabledSettingShownInApp = NO;
+ });
+ };
+ if ( ! [[NSThread currentThread] isMainThread]) {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ setupBlock();
+ });
+ }
+ else {
+ setupBlock();
+ }
return _sharedManager;
}
-+ (void) setMapboxMetricsEnabledSettingShownInApp:(BOOL)showsOptOut {
- [MGLAccountManager sharedInstance].showsOptOutInApp = showsOptOut;
++ (void) setMapboxMetricsEnabledSettingShownInApp:(BOOL)shown {
+ [MGLAccountManager sharedManager].mapboxMetricsEnabledSettingShownInApp = shown;
}
+ (BOOL) mapboxMetricsEnabledSettingShownInApp {
- return [MGLAccountManager sharedInstance].showsOptOutInApp;
+ return [MGLAccountManager sharedManager].mapboxMetricsEnabledSettingShownInApp;
}
+ (void) setAccessToken:(NSString *) accessToken {
- [[MGLAccountManager sharedInstance] setAccessToken:accessToken];
+ [[MGLAccountManager sharedManager] setAccessToken:accessToken];
// Update MGLMapboxEvents
// NOTE: This is (likely) the initial setup of MGLMapboxEvents
- [MGLMapboxEvents setToken:accessToken];
+ [MGLMapboxEvents sharedManager];
}
+ (NSString *) accessToken {
- return [MGLAccountManager sharedInstance].accessToken;
+ return [MGLAccountManager sharedManager].accessToken;
}
diff --git a/platform/ios/MGLMapView.mm b/platform/ios/MGLMapView.mm
index b6ae9eb99..dbc86b213 100644
--- a/platform/ios/MGLMapView.mm
+++ b/platform/ios/MGLMapView.mm
@@ -20,6 +20,7 @@
#import "NSString+MGLAdditions.h"
#import "NSProcessInfo+MGLAdditions.h"
#import "NSException+MGLAdditions.h"
+#import "MGLAccountManager.h"
#import "MGLAnnotation.h"
#import "MGLUserLocationAnnotationView.h"
#import "MGLUserLocation_Private.h"
@@ -158,7 +159,7 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
- (void)setAccessToken:(NSString *)accessToken
{
_mbglMap->setAccessToken(accessToken ? (std::string)[accessToken UTF8String] : "");
- [MGLMapboxEvents setToken:accessToken.mgl_stringOrNilIfEmpty];
+ [MGLAccountManager setAccessToken:accessToken.mgl_stringOrNilIfEmpty];
}
+ (NSSet *)keyPathsForValuesAffectingStyleURL
@@ -210,14 +211,6 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
// setup accessibility
//
self.accessibilityLabel = @"Map";
-
- // metrics: initial setup
- NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
- NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
- NSString *appBuildNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
- if (appName != nil) [MGLMapboxEvents setAppName:appName];
- if (appVersion != nil) [MGLMapboxEvents setAppVersion:appVersion];
- if (appBuildNumber != nil) [MGLMapboxEvents setAppBuildNumber:appBuildNumber];
// create GL view
//
@@ -711,6 +704,8 @@ std::chrono::steady_clock::duration secondsAsDuration(float duration)
if (self.isDormant)
{
self.dormant = NO;
+
+ [MGLMapboxEvents validate];
self.glSnapshotView.hidden = YES;
diff --git a/platform/ios/MGLMapboxEvents.m b/platform/ios/MGLMapboxEvents.m
index cb4cdce60..9fcfc7153 100644
--- a/platform/ios/MGLMapboxEvents.m
+++ b/platform/ios/MGLMapboxEvents.m
@@ -1,15 +1,15 @@
-#import "MGLMapboxEvents.h"
+#import "MGLMapboxEvents_Private.h"
#import <UIKit/UIKit.h>
#import <SystemConfiguration/CaptiveNetwork.h>
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
#import <CoreTelephony/CTCarrier.h>
+#import <CoreLocation/CoreLocation.h>
-#import "MGLMetricsLocationManager.h"
+#import "MGLAccountManager.h"
#import "NSProcessInfo+MGLAdditions.h"
#import "NSBundle+MGLAdditions.h"
#import "NSException+MGLAdditions.h"
-#import "MGLAccountManager.m"
#include <sys/sysctl.h>
@@ -44,6 +44,79 @@ NSString *const MGLEventGesturePanStart = @"Pan";
NSString *const MGLEventGesturePinchStart = @"Pinch";
NSString *const MGLEventGestureRotateStart = @"Rotation";
+const NSUInteger MGLMaximumEventsPerFlush = 20;
+const NSTimeInterval MGLFlushInterval = 60;
+
+@interface MGLMapboxEventsData : NSObject
+
+// All of the following properties are written to only from
+// the main thread, but can be read on any thread.
+//
+@property (atomic) NSString *instanceID;
+@property (atomic) NSString *advertiserId;
+@property (atomic) NSString *vendorId;
+@property (atomic) NSString *model;
+@property (atomic) NSString *iOSVersion;
+@property (atomic) NSString *carrier;
+@property (atomic) CGFloat scale;
+
+@end
+
+@implementation MGLMapboxEventsData
+
+- (instancetype)init {
+ if (self = [super init]) {
+ _instanceID = [[NSUUID UUID] UUIDString];
+
+ // Dynamic detection of ASIdentifierManager from Mixpanel
+ // https://github.com/mixpanel/mixpanel-iphone/blob/master/LICENSE
+ _advertiserId = @"";
+ Class ASIdentifierManagerClass = NSClassFromString(@"ASIdentifierManager");
+ if (ASIdentifierManagerClass) {
+ SEL sharedManagerSelector = NSSelectorFromString(@"sharedManager");
+ id sharedManager = ((id (*)(id, SEL))[ASIdentifierManagerClass methodForSelector:sharedManagerSelector])(ASIdentifierManagerClass, sharedManagerSelector);
+ // Add check here
+ SEL isAdvertisingTrackingEnabledSelector = NSSelectorFromString(@"isAdvertisingTrackingEnabled");
+ BOOL trackingEnabled = ((BOOL (*)(id, SEL))[sharedManager methodForSelector:isAdvertisingTrackingEnabledSelector])(sharedManager, isAdvertisingTrackingEnabledSelector);
+ if (trackingEnabled) {
+ SEL advertisingIdentifierSelector = NSSelectorFromString(@"advertisingIdentifier");
+ NSUUID *uuid = ((NSUUID* (*)(id, SEL))[sharedManager methodForSelector:advertisingIdentifierSelector])(sharedManager, advertisingIdentifierSelector);
+ _advertiserId = [uuid UUIDString];
+ }
+ }
+ _vendorId = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
+
+ _model = [self sysInfoByName:"hw.machine"];
+ _iOSVersion = [NSString stringWithFormat:@"%@ %@", [UIDevice currentDevice].systemName, [UIDevice currentDevice].systemVersion];
+ if ([UIScreen instancesRespondToSelector:@selector(nativeScale)]) {
+ _scale = [UIScreen mainScreen].nativeScale;
+ } else {
+ _scale = [UIScreen mainScreen].scale;
+ }
+ CTCarrier *carrierVendor = [[[CTTelephonyNetworkInfo alloc] init] subscriberCellularProvider];
+ _carrier = [carrierVendor carrierName];
+ }
+ return self;
+}
+
+// Can be called from any thread.
+//
+- (NSString *)sysInfoByName:(char *)typeSpecifier
+{
+ size_t size;
+ sysctlbyname(typeSpecifier, NULL, &size, NULL, 0);
+
+ char *answer = malloc(size);
+ sysctlbyname(typeSpecifier, answer, &size, NULL, 0);
+
+ NSString *results = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding];
+
+ free(answer);
+ return results;
+}
+
+@end
+
//
// Threadsafety conventions:
//
@@ -60,30 +133,23 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
// strong references.
//
-@interface MGLMapboxEvents()
+@interface MGLMapboxEvents () <CLLocationManagerDelegate>
// All of the following properties are written to only from
// the main thread, but can be read on any thread.
//
-@property (atomic) NSString *token;
+@property (atomic) MGLMapboxEventsData *data;
+@property (atomic) NSString *appBundleId;
@property (atomic) NSString *appName;
@property (atomic) NSString *appVersion;
@property (atomic) NSString *appBuildNumber;
-@property (atomic) NSString *instanceID;
-@property (atomic) NSString *advertiserId;
-@property (atomic) NSString *vendorId;
-@property (atomic) NSString *appBundleId;
-@property (atomic) NSString *userAgent;
-@property (atomic) NSString *model;
-@property (atomic) NSString *iOSVersion;
-@property (atomic) NSString *carrier;
-@property (atomic) NSUInteger flushAt;
@property (atomic) NSDateFormatter *rfc3339DateFormatter;
-@property (atomic) CGFloat scale;
@property (atomic) NSURLSession *session;
@property (atomic) NSData *digicertCert;
@property (atomic) NSData *geoTrustCert;
+// Main thread only
+@property (nonatomic) CLLocationManager *locationManager;
// The paused state tracker is only ever accessed from the main thread.
//
@@ -93,10 +159,6 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
//
@property (nonatomic) NSTimer *timer;
-// The flush expiration time is only ever accessed from the main thread.
-//
-@property (nonatomic) NSTimeInterval flushAfter;
-
// This is an array of events to push. All access to it will be
// from our own serial queue.
//
@@ -108,7 +170,23 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
@end
-@implementation MGLMapboxEvents
+@implementation MGLMapboxEvents {
+ id _userDefaultsObserver;
+}
+
++ (void)initialize {
+ if (self == [MGLMapboxEvents class]) {
+ [[NSUserDefaults standardUserDefaults] registerDefaults:@{
+ @"MGLMapboxAccountType": @0,
+ @"MGLMapboxMetricsEnabled": @YES,
+ }];
+ }
+}
+
++ (BOOL)isEnabled {
+ return ([[NSUserDefaults standardUserDefaults] boolForKey:@"MGLMapboxMetricsEnabled"] &&
+ [[NSUserDefaults standardUserDefaults] integerForKey:@"MGLMapboxAccountType"] == 0);
+}
// Must be called from the main thread. Only called internally.
//
@@ -117,30 +195,35 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
self = [super init];
if (self) {
-
if (! [MGLAccountManager mapboxMetricsEnabledSettingShownInApp]) {
// Opt Out is not configured in UI, so check for Settings.bundle
// Put Settings bundle into memory
+ id defaultEnabledValue;
NSString *appSettingsBundle = [[NSBundle mainBundle] pathForResource:@"Settings" ofType:@"bundle"];
- NSAssert(appSettingsBundle, @"End users must be able to opt out of Metrics in your app, either inside Settings (via Settings.bundle) or inside this app. If you implement the opt-out control inside this app, disable this assertion by setting [MGLAccountManager setMapboxMetricsEnabledSettingShownInApp:YES] before the app initializes any Mapbox GL classes.");
-
- // Dynamic Settings.bundle loading based on:
- // http://stackoverflow.com/questions/510216/can-you-make-the-settings-in-settings-bundle-default-even-if-you-dont-open-the
- NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[appSettingsBundle stringByAppendingPathComponent:@"Root.plist"]];
- NSArray *preferences = [settings objectForKey:@"PreferenceSpecifiers"];
- NSMutableDictionary *defaultsToRegister = [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];
- for(NSDictionary *prefSpecification in preferences) {
- NSString *key = [prefSpecification objectForKey:@"Key"];
- if(key && [[prefSpecification allKeys] containsObject:@"DefaultValue"]) {
- [defaultsToRegister setObject:[prefSpecification objectForKey:@"DefaultValue"] forKey:key];
+ if (appSettingsBundle) {
+ // Dynamic Settings.bundle loading based on:
+ // http://stackoverflow.com/questions/510216/can-you-make-the-settings-in-settings-bundle-default-even-if-you-dont-open-the
+ NSDictionary *settings = [NSDictionary dictionaryWithContentsOfFile:[appSettingsBundle stringByAppendingPathComponent:@"Root.plist"]];
+ NSArray *preferences = settings[@"PreferenceSpecifiers"];
+ for (NSDictionary *prefSpecification in preferences) {
+ if ([prefSpecification[@"Key"] isEqualToString:@"MGLMapboxMetricsEnabled"]) {
+ defaultEnabledValue = prefSpecification[@"DefaultValue"];
+ }
}
}
- [[NSUserDefaults standardUserDefaults] registerDefaults:defaultsToRegister];
+ NSAssert(defaultEnabledValue, @"End users must be able to opt out of Metrics in your app, either inside Settings (via Settings.bundle) or inside this app. If you implement the opt-out control inside this app, disable this assertion by setting [MGLAccountManager setMapboxMetricsEnabledSettingShownInApp:YES] before the app initializes any Mapbox GL classes.");
+ [[NSUserDefaults standardUserDefaults] registerDefaults:@{
+ @"MGLMapboxMetricsEnabled": defaultEnabledValue,
+ }];
}
_appBundleId = [[NSBundle mainBundle] bundleIdentifier];
+ _appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
+ _appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
+ _appBuildNumber = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
+
NSString *uniqueID = [[NSProcessInfo processInfo] globallyUniqueString];
_serialQueue = dispatch_queue_create([[NSString stringWithFormat:@"%@.%@.events.serial", _appBundleId, uniqueID] UTF8String], DISPATCH_QUEUE_SERIAL);
@@ -158,7 +241,6 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
_geoTrustCert = [NSData dataWithContentsOfFile:cerPath];
}
- cerPath = nil;
cerPath = [resourceBundle pathForResource:@"api_mapbox_com-digicert" ofType:@"der"];
if (cerPath != nil) {
_digicertCert = [NSData dataWithContentsOfFile:cerPath];
@@ -166,40 +248,6 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
// Events Control
_eventQueue = [[NSMutableArray alloc] init];
- _flushAt = 20;
- _flushAfter = 60;
- _token = nil;
- _instanceID = [[NSUUID UUID] UUIDString];
-
- // Dynamic detection of ASIdentifierManager from Mixpanel
- // https://github.com/mixpanel/mixpanel-iphone/blob/master/LICENSE
- _advertiserId = @"";
- Class ASIdentifierManagerClass = NSClassFromString(@"ASIdentifierManager");
- if (ASIdentifierManagerClass) {
- SEL sharedManagerSelector = NSSelectorFromString(@"sharedManager");
- id sharedManager = ((id (*)(id, SEL))[ASIdentifierManagerClass methodForSelector:sharedManagerSelector])(ASIdentifierManagerClass, sharedManagerSelector);
- // Add check here
- SEL isAdvertisingTrackingEnabledSelector = NSSelectorFromString(@"isAdvertisingTrackingEnabled");
- BOOL trackingEnabled = ((BOOL (*)(id, SEL))[sharedManager methodForSelector:isAdvertisingTrackingEnabledSelector])(sharedManager, isAdvertisingTrackingEnabledSelector);
- if (trackingEnabled) {
- SEL advertisingIdentifierSelector = NSSelectorFromString(@"advertisingIdentifier");
- NSUUID *uuid = ((NSUUID* (*)(id, SEL))[sharedManager methodForSelector:advertisingIdentifierSelector])(sharedManager, advertisingIdentifierSelector);
- _advertiserId = [uuid UUIDString];
- }
- }
- _vendorId = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
-
- _model = [self getSysInfoByName:"hw.machine"];
- _iOSVersion = [NSString stringWithFormat:@"%@ %@", [UIDevice currentDevice].systemName, [UIDevice currentDevice].systemVersion];
- if ([UIScreen instancesRespondToSelector:@selector(nativeScale)]) {
- _scale = [UIScreen mainScreen].nativeScale;
- } else {
- _scale = [UIScreen mainScreen].scale;
- }
- CTCarrier *carrierVendor = [[[CTTelephonyNetworkInfo alloc] init] subscriberCellularProvider];
- _carrier = [carrierVendor carrierName];
-
- _userAgent = MGLMapboxEventsUserAgent;
// Setup Date Format
_rfc3339DateFormatter = [[NSDateFormatter alloc] init];
@@ -213,6 +261,16 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
// Enable Battery Monitoring
[UIDevice currentDevice].batteryMonitoringEnabled = YES;
+
+ __weak MGLMapboxEvents *weakSelf = self;
+ _userDefaultsObserver = [[NSNotificationCenter defaultCenter] addObserverForName:NSUserDefaultsDidChangeNotification
+ object:nil
+ queue:[NSOperationQueue mainQueue]
+ usingBlock:
+ ^(NSNotification *notification) {
+ MGLMapboxEvents *strongSelf = weakSelf;
+ [strongSelf validate];
+ }];
}
return self;
}
@@ -221,57 +279,62 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
// public class convenience methods. May return nil if this feature is disabled.
//
+ (instancetype)sharedManager {
+ if (NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent) {
+ return;
+ }
static dispatch_once_t onceToken;
static MGLMapboxEvents *_sharedManager;
- dispatch_once(&onceToken, ^{
- if ( ! NSProcessInfo.processInfo.mgl_isInterfaceBuilderDesignablesAgent &&
- [[NSUserDefaults standardUserDefaults] integerForKey:@"MGLMapboxAccountType"] == 0) {
- void (^setupBlock)() = ^{
- _sharedManager = [[self alloc] init];
- };
- if ( ! [[NSThread currentThread] isMainThread]) {
- dispatch_sync(dispatch_get_main_queue(), ^{
- setupBlock();
- });
- }
- else {
- setupBlock();
- }
- }
- });
+ void (^setupBlock)() = ^{
+ dispatch_once(&onceToken, ^{
+ _sharedManager = [[self alloc] init];
+ });
+ };
+ if ( ! [[NSThread currentThread] isMainThread]) {
+ dispatch_sync(dispatch_get_main_queue(), ^{
+ setupBlock();
+ });
+ }
+ else {
+ setupBlock();
+ }
return _sharedManager;
}
- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:_userDefaultsObserver];
[self pauseMetricsCollection];
}
-// Must be called from the main thread.
-//
-+ (void) setToken:(NSString *)token {
- MGLAssertIsMainThread();
- [MGLMapboxEvents sharedManager].token = token;
-}
-
-// Must be called from the main thread.
-//
-+ (void) setAppName:(NSString *)appName {
- MGLAssertIsMainThread();
- [MGLMapboxEvents sharedManager].appName = appName;
++ (void)validate {
+ [[MGLMapboxEvents sharedManager] validate];
}
-// Must be called from the main thread.
-//
-+ (void) setAppVersion:(NSString *)appVersion {
+- (void)validate {
MGLAssertIsMainThread();
- [MGLMapboxEvents sharedManager].appVersion = appVersion;
+ BOOL enabledInSettings = [[self class] isEnabled];
+ if (self.paused && enabledInSettings) {
+ [self resumeMetricsCollection];
+ } else if (!self.paused && !enabledInSettings) {
+ [self pauseMetricsCollection];
+ }
+
+ [self validateUpdatingLocation];
}
-// Must be called from the main thread.
-//
-+ (void) setAppBuildNumber:(NSString *)appBuildNumber {
+- (void)validateUpdatingLocation {
MGLAssertIsMainThread();
- [MGLMapboxEvents sharedManager].appBuildNumber = appBuildNumber;
+ if (self.paused) {
+ [self stopUpdatingLocation];
+ } else {
+ CLAuthorizationStatus authStatus = [CLLocationManager authorizationStatus];
+ if (authStatus == kCLAuthorizationStatusDenied ||
+ authStatus == kCLAuthorizationStatusRestricted) {
+ [self stopUpdatingLocation];
+ } else if (authStatus == kCLAuthorizationStatusAuthorized ||
+ authStatus == kCLAuthorizationStatusAuthorizedWhenInUse) {
+ [self startUpdatingLocation];
+ }
+ }
}
+ (void)pauseMetricsCollection {
@@ -286,11 +349,25 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
return;
}
self.paused = YES;
+ [_timer invalidate];
+ _timer = nil;
+ [_eventQueue removeAllObjects];
+ _data = nil;
[_session invalidateAndCancel];
_session = nil;
- MGLMetricsLocationManager *sharedLocationManager = [MGLMetricsLocationManager sharedManager];
- [sharedLocationManager stopUpdatingLocation];
- [sharedLocationManager stopMonitoringVisits];
+
+ [self validateUpdatingLocation];
+}
+
+- (void)stopUpdatingLocation {
+ [_locationManager stopUpdatingLocation];
+
+ // -[CLLocationManager stopMonitoringVisits] is only available in iOS 8+.
+ if ([_locationManager respondsToSelector:@selector(stopMonitoringVisits)]) {
+ [_locationManager stopMonitoringVisits];
+ }
+
+ _locationManager = nil;
}
+ (void)resumeMetricsCollection {
@@ -301,14 +378,34 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
//
- (void)resumeMetricsCollection {
MGLAssertIsMainThread();
- if (!self.isPaused) {
+ if (!self.paused || ![[self class] isEnabled]) {
return;
}
self.paused = NO;
+ _data = [[MGLMapboxEventsData alloc] init];
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
- MGLMetricsLocationManager *sharedLocationManager = [MGLMetricsLocationManager sharedManager];
- [sharedLocationManager startUpdatingLocation];
- [sharedLocationManager startMonitoringVisits];
+
+ [self validateUpdatingLocation];
+}
+
+- (void)startUpdatingLocation {
+ MGLAssertIsMainThread();
+ if (_locationManager || _paused) {
+ NSAssert(!(_locationManager && _paused),
+ @"MGLMapboxEvents should not have a CLLocationManager while paused.");
+ return;
+ }
+ _locationManager = [[CLLocationManager alloc] init];
+ _locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
+ _locationManager.distanceFilter = 10;
+ _locationManager.delegate = self;
+
+ [_locationManager startUpdatingLocation];
+
+ // -[CLLocationManager startMonitoringVisits] is only available in iOS 8+.
+ if ([_locationManager respondsToSelector:@selector(startMonitoringVisits)]) {
+ [_locationManager startMonitoringVisits];
+ }
}
// Can be called from any thread. Can be called rapidly from
@@ -330,12 +427,6 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
MGLMapboxEvents *strongSelf = weakSelf;
if ( ! strongSelf) return;
- // User has opted out
- if (![[NSUserDefaults standardUserDefaults] boolForKey:@"MGLMapboxMetricsEnabled"]) {
- [_eventQueue removeAllObjects];
- return;
- }
-
// Metrics Collection Has Been Paused
if (_paused) {
return;
@@ -348,34 +439,34 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
[evt setObject:event forKey:@"event"];
[evt setObject:@(1) forKey:@"version"];
[evt setObject:[strongSelf.rfc3339DateFormatter stringFromDate:[NSDate date]] forKey:@"created"];
- [evt setObject:strongSelf.instanceID forKey:@"instance"];
- [evt setObject:strongSelf.advertiserId forKey:@"advertiserId"];
- [evt setObject:strongSelf.vendorId forKey:@"vendorId"];
+ [evt setObject:strongSelf.data.instanceID forKey:@"instance"];
+ [evt setObject:strongSelf.data.advertiserId forKey:@"advertiserId"];
+ [evt setObject:strongSelf.data.vendorId forKey:@"vendorId"];
[evt setObject:strongSelf.appBundleId forKeyedSubscript:@"appBundleId"];
// mapbox-events-ios stock attributes
- [evt setValue:strongSelf.model forKey:@"model"];
- [evt setValue:strongSelf.iOSVersion forKey:@"operatingSystem"];
- [evt setValue:[strongSelf getDeviceOrientation] forKey:@"orientation"];
+ [evt setValue:strongSelf.data.model forKey:@"model"];
+ [evt setValue:strongSelf.data.iOSVersion forKey:@"operatingSystem"];
+ [evt setValue:[strongSelf deviceOrientation] forKey:@"orientation"];
[evt setValue:@((int)(100 * [UIDevice currentDevice].batteryLevel)) forKey:@"batteryLevel"];
- [evt setValue:@(strongSelf.scale) forKey:@"resolution"];
- [evt setValue:strongSelf.carrier forKey:@"carrier"];
+ [evt setValue:@(strongSelf.data.scale) forKey:@"resolution"];
+ [evt setValue:strongSelf.data.carrier forKey:@"carrier"];
- NSString *cell = [strongSelf getCurrentCellularNetworkConnectionType];
+ NSString *cell = [strongSelf currentCellularNetworkConnectionType];
if (cell) {
[evt setValue:cell forKey:@"cellularNetworkType"];
} else {
[evt setObject:[NSNull null] forKey:@"cellularNetworkType"];
}
- NSString *wifi = [strongSelf getWifiNetworkName];
+ NSString *wifi = [strongSelf wifiNetworkName];
if (wifi) {
[evt setValue:wifi forKey:@"wifi"];
} else {
[evt setObject:[NSNull null] forKey:@"wifi"];
}
- [evt setValue:@([strongSelf getContentSizeScale]) forKey:@"accessibilityFontScale"];
+ [evt setValue:@([strongSelf contentSizeScale]) forKey:@"accessibilityFontScale"];
// Make Immutable Version
NSDictionary *finalEvent = [NSDictionary dictionaryWithDictionary:evt];
@@ -384,7 +475,7 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
[_eventQueue addObject:finalEvent];
// Has Flush Limit Been Reached?
- if (_eventQueue.count >= strongSelf.flushAt) {
+ if (_eventQueue.count >= MGLMaximumEventsPerFlush) {
[strongSelf flush];
} else if (_eventQueue.count == 1) {
// If this is first new event on queue start timer,
@@ -402,18 +493,16 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
// Can be called from any thread.
//
- (void) flush {
- if (self.token == nil) return;
+ if ([MGLAccountManager accessToken] == nil) return;
__weak MGLMapboxEvents *weakSelf = self;
dispatch_async(_serialQueue, ^{
MGLMapboxEvents *strongSelf = weakSelf;
- if ( ! strongSelf) return;
-
- __block NSArray *events;
+ if ( ! strongSelf || [_eventQueue count] == 0) return;
// Make an immutable copy
- events = [NSArray arrayWithArray:_eventQueue];
+ NSArray *events = [NSArray arrayWithArray:_eventQueue];
// Update Queue to remove events sent to server
[_eventQueue removeAllObjects];
@@ -440,9 +529,9 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
if (!strongSelf) return;
// Setup URL Request
- NSString *url = [NSString stringWithFormat:@"%@/events/v1?access_token=%@", MGLMapboxEventsAPIBase, strongSelf.token];
+ NSString *url = [NSString stringWithFormat:@"%@/events/v1?access_token=%@", MGLMapboxEventsAPIBase, [MGLAccountManager accessToken]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
- [request setValue:[strongSelf getUserAgent] forHTTPHeaderField:@"User-Agent"];
+ [request setValue:strongSelf.userAgent forHTTPHeaderField:@"User-Agent"];
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
[request setHTTPMethod:@"POST"];
@@ -463,14 +552,11 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
- (void) startTimer {
void (^timerBlock)() = ^{
// Stop Timer if it already exists
- if (_timer) {
- [_timer invalidate];
- _timer = nil;
- }
+ [_timer invalidate];
// Start New Timer
- _timer = [NSTimer scheduledTimerWithTimeInterval:_flushAfter
- target:[self class]
+ _timer = [NSTimer scheduledTimerWithTimeInterval:MGLFlushInterval
+ target:self
selector:@selector(flush)
userInfo:nil
repeats:YES];
@@ -487,22 +573,13 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
// Can be called from any thread.
//
-- (NSString *) getUserAgent {
- if (self.appName != nil && self.appVersion != nil && self.appBuildNumber != nil && ([self.userAgent rangeOfString:self.appName].location == NSNotFound)) {
- self.userAgent = [NSString stringWithFormat:@"%@/%@/%@ %@", self.appName, self.appVersion, self.appBuildNumber, self.userAgent];
- }
- return self.userAgent;
-}
-
-// Can be called from any thread.
-//
-- (NSString *) formatDate:(NSDate *)date {
- return [self.rfc3339DateFormatter stringFromDate:date];
+- (NSString *) userAgent {
+ return [NSString stringWithFormat:@"%@/%@/%@ %@", self.appName, self.appVersion, self.appBuildNumber, MGLMapboxEventsUserAgent];
}
// Can be called from any thread.
//
-- (NSString *) getDeviceOrientation {
+- (NSString *) deviceOrientation {
__block NSString *result;
NSString *(^deviceOrientationBlock)(void) = ^{
@@ -549,7 +626,7 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
// Can be called from any thread.
//
-- (NSInteger) getContentSizeScale {
+- (NSInteger) contentSizeScale {
__block NSInteger result = -9999;
NSInteger (^contentSizeScaleBlock)(void) = ^{
@@ -597,23 +674,7 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
// Can be called from any thread.
//
-- (NSString *)getSysInfoByName:(char *)typeSpecifier
-{
- size_t size;
- sysctlbyname(typeSpecifier, NULL, &size, NULL, 0);
-
- char *answer = malloc(size);
- sysctlbyname(typeSpecifier, answer, &size, NULL, 0);
-
- NSString *results = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding];
-
- free(answer);
- return results;
-}
-
-// Can be called from any thread.
-//
-- (NSString *) getWifiNetworkName {
+- (NSString *) wifiNetworkName {
NSString *ssid = nil;
CFArrayRef interfaces = CNCopySupportedInterfaces();
@@ -630,7 +691,7 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
// Can be called from any thread.
//
-- (NSString *) getCurrentCellularNetworkConnectionType {
+- (NSString *) currentCellularNetworkConnectionType {
CTTelephonyNetworkInfo *telephonyInfo = [[CTTelephonyNetworkInfo alloc] init];
NSString *radioTech = telephonyInfo.currentRadioAccessTechnology;
@@ -695,6 +756,36 @@ NSString *const MGLEventGestureRotateStart = @"Rotation";
return result;
}
+#pragma mark CLLocationManagerDelegate
+- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
+ // Iterate through locations to pass all data
+ for (CLLocation *loc in locations) {
+ [MGLMapboxEvents pushEvent:MGLEventTypeLocation withAttributes:@{
+ MGLEventKeyLatitude: @(loc.coordinate.latitude),
+ MGLEventKeyLongitude: @(loc.coordinate.longitude),
+ MGLEventKeySpeed: @(loc.speed),
+ MGLEventKeyCourse: @(loc.course),
+ MGLEventKeyAltitude: @(loc.altitude),
+ MGLEventKeyHorizontalAccuracy: @(loc.horizontalAccuracy),
+ MGLEventKeyVerticalAccuracy: @(loc.verticalAccuracy)
+ }];
+ }
+}
+
+- (void)locationManager:(CLLocationManager *)manager didVisit:(CLVisit *)visit {
+ [MGLMapboxEvents pushEvent:MGLEventTypeVisit withAttributes:@{
+ MGLEventKeyLatitude: @(visit.coordinate.latitude),
+ MGLEventKeyLongitude: @(visit.coordinate.longitude),
+ MGLEventKeyHorizontalAccuracy: @(visit.horizontalAccuracy),
+ MGLEventKeyArrivalDate: [[NSDate distantPast] isEqualToDate:visit.arrivalDate] ? [NSNull null] : [_rfc3339DateFormatter stringFromDate:visit.arrivalDate],
+ MGLEventKeyDepartureDate: [[NSDate distantFuture] isEqualToDate:visit.departureDate] ? [NSNull null] : [_rfc3339DateFormatter stringFromDate:visit.departureDate]
+ }];
+}
+
+- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
+ [self validateUpdatingLocation];
+}
+
#pragma mark NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^) (NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler {
diff --git a/platform/ios/MGLMapboxEvents_Private.h b/platform/ios/MGLMapboxEvents_Private.h
new file mode 100644
index 000000000..6a48b31f0
--- /dev/null
+++ b/platform/ios/MGLMapboxEvents_Private.h
@@ -0,0 +1,7 @@
+#import "MGLMapboxEvents.h"
+
+@interface MGLMapboxEvents (Private)
+
++ (instancetype)sharedManager;
+
+@end
diff --git a/platform/ios/MGLMetricsLocationManager.m b/platform/ios/MGLMetricsLocationManager.m
deleted file mode 100644
index 9c186e4bf..000000000
--- a/platform/ios/MGLMetricsLocationManager.m
+++ /dev/null
@@ -1,107 +0,0 @@
-#import "MGLMapboxEvents.h"
-#import "MGLMetricsLocationManager.h"
-
-#import <CoreLocation/CoreLocation.h>
-
-@interface MGLMetricsLocationManager() <CLLocationManagerDelegate>
-
-@property (nonatomic) CLLocationManager *locationManager;
-@property (atomic) NSDateFormatter *rfc3339DateFormatter;
-
-@end
-
-@implementation MGLMetricsLocationManager
-
-- (instancetype) init {
- if (self = [super init]) {
- _locationManager = [[CLLocationManager alloc] init];
- _locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
- _locationManager.distanceFilter = 10;
- [_locationManager setDelegate:self];
-
- _rfc3339DateFormatter = [[NSDateFormatter alloc] init];
- NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
-
- [_rfc3339DateFormatter setLocale:enUSPOSIXLocale];
- [_rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"];
- // Clear Any System TimeZone Cache
- [NSTimeZone resetSystemTimeZone];
- [_rfc3339DateFormatter setTimeZone:[NSTimeZone systemTimeZone]];
- }
- return self;
-}
-
-+ (instancetype)sharedManager {
- static dispatch_once_t onceToken;
- static MGLMetricsLocationManager *sharedManager;
- dispatch_once(&onceToken, ^{
- sharedManager = [[self alloc] init];
- });
- return sharedManager;
-}
-
-- (void)startUpdatingLocation {
- [self.locationManager startUpdatingLocation];
-}
-
-- (void)stopUpdatingLocation {
- [self.locationManager stopUpdatingLocation];
-}
-
-- (void)startMonitoringVisits {
- // -[CLLocationManager startMonitoringVisits] is only available in iOS 8+.
- if ([self.locationManager respondsToSelector:@selector(startMonitoringVisits)]) {
- [self.locationManager startMonitoringVisits];
- }
-}
-
-- (void)stopMonitoringVisits {
- // -[CLLocationManager stopMonitoringVisits] is only available in iOS 8+.
- if ([self.locationManager respondsToSelector:@selector(stopMonitoringVisits)]) {
- [self.locationManager stopMonitoringVisits];
- }
-}
-
-#pragma mark CLLocationManagerDelegate
-- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
- // Iterate through locations to pass all data
- for (CLLocation *loc in locations) {
- [MGLMapboxEvents pushEvent:MGLEventTypeLocation withAttributes:@{
- MGLEventKeyLatitude: @(loc.coordinate.latitude),
- MGLEventKeyLongitude: @(loc.coordinate.longitude),
- MGLEventKeySpeed: @(loc.speed),
- MGLEventKeyCourse: @(loc.course),
- MGLEventKeyAltitude: @(loc.altitude),
- MGLEventKeyHorizontalAccuracy: @(loc.horizontalAccuracy),
- MGLEventKeyVerticalAccuracy: @(loc.verticalAccuracy)
- }];
- }
-}
-
-- (void)locationManager:(CLLocationManager *)manager didVisit:(CLVisit *)visit {
- [MGLMapboxEvents pushEvent:MGLEventTypeVisit withAttributes:@{
- MGLEventKeyLatitude: @(visit.coordinate.latitude),
- MGLEventKeyLongitude: @(visit.coordinate.longitude),
- MGLEventKeyHorizontalAccuracy: @(visit.horizontalAccuracy),
- MGLEventKeyArrivalDate: [[NSDate distantPast] isEqualToDate:visit.arrivalDate] ? [NSNull null] : [_rfc3339DateFormatter stringFromDate:visit.arrivalDate],
- MGLEventKeyDepartureDate: [[NSDate distantFuture] isEqualToDate:visit.departureDate] ? [NSNull null] : [_rfc3339DateFormatter stringFromDate:visit.departureDate]
- }];
-}
-
-- (void)locationManager:(CLLocationManager *)manager didChangeAuthorizationStatus:(CLAuthorizationStatus)status {
- switch (status) {
- case kCLAuthorizationStatusDenied:
- [self stopUpdatingLocation];
- [self stopMonitoringVisits];
- break;
- case kCLAuthorizationStatusAuthorized:
- case kCLAuthorizationStatusAuthorizedWhenInUse:
- [self startUpdatingLocation];
- [self startMonitoringVisits];
- break;
- default:
- break;
- }
-}
-
-@end
diff --git a/test/ios/ios-tests.xcodeproj/project.pbxproj b/test/ios/ios-tests.xcodeproj/project.pbxproj
index d6c6344c2..50e12595f 100644
--- a/test/ios/ios-tests.xcodeproj/project.pbxproj
+++ b/test/ios/ios-tests.xcodeproj/project.pbxproj
@@ -631,6 +631,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"../../build/ios/pkg/static/**",
OCMock,
+ OHHTTPStubs/OHHTTPStubs/Sources,
);
INFOPLIST_FILE = "Bundle-Info.plist";
LIBRARY_SEARCH_PATHS = (
@@ -665,6 +666,7 @@
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"../../build/ios/pkg/static/**",
OCMock,
+ OHHTTPStubs/OHHTTPStubs/Sources,
);
INFOPLIST_FILE = "Bundle-Info.plist";
LIBRARY_SEARCH_PATHS = (
diff --git a/test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/ios-tests.xccheckout b/test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/ios-tests.xccheckout
index e2f98de8f..b6ff646f1 100644
--- a/test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/ios-tests.xccheckout
+++ b/test/ios/ios-tests.xcodeproj/project.xcworkspace/xcshareddata/ios-tests.xccheckout
@@ -12,6 +12,8 @@
<dict>
<key>10265E242415D473A6A613214DB7AC3EE3D43F93</key>
<string>https://github.com/mapbox/KIF.git</string>
+ <key>38C2A0D4F62B675E8C16C8BC1437C7753846C8AC</key>
+ <string>https://github.com/AliSoftware/OHHTTPStubs.git</string>
<key>7E68CB584078A487C0535CC191D3B7551EEE2095</key>
<string>github.com:mapbox/mapbox-gl-native.git</string>
</dict>
@@ -21,6 +23,8 @@
<dict>
<key>10265E242415D473A6A613214DB7AC3EE3D43F93</key>
<string>../../../..test/ios/KIF</string>
+ <key>38C2A0D4F62B675E8C16C8BC1437C7753846C8AC</key>
+ <string>../../../../test/ios/OHHTTPStubs/</string>
<key>7E68CB584078A487C0535CC191D3B7551EEE2095</key>
<string>../../../..</string>
</dict>
@@ -48,6 +52,14 @@
<key>IDESourceControlWCCName</key>
<string>KIF</string>
</dict>
+ <dict>
+ <key>IDESourceControlRepositoryExtensionIdentifierKey</key>
+ <string>public.vcs.git</string>
+ <key>IDESourceControlWCCIdentifierKey</key>
+ <string>38C2A0D4F62B675E8C16C8BC1437C7753846C8AC</string>
+ <key>IDESourceControlWCCName</key>
+ <string>OHHTTPStubs</string>
+ </dict>
</array>
</dict>
</plist>