Merge pull request #1132 from mapbox/metrics-stability-squash
fixes #1116: metrics stability & performance refactor
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
#import "MGLTypes.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
#import "MGLTypes.h"
|
||||
|
||||
@class MGLUserLocation;
|
||||
|
||||
@protocol MGLMapViewDelegate;
|
||||
|
||||
@@ -1,26 +1,43 @@
|
||||
//
|
||||
// MapboxEvents.h
|
||||
// MapboxEvents
|
||||
//
|
||||
// Created by Brad Leege on 3/5/15.
|
||||
// Copyright (c) 2015 Mapbox. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
extern NSString *const MGLEventMapLoad;
|
||||
extern NSString *const MGLEventMapTap;
|
||||
extern NSString *const MGLEventMapSingleTap;
|
||||
extern NSString *const MGLEventMapDoubleTap;
|
||||
extern NSString *const MGLEventMapTwoFingerSingleTap;
|
||||
extern NSString *const MGLEventMapQuickZoom;
|
||||
extern NSString *const MGLEventMapPanStart;
|
||||
extern NSString *const MGLEventMapPanEnd;
|
||||
extern NSString *const MGLEventMapPinchStart;
|
||||
extern NSString *const MGLEventMapRotateStart;
|
||||
extern NSString *const MGLEventMapLocation;
|
||||
|
||||
@interface MGLMapboxEvents : NSObject
|
||||
|
||||
@property (atomic) NSInteger flushAt;
|
||||
@property (atomic) NSInteger flushAfter;
|
||||
@property (atomic) NSString *api;
|
||||
@property (atomic) NSString *token;
|
||||
@property (atomic) NSString *appName;
|
||||
@property (atomic) NSString *appVersion;
|
||||
// You must call these methods from the main thread.
|
||||
//
|
||||
+ (void) setToken:(NSString *)token;
|
||||
+ (void) setAppName:(NSString *)appName;
|
||||
+ (void) setAppVersion:(NSString *)appVersion;
|
||||
|
||||
+ (id)sharedManager;
|
||||
// You can call this method from any thread. Significant work will
|
||||
// be dispatched to a low-priority background queue and all
|
||||
// resulting calls are guaranteed threadsafe.
|
||||
//
|
||||
// Events or attributes passed could be accessed on non-main threads,
|
||||
// so you must not reference UI elements from within any arguments.
|
||||
// Copy any values needed first or create dedicated methods in this
|
||||
// class for threadsafe access to UIKit classes.
|
||||
//
|
||||
+ (void) pushEvent:(NSString *)event withAttributes:(NSDictionary *)attributeDictionary;
|
||||
|
||||
- (void) pushEvent:(NSString *)event withAttributes:(NSDictionary *)attributeDictionary;
|
||||
// You can call these methods from any thread.
|
||||
//
|
||||
+ (NSString *) checkEmailEnabled;
|
||||
+ (BOOL) checkPushEnabled;
|
||||
|
||||
- (void) flush;
|
||||
// You can call this method from any thread.
|
||||
//
|
||||
+ (void) flush;
|
||||
|
||||
@end
|
||||
@end
|
||||
|
||||
@@ -1,22 +1,9 @@
|
||||
//
|
||||
// MBLocationManager.h
|
||||
// Hermes
|
||||
//
|
||||
// Created by Brad Leege on 3/8/15.
|
||||
// Copyright (c) 2015 Mapbox. All rights reserved.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "CoreLocation/CoreLocation.h"
|
||||
|
||||
@interface MGLMetricsLocationManager : NSObject <CLLocationManagerDelegate>
|
||||
@interface MGLMetricsLocationManager : NSObject
|
||||
|
||||
+ (id)sharedManager;
|
||||
|
||||
- (BOOL) isAuthorizedStatusDetermined;
|
||||
- (void) requestAlwaysAuthorization;
|
||||
|
||||
- (void) startUpdatingLocation;
|
||||
- (void) stopUpdatingLocation;
|
||||
// This method can be called from any thread.
|
||||
//
|
||||
+ (instancetype)sharedManager;
|
||||
|
||||
@end
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#import "SMCalloutView.h"
|
||||
|
||||
#import "MGLMapboxEvents.h"
|
||||
#import "MGLMetricsLocationManager.h"
|
||||
|
||||
#import <algorithm>
|
||||
|
||||
@@ -155,7 +154,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
|
||||
if (accessToken)
|
||||
{
|
||||
mbglMap->setAccessToken((std::string)[accessToken UTF8String]);
|
||||
[[MGLMapboxEvents sharedManager] setToken:accessToken];
|
||||
[MGLMapboxEvents setToken:accessToken];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -195,16 +194,11 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
|
||||
//
|
||||
self.accessibilityLabel = @"Map";
|
||||
|
||||
// setup Metrics
|
||||
MGLMapboxEvents *events = [MGLMapboxEvents sharedManager];
|
||||
// metrics: initial setup
|
||||
NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleName"];
|
||||
NSString *appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
|
||||
if (appName != nil) {
|
||||
events.appName = appName;
|
||||
}
|
||||
if (appVersion != nil) {
|
||||
events.appVersion = appVersion;
|
||||
}
|
||||
if (appName != nil) [MGLMapboxEvents setAppName:appName];
|
||||
if (appVersion != nil) [MGLMapboxEvents setAppVersion:appVersion];
|
||||
|
||||
// create GL view
|
||||
//
|
||||
@@ -345,9 +339,6 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appDidBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
|
||||
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appWillForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
|
||||
|
||||
// setup dedicated location manager for metrics
|
||||
[MGLMetricsLocationManager sharedManager];
|
||||
|
||||
// set initial position
|
||||
//
|
||||
mbglMap->setLatLngZoom(mbgl::LatLng(0, 0), mbglMap->getMinZoom());
|
||||
@@ -360,41 +351,18 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
|
||||
// start the main loop
|
||||
mbglMap->start();
|
||||
|
||||
|
||||
// Fire map.load on a background thread
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
|
||||
NSMutableDictionary *evt = [[NSMutableDictionary alloc] init];
|
||||
[evt setValue:@(mbglMap->getLatLng().latitude) forKey:@"lat"];
|
||||
[evt setValue:@(mbglMap->getLatLng().longitude) forKey:@"lng"];
|
||||
[evt setValue:@(mbglMap->getZoom()) forKey:@"zoom"];
|
||||
|
||||
BOOL isRegisteredForRemoteNotifications;
|
||||
if ([[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]) {
|
||||
// iOS 8+
|
||||
isRegisteredForRemoteNotifications = [[UIApplication sharedApplication] isRegisteredForRemoteNotifications];
|
||||
} else {
|
||||
// iOS 7
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
|
||||
isRegisteredForRemoteNotifications = (types == UIRemoteNotificationTypeNone) ? NO : YES;
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
[evt setValue:@(isRegisteredForRemoteNotifications) forKey:@"enabled.push"];
|
||||
|
||||
NSString *email = @"Unknown";
|
||||
Class MFMailComposeViewController = NSClassFromString(@"MFMailComposeViewController");
|
||||
if (MFMailComposeViewController) {
|
||||
SEL canSendMail = NSSelectorFromString(@"canSendMail");
|
||||
BOOL sendMail = ((BOOL (*)(id, SEL))[MFMailComposeViewController methodForSelector:canSendMail])(MFMailComposeViewController, canSendMail);
|
||||
email = [NSString stringWithFormat:@"%i", sendMail];
|
||||
}
|
||||
[evt setValue:email forKey:@"enabled.email"];
|
||||
// metrics: map load event
|
||||
const mbgl::LatLng latLng = mbglMap->getLatLng();
|
||||
const double zoom = mbglMap->getZoom();
|
||||
|
||||
[MGLMapboxEvents pushEvent:MGLEventMapLoad withAttributes:@{
|
||||
@"lat": @(latLng.latitude),
|
||||
@"lng": @(latLng.longitude),
|
||||
@"zoom": @(zoom),
|
||||
@"enabled.push": @([MGLMapboxEvents checkPushEnabled]),
|
||||
@"enabled.email": [MGLMapboxEvents checkEmailEnabled]
|
||||
}];
|
||||
|
||||
[[MGLMapboxEvents sharedManager] pushEvent:@"map.load" withAttributes:evt];
|
||||
});
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@@ -579,8 +547,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
|
||||
|
||||
- (void)appDidBackground:(NSNotification *)notification
|
||||
{
|
||||
// Flush Any Events Still In Queue
|
||||
[[MGLMapboxEvents sharedManager] flush];
|
||||
[MGLMapboxEvents flush];
|
||||
|
||||
mbglMap->stop();
|
||||
|
||||
@@ -619,7 +586,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
|
||||
|
||||
- (void)handlePanGesture:(UIPanGestureRecognizer *)pan
|
||||
{
|
||||
[self trackGestureEvent:@"Pan" forRecognizer:pan];
|
||||
[self trackGestureEvent:MGLEventMapPanStart forRecognizer:pan];
|
||||
|
||||
if ( ! self.isScrollEnabled) return;
|
||||
|
||||
@@ -681,21 +648,22 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
|
||||
[self notifyMapChange:@(mbgl::MapChangeRegionDidChange)];
|
||||
}
|
||||
|
||||
// Send Map Drag End Event
|
||||
CGPoint ptInView = CGPointMake([pan locationInView:pan.view].x, [pan locationInView:pan.view].y);
|
||||
CLLocationCoordinate2D coord = [self convertPoint:ptInView toCoordinateFromView:pan.view];
|
||||
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
||||
[dict setValue:[[NSNumber alloc] initWithDouble:coord.latitude] forKey:@"lat"];
|
||||
[dict setValue:[[NSNumber alloc] initWithDouble:coord.longitude] forKey:@"lng"];
|
||||
[dict setValue:[[NSNumber alloc] initWithDouble:[self zoomLevel]] forKey:@"zoom"];
|
||||
// metrics: pan end
|
||||
CGPoint pointInView = CGPointMake([pan locationInView:pan.view].x, [pan locationInView:pan.view].y);
|
||||
CLLocationCoordinate2D panCoordinate = [self convertPoint:pointInView toCoordinateFromView:pan.view];
|
||||
double zoom = [self zoomLevel];
|
||||
|
||||
[[MGLMapboxEvents sharedManager] pushEvent:@"map.dragend" withAttributes:dict];
|
||||
[MGLMapboxEvents pushEvent:MGLEventMapPanEnd withAttributes:@{
|
||||
@"lat": @(panCoordinate.latitude),
|
||||
@"lng": @(panCoordinate.longitude),
|
||||
@"zoom": @(zoom)
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)pinch
|
||||
{
|
||||
[self trackGestureEvent:@"Pinch" forRecognizer:pinch];
|
||||
[self trackGestureEvent:MGLEventMapPinchStart forRecognizer:pinch];
|
||||
|
||||
if ( ! self.isZoomEnabled) return;
|
||||
|
||||
@@ -733,7 +701,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
|
||||
|
||||
- (void)handleRotateGesture:(UIRotationGestureRecognizer *)rotate
|
||||
{
|
||||
[self trackGestureEvent:@"Rotation" forRecognizer:rotate];
|
||||
[self trackGestureEvent:MGLEventMapRotateStart forRecognizer:rotate];
|
||||
|
||||
if ( ! self.isRotateEnabled) return;
|
||||
|
||||
@@ -775,7 +743,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
|
||||
|
||||
- (void)handleSingleTapGesture:(UITapGestureRecognizer *)singleTap
|
||||
{
|
||||
[self trackGestureEvent:@"SingleTap" forRecognizer:singleTap];
|
||||
[self trackGestureEvent:MGLEventMapSingleTap forRecognizer:singleTap];
|
||||
|
||||
CGPoint tapPoint = [singleTap locationInView:self];
|
||||
|
||||
@@ -898,7 +866,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
|
||||
|
||||
- (void)handleDoubleTapGesture:(UITapGestureRecognizer *)doubleTap
|
||||
{
|
||||
[self trackGestureEvent:@"DoubleTap" forRecognizer:doubleTap];
|
||||
[self trackGestureEvent:MGLEventMapDoubleTap forRecognizer:doubleTap];
|
||||
|
||||
if ( ! self.isZoomEnabled) return;
|
||||
|
||||
@@ -929,7 +897,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
|
||||
|
||||
- (void)handleTwoFingerTapGesture:(UITapGestureRecognizer *)twoFingerTap
|
||||
{
|
||||
[self trackGestureEvent:@"TwoFingerTap" forRecognizer:twoFingerTap];
|
||||
[self trackGestureEvent:MGLEventMapTwoFingerSingleTap forRecognizer:twoFingerTap];
|
||||
|
||||
if ( ! self.isZoomEnabled) return;
|
||||
|
||||
@@ -962,7 +930,7 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
|
||||
|
||||
- (void)handleQuickZoomGesture:(UILongPressGestureRecognizer *)quickZoom
|
||||
{
|
||||
[self trackGestureEvent:@"QuickZoom" forRecognizer:quickZoom];
|
||||
[self trackGestureEvent:MGLEventMapQuickZoom forRecognizer:quickZoom];
|
||||
|
||||
if ( ! self.isZoomEnabled) return;
|
||||
|
||||
@@ -1010,20 +978,18 @@ mbgl::DefaultFileSource *mbglFileSource = nullptr;
|
||||
return ([validSimultaneousGestures containsObject:gestureRecognizer] && [validSimultaneousGestures containsObject:otherGestureRecognizer]);
|
||||
}
|
||||
|
||||
- (void)trackGestureEvent:(NSString *)gesture forRecognizer:(UIGestureRecognizer *)recognizer
|
||||
- (void)trackGestureEvent:(NSString *)gestureID forRecognizer:(UIGestureRecognizer *)recognizer
|
||||
{
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
// Send Map Zoom Event
|
||||
CGPoint ptInView = CGPointMake([recognizer locationInView:recognizer.view].x, [recognizer locationInView:recognizer.view].y);
|
||||
CLLocationCoordinate2D coord = [self convertPoint:ptInView toCoordinateFromView:recognizer.view];
|
||||
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
|
||||
[dict setValue:[[NSNumber alloc] initWithDouble:coord.latitude] forKey:@"lat"];
|
||||
[dict setValue:[[NSNumber alloc] initWithDouble:coord.longitude] forKey:@"lng"];
|
||||
[dict setValue:[[NSNumber alloc] initWithDouble:[self zoomLevel]] forKey:@"zoom"];
|
||||
[dict setValue:gesture forKey:@"gesture"];
|
||||
|
||||
[[MGLMapboxEvents sharedManager] pushEvent:@"map.click" withAttributes:dict];
|
||||
});
|
||||
CGPoint pointInView = CGPointMake([recognizer locationInView:recognizer.view].x, [recognizer locationInView:recognizer.view].y);
|
||||
CLLocationCoordinate2D gestureCoordinate = [self convertPoint:pointInView toCoordinateFromView:recognizer.view];
|
||||
double zoom = [self zoomLevel];
|
||||
|
||||
[MGLMapboxEvents pushEvent:MGLEventMapTap withAttributes:@{
|
||||
@"lat": @(gestureCoordinate.latitude),
|
||||
@"lng": @(gestureCoordinate.longitude),
|
||||
@"zoom": @(zoom),
|
||||
@"gesture": gestureID
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark - Properties -
|
||||
|
||||
@@ -1,44 +1,89 @@
|
||||
//
|
||||
// MapboxEvents.m
|
||||
// MapboxEvents
|
||||
//
|
||||
// Dynamic detection of ASIdentifierManager from Mixpanel
|
||||
// https://github.com/mixpanel/mixpanel-iphone/blob/master/LICENSE
|
||||
//
|
||||
// Created by Brad Leege on 3/5/15.
|
||||
// Copyright (c) 2015 Mapbox. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MGLMapboxEvents.h"
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <SystemConfiguration/CaptiveNetwork.h>
|
||||
#import <CoreTelephony/CTTelephonyNetworkInfo.h>
|
||||
#import <CoreTelephony/CTCarrier.h>
|
||||
|
||||
#import "MGLMetricsLocationManager.h"
|
||||
|
||||
#include <sys/sysctl.h>
|
||||
#import <SystemConfiguration/CaptiveNetwork.h>
|
||||
|
||||
static NSString *const MGLMapboxEventsUserAgent = @"MapboxEventsiOS/1.0";
|
||||
static NSString *const MGLMapboxEventsAPIBase = @"https://api.tiles.mapbox.com";
|
||||
|
||||
NSString *const MGLEventMapLoad = @"map.load";
|
||||
NSString *const MGLEventMapTap = @"map.click";
|
||||
NSString *const MGLEventMapSingleTap = @"SingleTap";
|
||||
NSString *const MGLEventMapDoubleTap = @"DoubleTap";
|
||||
NSString *const MGLEventMapTwoFingerSingleTap = @"TwoFingerTap";
|
||||
NSString *const MGLEventMapQuickZoom = @"QuickZoom";
|
||||
NSString *const MGLEventMapPanStart = @"Pan";
|
||||
NSString *const MGLEventMapPanEnd = @"map.dragend";
|
||||
NSString *const MGLEventMapPinchStart = @"Pinch";
|
||||
NSString *const MGLEventMapRotateStart = @"Rotation";
|
||||
NSString *const MGLEventMapLocation = @"Location";
|
||||
|
||||
//
|
||||
// Threadsafety conventions:
|
||||
//
|
||||
// All variables accessed from more than one thread are
|
||||
// designated `atomic` and accessed through dot syntax. The
|
||||
// main thread uses underscore syntax during the
|
||||
// initialization of the variable.
|
||||
//
|
||||
// All variables accessed outside of initialization and
|
||||
// from within a single thread use underscore syntax.
|
||||
//
|
||||
// All captures of `self` from within asynchronous
|
||||
// dispatches will use a `weakSelf` to avoid cyclical
|
||||
// strong references.
|
||||
//
|
||||
|
||||
@interface MGLMapboxEvents()
|
||||
|
||||
@property (atomic) NSMutableArray *queue;
|
||||
@property (atomic) NSString *instance;
|
||||
@property (atomic) NSString *anonid;
|
||||
@property (atomic) NSTimer *timer;
|
||||
// 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) NSString *appName;
|
||||
@property (atomic) NSString *appVersion;
|
||||
@property (atomic) NSString *instanceID;
|
||||
@property (atomic) NSString *anonID;
|
||||
@property (atomic) NSString *userAgent;
|
||||
@property (atomic) dispatch_queue_t serialqPush;
|
||||
@property (atomic) dispatch_queue_t serialqFlush;
|
||||
@property (atomic) NSString *model;
|
||||
@property (atomic) NSString *iOSVersion;
|
||||
@property (atomic) NSString *carrier;
|
||||
@property (atomic) NSUInteger flushAt;
|
||||
@property (atomic) NSDateFormatter *rfc3339DateFormatter;
|
||||
@property (atomic) CGFloat scale;
|
||||
|
||||
// The timer is only ever accessed from the main thread.
|
||||
//
|
||||
@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.
|
||||
//
|
||||
@property (nonatomic) NSMutableArray *eventQueue;
|
||||
|
||||
// This is a custom serial queue for accessing the event queue.
|
||||
//
|
||||
@property (nonatomic) dispatch_queue_t serialQueue;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MGLMapboxEvents
|
||||
|
||||
static MGLMapboxEvents *sharedManager = nil;
|
||||
// Must be called from the main thread. Only called internally.
|
||||
//
|
||||
- (instancetype) init {
|
||||
assert([[NSThread currentThread] isMainThread]);
|
||||
|
||||
NSDateFormatter *rfc3339DateFormatter = nil;
|
||||
NSString *model;
|
||||
NSString *iOSVersion;
|
||||
NSString *carrier;
|
||||
NSNumber *scale;
|
||||
|
||||
- (id) init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
|
||||
@@ -47,6 +92,8 @@ NSNumber *scale;
|
||||
if(!settingsBundle) {
|
||||
NSLog(@"Could not find Settings.bundle");
|
||||
} else {
|
||||
// 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:[settingsBundle stringByAppendingPathComponent:@"Root.plist"]];
|
||||
NSArray *preferences = [settings objectForKey:@"PreferenceSpecifiers"];
|
||||
NSMutableDictionary *defaultsToRegister = [[NSMutableDictionary alloc] initWithCapacity:[preferences count]];
|
||||
@@ -61,16 +108,16 @@ NSNumber *scale;
|
||||
}
|
||||
NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier];
|
||||
NSString *uniqueID = [[NSProcessInfo processInfo] globallyUniqueString];
|
||||
_serialqPush = dispatch_queue_create([[NSString stringWithFormat:@"%@.%@.SERIALQPUSH", bundleID, uniqueID] UTF8String], DISPATCH_QUEUE_SERIAL);
|
||||
_serialqFlush = dispatch_queue_create([[NSString stringWithFormat:@"%@.%@.SERIALQFLUSH", bundleID, uniqueID] UTF8String], DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
_serialQueue = dispatch_queue_create([[NSString stringWithFormat:@"%@.%@.events.serial", bundleID, uniqueID] UTF8String], DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
// Configure Events Infrastructure
|
||||
_queue = [[NSMutableArray alloc] init];
|
||||
_eventQueue = [[NSMutableArray alloc] init];
|
||||
_flushAt = 20;
|
||||
_flushAfter = 10000;
|
||||
_api = @"https://api.tiles.mapbox.com";
|
||||
_flushAfter = 60;
|
||||
_token = nil;
|
||||
_instance = [[NSUUID UUID] UUIDString];
|
||||
_instanceID = [[NSUUID UUID] UUIDString];
|
||||
// Dynamic detection of ASIdentifierManager from Mixpanel
|
||||
// https://github.com/mixpanel/mixpanel-iphone/blob/master/LICENSE
|
||||
Class ASIdentifierManagerClass = NSClassFromString(@"ASIdentifierManager");
|
||||
if (ASIdentifierManagerClass) {
|
||||
SEL sharedManagerSelector = NSSelectorFromString(@"sharedManager");
|
||||
@@ -81,84 +128,130 @@ NSNumber *scale;
|
||||
if (trackingEnabled) {
|
||||
SEL advertisingIdentifierSelector = NSSelectorFromString(@"advertisingIdentifier");
|
||||
NSUUID *uuid = ((NSUUID* (*)(id, SEL))[sharedManager methodForSelector:advertisingIdentifierSelector])(sharedManager, advertisingIdentifierSelector);
|
||||
_anonid = [uuid UUIDString];
|
||||
_anonID = [uuid UUIDString];
|
||||
} else {
|
||||
_anonid = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
|
||||
_anonID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
|
||||
}
|
||||
} else {
|
||||
_anonid = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
|
||||
_anonID = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
|
||||
}
|
||||
|
||||
model = [self getSysInfoByName:"hw.machine"];
|
||||
iOSVersion = [NSString stringWithFormat:@"%@ %@", [UIDevice currentDevice].systemName, [UIDevice currentDevice].systemVersion];
|
||||
_model = [self getSysInfoByName:"hw.machine"];
|
||||
_iOSVersion = [NSString stringWithFormat:@"%@ %@", [UIDevice currentDevice].systemName, [UIDevice currentDevice].systemVersion];
|
||||
if ([UIScreen instancesRespondToSelector:@selector(nativeScale)]) {
|
||||
scale = [[NSNumber alloc] initWithFloat:[UIScreen mainScreen].nativeScale];
|
||||
_scale = [UIScreen mainScreen].nativeScale;
|
||||
} else {
|
||||
scale = [[NSNumber alloc] initWithFloat:[UIScreen mainScreen].scale];
|
||||
_scale = [UIScreen mainScreen].scale;
|
||||
}
|
||||
CTCarrier *carrierVendor = [[[CTTelephonyNetworkInfo alloc] init] subscriberCellularProvider];
|
||||
carrier = [carrierVendor carrierName];
|
||||
_carrier = [carrierVendor carrierName];
|
||||
|
||||
_userAgent = @"MapboxEventsiOS/1.0";
|
||||
_userAgent = MGLMapboxEventsUserAgent;
|
||||
|
||||
// Setup Date Format
|
||||
rfc3339DateFormatter = [[NSDateFormatter alloc] init];
|
||||
_rfc3339DateFormatter = [[NSDateFormatter alloc] init];
|
||||
NSLocale *enUSPOSIXLocale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
||||
|
||||
[rfc3339DateFormatter setLocale:enUSPOSIXLocale];
|
||||
[rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
|
||||
[rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
|
||||
[_rfc3339DateFormatter setLocale:enUSPOSIXLocale];
|
||||
[_rfc3339DateFormatter setDateFormat:@"yyyy'-'MM'-'dd'T'HH':'mm':'ss'Z'"];
|
||||
[_rfc3339DateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (id)sharedManager {
|
||||
// Can be called from any thread. Called implicitly from any
|
||||
// public class convenience methods.
|
||||
//
|
||||
+ (instancetype)sharedManager {
|
||||
static dispatch_once_t onceToken;
|
||||
static MGLMapboxEvents *_sharedManager;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedManager = [[self alloc] init];
|
||||
void (^setupBlock)() = ^{
|
||||
_sharedManager = [[self alloc] init];
|
||||
// setup dedicated location manager on first use
|
||||
[MGLMetricsLocationManager sharedManager];
|
||||
};
|
||||
if ( ! [[NSThread currentThread] isMainThread]) {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
setupBlock();
|
||||
});
|
||||
} else {
|
||||
setupBlock();
|
||||
}
|
||||
});
|
||||
return sharedManager;
|
||||
return _sharedManager;
|
||||
}
|
||||
|
||||
- (void) pushEvent:(NSString *)event withAttributes:(NSDictionary *)attributeDictionary {
|
||||
|
||||
// Opt Out Checking When Built
|
||||
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"mapbox_metrics_enabled_preference"]) {
|
||||
[_queue removeAllObjects];
|
||||
return;
|
||||
}
|
||||
// Must be called from the main thread.
|
||||
//
|
||||
+ (void) setToken:(NSString *)token {
|
||||
assert([[NSThread currentThread] isMainThread]);
|
||||
[MGLMapboxEvents sharedManager].token = token;
|
||||
}
|
||||
|
||||
// Add Metrics Disabled App Wide Check
|
||||
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"mapbox_metrics_disabled"] != nil) {
|
||||
[_queue removeAllObjects];
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(_serialqPush, ^{
|
||||
// Must be called from the main thread.
|
||||
//
|
||||
+ (void) setAppName:(NSString *)appName {
|
||||
assert([[NSThread currentThread] isMainThread]);
|
||||
[MGLMapboxEvents sharedManager].appName = appName;
|
||||
}
|
||||
|
||||
// Must be called from the main thread.
|
||||
//
|
||||
+ (void) setAppVersion:(NSString *)appVersion {
|
||||
assert([[NSThread currentThread] isMainThread]);
|
||||
[MGLMapboxEvents sharedManager].appVersion = appVersion;
|
||||
}
|
||||
|
||||
// Can be called from any thread. Can be called rapidly from
|
||||
// the UI thread, so performance is paramount.
|
||||
//
|
||||
+ (void) pushEvent:(NSString *)event withAttributes:(NSDictionary *)attributeDictionary {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
[[MGLMapboxEvents sharedManager] pushEvent:event withAttributes:attributeDictionary];
|
||||
});
|
||||
}
|
||||
|
||||
// Can be called from any thread. Called implicitly from public
|
||||
// use of +pushEvent:withAttributes:.
|
||||
//
|
||||
- (void) pushEvent:(NSString *)event withAttributes:(NSDictionary *)attributeDictionary {
|
||||
__weak MGLMapboxEvents *weakSelf = self;
|
||||
|
||||
dispatch_async(_serialQueue, ^{
|
||||
// Opt Out Checking When Built
|
||||
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"mapbox_metrics_enabled_preference"]) {
|
||||
[_eventQueue removeAllObjects];
|
||||
return;
|
||||
}
|
||||
|
||||
// Add Metrics Disabled App Wide Check
|
||||
if ([[NSUserDefaults standardUserDefaults] objectForKey:@"mapbox_metrics_disabled"] != nil) {
|
||||
[_eventQueue removeAllObjects];
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event) return;
|
||||
|
||||
NSMutableDictionary *evt = [[NSMutableDictionary alloc] init];
|
||||
// mapbox-events stock attributes
|
||||
[evt setObject:event forKey:@"event"];
|
||||
[evt setObject:[NSNumber numberWithInt:1] forKey:@"version"];
|
||||
[evt setObject:[self formatDate:[NSDate date]] forKey:@"created"];
|
||||
[evt setObject:self.instance forKey:@"instance"];
|
||||
[evt setObject:self.anonid forKey:@"anonid"];
|
||||
[evt setObject:@(1) forKey:@"version"];
|
||||
[evt setObject:[weakSelf formatDate:[NSDate date]] forKey:@"created"];
|
||||
[evt setObject:weakSelf.instanceID forKey:@"instance"];
|
||||
[evt setObject:weakSelf.anonID forKey:@"anonid"];
|
||||
|
||||
// mapbox-events-ios stock attributes
|
||||
[evt setValue:[rfc3339DateFormatter stringFromDate:[NSDate date]] forKey:@"created"];
|
||||
[evt setValue:model forKey:@"model"];
|
||||
[evt setValue:iOSVersion forKey:@"operatingSystem"];
|
||||
[evt setValue:[self getDeviceOrientation] forKey:@"orientation"];
|
||||
[evt setValue:[[NSNumber alloc] initWithFloat:(100 * [UIDevice currentDevice].batteryLevel)] forKey:@"batteryLevel"];
|
||||
[evt setValue:scale forKey:@"resolution"];
|
||||
[evt setValue:carrier forKey:@"carrier"];
|
||||
[evt setValue:[self getCurrentCellularNetworkConnectionType] forKey:@"cellularNetworkType"];
|
||||
[evt setValue:[self getWifiNetworkName] forKey:@"wifi"];
|
||||
[evt setValue:[NSNumber numberWithInt:[self getContentSizeScale]] forKey:@"accessibilityFontScale"];
|
||||
[evt setValue:[weakSelf.rfc3339DateFormatter stringFromDate:[NSDate date]] forKey:@"created"];
|
||||
[evt setValue:weakSelf.model forKey:@"model"];
|
||||
[evt setValue:weakSelf.iOSVersion forKey:@"operatingSystem"];
|
||||
[evt setValue:[weakSelf getDeviceOrientation] forKey:@"orientation"];
|
||||
[evt setValue:@(100 * [UIDevice currentDevice].batteryLevel) forKey:@"batteryLevel"];
|
||||
[evt setValue:@(weakSelf.scale) forKey:@"resolution"];
|
||||
[evt setValue:weakSelf.carrier forKey:@"carrier"];
|
||||
[evt setValue:[weakSelf getCurrentCellularNetworkConnectionType] forKey:@"cellularNetworkType"];
|
||||
[evt setValue:[weakSelf getWifiNetworkName] forKey:@"wifi"];
|
||||
[evt setValue:@([weakSelf getContentSizeScale]) forKey:@"accessibilityFontScale"];
|
||||
|
||||
for (NSString *key in [attributeDictionary allKeys]) {
|
||||
[evt setObject:[attributeDictionary valueForKey:key] forKey:key];
|
||||
@@ -168,149 +261,220 @@ NSNumber *scale;
|
||||
NSDictionary *finalEvent = [NSDictionary dictionaryWithDictionary:evt];
|
||||
|
||||
// Put On The Queue
|
||||
[self.queue addObject:finalEvent];
|
||||
[_eventQueue addObject:finalEvent];
|
||||
|
||||
// Has Flush Limit Been Reached?
|
||||
if ((int)_queue.count >= (int)_flushAt) {
|
||||
[self flush];
|
||||
if (_eventQueue.count >= weakSelf.flushAt) {
|
||||
[weakSelf flush];
|
||||
}
|
||||
|
||||
// Reset Timer (Initial Starting of Timer after first event is pushed)
|
||||
[self startTimer];
|
||||
|
||||
[weakSelf startTimer];
|
||||
});
|
||||
}
|
||||
|
||||
// Can be called from any thread.
|
||||
//
|
||||
+ (void) flush {
|
||||
[[MGLMapboxEvents sharedManager] flush];
|
||||
}
|
||||
|
||||
// Can be called from any thread.
|
||||
//
|
||||
- (void) flush {
|
||||
if (_token == nil) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch_async(_serialqFlush, ^{
|
||||
|
||||
int upper = (int)_flushAt;
|
||||
if (_flushAt > [_queue count]) {
|
||||
if ([_queue count] == 0) {
|
||||
if (self.token == nil) return;
|
||||
|
||||
__weak MGLMapboxEvents *weakSelf = self;
|
||||
|
||||
dispatch_async(_serialQueue, ^{
|
||||
__block NSArray *events;
|
||||
|
||||
NSUInteger upper = weakSelf.flushAt;
|
||||
if (weakSelf.flushAt > [_eventQueue count]) {
|
||||
if ([_eventQueue count] == 0) {
|
||||
return;
|
||||
}
|
||||
upper = (int)[_queue count];
|
||||
upper = [_eventQueue count];
|
||||
}
|
||||
|
||||
// Create Array of Events to push to the Server
|
||||
NSRange theRange = NSMakeRange(0, upper);
|
||||
NSArray *events = [_queue subarrayWithRange:theRange];
|
||||
events = [_eventQueue subarrayWithRange:theRange];
|
||||
|
||||
// Update Queue to remove events sent to server
|
||||
[_queue removeObjectsInRange:theRange];
|
||||
|
||||
[_eventQueue removeObjectsInRange:theRange];
|
||||
|
||||
// Send Array of Events to Server
|
||||
[self postEvents:events];
|
||||
[weakSelf postEvents:events];
|
||||
});
|
||||
}
|
||||
|
||||
// Can be called from any thread. Called implicitly from public
|
||||
// use of +flush. Posts an async network request to upload metrics.
|
||||
//
|
||||
- (void) postEvents:(NSArray *)events {
|
||||
// Setup URL Request
|
||||
NSString *url = [NSString stringWithFormat:@"%@/events/v1?access_token=%@", _api, _token];
|
||||
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
|
||||
[request setValue:[self getUserAgent] forHTTPHeaderField:@"User-Agent"];
|
||||
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
[request setHTTPMethod:@"POST"];
|
||||
|
||||
// Convert Array of Dictionaries to JSON
|
||||
if ([NSJSONSerialization isValidJSONObject:events]) {
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:events options:NSJSONWritingPrettyPrinted error:nil];
|
||||
[request setHTTPBody:jsonData];
|
||||
__weak MGLMapboxEvents *weakSelf = self;
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
// Setup URL Request
|
||||
NSString *url = [NSString stringWithFormat:@"%@/events/v1?access_token=%@", MGLMapboxEventsAPIBase, weakSelf.token];
|
||||
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
|
||||
[request setValue:[weakSelf getUserAgent] forHTTPHeaderField:@"User-Agent"];
|
||||
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
[request setHTTPMethod:@"POST"];
|
||||
|
||||
// Send non blocking HTTP Request to server
|
||||
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:nil];
|
||||
}
|
||||
// Convert Array of Dictionaries to JSON
|
||||
if ([NSJSONSerialization isValidJSONObject:events]) {
|
||||
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:events options:NSJSONWritingPrettyPrinted error:nil];
|
||||
[request setHTTPBody:jsonData];
|
||||
|
||||
// Send non blocking HTTP Request to server
|
||||
[NSURLConnection sendAsynchronousRequest:request
|
||||
queue:nil
|
||||
completionHandler:nil];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Can be called from any thread.
|
||||
//
|
||||
- (void) startTimer {
|
||||
// Stop Timer if it already exists
|
||||
if (_timer) {
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
void (^timerBlock)() = ^{
|
||||
// Stop Timer if it already exists
|
||||
if (_timer) {
|
||||
[_timer invalidate];
|
||||
_timer = nil;
|
||||
}
|
||||
|
||||
// Start New Timer
|
||||
_timer = [NSTimer scheduledTimerWithTimeInterval:_flushAfter
|
||||
target:[self class]
|
||||
selector:@selector(flush)
|
||||
userInfo:nil
|
||||
repeats:YES];
|
||||
};
|
||||
|
||||
if ( ! [[NSThread currentThread] isMainThread]) {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
timerBlock();
|
||||
});
|
||||
} else {
|
||||
timerBlock();
|
||||
}
|
||||
|
||||
// Start New Timer
|
||||
NSTimeInterval interval = (double)((NSInteger)_flushAfter);
|
||||
_timer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(flush) userInfo:nil repeats:YES];
|
||||
}
|
||||
|
||||
// Can be called from any thread.
|
||||
//
|
||||
- (NSString *) getUserAgent {
|
||||
|
||||
if (_appName != nil && _appVersion != nil && ([_userAgent rangeOfString:_appName].location == NSNotFound)) {
|
||||
_userAgent = [NSString stringWithFormat:@"%@/%@ %@", _appName, _appVersion, _userAgent];
|
||||
if (self.appName != nil && self.appVersion != nil && ([self.userAgent rangeOfString:self.appName].location == NSNotFound)) {
|
||||
self.userAgent = [NSString stringWithFormat:@"%@/%@ %@", self.appName, self.appVersion, self.userAgent];
|
||||
}
|
||||
return _userAgent;
|
||||
return self.userAgent;
|
||||
}
|
||||
|
||||
// Can be called from any thread.
|
||||
//
|
||||
- (NSString *) formatDate:(NSDate *)date {
|
||||
return [rfc3339DateFormatter stringFromDate:date];
|
||||
return [self.rfc3339DateFormatter stringFromDate:date];
|
||||
}
|
||||
|
||||
// Can be called from any thread.
|
||||
//
|
||||
- (NSString *) getDeviceOrientation {
|
||||
switch ([UIDevice currentDevice].orientation) {
|
||||
case UIDeviceOrientationUnknown:
|
||||
return @"Unknown";
|
||||
break;
|
||||
case UIDeviceOrientationPortrait:
|
||||
return @"Portrait";
|
||||
break;
|
||||
case UIDeviceOrientationPortraitUpsideDown:
|
||||
return @"PortraitUpsideDown";
|
||||
break;
|
||||
case UIDeviceOrientationLandscapeLeft:
|
||||
return @"LandscapeLeft";
|
||||
break;
|
||||
case UIDeviceOrientationLandscapeRight:
|
||||
return @"LandscapeRight";
|
||||
break;
|
||||
case UIDeviceOrientationFaceUp:
|
||||
return @"FaceUp";
|
||||
break;
|
||||
case UIDeviceOrientationFaceDown:
|
||||
return @"FaceDown";
|
||||
break;
|
||||
default:
|
||||
return @"Default - Unknown";
|
||||
break;
|
||||
__block NSString *result;
|
||||
|
||||
NSString *(^deviceOrientationBlock)(void) = ^{
|
||||
switch ([UIDevice currentDevice].orientation) {
|
||||
case UIDeviceOrientationUnknown:
|
||||
result = @"Unknown";
|
||||
break;
|
||||
case UIDeviceOrientationPortrait:
|
||||
result = @"Portrait";
|
||||
break;
|
||||
case UIDeviceOrientationPortraitUpsideDown:
|
||||
result = @"PortraitUpsideDown";
|
||||
break;
|
||||
case UIDeviceOrientationLandscapeLeft:
|
||||
result = @"LandscapeLeft";
|
||||
break;
|
||||
case UIDeviceOrientationLandscapeRight:
|
||||
result = @"LandscapeRight";
|
||||
break;
|
||||
case UIDeviceOrientationFaceUp:
|
||||
result = @"FaceUp";
|
||||
break;
|
||||
case UIDeviceOrientationFaceDown:
|
||||
result = @"FaceDown";
|
||||
break;
|
||||
default:
|
||||
result = @"Default - Unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
if ( ! [[NSThread currentThread] isMainThread]) {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
result = deviceOrientationBlock();
|
||||
});
|
||||
} else {
|
||||
result = deviceOrientationBlock();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (int) getContentSizeScale {
|
||||
NSString *sc = [UIApplication sharedApplication].preferredContentSizeCategory;
|
||||
|
||||
if ([sc isEqualToString:UIContentSizeCategoryExtraSmall]) {
|
||||
return -3;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategorySmall]) {
|
||||
return -2;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryMedium]) {
|
||||
return -1;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryLarge]) {
|
||||
return 0;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryExtraLarge]) {
|
||||
return 1;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
|
||||
return 2;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
|
||||
return 3;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
|
||||
return -11;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
|
||||
return 10;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
|
||||
return 11;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
|
||||
return 12;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
|
||||
return 13;
|
||||
// Can be called from any thread.
|
||||
//
|
||||
- (NSInteger) getContentSizeScale {
|
||||
__block NSInteger result = -9999;
|
||||
|
||||
NSInteger (^contentSizeScaleBlock)(void) = ^{
|
||||
NSString *sc = [UIApplication sharedApplication].preferredContentSizeCategory;
|
||||
|
||||
if ([sc isEqualToString:UIContentSizeCategoryExtraSmall]) {
|
||||
result = -3;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategorySmall]) {
|
||||
result = -2;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryMedium]) {
|
||||
result = -1;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryLarge]) {
|
||||
result = 0;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryExtraLarge]) {
|
||||
result = 1;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryExtraExtraLarge]) {
|
||||
result = 2;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryExtraExtraExtraLarge]) {
|
||||
result = 3;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryAccessibilityMedium]) {
|
||||
result = -11;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryAccessibilityLarge]) {
|
||||
result = 10;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryAccessibilityExtraLarge]) {
|
||||
result = 11;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraLarge]) {
|
||||
result = 12;
|
||||
} else if ([sc isEqualToString:UIContentSizeCategoryAccessibilityExtraExtraExtraLarge]) {
|
||||
result = 13;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
if ( ! [[NSThread currentThread] isMainThread]) {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
result = contentSizeScaleBlock();
|
||||
});
|
||||
} else {
|
||||
result = contentSizeScaleBlock();
|
||||
}
|
||||
return -9999;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Can be called from any thread.
|
||||
//
|
||||
- (NSString *)getSysInfoByName:(char *)typeSpecifier
|
||||
{
|
||||
size_t size;
|
||||
@@ -325,6 +489,8 @@ NSNumber *scale;
|
||||
return results;
|
||||
}
|
||||
|
||||
// Can be called from any thread.
|
||||
//
|
||||
- (NSString *) getWifiNetworkName {
|
||||
|
||||
NSString *ssid = @"";
|
||||
@@ -343,6 +509,8 @@ NSNumber *scale;
|
||||
return ssid;
|
||||
}
|
||||
|
||||
// Can be called from any thread.
|
||||
//
|
||||
- (NSString *) getCurrentCellularNetworkConnectionType {
|
||||
CTTelephonyNetworkInfo *telephonyInfo = [CTTelephonyNetworkInfo new];
|
||||
NSString *radioTech = telephonyInfo.currentRadioAccessTechnology;
|
||||
@@ -376,6 +544,63 @@ NSNumber *scale;
|
||||
}
|
||||
}
|
||||
|
||||
// Can be called from any thread.
|
||||
//
|
||||
+ (NSString *) checkEmailEnabled {
|
||||
__block NSString *result;
|
||||
|
||||
NSString *(^mailCheckBlock)(void) = ^{
|
||||
NSString *email = @"Unknown";
|
||||
Class MFMailComposeViewController = NSClassFromString(@"MFMailComposeViewController");
|
||||
if (MFMailComposeViewController) {
|
||||
SEL canSendMail = NSSelectorFromString(@"canSendMail");
|
||||
BOOL sendMail = ((BOOL (*)(id, SEL))[MFMailComposeViewController methodForSelector:canSendMail])
|
||||
(MFMailComposeViewController, canSendMail);
|
||||
email = [NSString stringWithFormat:@"%i", sendMail];
|
||||
}
|
||||
return email;
|
||||
};
|
||||
|
||||
@end
|
||||
if ( ! [[NSThread currentThread] isMainThread]) {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
result = mailCheckBlock();
|
||||
});
|
||||
} else {
|
||||
result = mailCheckBlock();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Can be called from any thread.
|
||||
//
|
||||
+ (BOOL) checkPushEnabled {
|
||||
__block BOOL result;
|
||||
|
||||
BOOL (^pushCheckBlock)(void) = ^{
|
||||
if ([[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]) {
|
||||
// iOS 8+
|
||||
result = [[UIApplication sharedApplication] isRegisteredForRemoteNotifications];
|
||||
} else {
|
||||
// iOS 7
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
UIRemoteNotificationType types = [[UIApplication sharedApplication] enabledRemoteNotificationTypes];
|
||||
result = (types == UIRemoteNotificationTypeNone) ? NO : YES;
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
if ( ! [[NSThread currentThread] isMainThread]) {
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
result = pushCheckBlock();
|
||||
});
|
||||
} else {
|
||||
result = pushCheckBlock();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -1,29 +1,17 @@
|
||||
//
|
||||
// MBLocationManager.m
|
||||
// Hermes
|
||||
//
|
||||
// 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
|
||||
//
|
||||
// Created by Brad Leege on 3/8/15.
|
||||
// Copyright (c) 2015 Mapbox. All rights reserved.
|
||||
//
|
||||
|
||||
#import "MGLMetricsLocationManager.h"
|
||||
#import "CoreLocation/CoreLocation.h"
|
||||
#import "MGLMapboxEvents.h"
|
||||
#import "MGLMetricsLocationManager.h"
|
||||
|
||||
@interface MGLMetricsLocationManager()
|
||||
#import <CoreLocation/CoreLocation.h>
|
||||
|
||||
@property (atomic) CLLocationManager *locationManager;
|
||||
@interface MGLMetricsLocationManager() <CLLocationManagerDelegate>
|
||||
|
||||
@property (nonatomic) CLLocationManager *locationManager;
|
||||
|
||||
@end
|
||||
|
||||
@implementation MGLMetricsLocationManager
|
||||
|
||||
static MGLMetricsLocationManager *sharedManager = nil;
|
||||
|
||||
- (id) init {
|
||||
- (instancetype) init {
|
||||
if (self = [super init]) {
|
||||
_locationManager = [[CLLocationManager alloc] init];
|
||||
_locationManager.distanceFilter = 2;
|
||||
@@ -34,43 +22,31 @@ static MGLMetricsLocationManager *sharedManager = nil;
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (id)sharedManager {
|
||||
+ (instancetype)sharedManager {
|
||||
static dispatch_once_t onceToken;
|
||||
static MGLMetricsLocationManager *sharedManager;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedManager = [[self alloc] init];
|
||||
});
|
||||
return sharedManager;
|
||||
}
|
||||
|
||||
- (BOOL) isAuthorizedStatusDetermined {
|
||||
return ([CLLocationManager authorizationStatus] != kCLAuthorizationStatusNotDetermined);
|
||||
+ (void) startUpdatingLocation {
|
||||
[[MGLMetricsLocationManager sharedManager].locationManager startUpdatingLocation];
|
||||
}
|
||||
|
||||
- (void) requestAlwaysAuthorization {
|
||||
if ([self.locationManager respondsToSelector:@selector(requestAlwaysAuthorization)]) {
|
||||
[self.locationManager requestAlwaysAuthorization];
|
||||
} else {
|
||||
// This is iOS 7 or below so Starting Location Updates will trigger authorization request
|
||||
[self startUpdatingLocation];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) startUpdatingLocation {
|
||||
[self.locationManager startUpdatingLocation];
|
||||
}
|
||||
|
||||
- (void) stopUpdatingLocation {
|
||||
[self.locationManager stopUpdatingLocation];
|
||||
+ (void) stopUpdatingLocation {
|
||||
[[MGLMetricsLocationManager sharedManager].locationManager stopUpdatingLocation];
|
||||
}
|
||||
|
||||
#pragma mark CLLocationManagerDelegate
|
||||
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations {
|
||||
// Iterate through locations to pass all data
|
||||
for (CLLocation *loc in locations) {
|
||||
NSMutableDictionary *evt = [[NSMutableDictionary alloc] init];
|
||||
[evt setValue:[[NSNumber alloc] initWithDouble:loc.coordinate.latitude] forKey:@"lat"];
|
||||
[evt setValue:[[NSNumber alloc] initWithDouble:loc.coordinate.longitude] forKey:@"lng"];
|
||||
[[MGLMapboxEvents sharedManager] pushEvent:@"location" withAttributes:evt];
|
||||
[MGLMapboxEvents pushEvent:MGLEventMapLocation withAttributes:@{
|
||||
@"lat": @(loc.coordinate.latitude),
|
||||
@"lng": @(loc.coordinate.longitude)
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,18 +61,18 @@ static MGLMetricsLocationManager *sharedManager = nil;
|
||||
break;
|
||||
case kCLAuthorizationStatusDenied:
|
||||
newStatus = @"User Explcitly Denied";
|
||||
[[MGLMetricsLocationManager sharedManager] stopUpdatingLocation];
|
||||
[MGLMetricsLocationManager stopUpdatingLocation];
|
||||
break;
|
||||
case kCLAuthorizationStatusAuthorized:
|
||||
newStatus = @"User Has Authorized / Authorized Always";
|
||||
[[MGLMetricsLocationManager sharedManager] startUpdatingLocation];
|
||||
[MGLMetricsLocationManager startUpdatingLocation];
|
||||
break;
|
||||
// case kCLAuthorizationStatusAuthorizedAlways:
|
||||
// newStatus = @"Not Determined";
|
||||
// break;
|
||||
case kCLAuthorizationStatusAuthorizedWhenInUse:
|
||||
newStatus = @"User Has Authorized When In Use Only";
|
||||
[[MGLMetricsLocationManager sharedManager] startUpdatingLocation];
|
||||
[MGLMetricsLocationManager startUpdatingLocation];
|
||||
break;
|
||||
default:
|
||||
newStatus = @"Unknown";
|
||||
|
||||
Reference in New Issue
Block a user