Search Unity

Cannot get Apple Search Attribution to work as a plugin

Discussion in 'iOS and tvOS' started by resilio, Dec 2, 2019.

  1. resilio

    resilio

    Joined:
    Sep 7, 2017
    Posts:
    12
    Hello there!

    I have been trying to implement the Apple Search Attribution API as a plugin in Unity.
    I researched and found several projects which already have implemented such a feature, so I was quite surprised when it didn't work on my side.

    Here is my `AppleSearchAd.mm` posting it in its entirety to perhaps help someone else in the future:

    Code (CSharp):
    1. #import "AppleSearchAd.h"
    2. #include <cstring>
    3.  
    4. @interface AppleSearchAd()
    5. + (void)requestSearchAdAttributionDetailsWithTimeBetweenRetries:(NSTimeInterval)timeBetweenRetries numberOfRetries:(NSInteger)numberOfRetries successBlock:(NSString*(^)(NSDictionary *))successBlock failureBlock:(NSString*(^)(void))failureBlock;
    6. @end
    7.  
    8. typedef NSString*(^AppleSearchAdTimerHandlerSuccessBlock)(NSDictionary *);
    9. typedef NSString*(^AppleSearchAdTimerHandlerFailureBlock)(void);
    10.  
    11. @interface AppleSearchAdTimerHandler : NSObject
    12.  
    13. @property (nonatomic,assign) NSTimeInterval timeBetweenRetries;
    14. @property (nonatomic,assign) NSInteger numberOfRetries;
    15. @property (nonatomic,strong) AppleSearchAdTimerHandlerSuccessBlock successBlock;
    16. @property (nonatomic,strong) AppleSearchAdTimerHandlerFailureBlock failureBlock;
    17.  
    18. @end
    19.  
    20. @implementation AppleSearchAdTimerHandler
    21.  
    22. -(void)retryTimer:(NSTimer *)timer{
    23.     NSLog(@"Retrying to request for Search Ad attribution details.");
    24.     [AppleSearchAd requestSearchAdAttributionDetailsWithTimeBetweenRetries:self.timeBetweenRetries numberOfRetries:self.numberOfRetries successBlock:self.successBlock failureBlock:self.failureBlock];
    25.     self.successBlock = nil;
    26.     self.failureBlock = nil;//so that obj does not retain success and failure blocks
    27. }
    28.  
    29. @end
    30.  
    31. @implementation AppleSearchAd
    32.  
    33. NSString *const HasReceivedSearchAdAttributionKey = @"HasReceivedAppleSearchAdAttibution";
    34.  
    35. + (NSString*) retrieveAppleSearchAd {
    36.     NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    37.     // If we haven't already registered HasReceivedSearchAdAttributionKey bool value to NSUserDefaults.
    38.     if ([userDefaults objectForKey:HasReceivedSearchAdAttributionKey] == nil) {
    39.         NSLog(@"AppleSearchAd-> retrieveAppleSearchAd");
    40.        
    41.         [AppleSearchAd requestSearchAdAttributionDetailsWithTimeBetweenRetries:5 numberOfRetries:3 successBlock:^(NSDictionary *attributionDetails) {
    42.             NSLog(@"Successfully got attribution dictionary for search ads.");
    43.             NSDictionary *actualData = attributionDetails[@"Version3.1"];
    44.             int line = __LINE__;
    45.             NSMutableDictionary *d = nil;
    46.             NSError * err;
    47.  
    48.             if(actualData) {
    49.                 d = [NSMutableDictionary dictionaryWithDictionary:actualData];
    50.                 d[@"Attribution Dictionary Version"] = @"3.1";
    51.             }
    52.             else {
    53.                 NSLog(@"*****************************************");
    54.                 NSLog(@"IMPORTANT:");
    55.                 NSLog(@"APPLE CHANGED THE VERSION INFO DICTIONARY FOR SEARCH ADS");
    56.                 NSLog(@"PLEASE TAKE A LOOK AT THIS CODE AND UPDATE IT");
    57.                 NSLog(@"file:%s line:%d",__FILE__,line);
    58.                 NSLog(@"*****************************************");
    59.                
    60.                 if(attributionDetails.count) {
    61.                     NSString *firstKey = [[attributionDetails allKeys]firstObject];
    62.                     if([actualData = [attributionDetails objectForKey:firstKey] isKindOfClass:[NSDictionary     class]]){
    63.                         d = [NSMutableDictionary dictionaryWithDictionary:actualData];
    64.                         d[@"Attribution Dictionary Version"] = firstKey;
    65.                         d[@"iad-message"] = @"iad Success";
    66.                     }
    67.                     else {
    68.                         d = [NSMutableDictionary dictionary];
    69.                         d[@"Attribution Dictionary Version"] = @"Unknown";
    70.                         d[@"iad-message"] = @"Incorrect iad Version info {3.1} - Update it!";
    71.                         for(NSString *key in attributionDetails) {
    72.                             [d setObject:[attributionDetails[key]description] forKey:key];
    73.                         }
    74.                     }
    75.                 }
    76.             }
    77.             d[@"App Id"] = [[NSBundle mainBundle]bundleIdentifier];
    78.            
    79.             // Register HasReceivedSearchAdAttributionKey bool value so that we don't make more than one Search Ad attribution request per lifetime of app install.
    80.             [userDefaults setValue:[NSNumber numberWithBool:YES] forKey:HasReceivedSearchAdAttributionKey];
    81.        
    82.             NSData *jsonData = [NSJSONSerialization dataWithJSONObject:d options:0 error:&err];
    83.             return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    84.         } failureBlock:^{
    85.             NSMutableDictionary *d = nil;
    86.             NSError * err;
    87.             d = [NSMutableDictionary dictionary];
    88.             d[@"Attribution Dictionary Version"] = @"Unknown";
    89.             d[@"App Id"] = [[NSBundle mainBundle]bundleIdentifier];
    90.             d[@"iad-message"] = @"Failed to get attribution dictionary for search ads [Maybe asked too many times?]";
    91.             NSData *jsonData = [NSJSONSerialization dataWithJSONObject:d options:0 error:&err];
    92.  
    93.             NSLog(@"Failed to get attribution dictionary for search ads [Maybe asked too many times?]");
    94.             return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    95.         }];
    96.         NSMutableDictionary *d = nil;
    97.         NSError * err;
    98.         d = [NSMutableDictionary dictionary];
    99.         d[@"Attribution Dictionary Version"] = @"Unknown";
    100.         d[@"App Id"] = [[NSBundle mainBundle]bundleIdentifier];
    101.         d[@"iad-message"] = @"ADClient not available - Search ads logic skipped";
    102.         NSData *jsonData = [NSJSONSerialization dataWithJSONObject:d options:0 error:&err];
    103.  
    104.         NSLog(@"ADClient not available - Search ads logic skipped");
    105.         return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    106.     } else {
    107.         NSMutableDictionary *d = nil;
    108.         NSError * err;
    109.         d = [NSMutableDictionary dictionary];
    110.         d[@"Attribution Dictionary Version"] = @"Unknown";
    111.         d[@"App Id"] = [[NSBundle mainBundle]bundleIdentifier];
    112.         d[@"iad-message"] = @"Search ads already requested";
    113.         NSData *jsonData = [NSJSONSerialization dataWithJSONObject:d options:0 error:&err];
    114.  
    115.         NSLog(@"Search ads already requested");
    116.         return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    117.     }
    118. }
    119.  
    120. + (void)requestSearchAdAttributionDetailsWithTimeBetweenRetries:(NSTimeInterval)timeBetweenRetries numberOfRetries:(NSInteger)numberOfRetries successBlock:(NSString*(^)(NSDictionary *))successBlock failureBlock:(NSString*(^)(void))failureBlock {
    121.     NSLog(@"AppleSearchAd-> requestSearchAdAttributionDetailsWithTimeBetweenRetries");
    122.      Class adClient = NSClassFromString(@"ADClient");
    123.      if(adClient != nil) {
    124.         if ([[ADClient sharedClient] respondsToSelector:@selector(requestAttributionDetailsWithBlock:)]) {
    125.             [[ADClient sharedClient] requestAttributionDetailsWithBlock:^(NSDictionary *attributionDetails, NSError *error) {
    126.                 if (error) {
    127.                     switch(error.code) {
    128.                         case ADClientErrorUnknown:
    129.                         {
    130.                             dispatch_async(dispatch_get_main_queue(), ^{
    131.                                 if(numberOfRetries > 0) {
    132.                                     AppleSearchAdTimerHandler *obj = [[AppleSearchAdTimerHandler alloc]init];
    133.                                     obj.numberOfRetries = numberOfRetries - 1;
    134.                                     obj.timeBetweenRetries = timeBetweenRetries;
    135.                                     obj.failureBlock = failureBlock;
    136.                                     obj.successBlock = successBlock;
    137.                                     [NSTimer scheduledTimerWithTimeInterval:timeBetweenRetries target:obj selector:@selector(retryTimer:) userInfo:nil repeats:NO];
    138.                                     obj = nil;//so that the block does not retain obj
    139.                                 }
    140.                                 else {
    141.                                     dispatch_async(dispatch_get_main_queue(), ^{
    142.                                         failureBlock();
    143.                                     });
    144.                                 }
    145.                             });
    146.                         }
    147.                             break;
    148.                         case ADClientErrorLimitAdTracking:
    149.                             dispatch_async(dispatch_get_main_queue(), ^{
    150.                                 failureBlock();
    151.                             });
    152.                             break;
    153.                     }
    154.                 }
    155.                 else {
    156.                     dispatch_async(dispatch_get_main_queue(), ^{
    157.                         successBlock(attributionDetails);
    158.                     });
    159.                 }
    160.             }];
    161.          }
    162.          else {
    163.              NSLog(@"iOS 10 ADClient call does not exist");
    164.          }
    165.      }
    166.      else {
    167.          NSLog(@"ADClient Not Available");
    168.      }
    169. }
    170.  
    171. @end
    172.  
    173. @implementation AppleSearchAdRetriever
    174.  
    175. extern "C" {
    176.     char* _RetrieveAppleSearchAd() {
    177.         NSString* appleSearchAdOutput = [AppleSearchAd retrieveAppleSearchAd];
    178.         char* res = (char*)malloc(strlen([appleSearchAdOutput UTF8String]) + 1);
    179.         strcpy(res, [appleSearchAdOutput UTF8String]);
    180.         return res;
    181.     }
    182. }
    183.  
    184. @end
    All I get is always: "ADClient not available - Search ads logic skipped"

    I also added the `AdSupport.framework` and `Foundation.framework` both in Unity (selecting the files in the Plugins folder) and as a PostBuildProcessor (together with iAd.framework):

    Code (CSharp):
    1.             var target = proj.TargetGuidByName("Unity-iPhone");
    2.  
    3.             proj.AddBuildProperty(target, "GCC_ENABLE_OBJC_EXCEPTIONS", "YES");
    4.             proj.AddBuildProperty(target, "CLANG_ENABLE_MODULES", "YES");
    5.  
    6.             proj.AddFrameworkToProject(target, "Metal.framework", false);
    7.             proj.AddFrameworkToProject(target, "CoreImage.framework", false);
    8.  
    9.             Debug.Log("[AdTrace]: Adding AdSupport.framework to Xcode project.");
    10.             proj.AddFrameworkToProject(target, "AdSupport.framework", true);
    11.             Debug.Log("[AdTrace]: AdSupport.framework added successfully.");
    12.  
    13.             // adding iAd framework for Apple Source Attribution API
    14.             Debug.Log("[AdTrace]: Adding iAd.framework to Xcode project.");
    15.             proj.AddFrameworkToProject(target, "iAd.framework", true);
    16.             Debug.Log("[AdTrace]: iAd.framework added successfully.");
    17.  
    18.             Debug.Log("[AdTrace]: Adding Foundation.framework to Xcode project.");
    19.             proj.AddFrameworkToProject(target, "Foundation.framework", false);
    20.             Debug.Log("[AdTrace]: Foundation.framework added successfully.");
    Screen Shot 2019-12-02 at 11.42.43.png

    Conclusions:

    I am capable of executing the code and read out the created dictionary, which means that the plugin itself works.
    No crash is recorded (it did crash before when I had an actual syntax error in it) and everything keeps working normally.

    The only thing that happens is that I am never able to actually see a success or failure result.
    Only skipped.

    Why would that be?

    Thanks in advance!
     
    danishgoel likes this.
  2. resilio

    resilio

    Joined:
    Sep 7, 2017
    Posts:
    12
    Has anyone had a similar issue?
     
  3. neshius108

    neshius108

    Joined:
    Nov 19, 2015
    Posts:
    110
    This happened to me but I had to give up on it.

    Bump!
     
  4. resilio

    resilio

    Joined:
    Sep 7, 2017
    Posts:
    12
    Anyone can help? :(