Created
March 23, 2020 20:54
-
-
Save thomasmso/7bbbd4f64cb90e768b7db061d6b147c0 to your computer and use it in GitHub Desktop.
MAX Facebook Adapter - iOS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// MAFacebookMediationAdapter.m | |
// AppLovinSDK | |
// | |
// Created by Santosh Bagadi on 8/31/18. | |
// Copyright © 2019 AppLovin Corporation. All rights reserved. | |
// | |
#import "ALFacebookMediationAdapter.h" | |
#import <FBAudienceNetwork/FBAudienceNetwork.h> | |
#import "ALSdk.h" | |
#import "ALUtils.h" | |
#import "NSDictionary+ALUtils.h" | |
#import "NSString+ALUtils.h" | |
#import "ALAtomicBoolean.h" | |
#define ADAPTER_VERSION @"5.7.1.0" | |
#define MEDIATION_IDENTIFIER [NSString stringWithFormat: @"APPLOVIN_%@:%@", [ALSdk version], self.adapterVersion] | |
@interface ALFacebookMediationAdapterInterstitialAdDelegate : NSObject<FBInterstitialAdDelegate> | |
@property (nonatomic, weak) ALFacebookMediationAdapter *parentAdapter; | |
@property (nonatomic, strong) id<MAInterstitialAdapterDelegate> delegate; | |
- (instancetype)initWithParentAdapter:(ALFacebookMediationAdapter *)parentAdapter andNotify:(id<MAInterstitialAdapterDelegate>)delegate; | |
@end | |
@interface ALFacebookMediationAdapterRewardedVideoAdDelegate : NSObject<FBRewardedVideoAdDelegate> | |
@property (nonatomic, weak) ALFacebookMediationAdapter *parentAdapter; | |
@property (nonatomic, strong) id<MARewardedAdapterDelegate> delegate; | |
@property (nonatomic, assign, getter=hasGrantedReward) BOOL grantedReward; | |
- (instancetype)initWithParentAdapter:(ALFacebookMediationAdapter *)parentAdapter andNotify:(id<MARewardedAdapterDelegate>)delegate; | |
@end | |
@interface ALFacebookMediationAdapterAdViewDelegate : NSObject<FBAdViewDelegate> | |
@property (nonatomic, weak) ALFacebookMediationAdapter *parentAdapter; | |
@property (nonatomic, strong) id<MAAdViewAdapterDelegate> delegate; | |
- (instancetype)initWithParentAdapter:(ALFacebookMediationAdapter *)parentAdapter andNotify:(id<MAAdViewAdapterDelegate>)delegate; | |
@end | |
@interface ALFacebookMediationAdapter() | |
@property (nonatomic, strong) FBInterstitialAd *interstitialAd; | |
@property (nonatomic, strong) FBRewardedVideoAd *rewardedVideoAd; | |
@property (nonatomic, strong) FBAdView *adView; | |
@property (nonatomic, strong) ALFacebookMediationAdapterInterstitialAdDelegate *interstitialAdapterDelegate; | |
@property (nonatomic, strong) ALFacebookMediationAdapterRewardedVideoAdDelegate *rewardedAdapterDelegate; | |
@property (nonatomic, strong) ALFacebookMediationAdapterAdViewDelegate *adViewAdapterDelegate; | |
@end | |
@implementation ALFacebookMediationAdapter | |
static NSObject *ALFBAdSettingsLock; | |
static ALAtomicBoolean *ALFacebookSDKInitialized; | |
static MAAdapterInitializationStatus ALFacebookSDKInitializationStatus = NSIntegerMin; | |
#pragma mark - Class Initialization | |
+ (void)initialize | |
{ | |
[super initialize]; | |
ALFBAdSettingsLock = [[NSObject alloc] init]; | |
ALFacebookSDKInitialized = [[ALAtomicBoolean alloc] init]; | |
} | |
#pragma mark - MAAdapter Methods | |
- (void)initializeWithParameters:(id<MAAdapterInitializationParameters>)parameters withCompletionHandler:(void(^)(void))completionHandler | |
{ | |
[self updateAdSettingsWithParameters: parameters]; | |
// If initialized already | |
if ( [ALFacebookSDKInitialized compareAndSet: NO update: YES] ) | |
{ | |
NSArray<NSString *> *placementIDs = [parameters.serverParameters al_arrayForKey: @"placement_ids"]; | |
void (^facebookCompletionHandler)(FBAdInitResults *results) = ^(FBAdInitResults *initResult) { | |
if ( [initResult isSuccess] ) | |
{ | |
[self log: @"Facebook SDK successfully finished initialization"]; | |
} | |
else | |
{ | |
[self log: @"Facebook SDK failed to finished initialization: %@", initResult.message]; | |
} | |
completionHandler(); | |
}; | |
[self log: @"Initializing Facebook SDK with placements: %@", placementIDs]; | |
FBAdInitSettings *initSettings = [[FBAdInitSettings alloc] initWithPlacementIDs: placementIDs mediationService: MEDIATION_IDENTIFIER]; | |
[FBAudienceNetworkAds initializeWithSettings: initSettings completionHandler: facebookCompletionHandler]; | |
} | |
else | |
{ | |
[self log: @"Facebook attempted initialization already - marking initialization as completed"]; | |
completionHandler(); | |
} | |
} | |
- (void)initializeWithParameters:(id<MAAdapterInitializationParameters>)parameters completionHandler:(void (^)(MAAdapterInitializationStatus, NSString * _Nullable))completionHandler | |
{ | |
[self updateAdSettingsWithParameters: parameters]; | |
// If initialized already | |
if ( [ALFacebookSDKInitialized compareAndSet: NO update: YES] ) | |
{ | |
ALFacebookSDKInitializationStatus = MAAdapterInitializationStatusInitializing; | |
NSArray<NSString *> *placementIDs = [parameters.serverParameters al_arrayForKey: @"placement_ids"]; | |
void (^facebookCompletionHandler)(FBAdInitResults *results) = ^(FBAdInitResults *initResult) { | |
if ( [initResult isSuccess] ) | |
{ | |
[self log: @"Facebook SDK successfully finished initialization: %@", initResult.message]; | |
ALFacebookSDKInitializationStatus = MAAdapterInitializationStatusInitializedSuccess; | |
completionHandler(ALFacebookSDKInitializationStatus, nil); | |
} | |
else | |
{ | |
[self log: @"Facebook SDK failed to finished initialization: %@", initResult.message]; | |
ALFacebookSDKInitializationStatus = MAAdapterInitializationStatusInitializedFailure; | |
completionHandler(ALFacebookSDKInitializationStatus, initResult.message); | |
} | |
}; | |
[self log: @"Initializing Facebook SDK with placements: %@", placementIDs]; | |
FBAdInitSettings *initSettings = [[FBAdInitSettings alloc] initWithPlacementIDs: placementIDs mediationService: MEDIATION_IDENTIFIER]; | |
[FBAudienceNetworkAds initializeWithSettings: initSettings completionHandler: facebookCompletionHandler]; | |
} | |
else | |
{ | |
[self log: @"Facebook attempted initialization already - marking initialization as %ld", ALFacebookSDKInitializationStatus]; | |
completionHandler(ALFacebookSDKInitializationStatus, nil); | |
} | |
} | |
- (NSString *)SDKVersion | |
{ | |
return FB_AD_SDK_VERSION; | |
} | |
- (NSString *)adapterVersion | |
{ | |
return ADAPTER_VERSION; | |
} | |
- (void)destroy | |
{ | |
// [self log: @"Destroy called for adapter %@", self ]; | |
// | |
// self.interstitialAd.delegate = nil; | |
// self.interstitialAd = nil; | |
// self.interstitialAdapterDelegate = nil; | |
// | |
// self.rewardedVideoAd.delegate = nil; | |
// self.rewardedVideoAd = nil; | |
// self.rewardedAdapterDelegate = nil; | |
// | |
// self.adView.delegate = nil; | |
// self.adView = nil; | |
// self.adViewAdapterDelegate = nil; | |
} | |
#pragma mark - MAInterstitialAdapter Methods | |
- (void)loadInterstitialAdForParameters:(id<MAAdapterResponseParameters>)parameters andNotify:(id<MAInterstitialAdapterDelegate>)delegate | |
{ | |
[self log: @"Loading interstitial ad: %@...", parameters.thirdPartyAdPlacementIdentifier]; | |
[self updateAdSettingsWithParameters: parameters]; | |
self.interstitialAd = [[FBInterstitialAd alloc] initWithPlacementID: parameters.thirdPartyAdPlacementIdentifier]; | |
self.interstitialAdapterDelegate = [[ALFacebookMediationAdapterInterstitialAdDelegate alloc] initWithParentAdapter: self andNotify: delegate]; | |
self.interstitialAd.delegate = self.interstitialAdapterDelegate; | |
NSString *bidResponse = parameters.bidResponse; | |
// If there is no bid response | |
if ( ![bidResponse al_isValidString] ) | |
{ | |
[self log: @"Loading mediated interstitial ad..."]; | |
[self.interstitialAd loadAd]; | |
} | |
// Otherwise (we have a bid response) | |
else | |
{ | |
[self log: @"Loading bidding interstitial ad..."]; | |
[self.interstitialAd loadAdWithBidPayload: bidResponse]; | |
} | |
} | |
- (void)showInterstitialAdForParameters:(id<MAAdapterResponseParameters>)parameters andNotify:(id<MAInterstitialAdapterDelegate>)delegate | |
{ | |
[self log: @"Showing interstitial: %@...", parameters.thirdPartyAdPlacementIdentifier]; | |
// Check if ad is already expired or invalidated, and do not show ad if that is the case. You will not get paid to show an invalidated ad. | |
if ( [self.interstitialAd isAdValid] ) | |
{ | |
[self.interstitialAd showAdFromRootViewController: [ALUtils topViewControllerFromKeyWindow]]; | |
} | |
else | |
{ | |
[self log: @"Unable to show interstitial ad: ad is not valid - marking as expired"]; | |
[delegate didFailToDisplayInterstitialAdWithError: MAAdapterError.adExpiredError]; | |
} | |
} | |
#pragma mark - MARewardedAdapter Methods | |
- (void)loadRewardedAdForParameters:(id<MAAdapterResponseParameters>)parameters andNotify:(id<MARewardedAdapterDelegate>)delegate | |
{ | |
[self log: @"Loading rewarded ad: %@...", parameters.thirdPartyAdPlacementIdentifier]; | |
[self updateAdSettingsWithParameters: parameters]; | |
self.rewardedVideoAd = [[FBRewardedVideoAd alloc] initWithPlacementID: parameters.thirdPartyAdPlacementIdentifier]; | |
self.rewardedAdapterDelegate = [[ALFacebookMediationAdapterRewardedVideoAdDelegate alloc] initWithParentAdapter: self andNotify: delegate]; | |
self.rewardedVideoAd.delegate = self.rewardedAdapterDelegate; | |
if ( [self.rewardedVideoAd isAdValid] ) | |
{ | |
[self log: @"A rewarded ad has been loaded already"]; | |
[delegate didLoadRewardedAd]; | |
} | |
else | |
{ | |
NSString *bidResponse = parameters.bidResponse; | |
// If there is no bid response | |
if ( ![bidResponse al_isValidString] ) | |
{ | |
[self log: @"Loading mediated rewarded ad..."]; | |
[self.rewardedVideoAd loadAd]; | |
} | |
// Otherwise (we have a bid response) | |
else | |
{ | |
[self log: @"Loading bidding rewarded ad..."]; | |
[self.rewardedVideoAd loadAdWithBidPayload: bidResponse]; | |
} | |
} | |
} | |
- (void)showRewardedAdForParameters:(id<MAAdapterResponseParameters>)parameters andNotify:(id<MARewardedAdapterDelegate>)delegate | |
{ | |
[self log: @"Showing rewarded ad: %@...", parameters.thirdPartyAdPlacementIdentifier]; | |
// Check if ad is already expired or invalidated, and do not show ad if that is the case. You will not get paid to show an invalidated ad. | |
if ( [self.rewardedVideoAd isAdValid] ) | |
{ | |
// Configure reward from server. | |
[self configureRewardForParameters: parameters]; | |
[self.rewardedVideoAd showAdFromRootViewController: [ALUtils topViewControllerFromKeyWindow]]; | |
} | |
else | |
{ | |
[self log: @"Unable to show rewarded ad: ad is not valid - marking as expired"]; | |
[delegate didFailToDisplayRewardedAdWithError: MAAdapterError.adExpiredError]; | |
} | |
} | |
#pragma mark - MASignalProvider Methods | |
- (void)collectSignalWithParameters:(id<MASignalCollectionParameters>)parameters andNotify:(id<MASignalCollectionDelegate>)delegate | |
{ | |
[self log: @"Collecting signal..."]; | |
NSString *signal = FBAdSettings.bidderToken; | |
[delegate didCollectSignal: signal]; | |
} | |
#pragma mark - MAAdViewAdapter Methods | |
- (void)loadAdViewAdForParameters:(id<MAAdapterResponseParameters>)parameters | |
adFormat:(MAAdFormat *)adFormat | |
andNotify:(id<MAAdViewAdapterDelegate>)delegate | |
{ | |
[self log: @"Loading banner ad: %@...", parameters.thirdPartyAdPlacementIdentifier]; | |
FBAdSize adSize = [self FBAdSizeFromAdFormat: adFormat]; | |
self.adView = [[FBAdView alloc] initWithPlacementID: parameters.thirdPartyAdPlacementIdentifier | |
adSize: adSize | |
rootViewController: [ALUtils topViewControllerFromKeyWindow]]; | |
self.adView.frame = CGRectMake(0, 0, adSize.size.width, adSize.size.height); | |
self.adViewAdapterDelegate = [[ALFacebookMediationAdapterAdViewDelegate alloc] initWithParentAdapter: self andNotify: delegate]; | |
self.adView.delegate = self.adViewAdapterDelegate; | |
NSString *bidResponse = parameters.bidResponse; | |
// If there is no bid response | |
if ( ![bidResponse al_isValidString] ) | |
{ | |
[self log: @"Loading mediated banner ad..."]; | |
[self.adView loadAd]; | |
} | |
// Otherwise (we have a bid response) | |
else | |
{ | |
[self log: @"Loading bidding banner ad..."]; | |
[self.adView loadAdWithBidPayload: bidResponse]; | |
} | |
} | |
#pragma mark - Shared Methods | |
- (void)updateAdSettingsWithParameters:(id<MAAdapterParameters>)parameters | |
{ | |
// FBAdSettings is apparently not thread-safe on iOS - and may occassionally crash :/ | |
@synchronized ( ALFBAdSettingsLock ) | |
{ | |
FBAdSettings.mixedAudience = [parameters isAgeRestrictedUser]; | |
NSString *testDevicesString = parameters.serverParameters[@"test_device_ids"]; | |
if ( [testDevicesString al_isValidString] ) | |
{ | |
NSArray<NSString *> *testDevices = [testDevicesString componentsSeparatedByString: @","]; | |
[FBAdSettings addTestDevices: testDevices]; | |
} | |
if ( [parameters isTesting] ) | |
{ | |
[FBAdSettings setLogLevel: FBAdLogLevelDebug]; | |
} | |
[FBAdSettings setMediationService: MEDIATION_IDENTIFIER]; | |
} | |
} | |
- (FBAdSize)FBAdSizeFromAdFormat:(MAAdFormat *)adFormat | |
{ | |
if ( adFormat == MAAdFormat.banner ) | |
{ | |
return kFBAdSizeHeight50Banner; | |
} | |
else if ( adFormat == MAAdFormat.leader ) | |
{ | |
return kFBAdSizeHeight90Banner; | |
} | |
else if ( adFormat == MAAdFormat.mrec ) | |
{ | |
return kFBAdSizeHeight250Rectangle; | |
} | |
else | |
{ | |
[NSException raise: NSInvalidArgumentException format: @"Invalid ad format: %@", adFormat]; | |
return kFBAdSizeHeight50Banner; | |
} | |
} | |
+ (MAAdapterError *)maxErrorFromFacebookError:(NSError *)error | |
{ | |
// From https://developers.facebook.com/docs/audience-network/testing/#errors | |
NSInteger errorCode; | |
NSInteger facebookCode = error.code; | |
if ( facebookCode == 1000 ) // Network Error | |
{ | |
errorCode = MAAdapterError.errorCodeNoConnection; // -5003 | |
} | |
else if ( facebookCode == 1001 ) // No Fill | |
{ | |
errorCode = MAAdapterError.errorCodeNoFill; // 204 | |
} | |
else if ( facebookCode == 1002 ) // Ad Load Too Frequently | |
{ | |
errorCode = MAAdapterError.errorCodeInvalidLoadState; | |
} | |
else if ( facebookCode == 1011 ) // Display Format Mismatch | |
{ | |
errorCode = MAAdapterError.errorCodeInvalidConfiguration; // -5202 | |
} | |
else if ( facebookCode == 2000 ) // Server Error | |
{ | |
errorCode = MAAdapterError.errorCodeServerError; // -5206 | |
} | |
else if ( facebookCode == 2001 ) // Internal Error - actually a timeout error | |
{ | |
errorCode = MAAdapterError.errorCodeTimeout; // -5206 (to be changed) | |
} | |
else | |
{ | |
errorCode = MAAdapterError.errorCodeUnspecified; // -5200 | |
} | |
return [MAAdapterError errorWithCode: errorCode adapterErrorCode: facebookCode]; | |
} | |
@end | |
#pragma mark - ALFacebookMediationAdapterInterstitialAdDelegate | |
@implementation ALFacebookMediationAdapterInterstitialAdDelegate | |
- (instancetype)initWithParentAdapter:(ALFacebookMediationAdapter *)parentAdapter andNotify:(id<MAInterstitialAdapterDelegate>)delegate | |
{ | |
self = [super init]; | |
if ( self ) | |
{ | |
self.parentAdapter = parentAdapter; | |
self.delegate = delegate; | |
} | |
return self; | |
} | |
- (void)interstitialAdDidLoad:(FBInterstitialAd *)interstitialAd | |
{ | |
[self.parentAdapter log: @"Interstitial ad loaded: %@", interstitialAd.placementID]; | |
[self.delegate didLoadInterstitialAd]; | |
} | |
- (void)interstitialAd:(FBInterstitialAd *)interstitialAd didFailWithError:(NSError *)error | |
{ | |
[self.parentAdapter log: @"Interstitial ad (%@) failed to load with error: %@", interstitialAd.placementID, error]; | |
MAAdapterError *adapterError = [ALFacebookMediationAdapter maxErrorFromFacebookError: error]; | |
[self.delegate didFailToLoadInterstitialAdWithError: adapterError]; | |
} | |
- (void)interstitialAdWillLogImpression:(FBInterstitialAd *)interstitialAd | |
{ | |
[self.parentAdapter log: @"Interstitial ad shown: %@", interstitialAd.placementID]; | |
[self.delegate didDisplayInterstitialAd]; | |
} | |
- (void)interstitialAdDidClick:(FBInterstitialAd *)interstitialAd | |
{ | |
[self.parentAdapter log: @"Interstitial ad clicked: %@", interstitialAd.placementID]; | |
[self.delegate didClickInterstitialAd]; | |
} | |
- (void)interstitialAdDidClose:(FBInterstitialAd *)interstitialAd | |
{ | |
[self.parentAdapter log: @"Interstitial ad hidden: %@", interstitialAd.placementID]; | |
[self.delegate didHideInterstitialAd]; | |
} | |
@end | |
#pragma mark - ALFacebookMediationAdapterRewardedVideoAdDelegate | |
@implementation ALFacebookMediationAdapterRewardedVideoAdDelegate | |
- (instancetype)initWithParentAdapter:(ALFacebookMediationAdapter *)parentAdapter andNotify:(id<MARewardedAdapterDelegate>)delegate | |
{ | |
self = [super init]; | |
if ( self ) | |
{ | |
self.parentAdapter = parentAdapter; | |
self.delegate = delegate; | |
} | |
return self; | |
} | |
- (void)rewardedVideoAdDidLoad:(FBRewardedVideoAd *)rewardedVideoAd | |
{ | |
[self.parentAdapter log: @"Rewarded ad loaded: %@", rewardedVideoAd.placementID]; | |
[self.delegate didLoadRewardedAd]; | |
} | |
- (void)rewardedVideoAd:(FBRewardedVideoAd *)rewardedVideoAd didFailWithError:(NSError *)error | |
{ | |
[self.parentAdapter log: @"Rewarded ad (%@) failed to load with error: %@", rewardedVideoAd.placementID, error]; | |
MAAdapterError *adapterError = [ALFacebookMediationAdapter maxErrorFromFacebookError: error]; | |
[self.delegate didFailToLoadRewardedAdWithError: adapterError]; | |
} | |
- (void)rewardedVideoAdDidClose:(FBRewardedVideoAd *)rewardedVideoAd | |
{ | |
if ( [self hasGrantedReward] || [self.parentAdapter shouldAlwaysRewardUser] ) | |
{ | |
MAReward *reward = [self.parentAdapter reward]; | |
[self.parentAdapter log: @"Rewarded user with reward: %@", reward]; | |
[self.delegate didRewardUserWithReward: reward]; | |
} | |
[self.parentAdapter log: @"Rewarded ad hidden: %@", rewardedVideoAd.placementID]; | |
[self.delegate didHideRewardedAd]; | |
} | |
- (void)rewardedVideoAdDidClick:(FBRewardedVideoAd *)rewardedVideoAd | |
{ | |
[self.parentAdapter log: @"Rewarded ad clicked: %@", rewardedVideoAd.placementID]; | |
[self.delegate didClickRewardedAd]; | |
} | |
- (void)rewardedVideoAdVideoComplete:(FBRewardedVideoAd *)rewardedVideoAd | |
{ | |
[self.parentAdapter log: @"Rewarded video completed: %@", rewardedVideoAd.placementID]; | |
[self.delegate didCompleteRewardedAdVideo]; | |
self.grantedReward = YES; | |
} | |
- (void)rewardedVideoAdWillLogImpression:(FBRewardedVideoAd *)rewardedVideoAd | |
{ | |
[self.parentAdapter log: @"Rewarded video started: %@", rewardedVideoAd.placementID]; | |
[self.delegate didDisplayRewardedAd]; | |
[self.delegate didStartRewardedAdVideo]; | |
} | |
@end | |
#pragma mark - ALFacebookMediationAdapterAdViewDelegate | |
@implementation ALFacebookMediationAdapterAdViewDelegate | |
- (instancetype)initWithParentAdapter:(ALFacebookMediationAdapter *)parentAdapter andNotify:(id<MAAdViewAdapterDelegate>)delegate | |
{ | |
self = [super init]; | |
if ( self ) | |
{ | |
self.parentAdapter = parentAdapter; | |
self.delegate = delegate; | |
} | |
return self; | |
} | |
- (void)adViewDidLoad:(FBAdView *)adView | |
{ | |
[self.parentAdapter log: @"Banner loaded: %@", adView.placementID]; | |
[self.delegate didLoadAdForAdView: adView]; | |
} | |
- (void)adView:(FBAdView *)adView didFailWithError:(NSError *)error | |
{ | |
[self.parentAdapter log: @"Banner (%@) failed to load with error: %@", adView.placementID, error]; | |
MAAdapterError *adapterError = [ALFacebookMediationAdapter maxErrorFromFacebookError: error]; | |
[self.delegate didFailToLoadAdViewAdWithError: adapterError]; | |
} | |
- (void)adViewWillLogImpression:(FBAdView *)adView | |
{ | |
[self.parentAdapter log: @"Banner shown: %@", adView.placementID]; | |
[self.delegate didDisplayAdViewAd]; | |
} | |
- (void)adViewDidClick:(FBAdView *)adView | |
{ | |
[self.parentAdapter log: @"Banner clicked: %@", adView.placementID]; | |
[self.delegate didClickAdViewAd]; | |
} | |
- (void)adViewDidFinishHandlingClick:(FBAdView *)adView | |
{ | |
[self.parentAdapter log: @"Banner finished handling click: %@", adView.placementID]; | |
[self.delegate didCollapseAdViewAd]; | |
} | |
- (UIViewController *)viewControllerForPresentingModalView | |
{ | |
return [ALUtils topViewControllerFromKeyWindow]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment