Cannot get Apple Search Attribution to work as a plugin

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:

#import "AppleSearchAd.h"
#include <cstring>

@interface AppleSearchAd()
+ (void)requestSearchAdAttributionDetailsWithTimeBetweenRetries:(NSTimeInterval)timeBetweenRetries numberOfRetries:(NSInteger)numberOfRetries successBlock:(NSString*(^)(NSDictionary *))successBlock failureBlock:(NSString*(^)(void))failureBlock;
@end

typedef NSString*(^AppleSearchAdTimerHandlerSuccessBlock)(NSDictionary *);
typedef NSString*(^AppleSearchAdTimerHandlerFailureBlock)(void);

@interface AppleSearchAdTimerHandler : NSObject

@property (nonatomic,assign) NSTimeInterval timeBetweenRetries;
@property (nonatomic,assign) NSInteger numberOfRetries;
@property (nonatomic,strong) AppleSearchAdTimerHandlerSuccessBlock successBlock;
@property (nonatomic,strong) AppleSearchAdTimerHandlerFailureBlock failureBlock;

@end

@implementation AppleSearchAdTimerHandler

-(void)retryTimer:(NSTimer *)timer{
    NSLog(@"Retrying to request for Search Ad attribution details.");
    [AppleSearchAd requestSearchAdAttributionDetailsWithTimeBetweenRetries:self.timeBetweenRetries numberOfRetries:self.numberOfRetries successBlock:self.successBlock failureBlock:self.failureBlock];
    self.successBlock = nil;
    self.failureBlock = nil;//so that obj does not retain success and failure blocks
}

@end

@implementation AppleSearchAd

NSString *const HasReceivedSearchAdAttributionKey = @"HasReceivedAppleSearchAdAttibution";

+ (NSString*) retrieveAppleSearchAd {
    NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    // If we haven't already registered HasReceivedSearchAdAttributionKey bool value to NSUserDefaults.
    if ([userDefaults objectForKey:HasReceivedSearchAdAttributionKey] == nil) {
        NSLog(@"AppleSearchAd-> retrieveAppleSearchAd");
       
        [AppleSearchAd requestSearchAdAttributionDetailsWithTimeBetweenRetries:5 numberOfRetries:3 successBlock:^(NSDictionary *attributionDetails) {
            NSLog(@"Successfully got attribution dictionary for search ads.");
            NSDictionary *actualData = attributionDetails[@"Version3.1"];
            int line = __LINE__;
            NSMutableDictionary *d = nil;
            NSError * err;

            if(actualData) {
                d = [NSMutableDictionary dictionaryWithDictionary:actualData];
                d[@"Attribution Dictionary Version"] = @"3.1";
            }
            else {
                NSLog(@"*****************************************");
                NSLog(@"IMPORTANT:");
                NSLog(@"APPLE CHANGED THE VERSION INFO DICTIONARY FOR SEARCH ADS");
                NSLog(@"PLEASE TAKE A LOOK AT THIS CODE AND UPDATE IT");
                NSLog(@"file:%s line:%d",__FILE__,line);
                NSLog(@"*****************************************");
               
                if(attributionDetails.count) {
                    NSString *firstKey = [[attributionDetails allKeys]firstObject];
                    if([actualData = [attributionDetails objectForKey:firstKey] isKindOfClass:[NSDictionary     class]]){
                        d = [NSMutableDictionary dictionaryWithDictionary:actualData];
                        d[@"Attribution Dictionary Version"] = firstKey;
                        d[@"iad-message"] = @"iad Success";
                    }
                    else {
                        d = [NSMutableDictionary dictionary];
                        d[@"Attribution Dictionary Version"] = @"Unknown";
                        d[@"iad-message"] = @"Incorrect iad Version info {3.1} - Update it!";
                        for(NSString *key in attributionDetails) {
                            [d setObject:[attributionDetails[key]description] forKey:key];
                        }
                    }
                }
            }
            d[@"App Id"] = [[NSBundle mainBundle]bundleIdentifier];
           
            // Register HasReceivedSearchAdAttributionKey bool value so that we don't make more than one Search Ad attribution request per lifetime of app install.
            [userDefaults setValue:[NSNumber numberWithBool:YES] forKey:HasReceivedSearchAdAttributionKey];
       
            NSData *jsonData = [NSJSONSerialization dataWithJSONObject:d options:0 error:&err];
            return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
        } failureBlock:^{
            NSMutableDictionary *d = nil;
            NSError * err;
            d = [NSMutableDictionary dictionary];
            d[@"Attribution Dictionary Version"] = @"Unknown";
            d[@"App Id"] = [[NSBundle mainBundle]bundleIdentifier];
            d[@"iad-message"] = @"Failed to get attribution dictionary for search ads [Maybe asked too many times?]";
            NSData *jsonData = [NSJSONSerialization dataWithJSONObject:d options:0 error:&err];

            NSLog(@"Failed to get attribution dictionary for search ads [Maybe asked too many times?]");
            return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
        }];
        NSMutableDictionary *d = nil;
        NSError * err;
        d = [NSMutableDictionary dictionary];
        d[@"Attribution Dictionary Version"] = @"Unknown";
        d[@"App Id"] = [[NSBundle mainBundle]bundleIdentifier];
        d[@"iad-message"] = @"ADClient not available - Search ads logic skipped";
        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:d options:0 error:&err];

        NSLog(@"ADClient not available - Search ads logic skipped");
        return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    } else {
        NSMutableDictionary *d = nil;
        NSError * err;
        d = [NSMutableDictionary dictionary];
        d[@"Attribution Dictionary Version"] = @"Unknown";
        d[@"App Id"] = [[NSBundle mainBundle]bundleIdentifier];
        d[@"iad-message"] = @"Search ads already requested";
        NSData *jsonData = [NSJSONSerialization dataWithJSONObject:d options:0 error:&err];

        NSLog(@"Search ads already requested");
        return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
    }
}

+ (void)requestSearchAdAttributionDetailsWithTimeBetweenRetries:(NSTimeInterval)timeBetweenRetries numberOfRetries:(NSInteger)numberOfRetries successBlock:(NSString*(^)(NSDictionary *))successBlock failureBlock:(NSString*(^)(void))failureBlock {
    NSLog(@"AppleSearchAd-> requestSearchAdAttributionDetailsWithTimeBetweenRetries");
     Class adClient = NSClassFromString(@"ADClient");
     if(adClient != nil) {
        if ([[ADClient sharedClient] respondsToSelector:@selector(requestAttributionDetailsWithBlock:)]) {
            [[ADClient sharedClient] requestAttributionDetailsWithBlock:^(NSDictionary *attributionDetails, NSError *error) {
                if (error) {
                    switch(error.code) {
                        case ADClientErrorUnknown:
                        {
                            dispatch_async(dispatch_get_main_queue(), ^{
                                if(numberOfRetries > 0) {
                                    AppleSearchAdTimerHandler *obj = [[AppleSearchAdTimerHandler alloc]init];
                                    obj.numberOfRetries = numberOfRetries - 1;
                                    obj.timeBetweenRetries = timeBetweenRetries;
                                    obj.failureBlock = failureBlock;
                                    obj.successBlock = successBlock;
                                    [NSTimer scheduledTimerWithTimeInterval:timeBetweenRetries target:obj selector:@selector(retryTimer:) userInfo:nil repeats:NO];
                                    obj = nil;//so that the block does not retain obj
                                }
                                else {
                                    dispatch_async(dispatch_get_main_queue(), ^{
                                        failureBlock();
                                    });
                                }
                            });
                        }
                            break;
                        case ADClientErrorLimitAdTracking:
                            dispatch_async(dispatch_get_main_queue(), ^{
                                failureBlock();
                            });
                            break;
                    }
                }
                else {
                    dispatch_async(dispatch_get_main_queue(), ^{
                        successBlock(attributionDetails);
                    });
                }
            }];
         }
         else {
             NSLog(@"iOS 10 ADClient call does not exist");
         }
     }
     else {
         NSLog(@"ADClient Not Available");
     }
}

@end

@implementation AppleSearchAdRetriever

extern "C" {
    char* _RetrieveAppleSearchAd() {
        NSString* appleSearchAdOutput = [AppleSearchAd retrieveAppleSearchAd];
        char* res = (char*)malloc(strlen([appleSearchAdOutput UTF8String]) + 1);
        strcpy(res, [appleSearchAdOutput UTF8String]);
        return res;
    }
}

@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):

            var target = proj.TargetGuidByName("Unity-iPhone");

            proj.AddBuildProperty(target, "GCC_ENABLE_OBJC_EXCEPTIONS", "YES");
            proj.AddBuildProperty(target, "CLANG_ENABLE_MODULES", "YES");

            proj.AddFrameworkToProject(target, "Metal.framework", false);
            proj.AddFrameworkToProject(target, "CoreImage.framework", false);

            Debug.Log("[AdTrace]: Adding AdSupport.framework to Xcode project.");
            proj.AddFrameworkToProject(target, "AdSupport.framework", true);
            Debug.Log("[AdTrace]: AdSupport.framework added successfully.");

            // adding iAd framework for Apple Source Attribution API
            Debug.Log("[AdTrace]: Adding iAd.framework to Xcode project.");
            proj.AddFrameworkToProject(target, "iAd.framework", true);
            Debug.Log("[AdTrace]: iAd.framework added successfully.");

            Debug.Log("[AdTrace]: Adding Foundation.framework to Xcode project.");
            proj.AddFrameworkToProject(target, "Foundation.framework", false);
            Debug.Log("[AdTrace]: Foundation.framework added successfully.");

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!

1 Like

Has anyone had a similar issue?

This happened to me but I had to give up on it.

Bump!

Anyone can help? :frowning: