diff --git a/AdjustIO.podspec b/AdjustIO.podspec index 957351d91..92b755434 100644 --- a/AdjustIO.podspec +++ b/AdjustIO.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |s| s.name = "AdjustIO" - s.version = "2.1.4" + s.version = "2.2.0" s.summary = "This is the iOS SDK of AdjustIo. You can read more about it at http://adjust.io." s.homepage = "http://adjust.io" s.license = { :type => 'MIT', :file => 'MIT-LICENSE' } s.author = { "Christian Wellenbrock" => "welle@adeven.com" } - s.source = { :git => "https://github.com/adeven/adjust_ios_sdk.git", :tag => "v2.1.4" } + s.source = { :git => "https://github.com/adeven/adjust_ios_sdk.git", :tag => "v2.2.0" } s.platform = :ios, '4.3' s.framework = 'AdSupport', 'SystemConfiguration' s.source_files = 'AdjustIo/*.{h,m}', 'AdjustIo/AIAdditions/*.{h,m}' diff --git a/AdjustIo/AIActivityHandler.m b/AdjustIo/AIActivityHandler.m index 0701a2726..f36a4451e 100644 --- a/AdjustIo/AIActivityHandler.m +++ b/AdjustIo/AIActivityHandler.m @@ -283,11 +283,11 @@ - (BOOL)updateActivityState { - (void)readActivityState { @try { - NSString *filename = [self activityStateFilename]; + NSString *filename = self.activityStateFilename; id object = [NSKeyedUnarchiver unarchiveObjectWithFile:filename]; if ([object isKindOfClass:[AIActivityState class]]) { self.activityState = object; - [AILogger debug:@"Read activity state: %@", self.activityState]; + [AILogger debug:@"Read activity state: %@ uuid:%@", self.activityState, self.activityState.uuid]; return; } else if (object == nil) { [AILogger verbose:@"Activity state file not found"]; @@ -303,7 +303,7 @@ - (void)readActivityState { } - (void)writeActivityState { - NSString *filename = [self activityStateFilename]; + NSString *filename = self.activityStateFilename; BOOL result = [NSKeyedArchiver archiveRootObject:self.activityState toFile:filename]; if (result == YES) { [AIUtil excludeFromBackup:filename]; diff --git a/AdjustIo/AIActivityState.h b/AdjustIo/AIActivityState.h index a878e52e6..717a471e3 100644 --- a/AdjustIo/AIActivityState.h +++ b/AdjustIo/AIActivityState.h @@ -10,6 +10,9 @@ @interface AIActivityState : NSObject +// persistent data +@property (nonatomic, copy) NSString *uuid; + // global counters @property (nonatomic, assign) int eventCount; @property (nonatomic, assign) int sessionCount; @@ -19,8 +22,9 @@ @property (nonatomic, assign) double sessionLength; // all durations in seconds @property (nonatomic, assign) double timeSpent; @property (nonatomic, assign) double lastActivity; // all times in seconds since 1970 - @property (nonatomic, assign) double createdAt; + +// not persisted, only injected @property (nonatomic, assign) double lastInterval; - (void)resetSessionAttributes:(double)now; diff --git a/AdjustIo/AIActivityState.m b/AdjustIo/AIActivityState.m index 528f7a149..29006dd92 100644 --- a/AdjustIo/AIActivityState.m +++ b/AdjustIo/AIActivityState.m @@ -8,6 +8,7 @@ #import "AIActivityState.h" #import "AIPackageBuilder.h" +#import "UIDevice+AIAdditions.h" #pragma mark public implementation @@ -17,6 +18,9 @@ - (id)init { self = [super init]; if (self == nil) return nil; + // create UUID for new devices + self.uuid = [UIDevice.currentDevice aiCreateUuid]; + self.eventCount = 0; self.sessionCount = 0; self.subsessionCount = -1; // -1 means unknown @@ -67,6 +71,12 @@ - (id)initWithCoder:(NSCoder *)decoder { self.timeSpent = [decoder decodeDoubleForKey:@"timeSpent"]; self.createdAt = [decoder decodeDoubleForKey:@"createdAt"]; self.lastActivity = [decoder decodeDoubleForKey:@"lastActivity"]; + self.uuid = [decoder decodeObjectForKey:@"uuid"]; + + // create UUID for migrating devices + if (self.uuid == nil) { + self.uuid = [UIDevice.currentDevice aiCreateUuid]; + } self.lastInterval = -1; @@ -81,6 +91,7 @@ - (void)encodeWithCoder:(NSCoder *)encoder { [encoder encodeDouble:self.timeSpent forKey:@"timeSpent"]; [encoder encodeDouble:self.createdAt forKey:@"createdAt"]; [encoder encodeDouble:self.lastActivity forKey:@"lastActivity"]; + [encoder encodeObject:self.uuid forKey:@"uuid"]; } @@ -92,6 +103,7 @@ - (void)injectGeneralAttributes:(AIPackageBuilder *)builder { builder.sessionLength = self.sessionLength; builder.timeSpent = self.timeSpent; builder.createdAt = self.createdAt; + builder.uuid = self.uuid; } @end diff --git a/AdjustIo/AIAdditions/NSString+AIAdditions.h b/AdjustIo/AIAdditions/NSString+AIAdditions.h index af09d2c7a..846af17bd 100644 --- a/AdjustIo/AIAdditions/NSString+AIAdditions.h +++ b/AdjustIo/AIAdditions/NSString+AIAdditions.h @@ -16,4 +16,6 @@ - (NSString *)aiUrlEncode; - (NSString *)aiRemoveColons; ++ (NSString *)aiJoin:(NSString *)strings, ...; + @end diff --git a/AdjustIo/AIAdditions/NSString+AIAdditions.m b/AdjustIo/AIAdditions/NSString+AIAdditions.m index 9318ae9db..43442d044 100644 --- a/AdjustIo/AIAdditions/NSString+AIAdditions.m +++ b/AdjustIo/AIAdditions/NSString+AIAdditions.m @@ -54,4 +54,18 @@ - (NSString *)aiRemoveColons { return [self stringByReplacingOccurrencesOfString:@":" withString:@""]; } ++ (NSString *)aiJoin:(NSString *)first, ... { + NSString *iter, *result = first; + va_list strings; + va_start(strings, first); + + while ((iter = va_arg(strings, NSString*))) { + NSString *capitalized = iter.capitalizedString; + result = [result stringByAppendingString:capitalized]; + } + + va_end(strings); + return result; +} + @end diff --git a/AdjustIo/AIAdditions/UIDevice+AIAdditions.h b/AdjustIo/AIAdditions/UIDevice+AIAdditions.h index f928cb894..62707848a 100644 --- a/AdjustIo/AIAdditions/UIDevice+AIAdditions.h +++ b/AdjustIo/AIAdditions/UIDevice+AIAdditions.h @@ -16,5 +16,6 @@ - (NSString *)aiMacAddress; - (NSString *)aiDeviceType; - (NSString *)aiDeviceName; +- (NSString *)aiCreateUuid; @end diff --git a/AdjustIo/AIAdditions/UIDevice+AIAdditions.m b/AdjustIo/AIAdditions/UIDevice+AIAdditions.m index f1770f371..82d7a384b 100644 --- a/AdjustIo/AIAdditions/UIDevice+AIAdditions.m +++ b/AdjustIo/AIAdditions/UIDevice+AIAdditions.m @@ -9,26 +9,74 @@ #import "UIDevice+AIAdditions.h" #import "NSString+AIAdditions.h" -#import #import #import #import #import +#if !ADJUST_NO_IDFA +#import +#endif + @implementation UIDevice(AIAdditions) - (BOOL)aiTrackingEnabled { - if (NSClassFromString(@"ASIdentifierManager")) { - return ASIdentifierManager.sharedManager.advertisingTrackingEnabled; - } else { +#if !ADJUST_NO_IDFA + NSString *className = [NSString aiJoin:@"A", @"S", @"identifier", @"manager", nil]; + NSString *keyManager = [NSString aiJoin:@"shared", @"manager", nil]; + NSString *keyEnabled = [NSString aiJoin:@"is", @"advertising", @"tracking", @"enabled", nil]; + + Class class = NSClassFromString(className); + if (class) { + @try { + SEL selManager = NSSelectorFromString(keyManager); + SEL selEnabled = NSSelectorFromString(keyEnabled); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + id manager = [class performSelector:selManager]; + BOOL enabled = (BOOL)[manager performSelector:selEnabled]; +#pragma clang diagnostic pop + + return enabled; + } @catch (NSException *e) { + return NO; + } + } else +#endif + { return NO; } } - (NSString *)aiIdForAdvertisers { - if (NSClassFromString(@"ASIdentifierManager")) { - return ASIdentifierManager.sharedManager.advertisingIdentifier.UUIDString; - } else { +#if !ADJUST_NO_IDFA + NSString *className = [NSString aiJoin:@"A", @"S", @"identifier", @"manager", nil]; + NSString *keyManager = [NSString aiJoin:@"shared", @"manager", nil]; + NSString *keyIdentifier = [NSString aiJoin:@"advertising", @"identifier", nil]; + NSString *keyString = [NSString aiJoin:@"UUID", @"string", nil]; + + Class class = NSClassFromString(className); + if (class) { + @try { + SEL selManager = NSSelectorFromString(keyManager); + SEL selIdentifier = NSSelectorFromString(keyIdentifier); + SEL selString = NSSelectorFromString(keyString); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + id manager = [class performSelector:selManager]; + id identifier = [manager performSelector:selIdentifier]; + NSString *string = [identifier performSelector:selString]; +#pragma clang diagnostic pop + + return string; + } @catch (NSException *e) { + return @""; + } + } else +#endif + { return @""; } } @@ -101,4 +149,13 @@ - (NSString *)aiDeviceName { return machine; } +- (NSString *)aiCreateUuid { + CFUUIDRef newUniqueId = CFUUIDCreate(kCFAllocatorDefault); + CFStringRef stringRef = CFUUIDCreateString(kCFAllocatorDefault, newUniqueId); + NSString *uuidString = (__bridge_transfer NSString*)stringRef; + NSString *lowerUuid = [uuidString lowercaseString]; + CFRelease(newUniqueId); + return lowerUuid; +} + @end diff --git a/AdjustIo/AIPackageBuilder.h b/AdjustIo/AIPackageBuilder.h index 2aaeead3c..5af618f57 100644 --- a/AdjustIo/AIPackageBuilder.h +++ b/AdjustIo/AIPackageBuilder.h @@ -19,6 +19,7 @@ @property (nonatomic, copy) NSString *environment; @property (nonatomic, copy) NSString *userAgent; @property (nonatomic, copy) NSString *clientSdk; +@property (nonatomic, copy) NSString *uuid; @property (nonatomic, assign) BOOL trackingEnabled; // sessions diff --git a/AdjustIo/AIPackageBuilder.m b/AdjustIo/AIPackageBuilder.m index a8c5d44b6..e2ec18943 100644 --- a/AdjustIo/AIPackageBuilder.m +++ b/AdjustIo/AIPackageBuilder.m @@ -72,6 +72,7 @@ - (NSMutableDictionary *)defaultParameters { [self parameters:parameters setString:self.appToken forKey:@"app_token"]; [self parameters:parameters setString:self.macSha1 forKey:@"mac_sha1"]; [self parameters:parameters setString:self.macShortMd5 forKey:@"mac_md5"]; + [self parameters:parameters setString:self.uuid forKey:@"ios_uuid"]; [self parameters:parameters setString:self.idForAdvertisers forKey:@"idfa"]; [self parameters:parameters setString:self.fbAttributionId forKey:@"fb_id"]; [self parameters:parameters setString:self.environment forKey:@"environment"]; diff --git a/AdjustIo/AIPackageHandler.m b/AdjustIo/AIPackageHandler.m index eddb05c88..23539898a 100644 --- a/AdjustIo/AIPackageHandler.m +++ b/AdjustIo/AIPackageHandler.m @@ -123,7 +123,7 @@ - (void)sendNextInternal { #pragma mark - private - (void)readPackageQueue { @try { - NSString *filename = [self packageQueueFilename]; + NSString *filename = self.packageQueueFilename; id object = [NSKeyedUnarchiver unarchiveObjectWithFile:filename]; if ([object isKindOfClass:[NSArray class]]) { self.packageQueue = object; @@ -143,7 +143,7 @@ - (void)readPackageQueue { } - (void)writePackageQueue { - NSString *filename = [self packageQueueFilename]; + NSString *filename = self.packageQueueFilename; BOOL result = [NSKeyedArchiver archiveRootObject:self.packageQueue toFile:filename]; if (result == YES) { [AIUtil excludeFromBackup:filename]; diff --git a/AdjustIo/AIUtil.m b/AdjustIo/AIUtil.m index 34e8451e9..95817d46e 100644 --- a/AdjustIo/AIUtil.m +++ b/AdjustIo/AIUtil.m @@ -13,7 +13,7 @@ #include static NSString * const kBaseUrl = @"https://app.adjust.io"; -static NSString * const kClientSdk = @"ios2.1.4"; +static NSString * const kClientSdk = @"ios2.2.0"; #pragma mark - diff --git a/README.md b/README.md index 7f2ded24c..c39fcc43b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ If you're using [CocoaPods][cocoapods], you can add the following line to your `Podfile` and continue with [step 3](#step3): ```ruby -pod 'AdjustIO', :git => 'git://github.com/adeven/adjust_ios_sdk.git', :tag => 'v2.1.4' +pod 'AdjustIO', :git => 'git://github.com/adeven/adjust_ios_sdk.git', :tag => 'v2.2.0' ``` ### 1. Get the SDK diff --git a/VERSION b/VERSION index 7d2ed7c70..ccbccc3dc 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.1.4 +2.2.0 diff --git a/doc/idfa.md b/doc/idfa.md new file mode 100644 index 000000000..612639465 --- /dev/null +++ b/doc/idfa.md @@ -0,0 +1,44 @@ +## Remove IDFA support + +If your app got rejected by Apple because your app is using the advertising +identifier, this document is for you. + +1. Contact us at [support@adjust.io](mailto:support@adjust.io). We would like + to make sure that you are aware of the consequences of removing IDFA + support. + +2. After you talked with us, or when you just can't wait any longer, proceed + with the following steps to remove the IDFA support. + +### Remove the AdSupport framework + +- In the Project Navigator select your project. Make sure your target is + selected in the top left corner of the right hand window. + +- Select the `Build Phases` tab and expand the group `Link Binary with + Libraries`. + +- Select the `AdSupport.framework` and press the `-` button to remove it. + +- In the Project Navigator, expand the group `Frameworks`. + +- If the `AdSupport.framework` is in there, right click it, select `Delete` and + confirm `Remove Reference`. + +### Add the compiler flag `ADJUST_NO_IDFA` + +- In the Project Navigator select your project. Make sure your target is + selected in the top left corner of the right hand window. + +- Select the `Build Settings` tab and search for `Other C Flags` in the search + field below. + +- Double click on the right side of the `Other C Flags` line to change its + value + +- Press on the `+` button at the bottom of the overlay and paste the following + text into the selected line: + + ``` + -DADJUST_NO_IDFA + ``` diff --git a/doc/migration.md b/doc/migration.md index 8d908635f..3d0be2d39 100644 --- a/doc/migration.md +++ b/doc/migration.md @@ -1,7 +1,7 @@ -## Migrate your AdjustIo SDK for iOS from v1.x to v2.1.4 +## Migrate your AdjustIo SDK for iOS from v1.x to v2.2.0 1. Delete the old `AdjustIo` source folder from your Xcode project. Download - version v2.1.4 and drag the new folder into your Xcode project. + version v2.2.0 and drag the new folder into your Xcode project. ![][drag] @@ -55,7 +55,7 @@ 2. The `appDidLaunch` method now expects your App Token instead of your App ID. You can find your App Token in your [dashboard]. -2. The AdjustIo SDK for iOS 2.1.4 uses [ARC][arc]. If you haven't done already, +2. The AdjustIo SDK for iOS 2.2.0 uses [ARC][arc]. If you haven't done already, we recommend [transitioning your project to use ARC][transition] as well. If you don't want to use ARC, you have to enable ARC for all files of the AdjustIo SDK. Please consult the [README] for details.