From d7926d4d43828e7f251407c7b1196ceefb0bc47f Mon Sep 17 00:00:00 2001 From: Prateek Srivastava Date: Fri, 1 Jul 2016 12:53:53 -0600 Subject: [PATCH] Add tvOS Support (#572) --- .travis.yml | 2 +- Analytics.podspec | 1 + .../Classes/Internal/SEGAnalyticsRequest.m | 3 + Analytics/Classes/Internal/SEGLocation.h | 3 + Analytics/Classes/Internal/SEGLocation.m | 6 ++ .../Classes/Internal/SEGSegmentIntegration.m | 86 ++++++++++++++++--- Analytics/Classes/SEGAnalytics.m | 4 + Example/Podfile.lock | 4 +- .../Local Podspecs/Analytics.podspec.json | 7 +- Example/Pods/Manifest.lock | 4 +- .../Target Support Files/Analytics/Info.plist | 2 +- 11 files changed, 103 insertions(+), 19 deletions(-) diff --git a/.travis.yml b/.travis.yml index a79535212..4bd8b952c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ language: objective-c -osx_image: xcode7 +osx_image: xcode7.3 before_install: - make bootstrap diff --git a/Analytics.podspec b/Analytics.podspec index 6e9d64b4c..eba62c778 100644 --- a/Analytics.podspec +++ b/Analytics.podspec @@ -15,6 +15,7 @@ Pod::Spec.new do |s| s.social_media_url = 'https://twitter.com/segment' s.ios.deployment_target = '7.0' + s.tvos.deployment_target = '9.0' s.source_files = 'Analytics/Classes/**/*' end diff --git a/Analytics/Classes/Internal/SEGAnalyticsRequest.m b/Analytics/Classes/Internal/SEGAnalyticsRequest.m index b6e2659a9..19b1c999a 100644 --- a/Analytics/Classes/Internal/SEGAnalyticsRequest.m +++ b/Analytics/Classes/Internal/SEGAnalyticsRequest.m @@ -31,9 +31,12 @@ - (id)initWithURLRequest:(NSURLRequest *)urlRequest - (void)start { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" self.connection = [[NSURLConnection alloc] initWithRequest:self.urlRequest delegate:self startImmediately:NO]; +#pragma clang diagnostic pop [self.connection setDelegateQueue:[[self class] networkQueue]]; [self.connection start]; } diff --git a/Analytics/Classes/Internal/SEGLocation.h b/Analytics/Classes/Internal/SEGLocation.h index fa352dde1..0ca13ad50 100644 --- a/Analytics/Classes/Internal/SEGLocation.h +++ b/Analytics/Classes/Internal/SEGLocation.h @@ -15,6 +15,9 @@ @property (nonatomic, copy, readonly) NSDictionary *addressDictionary; @property (nonatomic, assign, readonly) BOOL hasKnownLocation; + +#if TARGET_OS_IOS || (TARGET_OS_MAC && !TARGET_OS_IPHONE) - (void)startUpdatingLocation; +#endif @end diff --git a/Analytics/Classes/Internal/SEGLocation.m b/Analytics/Classes/Internal/SEGLocation.m index 0eed1eac9..dbca061cd 100644 --- a/Analytics/Classes/Internal/SEGLocation.m +++ b/Analytics/Classes/Internal/SEGLocation.m @@ -47,7 +47,9 @@ - (id)init dispatch_async(dispatch_get_main_queue(), ^{ self.locationManager = [[CLLocationManager alloc] init]; self.locationManager.delegate = self; +#if TARGET_OS_IOS [self.locationManager startUpdatingLocation]; +#endif }); } return self; @@ -60,10 +62,13 @@ - (id)init LOCATION_STRING_PROPERTY(street, thoroughfare); LOCATION_NUMBER_PROPERTY(latitude, location.coordinate.latitude); LOCATION_NUMBER_PROPERTY(longitude, location.coordinate.longitude); + +#if TARGET_OS_IOS || (TARGET_OS_MAC && !TARGET_OS_IPHONE) LOCATION_NUMBER_PROPERTY(speed, location.speed); - (void)startUpdatingLocation { + if (self.locationManager && self.currentPlacemark) { CLLocation *location = self.currentPlacemark.location; NSDate *eventDate = location.timestamp; @@ -75,6 +80,7 @@ - (void)startUpdatingLocation } } } +#endif - (BOOL)hasKnownLocation { diff --git a/Analytics/Classes/Internal/SEGSegmentIntegration.m b/Analytics/Classes/Internal/SEGSegmentIntegration.m index 67d07dafc..2d5ecfcf8 100644 --- a/Analytics/Classes/Internal/SEGSegmentIntegration.m +++ b/Analytics/Classes/Internal/SEGSegmentIntegration.m @@ -1,8 +1,6 @@ #include #import -#import -#import #import "SEGAnalytics.h" #import "SEGAnalyticsUtils.h" #import "SEGAnalyticsRequest.h" @@ -11,7 +9,11 @@ #import "SEGReachability.h" #import "SEGLocation.h" #import "NSData+GZIP.h" -#import + +#if TARGET_OS_IOS +#import +#import +#endif NSString *const SEGSegmentDidSendRequestNotification = @"SegmentDidSendRequest"; NSString *const SEGSegmentRequestDidSucceedNotification = @"SegmentRequestDidSucceed"; @@ -90,6 +92,8 @@ - (id)initWithAnalytics:(SEGAnalytics *)analytics self.serialQueue = seg_dispatch_queue_create_specific("io.segment.analytics.segmentio", DISPATCH_QUEUE_SERIAL); self.flushTaskID = UIBackgroundTaskInvalid; self.analytics = analytics; + +#if !TARGET_OS_TV // Check for previous queue/track data in NSUserDefaults and remove if present [self dispatchBackground:^{ if ([[NSUserDefaults standardUserDefaults] objectForKey:SEGQueueKey]) { @@ -99,13 +103,17 @@ - (id)initWithAnalytics:(SEGAnalytics *)analytics [[NSUserDefaults standardUserDefaults] removeObjectForKey:SEGTraitsKey]; } }]; - dispatch_sync(dispatch_get_main_queue(), ^{ - self.flushTimer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:self selector:@selector(flush) userInfo:nil repeats:YES]; - }); + + [self addSkipBackupAttributeToItemAtPath:self.userIDURL]; [self addSkipBackupAttributeToItemAtPath:self.anonymousIDURL]; [self addSkipBackupAttributeToItemAtPath:self.traitsURL]; [self addSkipBackupAttributeToItemAtPath:self.queueURL]; +#endif + + dispatch_sync(dispatch_get_main_queue(), ^{ + self.flushTimer = [NSTimer scheduledTimerWithTimeInterval:30.0 target:self selector:@selector(flush) userInfo:nil repeats:YES]; + }); } return self; } @@ -119,7 +127,9 @@ - (id)initWithAnalytics:(SEGAnalytics *)analytics * Ref: http://stackoverflow.com/questions/14238586/coretelephony-crash */ +#if TARGET_OS_IOS static CTTelephonyNetworkInfo *_telephonyNetworkInfo; +#endif - (NSDictionary *)staticContext { @@ -163,6 +173,7 @@ - (NSDictionary *)staticContext @"version" : device.systemVersion }; +#if TARGET_OS_IOS static dispatch_once_t networkInfoOnceToken; dispatch_once(&networkInfoOnceToken, ^{ _telephonyNetworkInfo = [[CTTelephonyNetworkInfo alloc] init]; @@ -171,6 +182,7 @@ - (NSDictionary *)staticContext CTCarrier *carrier = [_telephonyNetworkInfo subscriberCellularProvider]; if (carrier.carrierName.length) dict[@"network"] = @{ @"carrier" : carrier.carrierName }; +#endif CGSize screenSize = [UIScreen mainScreen].bounds.size; dict[@"screen"] = @{ @@ -229,7 +241,11 @@ - (NSDictionary *)liveContext }); self.location = !self.location ? [self.configuration shouldUseLocationServices] ? [SEGLocation new] : nil : self.location; + +#if TARGET_OS_IOS || (TARGET_OS_MAC && !TARGET_OS_IPHONE) [self.location startUpdatingLocation]; +#endif + if (self.location.hasKnownLocation) context[@"location"] = self.location.locationDictionary; @@ -283,7 +299,12 @@ - (void)saveUserId:(NSString *)userId { [self dispatchBackground:^{ self.userId = userId; + +#if TARGET_OS_TV + [[NSUserDefaults standardUserDefaults] setValue:userId forKey:SEGUserIdKey]; +#else [self.userId writeToURL:self.userIDURL atomically:YES encoding:NSUTF8StringEncoding error:NULL]; +#endif }]; } @@ -291,8 +312,11 @@ - (void)saveAnonymousId:(NSString *)anonymousId { [self dispatchBackground:^{ self.anonymousId = anonymousId; +#if TARGET_OS_TV [[NSUserDefaults standardUserDefaults] setValue:anonymousId forKey:SEGAnonymousIdKey]; +#else [self.anonymousId writeToURL:self.anonymousIDURL atomically:YES encoding:NSUTF8StringEncoding error:NULL]; +#endif }]; } @@ -300,7 +324,12 @@ - (void)addTraits:(NSDictionary *)traits { [self dispatchBackground:^{ [self.traits addEntriesFromDictionary:traits]; + +#if TARGET_OS_TV + [[NSUserDefaults standardUserDefaults] setValue:[self.traits copy] forKey:SEGTraitsKey]; +#else [[self.traits copy] writeToURL:self.traitsURL atomically:YES]; +#endif }]; } @@ -425,7 +454,7 @@ - (void)queuePayload:(NSDictionary *)payload [self.queue removeObjectAtIndex:0]; } [self.queue addObject:payload]; - [[self.queue copy] writeToURL:[self queueURL] atomically:YES]; + [self persistQueue]; [self flushQueueByLength]; } @catch (NSException *exception) { @@ -496,9 +525,16 @@ - (void)reset [self dispatchBackgroundAndWait:^{ [[NSUserDefaults standardUserDefaults] setValue:nil forKey:SEGUserIdKey]; [[NSUserDefaults standardUserDefaults] setValue:nil forKey:SEGAnonymousIdKey]; + +#if TARGET_OS_TV + [[NSUserDefaults standardUserDefaults] setValue:@[] forKey:SEGQueueKey]; + [[NSUserDefaults standardUserDefaults] setValue:@{} forKey:SEGTraitsKey]; +#else [[NSFileManager defaultManager] removeItemAtURL:self.userIDURL error:NULL]; [[NSFileManager defaultManager] removeItemAtURL:self.traitsURL error:NULL]; [[NSFileManager defaultManager] removeItemAtURL:self.queueURL error:NULL]; +#endif + self.userId = nil; self.traits = [NSMutableDictionary dictionary]; self.queue = [NSMutableArray array]; @@ -553,7 +589,7 @@ - (void)sendData:(NSData *)data } else { SEGLog(@"%@ API request success 200", self); [self.queue removeObjectsInArray:self.batch]; - [[self.queue copy] writeToURL:[self queueURL] atomically:YES]; + [self persistQueue]; [self notifyForName:SEGSegmentRequestDidSucceedNotification userInfo:self.batch]; } @@ -577,7 +613,7 @@ - (void)applicationWillTerminate { [self dispatchBackgroundAndWait:^{ if (self.queue.count) - [[self.queue copy] writeToURL:self.queueURL atomically:YES]; + [self persistQueue]; }]; } @@ -586,16 +622,26 @@ - (void)applicationWillTerminate - (NSMutableArray *)queue { if (!_queue) { +#if TARGET_OS_TV + _queue = [[[NSUserDefaults standardUserDefaults] objectForKey:SEGQueueKey] mutableCopy] ?: [[NSMutableArray alloc] init]; +#else _queue = [NSMutableArray arrayWithContentsOfURL:self.queueURL] ?: [[NSMutableArray alloc] init]; +#endif } + return _queue; } - (NSMutableDictionary *)traits { if (!_traits) { +#if TARGET_OS_TV + _traits = [[[NSUserDefaults standardUserDefaults] objectForKey:SEGTraitsKey] mutableCopy] ?: [[NSMutableDictionary alloc] init]; +#else _traits = [NSMutableDictionary dictionaryWithContentsOfURL:self.traitsURL] ?: [[NSMutableDictionary alloc] init]; +#endif } + return _traits; } @@ -629,14 +675,25 @@ - (NSString *)getAnonymousId:(BOOL)reset // We've chosen to generate a UUID rather than use the UDID (deprecated in iOS 5), // identifierForVendor (iOS6 and later, can't be changed on logout), // or MAC address (blocked in iOS 7). For more info see https://segment.io/libraries/ios#ids + +#if TARGET_OS_TV + NSString *anonymousId = [[NSUserDefaults standardUserDefaults] valueForKey:SEGAnonymousIdKey]; +#else NSURL *url = self.anonymousIDURL; - NSString *anonymousId = [[NSUserDefaults standardUserDefaults] valueForKey:SEGAnonymousIdKey] ?: [[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:NULL]; + NSString *anonymousId = [[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:NULL]; +#endif + if (!anonymousId || reset) { anonymousId = GenerateUUIDString(); SEGLog(@"New anonymousId: %@", anonymousId); + +#if TARGET_OS_TV [[NSUserDefaults standardUserDefaults] setObject:anonymousId forKey:SEGAnonymousIdKey]; +#else [anonymousId writeToURL:url atomically:YES encoding:NSUTF8StringEncoding error:NULL]; +#endif } + return anonymousId; } @@ -645,4 +702,13 @@ - (NSString *)getUserId return [[NSUserDefaults standardUserDefaults] valueForKey:SEGUserIdKey] ?: [[NSString alloc] initWithContentsOfURL:self.userIDURL encoding:NSUTF8StringEncoding error:NULL]; } +- (void)persistQueue +{ +#if TARGET_OS_TV + [[NSUserDefaults standardUserDefaults] setValue:[self.queue copy] forKey:SEGQueueKey]; +#else + [[self.queue copy] writeToURL:[self queueURL] atomically:YES]; +#endif +} + @end diff --git a/Analytics/Classes/SEGAnalytics.m b/Analytics/Classes/SEGAnalytics.m index 08965b0b2..836306d1e 100644 --- a/Analytics/Classes/SEGAnalytics.m +++ b/Analytics/Classes/SEGAnalytics.m @@ -129,13 +129,17 @@ - (instancetype)initWithConfiguration:(SEGAnalyticsConfiguration *)configuration if (configuration.trackInAppPurchases) { _storeKitTracker = [SEGStoreKitTracker trackTransactionsForAnalytics:self]; } + [self trackApplicationLifecycleEvents:configuration.trackApplicationLifecycleEvents]; + +#if !TARGET_OS_TV if (configuration.trackPushNotifications && configuration.launchOptions) { NSDictionary *remoteNotification = configuration.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; if (remoteNotification) { [self trackPushNotification:remoteNotification fromLaunch:YES]; } } +#endif } return self; } diff --git a/Example/Podfile.lock b/Example/Podfile.lock index c27283666..7fa820eee 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,5 +1,5 @@ PODS: - - Analytics (3.2.4) + - Analytics (3.2.5) - Expecta (1.0.5) - Specta (1.0.5) @@ -13,7 +13,7 @@ EXTERNAL SOURCES: :path: ../ SPEC CHECKSUMS: - Analytics: 7db6a16e81dc98c500337846f8351de7e9067699 + Analytics: 9ff192fcc0ca6d2999822b8883b05c57362bbd8f Expecta: e1c022fcd33910b6be89c291d2775b3fe27a89fe Specta: ac94d110b865115fe60ff2c6d7281053c6f8e8a2 diff --git a/Example/Pods/Local Podspecs/Analytics.podspec.json b/Example/Pods/Local Podspecs/Analytics.podspec.json index ef49074b7..82b7f98ac 100644 --- a/Example/Pods/Local Podspecs/Analytics.podspec.json +++ b/Example/Pods/Local Podspecs/Analytics.podspec.json @@ -1,6 +1,6 @@ { "name": "Analytics", - "version": "3.2.4", + "version": "3.2.5", "summary": "The hassle-free way to add analytics to your iOS app.", "description": "Analytics for iOS provides a single API that lets you\nintegrate with over 100s of tools.", "homepage": "http://segment.com/", @@ -12,11 +12,12 @@ }, "source": { "git": "https://github.com/segmentio/analytics-ios.git", - "tag": "3.2.4" + "tag": "3.2.5" }, "social_media_url": "https://twitter.com/segment", "platforms": { - "ios": "7.0" + "ios": "7.0", + "tvos": "9.0" }, "source_files": "Analytics/Classes/**/*" } diff --git a/Example/Pods/Manifest.lock b/Example/Pods/Manifest.lock index c27283666..7fa820eee 100644 --- a/Example/Pods/Manifest.lock +++ b/Example/Pods/Manifest.lock @@ -1,5 +1,5 @@ PODS: - - Analytics (3.2.4) + - Analytics (3.2.5) - Expecta (1.0.5) - Specta (1.0.5) @@ -13,7 +13,7 @@ EXTERNAL SOURCES: :path: ../ SPEC CHECKSUMS: - Analytics: 7db6a16e81dc98c500337846f8351de7e9067699 + Analytics: 9ff192fcc0ca6d2999822b8883b05c57362bbd8f Expecta: e1c022fcd33910b6be89c291d2775b3fe27a89fe Specta: ac94d110b865115fe60ff2c6d7281053c6f8e8a2 diff --git a/Example/Pods/Target Support Files/Analytics/Info.plist b/Example/Pods/Target Support Files/Analytics/Info.plist index b6e86dd8b..8f12151cf 100644 --- a/Example/Pods/Target Support Files/Analytics/Info.plist +++ b/Example/Pods/Target Support Files/Analytics/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 3.2.4 + 3.2.5 CFBundleSignature ???? CFBundleVersion