Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

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? :(