Signed-off-by: Haoyi <haoyi.zhang@castbox.fm>
Haoyi 2025-08-08 18:58:14 +08:00
commit b8867ec134
93 changed files with 12983 additions and 0 deletions

Binary file not shown.

View File

@ -0,0 +1,12 @@
#import <Foundation/Foundation.h>
#import <AppLovinSDK/AppLovinSDK.h>
NS_ASSUME_NONNULL_BEGIN
@interface ALMCMediationAdapter : ALMediationAdapter
@end
NS_ASSUME_NONNULL_END

Binary file not shown.

View File

@ -0,0 +1,6 @@
framework module ALMCMediationAdapter {
umbrella header "ALMCMediationAdapter.h"
export *
module * { export * }
}

View File

@ -0,0 +1,796 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD51AFB9204008FA782 /* AppDelegate.swift */; };
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACD71AFB9204008FA782 /* ViewController.swift */; };
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 607FACD91AFB9204008FA782 /* Main.storyboard */; };
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDC1AFB9204008FA782 /* Images.xcassets */; };
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 607FACDE1AFB9204008FA782 /* LaunchScreen.xib */; };
607FACEC1AFB9204008FA782 /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 607FACEB1AFB9204008FA782 /* Tests.swift */; };
644E309B2D899D6A00050E04 /* HappyPathAdsSdk.swift in Sources */ = {isa = PBXBuildFile; fileRef = 644E309A2D899D5B00050E04 /* HappyPathAdsSdk.swift */; };
64B020AA2D93CCB200B0F238 /* FusionAdsExample.debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 64B020A92D93CCB200B0F238 /* FusionAdsExample.debug.xcconfig */; };
7F380C61845DF1BC864FB1F3 /* Pods_FusionAds_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1647017313EA85A491798026 /* Pods_FusionAds_Example.framework */; };
C551C6C178AA8D1465FBF62D /* Pods_FusionAds_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 94F874AE62963EE9EDA6A4F3 /* Pods_FusionAds_Tests.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
607FACE61AFB9204008FA782 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 607FACC81AFB9204008FA782 /* Project object */;
proxyType = 1;
remoteGlobalIDString = 607FACCF1AFB9204008FA782;
remoteInfo = FusionAds;
};
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
1647017313EA85A491798026 /* Pods_FusionAds_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FusionAds_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
217D84A11B0D93B5D7BAEB44 /* Pods-FusionAds_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FusionAds_Example.release.xcconfig"; path = "Target Support Files/Pods-FusionAds_Example/Pods-FusionAds_Example.release.xcconfig"; sourceTree = "<group>"; };
3DBB4B509E1C6B4B597B85ED /* Pods-FusionAds_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FusionAds_Tests.debug.xcconfig"; path = "Target Support Files/Pods-FusionAds_Tests/Pods-FusionAds_Tests.debug.xcconfig"; sourceTree = "<group>"; };
562BB989E504293AF3C37E19 /* README.md */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = net.daringfireball.markdown; name = README.md; path = ../README.md; sourceTree = "<group>"; };
607FACD01AFB9204008FA782 /* FusionAds_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FusionAds_Example.app; sourceTree = BUILT_PRODUCTS_DIR; };
607FACD41AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
607FACD51AFB9204008FA782 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
607FACD71AFB9204008FA782 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
607FACDA1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
607FACDC1AFB9204008FA782 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
607FACDF1AFB9204008FA782 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
607FACE51AFB9204008FA782 /* FusionAds_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FusionAds_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
607FACEA1AFB9204008FA782 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
607FACEB1AFB9204008FA782 /* Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests.swift; sourceTree = "<group>"; };
644E309A2D899D5B00050E04 /* HappyPathAdsSdk.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HappyPathAdsSdk.swift; sourceTree = "<group>"; };
64B020A92D93CCB200B0F238 /* FusionAdsExample.debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = FusionAdsExample.debug.xcconfig; sourceTree = SOURCE_ROOT; };
94F874AE62963EE9EDA6A4F3 /* Pods_FusionAds_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FusionAds_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
C3C7B70C80D4C136889B6E9F /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = "<group>"; };
CB2B8B14F76462E07EAC9E63 /* Pods-FusionAds_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FusionAds_Example.debug.xcconfig"; path = "Target Support Files/Pods-FusionAds_Example/Pods-FusionAds_Example.debug.xcconfig"; sourceTree = "<group>"; };
D642D8B715B7E7D56126D64A /* FusionAds.podspec */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = FusionAds.podspec; path = ../FusionAds.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
FA9307EAC19AE275617F247A /* Pods-FusionAds_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FusionAds_Tests.release.xcconfig"; path = "Target Support Files/Pods-FusionAds_Tests/Pods-FusionAds_Tests.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
607FACCD1AFB9204008FA782 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7F380C61845DF1BC864FB1F3 /* Pods_FusionAds_Example.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
607FACE21AFB9204008FA782 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C551C6C178AA8D1465FBF62D /* Pods_FusionAds_Tests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
031A8DA505F309DDA1866DDA /* Pods */ = {
isa = PBXGroup;
children = (
CB2B8B14F76462E07EAC9E63 /* Pods-FusionAds_Example.debug.xcconfig */,
217D84A11B0D93B5D7BAEB44 /* Pods-FusionAds_Example.release.xcconfig */,
3DBB4B509E1C6B4B597B85ED /* Pods-FusionAds_Tests.debug.xcconfig */,
FA9307EAC19AE275617F247A /* Pods-FusionAds_Tests.release.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
607FACC71AFB9204008FA782 = {
isa = PBXGroup;
children = (
607FACF51AFB993E008FA782 /* Podspec Metadata */,
607FACD21AFB9204008FA782 /* Example for FusionAds */,
607FACE81AFB9204008FA782 /* Tests */,
607FACD11AFB9204008FA782 /* Products */,
031A8DA505F309DDA1866DDA /* Pods */,
F055A86E03635AF331B617A6 /* Frameworks */,
);
sourceTree = "<group>";
};
607FACD11AFB9204008FA782 /* Products */ = {
isa = PBXGroup;
children = (
607FACD01AFB9204008FA782 /* FusionAds_Example.app */,
607FACE51AFB9204008FA782 /* FusionAds_Tests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
607FACD21AFB9204008FA782 /* Example for FusionAds */ = {
isa = PBXGroup;
children = (
607FACD51AFB9204008FA782 /* AppDelegate.swift */,
644E309A2D899D5B00050E04 /* HappyPathAdsSdk.swift */,
607FACD71AFB9204008FA782 /* ViewController.swift */,
607FACD91AFB9204008FA782 /* Main.storyboard */,
607FACDC1AFB9204008FA782 /* Images.xcassets */,
607FACDE1AFB9204008FA782 /* LaunchScreen.xib */,
607FACD31AFB9204008FA782 /* Supporting Files */,
);
name = "Example for FusionAds";
path = FusionAds;
sourceTree = "<group>";
};
607FACD31AFB9204008FA782 /* Supporting Files */ = {
isa = PBXGroup;
children = (
64B020A92D93CCB200B0F238 /* FusionAdsExample.debug.xcconfig */,
607FACD41AFB9204008FA782 /* Info.plist */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
607FACE81AFB9204008FA782 /* Tests */ = {
isa = PBXGroup;
children = (
607FACEB1AFB9204008FA782 /* Tests.swift */,
607FACE91AFB9204008FA782 /* Supporting Files */,
);
path = Tests;
sourceTree = "<group>";
};
607FACE91AFB9204008FA782 /* Supporting Files */ = {
isa = PBXGroup;
children = (
607FACEA1AFB9204008FA782 /* Info.plist */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
607FACF51AFB993E008FA782 /* Podspec Metadata */ = {
isa = PBXGroup;
children = (
D642D8B715B7E7D56126D64A /* FusionAds.podspec */,
562BB989E504293AF3C37E19 /* README.md */,
C3C7B70C80D4C136889B6E9F /* LICENSE */,
);
name = "Podspec Metadata";
sourceTree = "<group>";
};
F055A86E03635AF331B617A6 /* Frameworks */ = {
isa = PBXGroup;
children = (
1647017313EA85A491798026 /* Pods_FusionAds_Example.framework */,
94F874AE62963EE9EDA6A4F3 /* Pods_FusionAds_Tests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
607FACCF1AFB9204008FA782 /* FusionAds_Example */ = {
isa = PBXNativeTarget;
buildConfigurationList = 607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FusionAds_Example" */;
buildPhases = (
85691E62AF861653F33F7D28 /* [CP] Check Pods Manifest.lock */,
607FACCC1AFB9204008FA782 /* Sources */,
607FACCD1AFB9204008FA782 /* Frameworks */,
607FACCE1AFB9204008FA782 /* Resources */,
2750E8ADDDEFDCE68C501BDC /* [CP] Embed Pods Frameworks */,
CBEDD8405524A33EC2AD32E7 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
dependencies = (
);
name = FusionAds_Example;
productName = FusionAds;
productReference = 607FACD01AFB9204008FA782 /* FusionAds_Example.app */;
productType = "com.apple.product-type.application";
};
607FACE41AFB9204008FA782 /* FusionAds_Tests */ = {
isa = PBXNativeTarget;
buildConfigurationList = 607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FusionAds_Tests" */;
buildPhases = (
91D0DD3A884742465EABB0C2 /* [CP] Check Pods Manifest.lock */,
607FACE11AFB9204008FA782 /* Sources */,
607FACE21AFB9204008FA782 /* Frameworks */,
607FACE31AFB9204008FA782 /* Resources */,
);
buildRules = (
);
dependencies = (
607FACE71AFB9204008FA782 /* PBXTargetDependency */,
);
name = FusionAds_Tests;
productName = Tests;
productReference = 607FACE51AFB9204008FA782 /* FusionAds_Tests.xctest */;
productType = "com.apple.product-type.bundle.unit-test";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
607FACC81AFB9204008FA782 /* Project object */ = {
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0830;
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = CocoaPods;
TargetAttributes = {
607FACCF1AFB9204008FA782 = {
CreatedOnToolsVersion = 6.3.1;
DevelopmentTeam = WP698B8M33;
LastSwiftMigration = 0900;
};
607FACE41AFB9204008FA782 = {
CreatedOnToolsVersion = 6.3.1;
DevelopmentTeam = SUAYNYNC5C;
LastSwiftMigration = 0900;
TestTargetID = 607FACCF1AFB9204008FA782;
};
};
};
buildConfigurationList = 607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "FusionAds" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
Base,
);
mainGroup = 607FACC71AFB9204008FA782;
productRefGroup = 607FACD11AFB9204008FA782 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
607FACCF1AFB9204008FA782 /* FusionAds_Example */,
607FACE41AFB9204008FA782 /* FusionAds_Tests */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
607FACCE1AFB9204008FA782 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
607FACDB1AFB9204008FA782 /* Main.storyboard in Resources */,
607FACE01AFB9204008FA782 /* LaunchScreen.xib in Resources */,
607FACDD1AFB9204008FA782 /* Images.xcassets in Resources */,
64B020AA2D93CCB200B0F238 /* FusionAdsExample.debug.xcconfig in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
607FACE31AFB9204008FA782 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
2750E8ADDDEFDCE68C501BDC /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-FusionAds_Example/Pods-FusionAds_Example-frameworks.sh",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ATOM-Standalone/ATOM.framework/ATOM",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/AmazonPublisherServicesSDK/DTBiOSSDK.framework/DTBiOSSDK",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/AppLovinSDK/AppLovinSDK.framework/AppLovinSDK",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/HyBid/Core/OMSDK_Pubnativenet.framework/OMSDK_Pubnativenet",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/InMobiSDK/InMobiSDK.framework/InMobiSDK",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MobileFuseSDK/MobileFuseSDK.framework/MobileFuseSDK",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MolocoSDKiOS/MolocoSDK.framework/MolocoSDK",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OMSDK_Appodeal/OMSDK_Appodeal.framework/OMSDK_Appodeal",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OguryAds/OMID/OMSDK_Ogury.framework/OMSDK_Ogury",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/OpenWrapSDK/OpenWrap/OMSDK_Pubmatic.framework/OMSDK_Pubmatic",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/smaato-ios-sdk/InApp/SmaatoSDKInAppBidding.framework/SmaatoSDKInAppBidding",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/smaato-ios-sdk/Modules/Banner/SmaatoSDKBanner.framework/SmaatoSDKBanner",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/smaato-ios-sdk/Modules/Core/SmaatoSDKCore.framework/SmaatoSDKCore",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/smaato-ios-sdk/Modules/Interstitial/SmaatoSDKInterstitial.framework/SmaatoSDKInterstitial",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/smaato-ios-sdk/Modules/Native/SmaatoSDKNative.framework/SmaatoSDKNative",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/smaato-ios-sdk/Modules/OpenMeasurement/SmaatoSDKOpenMeasurement.framework/SmaatoSDKOpenMeasurement",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/smaato-ios-sdk/Modules/OpenMeasurement/OMSDK_Smaato.framework/OMSDK_Smaato",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/smaato-ios-sdk/Modules/Outstream/SmaatoSDKOutstream.framework/SmaatoSDKOutstream",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/smaato-ios-sdk/Modules/RewardedAds/SmaatoSDKRewardedAds.framework/SmaatoSDKRewardedAds",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/smaato-ios-sdk/Modules/RichMedia/SmaatoSDKRichMedia.framework/SmaatoSDKRichMedia",
"${PODS_XCFRAMEWORKS_BUILD_DIR}/smaato-ios-sdk/Modules/Video/SmaatoSDKVideo.framework/SmaatoSDKVideo",
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ATOM.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DTBiOSSDK.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/AppLovinSDK.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OMSDK_Pubnativenet.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/InMobiSDK.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MobileFuseSDK.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MolocoSDK.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OMSDK_Appodeal.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OMSDK_Ogury.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OMSDK_Pubmatic.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SmaatoSDKInAppBidding.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SmaatoSDKBanner.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SmaatoSDKCore.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SmaatoSDKInterstitial.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SmaatoSDKNative.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SmaatoSDKOpenMeasurement.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/OMSDK_Smaato.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SmaatoSDKOutstream.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SmaatoSDKRewardedAds.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SmaatoSDKRichMedia.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SmaatoSDKVideo.framework",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FusionAds_Example/Pods-FusionAds_Example-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
85691E62AF861653F33F7D28 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-FusionAds_Example-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
91D0DD3A884742465EABB0C2 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-FusionAds_Tests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
CBEDD8405524A33EC2AD32E7 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-FusionAds_Example/Pods-FusionAds_Example-resources.sh",
"${PODS_ROOT}/Ads-Global/SDK/PAGAdSDK.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/Ads-Global/AdsGlobalSDK.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/BidMachine/BidMachine.bundle",
"${PODS_ROOT}/ChartboostSDK/ChartboostSDKResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/FBAudienceNetwork/FBAudienceNetwork.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/Fyber_Marketplace_SDK/Fyber_Marketplace_SDK.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/Google-Mobile-Ads-SDK/GoogleMobileAdsResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/GoogleUserMessagingPlatform/UserMessagingPlatformResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/GuruConsent/GuruConsent.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/HyBid/HyBidResources.bundle",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/close.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/close@2x.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/close@3x.png",
"${BUILT_PRODUCTS_DIR}/HyBid/HyBid.framework/HyBidCustomCTAView.nib",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/HyBidLocalizable.xcstrings",
"${BUILT_PRODUCTS_DIR}/HyBid/HyBid.framework/HyBidMRAIDCloseCardView.nib",
"${BUILT_PRODUCTS_DIR}/HyBid/HyBid.framework/InternalWebBrowser.storyboardc",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/PNLiteExternalLink.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/PNLiteExternalLink1.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/PNLiteExternalLink1@2x.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/PNLiteExternalLink1@3x.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/PNLiteExternalLink@2x.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/PNLiteFullScreen.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/PNLiteFullScreen@2x.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/PNLitePlay.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/PNLitePlay@2x.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/PNLiteSkip.png",
"${BUILT_PRODUCTS_DIR}/HyBid/HyBid.framework/PNLiteVASTPlayerFullScreenViewController.nib",
"${BUILT_PRODUCTS_DIR}/HyBid/HyBid.framework/PNLiteVASTPlayerInterstitialViewController.nib",
"${BUILT_PRODUCTS_DIR}/HyBid/HyBid.framework/PNLiteVASTPlayerRewardedViewController.nib",
"${BUILT_PRODUCTS_DIR}/HyBid/HyBid.framework/PNLiteVASTPlayerViewController.nib",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/skip.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/skip@2x.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/skip@3x.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/sound-off.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/sound-off@2x.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/sound-off@3x.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/sound-on.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/sound-on@2x.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/sound-on@3x.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Resources/VerveContentInfo.png",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/OMSDK-1.5.2/omsdk.js",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Core/MRAID/hybidmraidscaling.js",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Core/MRAID/hybidscaling.js",
"${PODS_ROOT}/HyBid/PubnativeLite/PubnativeLite/Core/MRAID/navigation_geolocation.js",
"${PODS_CONFIGURATION_BUILD_DIR}/IronSourceAdMobAdapter/ISAdMobResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/IronSourceSDK/IronSourcePrivacyInfo.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/MintegralAdSDK/MTGSDK.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/MolocoSDKiOS/MolocoSDK.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/OpenWrapSDK/OpenWrapSDKPrivacyManifest.bundle",
"${PODS_ROOT}/OpenWrapSDK/OpenWrapSDK/POBResources.bundle",
"${PODS_ROOT}/OpenWrapSDK/OpenWrapSDK/POBNativeResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/StackModules/StackModules.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/StackModules/StackProductPresentation.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/StackModules/StackRendering.bundle",
"${PODS_ROOT}/TPExchange/TPExchange/TradPlusADX.bundle",
"${PODS_ROOT}/TradPlusAdSDK/TradPlusAdSDK/TradPlusAds.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/UnityAds/UnityAdsResources.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/VungleAds/VungleAds.bundle",
"${PODS_CONFIGURATION_BUILD_DIR}/smaato-ios-sdk/SmaatoResources.bundle",
);
name = "[CP] Copy Pods Resources";
outputPaths = (
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PAGAdSDK.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AdsGlobalSDK.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/BidMachine.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ChartboostSDKResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBAudienceNetwork.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Fyber_Marketplace_SDK.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMobileAdsResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/UserMessagingPlatformResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GuruConsent.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/HyBidResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/close.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/close@2x.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/close@3x.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/HyBidCustomCTAView.nib",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/HyBidLocalizable.xcstrings",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/HyBidMRAIDCloseCardView.nib",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/InternalWebBrowser.storyboardc",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PNLiteExternalLink.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PNLiteExternalLink1.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PNLiteExternalLink1@2x.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PNLiteExternalLink1@3x.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PNLiteExternalLink@2x.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PNLiteFullScreen.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PNLiteFullScreen@2x.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PNLitePlay.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PNLitePlay@2x.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PNLiteSkip.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PNLiteVASTPlayerFullScreenViewController.nib",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PNLiteVASTPlayerInterstitialViewController.nib",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PNLiteVASTPlayerRewardedViewController.nib",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/PNLiteVASTPlayerViewController.nib",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/skip.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/skip@2x.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/skip@3x.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sound-off.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sound-off@2x.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sound-off@3x.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sound-on.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sound-on@2x.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/sound-on@3x.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/VerveContentInfo.png",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/omsdk.js",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/hybidmraidscaling.js",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/hybidscaling.js",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/navigation_geolocation.js",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ISAdMobResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/IronSourcePrivacyInfo.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MTGSDK.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MolocoSDK.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/OpenWrapSDKPrivacyManifest.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/POBResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/POBNativeResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/StackModules.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/StackProductPresentation.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/StackRendering.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TradPlusADX.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/TradPlusAds.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/UnityAdsResources.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/VungleAds.bundle",
"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/SmaatoResources.bundle",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-FusionAds_Example/Pods-FusionAds_Example-resources.sh\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
607FACCC1AFB9204008FA782 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
644E309B2D899D6A00050E04 /* HappyPathAdsSdk.swift in Sources */,
607FACD81AFB9204008FA782 /* ViewController.swift in Sources */,
607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
607FACE11AFB9204008FA782 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
607FACEC1AFB9204008FA782 /* Tests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
607FACE71AFB9204008FA782 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = 607FACCF1AFB9204008FA782 /* FusionAds_Example */;
targetProxy = 607FACE61AFB9204008FA782 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
607FACD91AFB9204008FA782 /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
607FACDA1AFB9204008FA782 /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
607FACDE1AFB9204008FA782 /* LaunchScreen.xib */ = {
isa = PBXVariantGroup;
children = (
607FACDF1AFB9204008FA782 /* Base */,
);
name = LaunchScreen.xib;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
607FACED1AFB9204008FA782 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = NO;
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_ON_DEMAND_RESOURCES = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
EXCLUDED_ARCHS = "";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SUPPORTS_MACCATALYST = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
607FACEE1AFB9204008FA782 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = NO;
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_ON_DEMAND_RESOURCES = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.3;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SUPPORTS_MACCATALYST = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
607FACF01AFB9204008FA782 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 64B020A92D93CCB200B0F238 /* FusionAdsExample.debug.xcconfig */;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = WP698B8M33;
EXCLUDED_ARCHS = "";
INFOPLIST_FILE = FusionAds/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MODULE_NAME = ExampleApp;
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SUPPORTS_MACCATALYST = YES;
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
607FACF11AFB9204008FA782 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 217D84A11B0D93B5D7BAEB44 /* Pods-FusionAds_Example.release.xcconfig */;
buildSettings = {
ALLOW_TARGET_PLATFORM_SPECIALIZATION = NO;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = SUAYNYNC5C;
EXCLUDED_ARCHS = "";
"EXCLUDED_ARCHS[sdk=*]" = "";
INFOPLIST_FILE = FusionAds/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
MODULE_NAME = ExampleApp;
ONLY_ACTIVE_ARCH = NO;
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos";
SUPPORTS_MACCATALYST = YES;
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 5.0;
};
name = Release;
};
607FACF31AFB9204008FA782 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 3DBB4B509E1C6B4B597B85ED /* Pods-FusionAds_Tests.debug.xcconfig */;
buildSettings = {
DEVELOPMENT_TEAM = SUAYNYNC5C;
FRAMEWORK_SEARCH_PATHS = (
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
"$(inherited)",
);
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
INFOPLIST_FILE = Tests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FusionAds_Example.app/FusionAds_Example";
};
name = Debug;
};
607FACF41AFB9204008FA782 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = FA9307EAC19AE275617F247A /* Pods-FusionAds_Tests.release.xcconfig */;
buildSettings = {
DEVELOPMENT_TEAM = SUAYNYNC5C;
FRAMEWORK_SEARCH_PATHS = (
"$(PLATFORM_DIR)/Developer/Library/Frameworks",
"$(inherited)",
);
INFOPLIST_FILE = Tests/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_SWIFT3_OBJC_INFERENCE = Default;
SWIFT_VERSION = 4.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/FusionAds_Example.app/FusionAds_Example";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
607FACCB1AFB9204008FA782 /* Build configuration list for PBXProject "FusionAds" */ = {
isa = XCConfigurationList;
buildConfigurations = (
607FACED1AFB9204008FA782 /* Debug */,
607FACEE1AFB9204008FA782 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
607FACEF1AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FusionAds_Example" */ = {
isa = XCConfigurationList;
buildConfigurations = (
607FACF01AFB9204008FA782 /* Debug */,
607FACF11AFB9204008FA782 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
607FACF21AFB9204008FA782 /* Build configuration list for PBXNativeTarget "FusionAds_Tests" */ = {
isa = XCConfigurationList;
buildConfigurations = (
607FACF31AFB9204008FA782 /* Debug */,
607FACF41AFB9204008FA782 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 607FACC81AFB9204008FA782 /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:FusionAds.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0900"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
BuildableName = "FusionAds_Example.app"
BlueprintName = "FusionAds_Example"
ReferencedContainer = "container:FusionAds.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "607FACE41AFB9204008FA782"
BuildableName = "FusionAds_Tests.xctest"
BlueprintName = "FusionAds_Tests"
ReferencedContainer = "container:FusionAds.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
BuildableName = "FusionAds_Example.app"
BlueprintName = "FusionAds_Example"
ReferencedContainer = "container:FusionAds.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "607FACE41AFB9204008FA782"
BuildableName = "FusionAds_Tests.xctest"
BlueprintName = "FusionAds_Tests"
ReferencedContainer = "container:FusionAds.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
BuildableName = "FusionAds_Example.app"
BlueprintName = "FusionAds_Example"
ReferencedContainer = "container:FusionAds.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "607FACCF1AFB9204008FA782"
BuildableName = "FusionAds_Example.app"
BlueprintName = "FusionAds_Example"
ReferencedContainer = "container:FusionAds.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:FusionAds.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,45 @@
//
// AppDelegate.swift
// FusionAds
//
// Created by Haoyi on 02/28/2025.
// Copyright (c) 2025 Haoyi. All rights reserved.
//
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
}

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 CocoaPods. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<rect key="frame" x="20" y="439" width="441" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="FusionAds" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
<rect key="frame" x="20" y="140" width="441" height="43"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="548" y="455"/>
</view>
</objects>
</document>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="vXZ-lx-hvc">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="ufC-wZ-h7g">
<objects>
<viewController id="vXZ-lx-hvc" customClass="ViewController" customModule="FusionAds_Example" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="jyV-Pf-zRb"/>
<viewControllerLayoutGuide type="bottom" id="2fi-mo-0CV"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="kh9-bI-dsS">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="x5A-6p-PRh" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,286 @@
//
// HappyPathAdsSdk.swift
// FusionAds
//
// Created by 250102 on 2025/3/18.
// Copyright © 2025 CocoaPods. All rights reserved.
//
import FusionAds
let queue = DispatchQueue.main
/// 广sdk
class HappyPathAdsSdk: GuruAdsSdk {
static let shared = HappyPathAdsSdk()
public private(set) var ads: [String: Any] = [:]
private init() {
}
public static func obtain(viewController: UIViewController) -> GuruAdsSdk {
return shared
}
public var adPlatform: FusionAds.AdPlatform {
return AdPlatform.test
}
public var adsProfile: FusionAds.AdsProfile? = nil
func initialize(adsProfile: FusionAds.AdsProfile) async -> Bool {
self.adsProfile = adsProfile
return true
}
func obtainInterstitialAd(adConfig: FusionAds.AdConfig) -> FusionAds.GuruInterstitialAd {
let ad = ads[adConfig.adUnitId] as? HappyPathInterstitialAd
if(ad != nil) {
return ad!
}
let newAd = HappyPathInterstitialAd(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: adPlatform)
ads[adConfig.adUnitId] = newAd
return newAd
}
func obtainRewardedAd(adConfig: FusionAds.AdConfig) -> FusionAds.GuruRewardedAd {
let ad = ads[adConfig.adUnitId] as? HappyPathRewardedAd
if(ad != nil) {
return ad!
}
let newAd = HappyPathRewardedAd(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: adPlatform)
ads[adConfig.adUnitId] = newAd
return newAd
}
func obtainBannerAd(adConfig: FusionAds.AdConfig) -> FusionAds.GuruBannerAd {
let ad = ads[adConfig.adUnitId] as? HappyPathBannerAd
if(ad != nil) {
return ad!
}
let newAd = HappyPathBannerAd(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: adPlatform)
ads[adConfig.adUnitId] = newAd
return newAd
}
func obtainMRecAd(adConfig: FusionAds.AdConfig) -> FusionAds.GuruMRecAd {
let ad = ads[adConfig.adUnitId] as? HappyPathMRecAd
if(ad != nil) {
return ad!
}
let newAd = HappyPathMRecAd(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: adPlatform)
ads[adConfig.adUnitId] = newAd
return newAd
}
func processCrossAction(action: FusionAds.AdsCrossAction) -> Bool {
return false
}
}
class HappyPathInterstitialAd : GuruInterstitialAd {
private var ad: HappyPathOriginalAd
public private(set) var state: HappyPathAdState = HappyPathAdState()
override init(engineId: Int, adUnitId: String, adPlatform: AdPlatform) {
ad = HappyPathOriginalAd(adPlatform: adPlatform, adUnitId: adUnitId)
super.init(engineId: engineId, adUnitId: adUnitId, adPlatform: adPlatform)
}
override public func load() -> Bool {
state.isLoaded = true
let fusionAd = HappyPathFusionAd(engineId: engineId, adType: AdType.Interstitial, originAd: ad)
queue.async {
[weak self] in
self?.listener?.onAdLoaded(ad: fusionAd)
}
return true;
}
override public func show(_ request: InterstitialShowRequest? = nil) -> Bool {
state.isShown = true
ad.placement = request?.placement
let fusionAd = HappyPathFusionAd(engineId: engineId, adType: AdType.Interstitial, originAd: ad)
queue.async {
[weak self] in
self?.listener?.onAdDisplayed(ad: fusionAd)
}
return true;
}
override public func destroy() -> Bool {
state.isDestroyed = true
return true;
}
}
class HappyPathRewardedAd : GuruRewardedAd {
private var ad: HappyPathOriginalAd
public private(set) var state: HappyPathAdState = HappyPathAdState()
override init(engineId: Int, adUnitId: String, adPlatform: AdPlatform) {
ad = HappyPathOriginalAd(adPlatform: adPlatform, adUnitId: adUnitId)
super.init(engineId: engineId, adUnitId: adUnitId, adPlatform: adPlatform)
}
override public func load() -> Bool {
let fusionAd = HappyPathFusionAd(engineId: engineId, adType: AdType.Rewarded, originAd: ad)
state.isLoaded = true
queue.async {
[weak self] in
self?.listener?.onAdLoaded(ad: fusionAd)
}
return true;
}
override public func show(_ request: RewardedShowRequest? = nil) -> Bool {
state.isShown = true
ad.placement = request?.placement
let fusionAd = HappyPathFusionAd(engineId: engineId, adType: AdType.Rewarded, originAd: ad)
queue.async {
[weak self] in
self?.listener?.onAdDisplayed(ad: fusionAd)
}
return true;
}
override public func destroy() -> Bool {
state.isDestroyed = true
return true;
}
}
class HappyPathBannerAd : GuruBannerAd {
private var ad: HappyPathOriginalAd
public private(set) var state: HappyPathAdState = HappyPathAdState()
public private(set) var lastShowRequest: BannerShowRequest? = nil
override init(engineId: Int, adUnitId: String, adPlatform: AdPlatform) {
ad = HappyPathOriginalAd(adPlatform: adPlatform, adUnitId: adUnitId)
super.init(engineId: engineId, adUnitId: adUnitId, adPlatform: adPlatform)
}
override public func load() -> Bool {
state.isLoaded = true
let fusionAd = HappyPathFusionAd(engineId: engineId, adType: AdType.Banner, originAd: ad)
queue.async {
[weak self] in
self?.listener?.onAdLoaded(ad: fusionAd)
}
return true;
}
override public func show(_ request: BannerShowRequest? = nil) -> Bool {
state.isShown = true
lastShowRequest = request
ad.placement = request?.placement
let fusionAd = HappyPathFusionAd(engineId: engineId, adType: AdType.Banner, originAd: ad)
return true;
}
override func hide() -> Bool {
state.isHidden = true
return true
}
override func destroy() -> Bool {
state.isDestroyed = true
return true
}
override func updateOrientation(orientation: ScreenOrientation) -> Bool {
state.orientation = orientation
return true
}
}
class HappyPathMRecAd : GuruMRecAd {
private var ad: HappyPathOriginalAd
public private(set) var state: HappyPathAdState = HappyPathAdState()
public private(set) var lastShowRequest: MRecShowRequest?
override init(engineId: Int, adUnitId: String, adPlatform: AdPlatform) {
ad = HappyPathOriginalAd(adPlatform: adPlatform, adUnitId: adUnitId)
super.init(engineId: engineId, adUnitId: adUnitId, adPlatform: adPlatform)
}
override public func load() -> Bool {
state.isLoaded = true
let fusionAd = HappyPathFusionAd(engineId: engineId, adType: AdType.MRec, originAd: ad)
queue.async {
[weak self] in
self?.listener?.onAdDisplayed(ad: fusionAd)
}
return true;
}
override public func show(request: MRecShowRequest? = nil) -> Bool {
state.isShown = true
lastShowRequest = request
ad.placement = request?.placement
let fusionAd = HappyPathFusionAd(engineId: engineId, adType: AdType.MRec, originAd: ad)
return true;
}
override func hide() -> Bool {
state.isHidden = true
return true
}
override func destroy() -> Bool {
state.isDestroyed = true
return true
}
override func updateOrientation(orientation: ScreenOrientation) -> Bool {
state.orientation = orientation
return true
}
}
struct HappyPathAdState {
var isLoaded = false
var isShown = false
var isHidden = false
var isDestroyed = false
var orientation: ScreenOrientation? = nil
}
struct HappyPathOriginalAd {
let adPlatform: AdPlatform
let adUnitId: String
var revenue: Double = 0
var waterfallName: String? = nil
var placement: String? = nil
var networkName: String? = nil
var networkPlacement: String? = nil
var creativeId: String? = nil
var adReviewCreativeId: String? = nil
}
class HappyPathFusionAd : FusionAd {
private let originAd: HappyPathOriginalAd
init(engineId: Int, adType: AdType, originAd: HappyPathOriginalAd) {
self.originAd = originAd
super.init(engineId: engineId, adType: adType)
}
override var adPlatform: AdPlatform { return originAd.adPlatform }
override var adUnitId: String? { return originAd.adUnitId }
override var revenue: Double { return originAd.revenue }
override var waterfallName: String? { return originAd.waterfallName }
override var placement: String? { return originAd.placement }
override var networkName: String? { return originAd.networkName }
override var networkPlacement: String? { return originAd.networkPlacement }
override var creativeId: String? { return originAd.creativeId }
override var adReviewCreativeId: String? { return originAd.adReviewCreativeId }
}

View File

@ -0,0 +1,53 @@
{
"images" : [
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "20x20",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "29x29",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "40x40",
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"scale" : "3x"
},
{
"idiom" : "ios-marketing",
"size" : "1024x1024",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,247 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
<key>NSAdvertisingAttributionReportEndpoint</key>
<string>https://postbacks-is.com</string>
<key>SKAdNetworkItems</key>
<array>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4fzdc2evr5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>2fnua5tdw4.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ydx93a7ass.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>p78axxw29g.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v72qych5uu.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ludvb6z3bs.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cp8zw746q7.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3sh42y64q3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>c6k4g5qg8m.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>s39g8k73mm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3qy4746246.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>f38h382jlk.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>hs6bdukanm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>mlmmfzh3r3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v4nxqhlyqp.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>wzmmz9fp6w.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>su67r6k2v3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>yclnxrl5pm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>t38b2kh725.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>7ug5zh24hu.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>gta9lk7p23.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>vutu7akeur.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>y5ghdn5j9k.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v9wttpbfk9.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>n38lu8286q.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>47vhws6wlr.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>kbd757ywx3.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>9t245vhmpl.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>a2p9lx4jpn.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>22mmun2rn5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>44jx6755aq.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>k674qkevps.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4468km3ulz.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>2u9pt9hc89.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>8s468mfl3y.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>klf5c3l5u5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>ppxm28t8ap.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>kbmxgpxpgc.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>uw77j35x4d.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>578prtvx9j.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>4dzt52r2t5.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>tl55sbb4fm.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>c3frkrj4fj.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>e5fvkxwrpn.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>8c4e2ghe7u.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3rd42ekr43.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>97r2b46745.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>3qcr597p9d.skadnetwork</string>
</dict>
</array>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
</array>
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-2436733915645843~7466230005</string>
</dict>
</plist>

View File

@ -0,0 +1,254 @@
//
// ViewController.swift
// FusionAds
//
// Created by Haoyi on 02/28/2025.
// Copyright (c) 2025 Haoyi. All rights reserved.
//
import UIKit
import FusionAds
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
Logger.setLogLevel(Logger.LogLevel.verbose)
FusionAdsSdk.registerExternalAdPlatformSdkMapping(platform: AdPlatform.test, initializer: HappyPathAdsSdk.obtain)
// testEngine()
// testMaxEngine()
// testIronSourceEngine()
// testAdMobEngine()
testMaxOfWithSameValue()
do {
try testAdsProfileJson()
try testAdEngineConfigJson()
testLogging()
} catch {}
}
func testAdEngineConfigJson() throws {
let config = AdEngineConfig(strategy: 0, faUnitId: "default", config: [
"test_key": "test_value",
"test_int_key": 1,
"test_bool_key": false,
"test_nested_array": ["1", "2", "3"],
"test_nested_dict": ["test":"value"]
])
let json = try! jsonEncode(config)
let configOut = try! jsonDecode(AdEngineConfig.self, from: json)
print(json)
print(configOut)
}
private func jsonEncode(_ value: any Codable) throws -> String {
let encoder = JSONEncoder()
let data = try! encoder.encode(value)
return String.init(data: data, encoding: .utf8)!
}
private func jsonDecode<T>(_ type: T.Type, from json: String) throws -> T where T : Decodable {
let jsonDecoder = JSONDecoder()
return try! jsonDecoder.decode(type, from: json.data(using: .utf8)!)
}
func testAdsProfileJson() throws {
let jsonEncoder = JSONEncoder()
let adsProfile = AdsProfile(adPlatforms: [AdPlatform.test], maxSdkKey: "")
let data = try! jsonEncoder.encode(adsProfile)
print(String.init(data: data, encoding: .utf8)!)
let jsonData = #"{"ad_platforms":["TEST"],"max_sdk_key":"","amazon_app_id":null,"pubmatic_store_url":null,"uid2_token":null,"user_id":"","tp_creative_key":"","debug_mode":false,"segments":{"segments":[]}}"#.data(using: .utf8)!
let jsonDecoder = JSONDecoder()
let adsProfileFromJson = try! jsonDecoder.decode(AdsProfile.self, from: jsonData)
print(adsProfileFromJson)
print(adsProfile)
}
@MainActor func testEngine() {
Task {
let sdk = FusionAdsSdk(controller: self)
let result = await sdk.initialize(adsProfile: AdsProfile(adPlatforms:[AdPlatform.max], maxSdkKey: "max_sdk_key"))
assert(result, "sdk initialized success!")
let engineConfigExtras = [
"ad_platform": "test",
"ad_unit_id" : "112233",
"ad_amz_slot_id": "test_slot"
]
let engine = sdk.createInterstitialAdEngine(adEngineConfig: AdEngineConfig(strategy: 0, faUnitId: "default", config: engineConfigExtras))
assert(engine != nil, "engine is Nil")
engine!.initialize()
try await Task.sleep(nanoseconds: 1000_000_000)
assert(engine!.load(), "load failed!")
try await Task.sleep(nanoseconds: 1000_000_000)
assert(engine!.show(InterstitialShowRequest(placement: "1234_mock")), "show failed!")
try await Task.sleep(nanoseconds: 1000_000_000)
assert(engine!.destroy(), "destroy failed!")
}
}
func testMaxEngine() {
testPlatformEngine(adPlatform: AdPlatform.max, adUnitId: "87a262be42a9b334")
}
func testIronSourceEngine() {
testPlatformEngine(adPlatform: AdPlatform.ironSource, adUnitId: "wmgt0712uuux8ju4")
}
func testAdMobEngine() {
testPlatformEngine(adPlatform: AdPlatform.adMob, adUnitId: "ca-app-pub-3940256099942544/4411468910")
}
var logger: FileLogHandler? = nil
var rotationLogger: FileLogHandler? = nil
var levelLogger: FileLogHandler? = nil
var manualRotationLogger: FileLogHandler? = nil
func testLogging() {
print("开始测试 FileLogHandler...")
let fileManager = FileManager.default
//
let documentsDir = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
let testLogDir = documentsDir.appendingPathComponent("Logs/guru_app", isDirectory: true)
// 1:
print("测试1: 基本日志记录")
let logger = FileLogHandler(directory: "guru_app", filename: "test.log")
logger.log("test", "这是一条调试消息", level: .debug)
logger.log("test", "这是一条信息消息", level: .info)
logger.log("test", "这是一条警告消息", level: .warning)
logger.log("test", "这是一条错误消息", level: .error)
logger.log("test", "这是一条致命消息", level: .fatal)
logger.log("test", "自定义日志级别消息", level: .info)
self.logger = logger
print("基本日志写入完成,日志文件位置: \(testLogDir.appendingPathComponent("test.log").path)")
// 3:
print("测试3: 测试日志轮转")
let rotationConfig = FileLogHandler.RotationConfig(
maxFileSize: 1024, // 1KB便
maxBackupCount: 3 // 3
)
let rotationLogger = FileLogHandler(
directory: "guru_app",
filename: "rotation_test.log",
rotationConfig: rotationConfig
)
self.rotationLogger = rotationLogger
//
print("写入数据以触发日志轮转...")
for i in 1...100 {
rotationLogger.log("test", "这是日志条目 #\(i): " + String(repeating: "测试数据 ", count: 10), level: .info)
}
//
if let files = try? fileManager.contentsOfDirectory(atPath: testLogDir.path) {
let rotationFiles = files.filter { $0.starts(with: "rotation_test.log") }
print("找到的轮转日志文件: \(rotationFiles)")
}
// 4:
print("测试4: 日志级别过滤")
let levelLogger = FileLogHandler(
directory: "guru_app",
filename: "level_test.log",
minimumLogLevel: .warning
)
self.levelLogger = levelLogger
levelLogger.log("level_test", "这是一条调试消息", level: .debug)
levelLogger.log("level_test", "这是一条信息消息", level: .info)
levelLogger.log("level_test", "这是一条警告消息", level: .warning)
levelLogger.log("level_test", "这是一条错误消息", level: .error)
levelLogger.log("level_test", "这是一条致命消息", level: .fatal)
levelLogger.log("level_test", "自定义日志级别消息", level: .info)
print("级别过滤测试完成")
// 5:
print("测试5: 手动轮转")
let manualRotationLogger = FileLogHandler(directory: "guru_app", filename: "manual_rotation.log")
manualRotationLogger.log("test", "轮转前的日志条目")
manualRotationLogger.rotateLogFile()
manualRotationLogger.log("test", "轮转后的日志条目")
print("手动轮转测试完成")
self.manualRotationLogger = manualRotationLogger
// 6:
print("测试6: 轮转后继续写入")
for i in 1...5 {
rotationLogger.log("test", "轮转后的日志条目 #\(i)")
}
print("轮转后写入测试完成")
//
func readLogFile(at path: String) -> String? {
return try? String(contentsOfFile: path, encoding: .utf8)
}
if let logContent = readLogFile(at: testLogDir.appendingPathComponent("test.log").path) {
print("基本日志文件的前200个字符:")
print(String(logContent.prefix(200)))
}
print("FileLogHandler 测试完成")
}
func testPlatformEngine(adPlatform: AdPlatform, adUnitId: String) {
Task {@MainActor in
let sdk = FusionAdsSdk(controller: self)
let result = await sdk.initialize(adsProfile: AdsProfile(adPlatforms:[adPlatform], maxSdkKey: "V5I0i-vOTXkc_HEyqLg0lNm_ivlzp1wPVF3Vs7Jk4ix6WMAwEKGHMpugavZp_7xgss186Frvss23NGSWZXSago", ironSourceSdkKey: "8545d445"))
assert(result, "sdk initialized success!")
let engineConfigExtras = [
"ad_platform": adPlatform.name,
"ad_unit_id" : adUnitId,
]
let engine = sdk.createInterstitialAdEngine(adEngineConfig: AdEngineConfig(strategy: 0, faUnitId: "default", config: engineConfigExtras))
assert(engine != nil, "engine is Nil")
engine!.initialize()
try await Task.sleep(nanoseconds: 1000_000_000)
assert(engine!.load(), "load failed!")
try await Task.sleep(nanoseconds: 20_000_000_000)
assert(engine!.currentStateIdentifier == InterstitialAdEngine.AdState.Identifier.LOADED, "not loaded after 20s, current state is \(engine!.currentStateIdentifier.name)")
assert(engine!.show(InterstitialShowRequest(placement: "1234_mock")), "show failed!")
try await Task.sleep(nanoseconds: 5000_000_000)
assert(engine!.destroy(), "destroy failed!")
}
}
func testMaxOfWithSameValue() {
let list = [TestValue("A", 1), TestValue("B", 2), TestValue("C", 3), TestValue("D", 3)]
let value = list.max(by: { $0.value <= $1.value })
assert(value?.name == "D")
print("Yes!")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
private struct TestValue {
let name: String;
let value: Int;
init(_ name: String, _ value: Int) {
self.name = name
self.value = value
}
}

View File

@ -0,0 +1,7 @@
#include? "Pods/Target Support Files/Pods-FusionAds_Example/Pods-FusionAds_Example.debug.xcconfig"
COCOAPODS_PARALLEL_CODE_SIGN=true
EXCLUDED_ARCHS[sdk=iphonesimulator*]=i386 arm64
EXCLUDED_ARCHS[sdk=iphoneos*]=armv7

46
Example/Podfile Normal file
View File

@ -0,0 +1,46 @@
platform :ios, '13.0'
#source 'https://cdn.cocoapods.org/'
source 'https://github.com/CocoaPods/Specs.git'
source 'git@github.com:castbox/GuruSpecs.git'
target 'FusionAds_Example' do
use_frameworks! :linkage => :static
use_modular_headers!
pod 'FusionAds', :path => '../'
target 'FusionAds_Tests' do
inherit! :search_paths
pod 'Quick', '~> 7.6.2'
pod 'Nimble', '~> 13.7.1'
# pod 'FBSnapshotTestCase' , '~> 2.1.4'
# pod 'Nimble-Snapshots' , '~> 9.4.0'
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '13.0'
config.build_settings['ENABLE_BITCODE'] = 'NO'
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
'AUDIO_SESSION_MICROPHONE=0',
## dart: PermissionGroup.photos
'PERMISSION_PHOTOS=1',
## dart: PermissionGroup.notification
'PERMISSION_NOTIFICATIONS=1'
]
config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO' if config.type == :debug
config.build_settings['CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER'] = 'NO'
# Override legacy Xcode 11 style VALID_ARCHS[sdk=iphonesimulator*]=x86_64 and prefer Xcode 12 EXCLUDED_ARCHS.
config.build_settings['VALID_ARCHS[sdk=iphonesimulator*]'] = '$(ARCHS_STANDARD)'
config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = '$(inherited) i386'
config.build_settings['EXCLUDED_ARCHS[sdk=iphoneos*]'] = '$(inherited) armv7'
end
end
end

518
Example/Podfile.lock Normal file
View File

@ -0,0 +1,518 @@
PODS:
- Ads-Global (6.5.0.8):
- Ads-Global/BUAdSDK (= 6.5.0.8)
- Ads-Global/BUAdSDK (6.5.0.8)
- AmazonPublisherServicesSDK (5.0.1)
- AppLovinMediationAmazonAdMarketplaceAdapter (5.0.1.0):
- AppLovinSDK
- AppLovinMediationBidMachineAdapter (3.2.0.0.0):
- AppLovinSDK (>= 13.0.0)
- BidMachine (= 3.2.0)
- AppLovinMediationByteDanceAdapter (6.5.0.8.0):
- Ads-Global (= 6.5.0.8)
- AppLovinSDK (>= 13.0.0)
- AppLovinMediationChartboostAdapter (9.8.0.0):
- AppLovinSDK (>= 13.0.0)
- ChartboostSDK (= 9.8.0)
- AppLovinMediationFacebookAdapter (6.16.0.1):
- AppLovinSDK (>= 13.0.0)
- FBAudienceNetwork (= 6.16.0)
- AppLovinMediationFyberAdapter (8.3.5.0):
- AppLovinSDK (>= 13.0.0)
- Fyber_Marketplace_SDK (= 8.3.5)
- AppLovinMediationGoogleAdapter (11.13.0.1):
- AppLovinSDK (>= 13.0.0)
- Google-Mobile-Ads-SDK (= 11.13.0)
- AppLovinMediationGoogleAdManagerAdapter (11.13.0.1):
- AppLovinSDK (>= 13.0.0)
- Google-Mobile-Ads-SDK (= 11.13.0)
- AppLovinMediationInMobiAdapter (10.8.0.0):
- AppLovinSDK (>= 13.0.0)
- InMobiSDK (= 10.8.0)
- AppLovinMediationMintegralAdapter (7.7.5.0.0):
- AppLovinSDK (>= 13.0.0)
- MintegralAdSDK (= 7.7.5)
- MintegralAdSDK/BidSplashAd (= 7.7.5)
- AppLovinMediationMobileFuseAdapter (1.9.0.0):
- AppLovinSDK (>= 13.0.0)
- MobileFuseSDK (= 1.9.0)
- AppLovinMediationMolocoAdapter (3.7.0.0):
- AppLovinSDK (>= 13.0.0)
- MolocoSDKiOS (= 3.7.0)
- AppLovinMediationOguryPresageAdapter (5.0.2.0):
- AppLovinSDK
- OgurySdk (= 5.0.2)
- AppLovinMediationPubMaticAdapter (4.4.0.0):
- AppLovinSDK (>= 13.0.0)
- OpenWrapSDK (= 4.4.0)
- AppLovinMediationSmaatoAdapter (22.9.2.0):
- AppLovinSDK (>= 13.0.0)
- smaato-ios-sdk (= 22.9.2)
- smaato-ios-sdk/InApp (= 22.9.2)
- AppLovinMediationUnityAdsAdapter (4.13.2.0):
- AppLovinSDK (>= 13.0.0)
- UnityAds (= 4.13.2)
- AppLovinMediationVerveAdapter (3.2.0.0):
- AppLovinSDK (>= 13.0.0)
- HyBid (= 3.2.0)
- AppLovinMediationVungleAdapter (7.4.4.0):
- AppLovinSDK (>= 13.0.0)
- VungleAds (= 7.4.4)
- AppLovinSDK (13.1.0)
- ATOM-Standalone (3.5.0)
- BidMachine (3.2.0):
- BidMachine/Static (= 3.2.0)
- BidMachine/Static (3.2.0):
- StackModules/Static (~> 3.1.0)
- ChartboostSDK (9.8.0)
- CwlCatchException (2.2.1):
- CwlCatchExceptionSupport (~> 2.2.1)
- CwlCatchExceptionSupport (2.2.1)
- CwlMachBadInstructionHandler (2.2.2)
- CwlPosixPreconditionTesting (2.2.2)
- CwlPreconditionTesting (2.2.2):
- CwlCatchException (~> 2.2.1)
- CwlMachBadInstructionHandler (~> 2.2.2)
- CwlPosixPreconditionTesting (~> 2.2.2)
- FBAudienceNetwork (6.16.0)
- FusionAds (1.3.0):
- FusionAds/Main (= 1.3.0)
- FusionAds/AdMobAdapters (1.3.0):
- Google-Mobile-Ads-SDK (= 11.13.0)
- GoogleMobileAdsMediationChartboost (= 9.8.0.0)
- GoogleMobileAdsMediationFacebook (= 6.16.0.0)
- GoogleMobileAdsMediationFyber (= 8.3.5.0)
- GoogleMobileAdsMediationInMobi (= 10.8.0.0)
- GoogleMobileAdsMediationMintegral (= 7.7.5.0)
- GoogleMobileAdsMediationVungle (= 7.4.4.0)
- FusionAds/IronSourceAdapters (1.3.0):
- IronSourceAdMobAdapter (= 4.3.60.0)
- IronSourceAppLovinAdapter (= 4.3.52.0)
- IronSourceAPSAdapter (= 4.3.18.0)
- IronSourceBidMachineAdapter (= 4.3.14.0)
- IronSourceFacebookAdapter (= 4.3.47.0)
- IronSourceFyberAdapter (= 4.3.41.0)
- IronSourceInMobiAdapter (= 4.3.26.0)
- IronSourceMintegralAdapter (= 4.3.31.0)
- IronSourceMolocoAdapter (= 4.3.11.0)
- IronSourcePangleAdapter (= 4.3.40.0)
- IronSourceSDK (= 8.7.0.0)
- IronSourceSmaatoAdapter (= 4.3.16.0)
- IronSourceUnityAdsAdapter (= 4.3.48.0)
- IronSourceVungleAdapter (= 4.3.42.0)
- FusionAds/Main (1.3.0):
- FusionAds/AdMobAdapters
- FusionAds/IronSourceAdapters
- FusionAds/MaxAdapters
- FusionAds/MaxAdapters (1.3.0):
- AmazonPublisherServicesSDK (= 5.0.1)
- AppLovinMediationAmazonAdMarketplaceAdapter (= 5.0.1.0)
- AppLovinMediationBidMachineAdapter (= 3.2.0.0.0)
- AppLovinMediationByteDanceAdapter (= 6.5.0.8.0)
- AppLovinMediationChartboostAdapter (= 9.8.0.0)
- AppLovinMediationFacebookAdapter (= 6.16.0.1)
- AppLovinMediationFyberAdapter (= 8.3.5.0)
- AppLovinMediationGoogleAdapter (= 11.13.0.1)
- AppLovinMediationGoogleAdManagerAdapter (= 11.13.0.1)
- AppLovinMediationInMobiAdapter (= 10.8.0.0)
- AppLovinMediationMintegralAdapter (= 7.7.5.0.0)
- AppLovinMediationMobileFuseAdapter (= 1.9.0.0)
- AppLovinMediationMolocoAdapter (= 3.7.0.0)
- AppLovinMediationOguryPresageAdapter (= 5.0.2.0)
- AppLovinMediationPubMaticAdapter (= 4.4.0.0)
- AppLovinMediationSmaatoAdapter (= 22.9.2.0)
- AppLovinMediationUnityAdsAdapter (= 4.13.2.0)
- AppLovinMediationVerveAdapter (= 3.2.0.0)
- AppLovinMediationVungleAdapter (= 7.4.4.0)
- AppLovinSDK (= 13.1.0)
- GuruConsent (= 1.4.6)
- TradPlusAdSDK (= 10.9.0)
- Fyber_Marketplace_SDK (8.3.5)
- Google-Mobile-Ads-SDK (11.13.0):
- GoogleUserMessagingPlatform (>= 1.1)
- GoogleMobileAdsMediationChartboost (9.8.0.0):
- ChartboostSDK (= 9.8.0)
- Google-Mobile-Ads-SDK (~> 11.0)
- GoogleMobileAdsMediationFacebook (6.16.0.0):
- FBAudienceNetwork (= 6.16.0)
- Google-Mobile-Ads-SDK (~> 11.0)
- GoogleMobileAdsMediationFyber (8.3.5.0):
- Fyber_Marketplace_SDK (= 8.3.5)
- Google-Mobile-Ads-SDK (~> 11.0)
- GoogleMobileAdsMediationInMobi (10.8.0.0):
- Google-Mobile-Ads-SDK (~> 11.0)
- InMobiSDK (= 10.8.0)
- GoogleMobileAdsMediationMintegral (7.7.5.0):
- Google-Mobile-Ads-SDK (~> 11.0)
- MintegralAdSDK/All (= 7.7.5)
- GoogleMobileAdsMediationVungle (7.4.4.0):
- Google-Mobile-Ads-SDK (~> 11.0)
- VungleAds (= 7.4.4)
- GoogleUserMessagingPlatform (2.3.0)
- GuruConsent (1.4.6):
- GoogleUserMessagingPlatform (= 2.3.0)
- GuruConsent/Privacy (= 1.4.6)
- GuruConsent/Privacy (1.4.6):
- GoogleUserMessagingPlatform (= 2.3.0)
- HyBid (3.2.0):
- HyBid/ATOM (= 3.2.0)
- HyBid/Banner (= 3.2.0)
- HyBid/Core (= 3.2.0)
- HyBid/FullScreen (= 3.2.0)
- HyBid/Native (= 3.2.0)
- HyBid/RewardedVideo (= 3.2.0)
- HyBid/ATOM (3.2.0):
- ATOM-Standalone (~> 3.5.0)
- HyBid/Core
- HyBid/Banner (3.2.0):
- HyBid/Core
- HyBid/Core (3.2.0)
- HyBid/FullScreen (3.2.0):
- HyBid/Core
- HyBid/Native (3.2.0):
- HyBid/Core
- HyBid/RewardedVideo (3.2.0):
- HyBid/Core
- InMobiSDK (10.8.0)
- IronSourceAdMobAdapter (4.3.60.0):
- Google-Mobile-Ads-SDK (= 11.13.0)
- IronSourceSDK (~> 8.4)
- IronSourceAppLovinAdapter (4.3.52.0):
- AppLovinSDK (= 13.1.0)
- IronSourceSDK (~> 8.4)
- IronSourceAPSAdapter (4.3.18.0):
- AmazonPublisherServicesSDK (= 5.0.1)
- IronSourceSDK (~> 8.4)
- IronSourceBidMachineAdapter (4.3.14.0):
- BidMachine (= 3.2.0)
- IronSourceSDK (~> 8.4)
- IronSourceFacebookAdapter (4.3.47.0):
- FBAudienceNetwork (= 6.16.0)
- IronSourceSDK (~> 8.5)
- IronSourceFyberAdapter (4.3.41.0):
- Fyber_Marketplace_SDK (= 8.3.5)
- IronSourceSDK (~> 8.4)
- IronSourceInMobiAdapter (4.3.26.0):
- InMobiSDK (= 10.8.0)
- IronSourceSDK (~> 8.4)
- IronSourceMintegralAdapter (4.3.31.0):
- IronSourceSDK (~> 8.4)
- MintegralAdSDK (= 7.7.5)
- IronSourceMolocoAdapter (4.3.11.0):
- IronSourceSDK (~> 8.4)
- MolocoSDKiOS (= 3.7.0)
- IronSourcePangleAdapter (4.3.40.0):
- Ads-Global (= 6.5.0.8)
- IronSourceSDK (~> 8.4)
- IronSourceSDK (8.7.0.0)
- IronSourceSmaatoAdapter (4.3.16.0):
- IronSourceSDK (~> 8.4)
- smaato-ios-sdk (= 22.9.2)
- smaato-ios-sdk/InApp (= 22.9.2)
- IronSourceUnityAdsAdapter (4.3.48.0):
- IronSourceSDK (~> 8.4)
- UnityAds (= 4.13.2)
- IronSourceVungleAdapter (4.3.42.0):
- IronSourceSDK (~> 8.4)
- VungleAds (= 7.4.4)
- MintegralAdSDK (7.7.5):
- MintegralAdSDK/BannerAd (= 7.7.5)
- MintegralAdSDK/BidBannerAd (= 7.7.5)
- MintegralAdSDK/BidInterstitialVideoAd (= 7.7.5)
- MintegralAdSDK/BidNativeAd (= 7.7.5)
- MintegralAdSDK/BidNewInterstitialAd (= 7.7.5)
- MintegralAdSDK/BidRewardVideoAd (= 7.7.5)
- MintegralAdSDK/InterstitialVideoAd (= 7.7.5)
- MintegralAdSDK/NativeAd (= 7.7.5)
- MintegralAdSDK/NewInterstitialAd (= 7.7.5)
- MintegralAdSDK/RewardVideoAd (= 7.7.5)
- MintegralAdSDK/All (7.7.5):
- MintegralAdSDK/BannerAd
- MintegralAdSDK/BidNativeAd
- MintegralAdSDK/InterstitialVideoAd
- MintegralAdSDK/NativeAd
- MintegralAdSDK/NativeAdvancedAd
- MintegralAdSDK/NewInterstitialAd
- MintegralAdSDK/RewardVideoAd
- MintegralAdSDK/SplashAd
- MintegralAdSDK/BannerAd (7.7.5):
- MintegralAdSDK/NativeAd
- MintegralAdSDK/BidBannerAd (7.7.5):
- MintegralAdSDK/BannerAd
- MintegralAdSDK/BidNativeAd
- MintegralAdSDK/BidInterstitialVideoAd (7.7.5):
- MintegralAdSDK/BidNativeAd
- MintegralAdSDK/InterstitialVideoAd
- MintegralAdSDK/BidNativeAd (7.7.5):
- MintegralAdSDK/NativeAd
- MintegralAdSDK/BidNewInterstitialAd (7.7.5):
- MintegralAdSDK/BidNativeAd
- MintegralAdSDK/NewInterstitialAd
- MintegralAdSDK/BidRewardVideoAd (7.7.5):
- MintegralAdSDK/BidNativeAd
- MintegralAdSDK/RewardVideoAd
- MintegralAdSDK/BidSplashAd (7.7.5):
- MintegralAdSDK/BidNativeAd
- MintegralAdSDK/SplashAd
- MintegralAdSDK/InterstitialVideoAd (7.7.5):
- MintegralAdSDK/NativeAd
- MintegralAdSDK/NativeAd (7.7.5)
- MintegralAdSDK/NativeAdvancedAd (7.7.5):
- MintegralAdSDK/NativeAd
- MintegralAdSDK/NewInterstitialAd (7.7.5):
- MintegralAdSDK/InterstitialVideoAd
- MintegralAdSDK/NativeAd
- MintegralAdSDK/RewardVideoAd (7.7.5):
- MintegralAdSDK/NativeAd
- MintegralAdSDK/SplashAd (7.7.5):
- MintegralAdSDK/NativeAd
- MobileFuseSDK (1.9.0)
- MolocoSDKiOS (3.7.0)
- Nimble (13.7.1):
- CwlPreconditionTesting (~> 2.2.0)
- OguryAds (4.0.2):
- OguryAds/OMID (= 4.0.2)
- OguryCore (= 2.0.0)
- OguryAds/OMID (4.0.2):
- OguryCore (= 2.0.0)
- OguryCore (2.0.0)
- OgurySdk (5.0.2):
- OguryCore (= 2.0.0)
- OgurySdk/OguryAds (= 5.0.2)
- OgurySdk/OguryAds (5.0.2):
- OguryAds (= 4.0.2)
- OguryCore (= 2.0.0)
- OMSDK_Appodeal (1.5.4)
- OpenWrapSDK (4.4.0):
- OpenWrapSDK/OpenWrap (= 4.4.0)
- OpenWrapSDK/OpenWrap (4.4.0)
- Quick (7.6.2)
- smaato-ios-sdk (22.9.2):
- smaato-ios-sdk/Full (= 22.9.2)
- smaato-ios-sdk/Banner (22.9.2):
- smaato-ios-sdk/Modules/Banner
- smaato-ios-sdk/Modules/Core
- smaato-ios-sdk/Modules/RichMedia
- smaato-ios-sdk/Full (22.9.2):
- smaato-ios-sdk/Banner
- smaato-ios-sdk/Interstitial
- smaato-ios-sdk/Native
- smaato-ios-sdk/Outstream
- smaato-ios-sdk/RewardedAds
- smaato-ios-sdk/InApp (22.9.2):
- smaato-ios-sdk/Modules/Core
- smaato-ios-sdk/Interstitial (22.9.2):
- smaato-ios-sdk/Modules/Interstitial
- smaato-ios-sdk/Modules/RichMedia
- smaato-ios-sdk/Modules/Video
- smaato-ios-sdk/Modules/Banner (22.9.2):
- smaato-ios-sdk/Modules/Core
- smaato-ios-sdk/Modules/Core (22.9.2)
- smaato-ios-sdk/Modules/Interstitial (22.9.2):
- smaato-ios-sdk/Modules/Core
- smaato-ios-sdk/Modules/Native (22.9.2):
- smaato-ios-sdk/Modules/Core
- smaato-ios-sdk/Modules/OpenMeasurement (22.9.2):
- smaato-ios-sdk/Modules/Core
- smaato-ios-sdk/Modules/Outstream (22.9.2):
- smaato-ios-sdk/Modules/Banner
- smaato-ios-sdk/Modules/Core
- smaato-ios-sdk/Modules/RichMedia
- smaato-ios-sdk/Modules/RewardedAds (22.9.2):
- smaato-ios-sdk/Modules/Core
- smaato-ios-sdk/Modules/RichMedia (22.9.2):
- smaato-ios-sdk/Modules/Core
- smaato-ios-sdk/Modules/OpenMeasurement
- smaato-ios-sdk/Modules/Video (22.9.2):
- smaato-ios-sdk/Modules/Core
- smaato-ios-sdk/Modules/OpenMeasurement
- smaato-ios-sdk/Native (22.9.2):
- smaato-ios-sdk/Modules/Core
- smaato-ios-sdk/Modules/Native
- smaato-ios-sdk/Outstream (22.9.2):
- smaato-ios-sdk/Modules/Outstream
- smaato-ios-sdk/RewardedAds (22.9.2):
- smaato-ios-sdk/Modules/RewardedAds
- smaato-ios-sdk/Modules/Video
- StackModules/Core-Static (3.1.0)
- StackModules/ProductPresentation-Static (3.1.0):
- StackModules/Core-Static
- StackModules/Rendering-Static (3.1.0):
- OMSDK_Appodeal (~> 1.5.2)
- StackModules/Core-Static
- StackModules/ProductPresentation-Static
- StackModules/Static (3.1.0):
- StackModules/Core-Static
- StackModules/ProductPresentation-Static
- StackModules/Rendering-Static
- TPExchange (10.9.0)
- TradPlusAdSDK (10.9.0):
- TradPlusAdSDK/TradPlusAds (= 10.9.0)
- TradPlusAdSDK/TradPlusAds (10.9.0):
- TPExchange (= 10.9.0)
- UnityAds (4.13.2)
- VungleAds (7.4.4)
DEPENDENCIES:
- FusionAds (from `../`)
- Nimble (~> 13.7.1)
- Quick (~> 7.6.2)
SPEC REPOS:
"git@github.com:castbox/GuruSpecs.git":
- GuruConsent
https://github.com/CocoaPods/Specs.git:
- Ads-Global
- AmazonPublisherServicesSDK
- AppLovinMediationAmazonAdMarketplaceAdapter
- AppLovinMediationBidMachineAdapter
- AppLovinMediationByteDanceAdapter
- AppLovinMediationChartboostAdapter
- AppLovinMediationFacebookAdapter
- AppLovinMediationFyberAdapter
- AppLovinMediationGoogleAdapter
- AppLovinMediationGoogleAdManagerAdapter
- AppLovinMediationInMobiAdapter
- AppLovinMediationMintegralAdapter
- AppLovinMediationMobileFuseAdapter
- AppLovinMediationMolocoAdapter
- AppLovinMediationOguryPresageAdapter
- AppLovinMediationPubMaticAdapter
- AppLovinMediationSmaatoAdapter
- AppLovinMediationUnityAdsAdapter
- AppLovinMediationVerveAdapter
- AppLovinMediationVungleAdapter
- AppLovinSDK
- ATOM-Standalone
- BidMachine
- ChartboostSDK
- CwlCatchException
- CwlCatchExceptionSupport
- CwlMachBadInstructionHandler
- CwlPosixPreconditionTesting
- CwlPreconditionTesting
- FBAudienceNetwork
- Fyber_Marketplace_SDK
- Google-Mobile-Ads-SDK
- GoogleMobileAdsMediationChartboost
- GoogleMobileAdsMediationFacebook
- GoogleMobileAdsMediationFyber
- GoogleMobileAdsMediationInMobi
- GoogleMobileAdsMediationMintegral
- GoogleMobileAdsMediationVungle
- GoogleUserMessagingPlatform
- HyBid
- InMobiSDK
- IronSourceAdMobAdapter
- IronSourceAppLovinAdapter
- IronSourceAPSAdapter
- IronSourceBidMachineAdapter
- IronSourceFacebookAdapter
- IronSourceFyberAdapter
- IronSourceInMobiAdapter
- IronSourceMintegralAdapter
- IronSourceMolocoAdapter
- IronSourcePangleAdapter
- IronSourceSDK
- IronSourceSmaatoAdapter
- IronSourceUnityAdsAdapter
- IronSourceVungleAdapter
- MintegralAdSDK
- MobileFuseSDK
- MolocoSDKiOS
- Nimble
- OguryAds
- OguryCore
- OgurySdk
- OMSDK_Appodeal
- OpenWrapSDK
- Quick
- smaato-ios-sdk
- StackModules
- TPExchange
- TradPlusAdSDK
- UnityAds
- VungleAds
EXTERNAL SOURCES:
FusionAds:
:path: "../"
SPEC CHECKSUMS:
Ads-Global: a5f72fa6a7de699d8190c22fbcb3cd39a42440f0
AmazonPublisherServicesSDK: 2c897ab0edf7d6f7bf2bcc6c16d63530dc7ec9c1
AppLovinMediationAmazonAdMarketplaceAdapter: 66c8897a2e120510f0fab0a645e989d6a860cf24
AppLovinMediationBidMachineAdapter: d7f9c6e5c391215ad5f5ec14275f6373999bb5c6
AppLovinMediationByteDanceAdapter: a08b5c8d4bfcff434079a5c35fc1be14bd9d3b95
AppLovinMediationChartboostAdapter: cff01475136bfb065c993b2f0a1e865842ab9f71
AppLovinMediationFacebookAdapter: f75ccd71f9d4f5425a7278f547f966551f0cd493
AppLovinMediationFyberAdapter: 59595bef15b724090c56751a1b1799df179dc1e1
AppLovinMediationGoogleAdapter: 9b56c6a93704215bc2fc366f77e7b0684444b5b5
AppLovinMediationGoogleAdManagerAdapter: 72943b93d0f4de003cdd89d35fb288ddaf4e8231
AppLovinMediationInMobiAdapter: 3c85806a4d9a96a58e894eff1b652fa38d4823d8
AppLovinMediationMintegralAdapter: 3116fbb59153ae3270ee252c878f7c5a8d6c0a6c
AppLovinMediationMobileFuseAdapter: 8b9c72c8cb55b3bdf44134bd285d54fa9ad983b0
AppLovinMediationMolocoAdapter: db6759753c21b880324611643f31c3af91009078
AppLovinMediationOguryPresageAdapter: e7f82ffd005d550b588b7ac4edc3e502e409548c
AppLovinMediationPubMaticAdapter: c5755d31ac44f785a18470f8d4fd0d24b599344b
AppLovinMediationSmaatoAdapter: 4610a78ce03df904f805d7d9ee9b78742cc95d50
AppLovinMediationUnityAdsAdapter: cf75181e59274a29740309dd4f64c37b299e1d4c
AppLovinMediationVerveAdapter: a328d229a9b8d8e7ccd0d738a814467e1f47ca83
AppLovinMediationVungleAdapter: a288c70db5ebb967c2e4b0cb3f2fc1ffc6ac9fae
AppLovinSDK: 539a0178d8bef4d68b2551a93526e0c1bba06cb2
ATOM-Standalone: c6f8d00c510cdeec33aa95cdf1f31d78b62d0aa7
BidMachine: b5d8236e691a0c71fbb95a72e86f3ec189f1a8a9
ChartboostSDK: f8422957daa675fa4a0bb9f159144b3b59ef5c27
CwlCatchException: 7acc161b299a6de7f0a46a6ed741eae2c8b4d75a
CwlCatchExceptionSupport: 54ccab8d8c78907b57f99717fb19d4cc3bce02dc
CwlMachBadInstructionHandler: dae4fdd124d45c9910ac240287cc7b898f4502a1
CwlPosixPreconditionTesting: ecd095aa2129e740b44301c34571e8d85906fb88
CwlPreconditionTesting: 67a0047dd4de4382b93442c0e3f25207f984f35a
FBAudienceNetwork: d1670939884e3a2e0ad98dca98d7e0c841417228
FusionAds: ed5b00827e7d467f8e6e01919e3926255bfd492d
Fyber_Marketplace_SDK: 8b042ecd850accf338a052ca23a4e105f71154ec
Google-Mobile-Ads-SDK: 14f57f2dc33532a24db288897e26494640810407
GoogleMobileAdsMediationChartboost: b6815e7b1ce697b08d752cef1cb24248f3598f75
GoogleMobileAdsMediationFacebook: 234b9cecc2f9c751567a71d035974c00e6803777
GoogleMobileAdsMediationFyber: d4d0945274d92cc63f03e58de0c3c4862a1aacb8
GoogleMobileAdsMediationInMobi: 1c09a0189c485bba2b3edc9ab0e062b9a716929f
GoogleMobileAdsMediationMintegral: fa41384b29c36699bf703ab75ca6407e59d29b9b
GoogleMobileAdsMediationVungle: d090c301d492c458986c447972808c8d9efc1439
GoogleUserMessagingPlatform: b1ad7bb1ee3ce64749d4fec24f667b36e45c396c
GuruConsent: 281da01094a2ba49ac81ad71bbbdf9ce39d405a1
HyBid: 575a647ce48fb4ed40dab1a1ce203b1ae0cdb294
InMobiSDK: 0cfb1a1eb5112fa0236ae1fab549e31fb96c9566
IronSourceAdMobAdapter: eab2e36036324f865f113c9ccdd8c919f888e335
IronSourceAppLovinAdapter: d6a1cac7a5e8143cdab3edd301bdc166957bba93
IronSourceAPSAdapter: 2aa3ccf44a220b2a38fae57817c3c73a945b6b22
IronSourceBidMachineAdapter: a514e6b5310917ada357de40b320637ab11cb029
IronSourceFacebookAdapter: b4ce8c7bf7db054336d0a18c4a15ad0796286563
IronSourceFyberAdapter: dc02376c7ba49165cbc9728ceb52862445184b3e
IronSourceInMobiAdapter: adbdf1204568af02545e994c1967518504f03923
IronSourceMintegralAdapter: 4c9857174622b5c7b6739aaf3c2264459a4eb152
IronSourceMolocoAdapter: 41b152f48bc0fbe2aa71b9d93f1925ee97470518
IronSourcePangleAdapter: 76dfb30134fa902d55c20c1b4e70fbd84e6c044f
IronSourceSDK: 0b5a640179ef91acc637332608183a05db1757df
IronSourceSmaatoAdapter: c33112852eb8830b6d2292b816df98d6cefdbb98
IronSourceUnityAdsAdapter: 1fcdfd61d67f11699dbc2da153d52db2a743e9de
IronSourceVungleAdapter: 623d3aa35a4d2495117d7b329e8178a725d35406
MintegralAdSDK: 88cc19314a1424a5d7f976c834030879017ed5f9
MobileFuseSDK: e5ea2384bc85fe8c1ae13748659d2799829a8de3
MolocoSDKiOS: 57d989984793ed15c7eed4aab36dd352cd5a2529
Nimble: 317d713c30c3336dd8571da1889f7ec3afc626e8
OguryAds: d9887cf3c756572175b22efadd08506d4b2f20fe
OguryCore: 1c7c2009f958b7026d5bca89e4c97dfa3fa9ef87
OgurySdk: 05f1a6433d0c291a62ea46b190cf0ab1b03ddee3
OMSDK_Appodeal: 9ed58fc8cabfbe31637cd267b35c1f86d227c49d
OpenWrapSDK: 70a433086186431c0eb2029ad049bc2175bfd3b5
Quick: b8bec97cd4b9f21da0472d45580f763b801fc353
smaato-ios-sdk: cd2dee212b2e160feb6443b5ce7b8f5eeedfda27
StackModules: f43fdc3fc047c0e208a6a44d604c97b2d1651bd7
TPExchange: ab140a1f77d531cd49f9534e7ce9d018fb313966
TradPlusAdSDK: d217c79660ef243cee51879c73d55c243cbc587e
UnityAds: 3588f23a66cd6d4956c2c54d50d10242b24e60af
VungleAds: 7ceb822f20a6619d8d9ed6a616dacd8cc5b05036
PODFILE CHECKSUM: 473f877be67a7223e79289b525f3bb558d259638
COCOAPODS: 1.16.2

26
Example/Tests/Info.plist Normal file
View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>BNDL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-2436733915645843~7466230005</string>
</dict>
</plist>

View File

@ -0,0 +1,2 @@
// https://github.com/Quick/Quick

209
FusionAds.podspec Normal file
View File

@ -0,0 +1,209 @@
#
# Be sure to run `pod lib lint FusionAds.podspec' to ensure this is a
# valid spec before submitting.
#
# Any lines starting with a # are optional, but their use is encouraged
# To learn more about a Podspec see https://guides.cocoapods.org/syntax/podspec.html
#
Pod::Spec.new do |s|
s.name = 'FusionAds'
s.version = '1.3.0'
s.summary = 'A short description of FusionAds.'
# This description is used to generate tags and improve search results.
# * Think: What does it do? Why did you write it? What is the focus?
# * Try to keep it short, snappy and to the point.
# * Write the description between the DESC delimiters below.
# * Finally, don't worry about the indent, CocoaPods strips it!
s.description = <<-DESC
TODO: Add long description of the pod here.
DESC
s.homepage = 'https://github.com/castbox/FusionAds-iOS'
# s.screenshots = 'www.example.com/screenshots_1', 'www.example.com/screenshots_2'
s.license = { :type => 'MIT', :file => 'LICENSE' }
s.author = { 'Haoyi' => 'haoyi.zhang@castbox.fm' }
s.source = { :git => 'git@github.com:castbox/FusionAds-iOS.git', :tag => s.version.to_s }
# s.social_media_url = 'https://twitter.com/<TWITTER_USERNAME>'
s.platform = :ios, "13.0"
s.ios.deployment_target = '13.0'
# s.resource_bundles = {
# 'FusionAds' => ['FusionAds/Assets/*.png']
# }
# s.public_header_files = 'Pod/Classes/**/*.h'
# s.frameworks = 'UIKit', 'MapKit'
# s.dependency 'AFNetworking', '~> 2.3'
# ======= MAX start =======
s.subspec 'MaxAdapters' do |sub|
sub.dependency 'AppLovinSDK', '13.1.0'
# IronSource
# commented for avoiding collision from IronSource collision
# s.dependency 'AppLovinMediationIronSourceAdapter', '8.7.0.0.0'
# Google
sub.dependency 'AppLovinMediationGoogleAdapter', '11.13.0.1'
sub.dependency 'AppLovinMediationGoogleAdManagerAdapter', '11.13.0.1'
# APS
sub.dependency 'AmazonPublisherServicesSDK', '5.0.1'
sub.dependency 'AppLovinMediationAmazonAdMarketplaceAdapter', '5.0.1.0'
# BidMachine
sub.dependency 'AppLovinMediationBidMachineAdapter', '3.2.0.0.0'
# Mintegral
sub.dependency 'AppLovinMediationMintegralAdapter', '7.7.5.0.0'
# DT Exchange
sub.dependency 'AppLovinMediationFyberAdapter', '8.3.5.0'
# Facebook
sub.dependency 'AppLovinMediationFacebookAdapter', '6.16.0.1'
# InMobi
sub.dependency 'AppLovinMediationInMobiAdapter', '10.8.0.0'
# Vungle (Liftoff Monetize)
sub.dependency 'AppLovinMediationVungleAdapter', '7.4.4.0'
# Pangle ByteDance
sub.dependency 'AppLovinMediationByteDanceAdapter', '6.5.0.8.0'
# Unity Ads
sub.dependency 'AppLovinMediationUnityAdsAdapter', '4.13.2.0'
# Moloc
sub.dependency 'AppLovinMediationMolocoAdapter', '3.7.0.0'
# Smaato
sub.dependency 'AppLovinMediationSmaatoAdapter', '22.9.2.0'
# Chartboost
sub.dependency 'AppLovinMediationChartboostAdapter', '9.8.0.0'
# Verve
sub.dependency 'AppLovinMediationVerveAdapter', '3.2.0.0'
# Mobile Fuse
sub.dependency 'AppLovinMediationMobileFuseAdapter', '1.9.0.0'
# Ogury
sub.dependency 'AppLovinMediationOguryPresageAdapter', '5.0.2.0'
# PubMatic
# s.dependency 'OpenWrapSDK', '3.3.0'
# s.dependency 'AppLovinPubMaticAdapter', '1.1.0'
sub.dependency 'AppLovinMediationPubMaticAdapter', '4.4.0.0'
sub.dependency 'GuruConsent', '1.4.6'
sub.dependency 'TradPlusAdSDK', '10.9.0'
sub.vendored_frameworks = 'ALMCMediationAdapter.framework'
end
# ======= MAX End =======
# ======= IronSource Start =======
s.subspec 'IronSourceAdapters' do |sub|
sub.dependency 'IronSourceSDK','8.7.0.0'
# IronSourceAdQualitySDK 7.24.3 版本会在banner展示时偶发crash类似如下崩溃堆栈故先移除
# SMLComparisonExp compareObject:toObject:
# s.dependency 'IronSourceAdQualitySDK','7.24.3'
# SDK 11.13.0
sub.dependency 'IronSourceAdMobAdapter','4.3.60.0'
# SDK 13.1.0
sub.dependency 'IronSourceAppLovinAdapter','4.3.52.0'
# SDK 5.0.1
sub.dependency 'IronSourceAPSAdapter','4.3.18.0'
# SDK 3.2.0
sub.dependency 'IronSourceBidMachineAdapter','4.3.14.0'
# SDK 7.7.7
sub.dependency 'IronSourceMintegralAdapter', '4.3.31.0'
# SDK 8.3.5 (DT Exchange)
sub.dependency 'IronSourceFyberAdapter','4.3.41.0'
# SDK 6.17.0
sub.dependency 'IronSourceFacebookAdapter','4.3.47.0'
# SDK 10.8.0
sub.dependency 'IronSourceInMobiAdapter','4.3.26.0'
# SDK 7.4.4 (Liftoff Monetize)
sub.dependency 'IronSourceVungleAdapter','4.3.42.0'
# SDK 6.5.0.8
sub.dependency 'IronSourcePangleAdapter','4.3.40.0'
# SDK 4.13.2
sub.dependency 'IronSourceUnityAdsAdapter','4.3.48.0'
# SDK 3.7.0
sub.dependency 'IronSourceMolocoAdapter', '4.3.11.0'
# s.dependency 'MolocoCustomAdapterIronSource', '3.1.1.0'
# SDK 22.9.2
sub.dependency 'IronSourceSmaatoAdapter','4.3.16.0'
end
# ======= IronSource End =======
# ======= AdMob Start =======
s.subspec 'AdMobAdapters' do |sub|
sub.dependency 'Google-Mobile-Ads-SDK', '11.13.0'
# https://developers.google.com/admob/ios/mediation/chartboost#chartboost-ios-mediation-adapter-changelog
sub.dependency 'GoogleMobileAdsMediationChartboost', '9.8.0.0'
# https://developers.google.com/admob/ios/mediation/dt-exchange#dt-exchange-ios-mediation-adapter-changelog
sub.dependency 'GoogleMobileAdsMediationFyber', '8.3.5.0'
# https://developers.google.com/admob/ios/mediation/inmobi#inmobi-ios-mediation-adapter-changelog
sub.dependency 'GoogleMobileAdsMediationInMobi', '10.8.0.0'
# commented to avoiding runtime issues with ironsource sdk
# # https://developers.google.com/admob/ios/mediation/ironsource#ironsource-ios-mediation-adapter-changelog
# sub.dependency 'GoogleMobileAdsMediationIronSource', '8.6.1.0.0'
# https://developers.google.com/admob/ios/mediation/liftoff-monetize#liftoff-monetize-ios-mediation-adapter-changelog
sub.dependency 'GoogleMobileAdsMediationVungle', '7.4.4.0'
# https://developers.google.com/admob/ios/mediation/meta#meta-audience-network-ios-mediation-adapter-changelog
sub.dependency 'GoogleMobileAdsMediationFacebook', '6.16.0.0'
# https://developers.google.com/admob/ios/mediation/mintegral#mintegral-ios-mediation-adapter-changelog
sub.dependency 'GoogleMobileAdsMediationMintegral', '7.7.5.0'
end
s.subspec 'Main' do |sub|
sub.source_files = 'FusionAds/Classes/**/*'
sub.dependency 'FusionAds/MaxAdapters'
sub.dependency 'FusionAds/IronSourceAdapters'
sub.dependency 'FusionAds/AdMobAdapters'
end
s.default_subspecs = "Main"
# ======= AdMob End =======
s.static_framework = true
# valid_archs = ['x86_64','arm64']
end

View File

View File

View File

@ -0,0 +1,186 @@
// FusionAdsSdk.swift
// Main entry point for the FusionAds iOS SDK
// Corresponds to GuruAdsSdk.kt in Android implementation
import Foundation
import UIKit
import AppLovinSDK
import DTBiOSSDK
import GuruConsent
import OpenWrapSDK
import InMobiSDK
import TradPlusAds
import MobileFuseSDK
import IronSource
import GoogleMobileAds
import ChartboostSDK
import IASDKCore
import InMobiAdapter
import VungleAdsSDK
import MTGSDK
#if DEBUG
extension FusionAdsSdk {
public static func registerExternalAdPlatformSdkMapping(
platform: AdPlatform,
initializer: @escaping GuruAdsInitializer
) {
externalAdPlatformSdkMapping[platform] = initializer
}
}
#endif
public class FusionAdsSdk {
private static let adPlatformSdkMapping: [AdPlatform:GuruAdsInitializer] = [
AdPlatform.max : GuruMaxAdsSdk.obtain,
AdPlatform.ironSource : GuruIronSourceAdsSdk.obtain,
AdPlatform.adMob : GuruAdMobAdsSdk.obtain
]
private static var externalAdPlatformSdkMapping: [AdPlatform:GuruAdsInitializer] = [:]
private let adsEngineManager: AdsEngineManager;
private let viewController: UIViewController;
public init(controller: UIViewController) {
viewController = controller
adsEngineManager = AdsEngineManager(viewController: viewController)
}
// Initialize the SDK with configuration profile
public func initialize(adsProfile: AdsProfile, analyticsEventHandler: AnalyticsEventHandler? = nil) async -> Bool {
let initializers = Self.externalAdPlatformSdkMapping.merging(Self.adPlatformSdkMapping) {
(current, _) in current
}
if(analyticsEventHandler != nil) {
AnalyticsEvents.shared.registerHandler(handler: analyticsEventHandler!)
}
for adPlatform in adsProfile.adPlatforms {
let initializer = initializers[adPlatform]
let sdk = initializer?(viewController)
if sdk != nil {
await sdk!.initialize(adsProfile: adsProfile)
adsEngineManager.addAdsSdk(sdk!)
}
}
return true;
}
// Create interstitial ad engine
public func createInterstitialAdEngine(adEngineConfig: AdEngineConfig) -> InterstitialAdEngine? {
return adsEngineManager.createInterstitialAdEngine(adEngineConfig: adEngineConfig)
}
// Create rewarded ad engine
public func createRewardedAdEngine(adEngineConfig: AdEngineConfig) -> RewardedAdEngine? {
return adsEngineManager.createRewardedAdEngine(adEngineConfig: adEngineConfig)
}
// Create banner ad engine
public func createBannerAdEngine(adEngineConfig: AdEngineConfig) -> BannerAdEngine? {
return adsEngineManager.createBannerAdEngine(adEngineConfig: adEngineConfig)
}
// Create mrec ad engine
public func createMRecAdEngine(adEngineConfig: AdEngineConfig) -> MRecAdEngine? {
return adsEngineManager.createMRecAdEngine(adEngineConfig: adEngineConfig)
}
public func getInterstitialAdEngine(id: Int) -> InterstitialAdEngine? {
return adsEngineManager.getInterstitialAdEngine(id: id)
}
public func getRewardedAdEngine(id: Int) -> RewardedAdEngine? {
return adsEngineManager.getRewardedAdEngine(id: id)
}
public func getBannerAdEngine(id: Int) -> BannerAdEngine? {
return adsEngineManager.getBannerAdEngine(id: id)
}
public func getMRecAdEngine(id: Int) -> MRecAdEngine? {
return adsEngineManager.getMRecAdEngine(id: id)
}
public func openDebugger(adPlatform: AdPlatform) -> Bool {
if (adPlatform == AdPlatform.max) {
ALSdk.shared().showMediationDebugger()
} else if (adPlatform == AdPlatform.ironSource) {
IronSource.launchTestSuite(viewController)
} else if (adPlatform == AdPlatform.adMob) {
GADMobileAds.sharedInstance().presentAdInspector(from: viewController)
}
return true;
}
public func isTablet() throws -> Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
public func afterAcceptPrivacy() -> Bool {
ALPrivacySettings.setHasUserConsent(true)
// ironsource
IronSource.setConsent(true)
// chartboost
let dataUseConsent = CHBDataUseConsent.GDPR(CHBDataUseConsent.GDPR.Consent.nonBehavioral)
Chartboost.addDataUseConsent(dataUseConsent)
// fyber
IASDKCore.sharedInstance().gdprConsent = IAGDPRConsentType.given
IASDKCore.sharedInstance().gdprConsentString = "myGdprConsentString"
// inmobi
var consentObject = Dictionary<String, String>()
consentObject["gdpr"] = "1"
consentObject[IMCommonConstants.IM_GDPR_CONSENT_AVAILABLE] = "true"
GADMInMobiConsent.updateGDPRConsent(consentObject)
// vungle
VunglePrivacySettings.setGDPRStatus(true)
VunglePrivacySettings.setGDPRMessageVersion("v1.0.0")
// mintegral
MTGSDK.sharedInstance().consentStatus = true
return true
}
public func updateOrientation(orientation: ScreenOrientation) {
adsEngineManager.dispatchOrientationUpdate(orientation: orientation)
}
public func updateUid2Token(uid2Token: String) {
if (AdsHelper.setUid2Token(uid2Token)) {
NSLog("setUid2Token: \(uid2Token) success!!!")
// PubMatic UID2
var userId = POBExternalUserId(source: "uidapi.com", andId: uid2Token)
userId.atype = 1
OpenWrapSDK.addExternalUserId(userId)
// Inmobi UID2
IMSdk.setPublisherProvidedUnifiedId(["uidapi.com": uid2Token])
// TradPlus
TradPlus.sharedInstance().uid2Info = {
let info = TradPlusUID2Info()
info.uid2Token = uid2Token
return info
}()
// MobileFuse
MobileFuseTargetingData.setExtendedUserId(uid2Token, forPartner: "uidapi.com")
}
}
// for testing only
public func clearCachedEngines() {
adsEngineManager.clearCachedEngines()
}
}

View File

@ -0,0 +1,55 @@
//
// AdMobFusionAd.swift
// Pods
//
// Created by 250102 on 2025/5/9.
//
import GoogleMobileAds
class AdMobFusionAd : FusionAd {
let adValue: GADAdValue?
let adInfo: GADResponseInfo?
let adUnitIdValue: String?
public init(engineId: Int, adType: AdType, adUnitId:String?, ad: GADResponseInfo?, adValue: GADAdValue?) {
adInfo = ad
self.adUnitIdValue = adUnitId
self.adValue = adValue
super.init(engineId: engineId, adType: adType)
}
override public var adPlatform: AdPlatform {
return AdPlatform.adMob
}
override public var adUnitId: String? {
return self.adUnitIdValue
}
override public var revenue: Double {
return (adValue?.value.doubleValue ?? 0.0) / 1000000.0
}
override public var waterfallName: String? {
return nil
}
override public var placement: String? {
return nil
}
override public var networkName: String? {
return adInfo?.loadedAdNetworkResponseInfo?.adNetworkClassName
}
override public var networkPlacement: String? {
return nil
}
override public var creativeId: String? {
return nil
}
override public var adReviewCreativeId: String? {
return nil
}
}

View File

@ -0,0 +1,21 @@
//
// AdMobFusionReward.swift
// Pods
//
// Created by 250102 on 2025/5/9.
//
import GoogleMobileAds
class AdMobFusionReward : FusionReward {
let reward: GADAdReward
public init(_ reward: GADAdReward) {
self.reward = reward
}
var rewardAmount: Int {
return reward.amount.intValue
}
}

View File

@ -0,0 +1,130 @@
//
// GuruAdmobAdsSdk.swift
// Pods
//
// Created by 250102 on 2025/5/9.
//
import GoogleMobileAds
import FBAudienceNetwork
class GuruAdMobAdsSdk : GuruAdsSdk {
private static var instance: GuruAdMobAdsSdk?
public var adPlatform: AdPlatform {
return AdPlatform.adMob
}
private var initialized = false
private static var interstitialAds: [String: GuruInterstitialAd] = [:]
private static var rewardedAds: [String: GuruRewardedAd] = [:]
private static var bannerAds: [String: GuruBannerAd] = [:]
private static var mrecAds: [String: GuruMRecAd] = [:]
private func logDebug(_ message: String) {
Logger.d(tag: "GuruAdMobAdsSdk", message: message)
}
private func logInfo(_ message: String) {
Logger.i(tag: "GuruAdMobAdsSdk", message: message)
}
private func logWarn(_ message: String) {
Logger.w(tag: "GuruAdMobAdsSdk", message: message)
}
private func logError(_ message: String) {
Logger.e(tag: "GuruAdMobAdsSdk", message: message)
}
private let viewController: UIViewController
private init(viewController: UIViewController) {
self.viewController = viewController
}
public static func obtain(viewController: UIViewController) -> GuruAdsSdk {
if let existingInstance = instance {
return existingInstance
}
let newInstance = GuruAdMobAdsSdk(viewController: viewController)
instance = newInstance
return newInstance
}
public func initialize(adsProfile: AdsProfile) async -> Bool {
guard !initialized else {
logWarn("SDK already initialized! Ignoring.")
return false
}
initialized = true
if(adsProfile.debugMode) {
}
return await withCheckedContinuation(isolation: MainActor.shared) { continuation in
FBAdSettings.setAdvertiserTrackingEnabled(true)
GADMobileAds.sharedInstance().start(completionHandler: { status in
var initedAtLeastOne = false
status.adapterStatusesByClassName.forEach {key, value in
if(value.state == GADAdapterInitializationState.ready) {
self.logInfo("adapter \(key) ready, latency \(value.latency)")
initedAtLeastOne = true
} else {
self.logWarn("adapter \(key) not ready, latency \(value.latency)")
}
}
continuation.resume(returning: initedAtLeastOne)
})
}
}
public func obtainInterstitialAd(adConfig: AdConfig) -> GuruInterstitialAd {
if let existingAd = Self.interstitialAds[adConfig.cacheKey] {
return existingAd
}
let newAd = GuruAdMobInterstitialAd(viewController: viewController, adConfig: adConfig)
Self.interstitialAds[adConfig.cacheKey] = newAd
return newAd
}
public func obtainRewardedAd(adConfig: AdConfig) -> GuruRewardedAd {
if let existingAd = Self.rewardedAds[adConfig.cacheKey] {
return existingAd
}
let newAd = GuruAdMobRewardedAd(viewController: viewController, adConfig: adConfig)
Self.rewardedAds[adConfig.cacheKey] = newAd
return newAd
}
public func obtainBannerAd(adConfig: AdConfig) -> GuruBannerAd {
if let existingAd = Self.bannerAds[adConfig.cacheKey] {
return existingAd
}
let newAd = GuruAdMobBannerAd(viewController: viewController, adConfig: adConfig)
Self.bannerAds[adConfig.cacheKey] = newAd
return newAd
}
public func obtainMRecAd(adConfig: AdConfig) -> GuruMRecAd {
if let existingAd = Self.mrecAds[adConfig.cacheKey] {
return existingAd
}
let newAd = GuruAdMobMRecAd(viewController: viewController, adConfig: adConfig)
Self.mrecAds[adConfig.cacheKey] = newAd
return newAd
}
public func processCrossAction(action: AdsCrossAction) -> Bool {
return false
}
}

View File

@ -0,0 +1,116 @@
//
// GuruAdMobBannerAd.swift
// Pods
//
// Created by 250102 on 2025/5/9.
//
import GoogleMobileAds
class GuruAdMobBannerView : GuruBannerView {
func view() -> UIView {
return bannerAdView
}
func setPlacementName(placement: String) {
}
func onShow() {
bannerAdView.isHidden = false
bannerAdView.isAutoloadEnabled = true
}
func onHide() {
bannerAdView.isHidden = true
bannerAdView.isAutoloadEnabled = false
}
private var bannerAdView: GADBannerView
public init(_ banner: GADBannerView) {
self.bannerAdView = banner
}
}
class GuruAdMobBannerAd : GuruBannerAd, GADBannerViewDelegate {
private let viewController: UIViewController
private let adConfig: AdConfig
private var bannerAd: GADBannerView?
private var coordinator: GuruBannerCoordinator<GuruAdMobBannerView>?
private var currentUid2Token: String = ""
public init(viewController: UIViewController, adConfig: AdConfig) {
self.viewController = viewController
self.adConfig = adConfig
super.init(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: AdPlatform.adMob)
}
override func load() -> Bool {
let isTablet = UIDevice.current.userInterfaceIdiom == .pad
let bannerSize = if (isTablet) {
GADAdSizeBanner
} else {
GADAdSizeLargeBanner
}
self.bannerAd = GADBannerView(adSize: bannerSize)
self.bannerAd?.adUnitID = adUnitId
self.bannerAd?.delegate = self
self.bannerAd?.paidEventHandler = { paidValue in
self.adRevenuePaid(ad: self.createFusionAd(self.bannerAd, adValue: paidValue))
}
coordinator = GuruBannerCoordinator(viewController: viewController, bannerAd: GuruAdMobBannerView(bannerAd!), contentId: engineId)
self.bannerAd?.load(GADRequest())
return true
}
override func show(_ request: BannerShowRequest? = nil) -> Bool {
return coordinator?.show(request) ?? false
}
override func hide() -> Bool {
return coordinator?.hide() ?? false
}
override func destroy() -> Bool {
let _ = coordinator?.destroy()
coordinator = nil
self.bannerAd?.delegate = nil
self.bannerAd?.paidEventHandler = nil
self.bannerAd = nil
return true
}
override func updateOrientation(orientation: ScreenOrientation) -> Bool {
return coordinator?.updateOrientation(orientation: orientation) ?? false
}
func bannerViewDidReceiveAd(_ bannerView: GADBannerView) {
adLoaded(ad: createFusionAd(bannerView))
}
func bannerViewDidRecordImpression(_ bannerView: GADBannerView) {
adDisplayed(ad: createFusionAd(bannerView))
}
func bannerViewDidRecordClick(_ bannerView: GADBannerView) {
adClicked(ad: createFusionAd(bannerView))
}
func bannerViewWillPresentScreen(_ bannerView: GADBannerView) {
adBannerExpanded(ad: createFusionAd(bannerView))
}
func bannerViewDidDismissScreen(_ bannerView: GADBannerView) {
adBannerCollapsed(ad: createFusionAd(bannerView))
}
func bannerView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: any Error) {
adLoadFailed(loadFailedInfo: LoadFailedInfo(engineId: engineId, adPlatform: adPlatform, adType: adType, adUnitId: adUnitId, error: SwiftFusionError(error)))
}
private func createFusionAd(_ ad: GADBannerView?, adValue: GADAdValue? = nil) -> FusionAd {
return AdMobFusionAd(engineId: engineId, adType: adType, adUnitId: adUnitId, ad: ad?.responseInfo, adValue: adValue)
}
}

View File

@ -0,0 +1,82 @@
//
// GuruAdMobInterstitialAd.swift
// Pods
//
// Created by 250102 on 2025/5/9.
//
import GoogleMobileAds
class GuruAdMobInterstitialAd : GuruInterstitialAd, GADFullScreenContentDelegate {
private let viewController: UIViewController
private let adConfig: AdConfig
private var ad: GADInterstitialAd?
public init(viewController: UIViewController, adConfig: AdConfig) {
self.viewController = viewController
self.adConfig = adConfig
super.init(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: AdPlatform.adMob)
}
override func load() -> Bool {
logInfo("request load")
Task { @MainActor in
do {
ad?.fullScreenContentDelegate = nil
ad?.paidEventHandler = nil
let interstitialAd = try await GADInterstitialAd.load(
withAdUnitID: adConfig.adUnitId, request: GADRequest())
interstitialAd.fullScreenContentDelegate = self
interstitialAd.paidEventHandler = { paidValue in
self.logInfo("revenue paid")
self.adRevenuePaid(ad: self.createFusionAd(interstitialAd, adValue: paidValue))
}
ad = interstitialAd
logInfo("loaded")
adLoaded(ad: createFusionAd(interstitialAd))
} catch {
logError("load failed, \(error.code) - \(error.localizedDescription)")
adLoadFailed(loadFailedInfo: LoadFailedInfo(engineId: engineId, adPlatform: adPlatform, adType: adType, adUnitId: adUnitId, error: SwiftFusionError(error)))
}
}
return true
}
override func show(_ request: InterstitialShowRequest? = nil) -> Bool {
logInfo("request show")
guard let ad = ad else {
logWarn("AdMob interstitialAd not ready for show")
return false
}
ad.present(fromRootViewController: viewController)
return true
}
override func destroy() -> Bool {
logInfo("request destroy")
self.ad?.fullScreenContentDelegate = nil
self.ad?.paidEventHandler = nil
self.ad = nil
return true
}
func adDidRecordClick(_ ad: any GADFullScreenPresentingAd) {
adClicked(ad: createFusionAd(self.ad))
}
func adDidRecordImpression(_ ad: any GADFullScreenPresentingAd) {
adDisplayed(ad: createFusionAd(self.ad))
}
func adDidDismissFullScreenContent(_ ad: any GADFullScreenPresentingAd) {
adHidden(ad: createFusionAd(self.ad))
}
func ad(_ ad: any GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: any Error) {
logError("displayed failed, \(error.code) - \(error.localizedDescription)")
adDisplayFailed(ad: createFusionAd(self.ad), error: SwiftFusionError(error))
}
private func createFusionAd(_ interstitialAd: GADInterstitialAd?, adValue: GADAdValue? = nil) -> FusionAd {
return AdMobFusionAd(engineId: engineId, adType: adType, adUnitId: adUnitId, ad: interstitialAd?.responseInfo, adValue: adValue)
}
}

View File

@ -0,0 +1,110 @@
//
// GuruAdMobBannerAd.swift
// Pods
//
// Created by 250102 on 2025/5/9.
//
import GoogleMobileAds
class GuruAdMobMRecView : GuruMRecView {
func view() -> UIView {
return bannerAdView
}
func setPlacementName(placement: String) {
}
func onShow() {
bannerAdView.isHidden = false
bannerAdView.isAutoloadEnabled = true
}
func onHide() {
bannerAdView.isHidden = true
bannerAdView.isAutoloadEnabled = false
}
private var bannerAdView: GADBannerView
public init(_ banner: GADBannerView) {
self.bannerAdView = banner
}
}
class GuruAdMobMRecAd : GuruMRecAd, GADBannerViewDelegate {
private let viewController: UIViewController
private let adConfig: AdConfig
private var bannerAd: GADBannerView?
private var coordinator: GuruMRecCoordinator?
private var currentUid2Token: String = ""
public init(viewController: UIViewController, adConfig: AdConfig) {
self.viewController = viewController
self.adConfig = adConfig
super.init(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: AdPlatform.adMob)
}
override func load() -> Bool {
self.bannerAd = GADBannerView(adSize: GADAdSizeMediumRectangle)
self.bannerAd?.adUnitID = adUnitId
self.bannerAd?.delegate = self
self.bannerAd?.paidEventHandler = { paidValue in
self.adRevenuePaid(ad: self.createFusionAd(self.bannerAd, adValue: paidValue))
}
coordinator = GuruMRecCoordinator(viewController: viewController, mrecAd: GuruAdMobMRecView(bannerAd!), contentId: engineId)
self.bannerAd?.load(GADRequest())
return true
}
override func show(request: MRecShowRequest? = nil) -> Bool {
return coordinator?.show(request: request) ?? false
}
override func hide() -> Bool {
return coordinator?.hide() ?? false
}
override func destroy() -> Bool {
coordinator?.destroy()
coordinator = nil
self.bannerAd?.delegate = nil
self.bannerAd?.paidEventHandler = nil
self.bannerAd = nil
return true
}
override func updateOrientation(orientation: ScreenOrientation) -> Bool {
return coordinator?.updateOrientation(orientation: orientation) ?? false
}
func bannerViewDidReceiveAd(_ bannerView: GADBannerView) {
adLoaded(ad: createFusionAd(bannerView))
}
func bannerViewDidRecordImpression(_ bannerView: GADBannerView) {
adDisplayed(ad: createFusionAd(bannerView))
}
func bannerViewDidRecordClick(_ bannerView: GADBannerView) {
adClicked(ad: createFusionAd(bannerView))
}
func bannerViewWillPresentScreen(_ bannerView: GADBannerView) {
adBannerExpanded(ad: createFusionAd(bannerView))
}
func bannerViewDidDismissScreen(_ bannerView: GADBannerView) {
adBannerCollapsed(ad: createFusionAd(bannerView))
}
func bannerView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: any Error) {
adLoadFailed(loadFailedInfo: LoadFailedInfo(engineId: engineId, adPlatform: adPlatform, adType: adType, adUnitId: adUnitId, error: SwiftFusionError(error)))
}
private func createFusionAd(_ ad: GADBannerView?, adValue: GADAdValue? = nil) -> FusionAd {
return AdMobFusionAd(engineId: engineId, adType: adType, adUnitId: adUnitId, ad: ad?.responseInfo, adValue: adValue)
}
}

View File

@ -0,0 +1,83 @@
//
// GuruAdMobInterstitialAd.swift
// Pods
//
// Created by 250102 on 2025/5/9.
//
import GoogleMobileAds
class GuruAdMobRewardedAd : GuruRewardedAd, GADFullScreenContentDelegate {
private let viewController: UIViewController
private let adConfig: AdConfig
private var ad: GADRewardedAd?
public init(viewController: UIViewController, adConfig: AdConfig) {
self.viewController = viewController
self.adConfig = adConfig
super.init(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: AdPlatform.adMob)
}
override func load() -> Bool {
logInfo("request load")
Task { @MainActor in
do {
ad?.fullScreenContentDelegate = nil
ad?.paidEventHandler = nil
let interstitialAd = try await GADRewardedAd.load(
withAdUnitID: adConfig.adUnitId, request: GADRequest())
interstitialAd.fullScreenContentDelegate = self
interstitialAd.paidEventHandler = { paidValue in
self.logInfo("revenue paid")
self.adRevenuePaid(ad: self.createFusionAd(interstitialAd, adValue: paidValue))
}
ad = interstitialAd
adLoaded(ad: createFusionAd(interstitialAd))
} catch {
logError("load failed, \(error.code) - \(error.localizedDescription)")
adLoadFailed(loadFailedInfo: LoadFailedInfo(engineId: engineId, adPlatform: adPlatform, adType: adType, adUnitId: adUnitId, error: SwiftFusionError(error)))
}
}
return true
}
override func show(_ request: RewardedShowRequest? = nil) -> Bool {
logInfo("request show")
guard let ad = ad else {
logWarn("AdMob rewardAd not ready for show")
return false
}
ad.present(fromRootViewController: viewController) {
self.adUserRewarded(ad: self.createFusionAd(ad), reward: AdMobFusionReward(ad.adReward))
}
return true
}
override func destroy() -> Bool {
logInfo("request destroy")
self.ad?.fullScreenContentDelegate = nil
self.ad?.paidEventHandler = nil
self.ad = nil
return true
}
func adDidRecordClick(_ ad: any GADFullScreenPresentingAd) {
adClicked(ad: createFusionAd(self.ad))
}
func adDidRecordImpression(_ ad: any GADFullScreenPresentingAd) {
adDisplayed(ad: createFusionAd(self.ad))
}
func adDidDismissFullScreenContent(_ ad: any GADFullScreenPresentingAd) {
adHidden(ad: createFusionAd(self.ad))
}
func ad(_ ad: any GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: any Error) {
logError("displayed failed, \(error.code) - \(error.localizedDescription)")
adDisplayFailed(ad: createFusionAd(self.ad), error: SwiftFusionError(error))
}
private func createFusionAd(_ rewardedAd: GADRewardedAd?, adValue: GADAdValue? = nil) -> FusionAd {
return AdMobFusionAd(engineId: engineId, adType: adType, adUnitId: adUnitId, ad: rewardedAd?.responseInfo, adValue: adValue)
}
}

View File

@ -0,0 +1,24 @@
// AdConfig.swift
// Configuration for ads
// Corresponds to AdConfig.kt in Android implementation
import Foundation
public struct AdConfig {
public let engineId: Int
public let adUnitId: String
public let adAmazonSlotId: String?
public let requireDisableAutoRetries: Bool
public init(engineId: Int, adUnitId: String, adAmazonSlotId: String? = nil, requireDisableAutoRetries: Bool = false) {
self.engineId = engineId
self.adUnitId = adUnitId
self.adAmazonSlotId = adAmazonSlotId
self.requireDisableAutoRetries = requireDisableAutoRetries
}
public var cacheKey: String {
return "\(engineId)_\(adUnitId)_\(adAmazonSlotId ?? "noamz")"
}
}

View File

@ -0,0 +1,104 @@
// AdsProfile.swift
// Defines advertisement profile configurations
// Corresponds to AdsProfile.kt in Android implementation
import Foundation
public struct SegmentWrapper: Codable {
public let key: Int
public let values: [Int]
public init(key: Int, values: [Int] = []) {
self.key = key
self.values = values
}
private enum CodingKeys: String, CodingKey {
case key = "k"
case values = "v"
}
}
public struct SegmentCollectionWrapper: Codable {
public let segments: [SegmentWrapper]
public init(segments: [SegmentWrapper] = []) {
self.segments = segments
}
}
public struct AdsProfile: Codable {
public let adPlatforms: [AdPlatform]
public let maxSdkKey: String
public let ironSourceSdkKey: String?
public let amazonAppId: String?
public let pubmaticStoreUrl: String?
public let uid2Token: String?
public let userId: String
public let tpCreativeKey: String
public let debugMode: Bool
public let segments: SegmentCollectionWrapper
public let maxB2BDisabledAdUnitIds: [String]?
public init(adPlatforms: [AdPlatform], maxSdkKey: String,
ironSourceSdkKey: String? = nil,
amazonAppId: String? = nil,
pubmaticStoreUrl: String? = nil,
uid2Token: String? = nil,
userId: String = "",
tpCreativeKey: String = "",
debugMode: Bool = false,
segments: SegmentCollectionWrapper = SegmentCollectionWrapper(segments: []),
maxB2BDisabledAdUnitIds: [String]? = nil
) {
self.adPlatforms = adPlatforms
self.maxSdkKey = maxSdkKey
self.ironSourceSdkKey = ironSourceSdkKey
self.amazonAppId = amazonAppId
self.pubmaticStoreUrl = pubmaticStoreUrl
self.uid2Token = uid2Token
self.userId = userId
self.tpCreativeKey = tpCreativeKey
self.debugMode = debugMode
self.segments = segments
self.maxB2BDisabledAdUnitIds = maxB2BDisabledAdUnitIds
}
private enum CodingKeys : String, CodingKey {
case adPlatforms = "ad_platforms",
maxSdkKey = "max_sdk_key",
ironSourceSdkKey = "iron_source_sdk_key",
amazonAppId = "amazon_app_id",
pubmaticStoreUrl = "pubmatic_store_url",
uid2Token = "uid2_token",
segments = "segments",
userId = "user_id",
debugMode = "debug_mode",
tpCreativeKey = "tp_creative_key",
maxB2BDisabledAdUnitIds = "max_disable_b2b_ad_unit_ids"
}
}
public enum AdsCommand {
// Add specific commands as needed
}
public class ActionResult {
// Base result class
}
public class BoolActionResult: ActionResult {
public let value: Bool
public init(value: Bool) {
self.value = value
}
}
public struct AdsCrossAction {
public let cmd: AdsCommand
public let params: Any
public let onResult: (ActionResult) -> Void
}

View File

@ -0,0 +1,26 @@
// FusionAd.swift
// Base class for all ad types
// Corresponds to FusionAd.kt in Android implementation
import Foundation
open class FusionAd {
public let engineId: Int
public let adType: AdType
public init(engineId: Int, adType: AdType) {
self.engineId = engineId
self.adType = adType
}
open var adPlatform: AdPlatform { fatalError("Subclass must implement") }
open var adUnitId: String? { fatalError("Subclass must implement") }
open var revenue: Double { fatalError("Subclass must implement") }
public var format: String? { adType.label }
open var waterfallName: String? { fatalError("Subclass must implement") }
open var placement: String? { fatalError("Subclass must implement") }
open var networkName: String? { fatalError("Subclass must implement") }
open var networkPlacement: String? { fatalError("Subclass must implement") }
open var creativeId: String? { fatalError("Subclass must implement") }
open var adReviewCreativeId: String? { fatalError("Subclass must implement") }
}

View File

@ -0,0 +1,43 @@
// FusionAdListener.swift
// Listener interface for ad events
// Corresponds to FusionAdListener.kt in Android implementation
import Foundation
public struct LoadFailedInfo {
public let engineId: Int
public let adPlatform: AdPlatform
public let adType: AdType
public let adUnitId: String
public let error: FusionError?
public init(engineId: Int, adPlatform: AdPlatform, adType: AdType, adUnitId: String, error: FusionError?) {
self.engineId = engineId
self.adPlatform = adPlatform
self.adType = adType
self.adUnitId = adUnitId
self.error = error
}
}
public protocol FusionAdListener: AnyObject {
func onAdLoaded(ad: FusionAd)
func onAdDisplayed(ad: FusionAd)
func onAdHidden(ad: FusionAd)
func onAdClicked(ad: FusionAd)
func onAdLoadFailed(loadFailedInfo: LoadFailedInfo)
func onAdDisplayFailed(ad: FusionAd, error: FusionError?)
func onAdRevenuePaid(ad: FusionAd)
// Optional methods with default empty implementations
func onUserRewarded(ad: FusionAd, reward: FusionReward?)
func onBannerAdExpanded(ad: FusionAd)
func onBannerAdCollapsed(ad: FusionAd)
}
// Extension to provide default implementations for optional methods
public extension FusionAdListener {
func onUserRewarded(ad: FusionAd, reward: FusionReward?) {}
func onBannerAdExpanded(ad: FusionAd) {}
func onBannerAdCollapsed(ad: FusionAd) {}
}

View File

@ -0,0 +1,21 @@
// FusionError.swift
// Interface for error handling in FusionAds
// Corresponds to FusionError.kt in Android implementation
import Foundation
enum FusionErrorCodes: Int {
case TIMEOUT = -100001
case UNKNOWN = -100000
var code: Int {
return self.rawValue
}
}
public protocol FusionError {
var errorCode: Int { get }
var message: String { get }
var cause: Error? { get }
var info: String? { get }
var waterfallName: String? { get }
}

View File

@ -0,0 +1,9 @@
// FusionReward.swift
// Interface for reward handling in FusionAds
// Corresponds to FusionReward.kt in Android implementation
import Foundation
public protocol FusionReward {
var rewardAmount: Int { get }
}

View File

@ -0,0 +1,26 @@
// GuruAdsSdk.swift
// Base class for SDK implementations
// Corresponds to GuruAdsSdk.kt in Android implementation
import Foundation
import UIKit
// Typealias for callbacks
public typealias GuruAdsInitializeCompletedCallback = (GuruAdsSdk, Bool) -> Void
public typealias GuruAdsInitializer = (UIViewController) -> GuruAdsSdk
public protocol GuruAdsSdk: AnyObject {
var adPlatform: AdPlatform { get }
func initialize(adsProfile: AdsProfile) async -> Bool
func obtainInterstitialAd(adConfig: AdConfig) -> GuruInterstitialAd
func obtainRewardedAd(adConfig: AdConfig) -> GuruRewardedAd
func obtainBannerAd(adConfig: AdConfig) -> GuruBannerAd
func obtainMRecAd(adConfig: AdConfig) -> GuruMRecAd
func processCrossAction(action: AdsCrossAction) -> Bool
}

View File

@ -0,0 +1,74 @@
// GuruBannerAd.swift
// Base class for banner ad implementations
// Corresponds to GuruBannerAd.kt in Android implementation
import Foundation
import UIKit
public struct BannerShowRequest: Equatable {
public let placement: String?
public let position: BannerPosition?
public let offset: Float?
public init(placement: String? = nil, position: BannerPosition? = nil, offset: Float? = nil) {
self.placement = placement
self.position = position
self.offset = offset
}
}
open class GuruBannerAd: RelayAd {
public let adUnitId: String
public let adPlatform: AdPlatform
private let name: String
public let adType = AdType.Banner
public init(engineId: Int, adUnitId: String, adPlatform: AdPlatform) {
self.adUnitId = adUnitId
self.adPlatform = adPlatform
self.name = "[BANNER-\(adPlatform)]"
super.init(engineId: engineId)
}
// MARK: - Logging Methods
public func logWarn(_ message: String) {
Logger.w(tag: "BANNER-\(adPlatform.name)[\(adUnitId)]", message: message)
}
public func logDebug(_ message: String) {
Logger.d(tag: "BANNER-\(adPlatform.name)[\(adUnitId)]", message: message)
}
public func logInfo(_ message: String) {
Logger.i(tag: "BANNER-\(adPlatform.name)[\(adUnitId)]", message: message)
}
public func logError(_ message: String) {
Logger.e(tag: "BANNER-\(adPlatform.name)[\(adUnitId)]", message: message)
}
// MARK: - Methods to override
open func load() -> Bool {
fatalError("Subclass must implement")
}
open func show(_ request: BannerShowRequest? = nil) -> Bool {
fatalError("Subclass must implement")
}
open func hide() -> Bool {
fatalError("Subclass must implement")
}
open func destroy() -> Bool {
fatalError("Subclass must implement")
}
open func updateOrientation(orientation: ScreenOrientation) -> Bool {
fatalError("Subclass must implement")
}
}

View File

@ -0,0 +1,62 @@
// GuruInterstitialAd.swift
// Base class for interstitial ad implementations
// Corresponds to GuruInterstitialAd.kt in Android implementation
import Foundation
import UIKit
public struct InterstitialShowRequest {
public let placement: String?
public init(placement: String?) {
self.placement = placement
}
}
open class GuruInterstitialAd: RelayAd {
public let adUnitId: String
public let adPlatform: AdPlatform
public let adType = AdType.Interstitial
private let name: String
public init(engineId: Int, adUnitId: String, adPlatform: AdPlatform) {
self.adUnitId = adUnitId
self.adPlatform = adPlatform
self.name = "[INTER-\(adPlatform)]"
super.init(engineId: engineId)
}
// MARK: - Logging Methods
public func logInfo(_ message: String) {
Logger.i(tag: "INTER-\(adPlatform.name)[\(adUnitId)]", message: message)
}
public func logWarn(_ message: String) {
Logger.w(tag: "INTER-\(adPlatform.name)[\(adUnitId)]", message: message)
}
public func logDebug(_ message: String) {
Logger.d(tag: "INTER-\(adPlatform.name)[\(adUnitId)]", message: message)
}
public func logError(_ message: String) {
Logger.e(tag: "INTER-\(adPlatform.name)[\(adUnitId)]", message: message)
}
// MARK: - Methods to override
open func load() -> Bool {
fatalError("Subclass must implement")
}
open func show(_ request: InterstitialShowRequest? = nil) -> Bool {
fatalError("Subclass must implement")
}
open func destroy() -> Bool {
fatalError("Subclass must implement")
}
}

View File

@ -0,0 +1,61 @@
// GuruRewardedAd.swift
// Base class for rewarded ad implementations
// Corresponds to GuruRewardedAd.kt in Android implementation
import Foundation
import UIKit
public struct RewardedShowRequest {
public let placement: String?
public init(placement: String?) {
self.placement = placement
}
}
open class GuruRewardedAd: RelayAd {
public let adUnitId: String
public let adPlatform: AdPlatform
private let name: String
public let adType = AdType.Rewarded
public init(engineId: Int, adUnitId: String, adPlatform: AdPlatform) {
self.adUnitId = adUnitId
self.adPlatform = adPlatform
self.name = "[RWD-\(adPlatform)]"
super.init(engineId: engineId)
}
// MARK: - Logging Methods
public func logInfo(_ message: String) {
Logger.i(tag: "RWD-\(adPlatform.name)[\(adUnitId)]", message: message)
}
public func logWarn(_ message: String) {
Logger.w(tag: "RWD-\(adPlatform.name)[\(adUnitId)]", message: message)
}
public func logDebug(_ message: String) {
Logger.d(tag: "RWD-\(adPlatform.name)[\(adUnitId)]", message: message)
}
public func logError(_ message: String) {
Logger.e(tag: "RWD-\(adPlatform.name)[\(adUnitId)]", message: message)
}
// MARK: - Methods to override
open func load() -> Bool {
fatalError("Subclass must implement")
}
open func show(_ request: RewardedShowRequest? = nil) -> Bool {
fatalError("Subclass must implement")
}
open func destroy() -> Bool {
fatalError("Subclass must implement")
}
}

View File

@ -0,0 +1,56 @@
// RelayAd.swift
// Base class for ad implementations with delegate forwarding
// Corresponds to RelayAd.kt in Android implementation
import Foundation
open class RelayAd: NSObject {
public var listener: FusionAdListener?
public let engineId: Int
public init(engineId: Int) {
self.engineId = engineId
}
// public methods for notifying listeners (called from subclasses)
public func adLoaded(ad: FusionAd) {
listener?.onAdLoaded(ad: ad)
}
public func adDisplayed(ad: FusionAd) {
listener?.onAdDisplayed(ad: ad)
}
public func adHidden(ad: FusionAd) {
listener?.onAdHidden(ad: ad)
}
public func adClicked(ad: FusionAd) {
listener?.onAdClicked(ad: ad)
}
public func adUserRewarded(ad: FusionAd, reward: FusionReward) {
listener?.onUserRewarded(ad: ad, reward: reward)
}
public func adLoadFailed(loadFailedInfo: LoadFailedInfo) {
listener?.onAdLoadFailed(loadFailedInfo: loadFailedInfo)
}
public func adDisplayFailed(ad: FusionAd, error: FusionError) {
listener?.onAdDisplayFailed(ad: ad, error: error)
}
public func adRevenuePaid(ad: FusionAd) {
listener?.onAdRevenuePaid(ad: ad)
}
public func adBannerExpanded(ad: FusionAd) {
listener?.onBannerAdExpanded(ad: ad)
}
public func adBannerCollapsed(ad: FusionAd) {
listener?.onBannerAdCollapsed(ad: ad)
}
}

View File

@ -0,0 +1,62 @@
// GuruMRecAd.swift
// Base class for MRec ad implementations
// Corresponds to GuruMRecAd.kt in Android implementation
import Foundation
import UIKit
open class GuruMRecAd: RelayAd {
public let adUnitId: String
public let adPlatform: AdPlatform
public let adType = AdType.MRec
private let name: String
public init(engineId: Int, adUnitId: String, adPlatform: AdPlatform) {
self.adUnitId = adUnitId
self.adPlatform = adPlatform
self.name = "[MREC-\(adPlatform)]"
super.init(engineId: engineId)
}
// MARK: - Logging Methods
public func logWarn(_ message: String) {
Logger.w(tag: "MRecAd", message: "\(name) \(message)")
}
public func logDebug(_ message: String) {
Logger.d(tag: "MRecAd", message: "\(name) \(message)")
}
public func logInfo(_ message: String) {
Logger.i(tag: "MRecAd", message: "\(name) \(message)")
}
public func logError(_ message: String) {
Logger.e(tag: "MRecAd", message: "\(name) \(message)")
}
// MARK: - Methods to override
open func load() -> Bool {
fatalError("Subclass must implement")
}
open func show(request: MRecShowRequest? = nil) -> Bool {
fatalError("Subclass must implement")
}
open func hide() -> Bool {
fatalError("Subclass must implement")
}
open func destroy() -> Bool {
fatalError("Subclass must implement")
}
open func updateOrientation(orientation: ScreenOrientation) -> Bool {
fatalError("Subclass must implement")
}
}

View File

@ -0,0 +1,17 @@
// MRecShowRequest.swift
// Request parameters for showing MRec ads
// Corresponds to MRecShowRequest.kt in Android implementation
import Foundation
public struct MRecShowRequest {
public let placement: String
public let horizontalOffset: Float
public let verticalOffset: Float
public init(placement: String, horizontalOffset: Float = 0.0, verticalOffset: Float = 0.0) {
self.placement = placement
self.horizontalOffset = horizontalOffset
self.verticalOffset = verticalOffset
}
}

View File

@ -0,0 +1,220 @@
// AdModels.swift
// Defines core ad models for the FusionAds SDK
// Corresponds to AdModels.kt in Android implementation
import Foundation
import UIKit
// MARK: - AdPlatform
public class AdPlatform: Hashable, CustomStringConvertible, Codable {
public let name: String
private init(_ name: String) {
self.name = name
}
required public init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
self.name = try container.decode(String.self)
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.name)
}
public static let adMob = AdPlatform("ADMOB")
public static let amazon = AdPlatform("AMZ")
public static let facebook = AdPlatform("FB")
public static let ironSource = AdPlatform("ULP")
public static let max = AdPlatform("MAX")
public static let tradPlus = AdPlatform("TP")
public static let pangle = AdPlatform("PG")
public static let invalid = AdPlatform("INVALID")
public static let fusion = AdPlatform("FUSION")
public static let test = AdPlatform("TEST")
public static func fromName(_ name: String) -> AdPlatform {
let upperName = name.uppercased()
switch upperName {
case AdPlatform.adMob.name:
return AdPlatform.adMob
case AdPlatform.amazon.name:
return AdPlatform.amazon
case AdPlatform.facebook.name:
return AdPlatform.facebook
case AdPlatform.ironSource.name:
return AdPlatform.ironSource
case AdPlatform.max.name:
return AdPlatform.max
case AdPlatform.test.name:
return AdPlatform.test
default:
fatalError("Invalid Ad Platform name")
}
}
public static func == (lhs: AdPlatform, rhs: AdPlatform) -> Bool {
return lhs.name == rhs.name
}
public func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
public var description: String {
return name
}
}
// MARK: - AdType
/**
* Ad format or type of an ad unit,
* [name] mainly for display scenario
* [label] for more formal scenario, like stats
*/
public class AdType: Equatable, Hashable, CustomStringConvertible {
public let name: String
public let label: String
private init(_ name: String, label: String) {
self.name = name
self.label = label
}
public static let Interstitial = AdType("Interstitial", label: "INTER")
public static let Rewarded = AdType("Rewarded", label: "REWARDED")
public static let Banner = AdType("Banner", label: "BANNER")
public static let MRec = AdType("MRec", label: "MREC")
public static func fromName(_ name: String) -> AdType {
switch name {
case AdType.Interstitial.name:
return AdType.Interstitial
case AdType.Rewarded.name:
return AdType.Rewarded
case AdType.Banner.name:
return AdType.Banner
case AdType.MRec.name:
return AdType.MRec
default:
fatalError("Invalid Ad Type name")
}
}
public static func == (lhs: AdType, rhs: AdType) -> Bool {
return lhs.name == rhs.name
}
public func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
public var description: String {
return name
}
}
// MARK: - BannerPosition
public enum BannerPosition: String {
case top
case bottom
case start
case end
}
// MARK: - ScreenOrientation
public enum ScreenOrientation: Int {
case portrait = 0
case landscape = 1
}
public class LoadAdTimeoutError: FusionError {
public var errorCode: Int { return -100001 }
public var message: String { return "Load ad timeout" }
public var cause: Error? { return nil }
public var info: String? { return nil }
public var waterfallName: String? { return nil }
}
public class LoadAdRequestError: FusionError {
public var errorCode: Int { return -100002 }
public let message: String
public var cause: Error? { return nil }
public var info: String? { return nil }
public var waterfallName: String? { return nil }
init(message: String = "Load Ad Request Error") {
self.message = message
}
}
public class ShowAdTimeoutError: FusionError {
public var errorCode: Int { return -100003 }
public var message: String { return "Show Ad Timeout Error" }
public var cause: Error? { return nil }
public var info: String? { return nil }
public var waterfallName: String? { return nil }
}
public class ShowAdRequestError: FusionError {
public var errorCode: Int { return -100004 }
public let message: String
public var cause: Error? { return nil }
public var info: String? { return nil }
public var waterfallName: String? { return nil }
init(message: String = "Show Ad Request Error") {
self.message = message
}
}
public class LoadAdNotAvailableError: FusionError {
public var errorCode: Int { return -100005 }
public let message: String
public var cause: Error? { return nil }
public var info: String? { return nil }
public var waterfallName: String? { return nil }
init(message: String = "Load Ad Not Avaialable Error") {
self.message = message
}
}
// MARK: - InvalidFusionAd
public class InvalidFusionAd: FusionAd {
public override var adPlatform: AdPlatform { return AdPlatform.invalid }
public override var adUnitId: String? { return "" }
public override var revenue: Double { return 0.0 }
public override var waterfallName: String? { return "" }
public override var placement: String? { return "" }
public override var networkName: String? { return "" }
public override var networkPlacement: String? { return "" }
public override var creativeId: String? { return "" }
public override var adReviewCreativeId: String? { return "" }
private override init(engineId: Int, adType: AdType) {
super.init(engineId: engineId, adType: adType)
}
public static func banner(engineId: Int) -> InvalidFusionAd {
return InvalidFusionAd(engineId: engineId, adType: AdType.Banner)
}
public static func interstitial(engineId: Int) -> InvalidFusionAd {
return InvalidFusionAd(engineId: engineId, adType: AdType.Interstitial)
}
public static func rewarded(engineId: Int) -> InvalidFusionAd {
return InvalidFusionAd(engineId: engineId, adType: AdType.Rewarded)
}
public static func mRec(engineId: Int) -> InvalidFusionAd {
return InvalidFusionAd(engineId: engineId, adType: AdType.MRec)
}
}

View File

@ -0,0 +1,361 @@
// AdsEngineManager.swift
// Manages all ad engines
// Corresponds to AdsEngineManager.kt in Android implementation
import Foundation
import UIKit
class AdsEngineManager {
private let viewController: UIViewController
private var interstitialAdEngines: [Int: InterstitialAdEngine] = [:]
private var rewardedAdEngines: [Int: RewardedAdEngine] = [:]
private var bannerAdEngines: [Int: BannerAdEngine] = [:]
private var mrecAdEngines: [Int: MRecAdEngine] = [:]
private var supportedAdsSdk: [AdPlatform: GuruAdsSdk] = [:]
init(viewController: UIViewController) {
self.viewController = viewController
}
// MARK: - Logging
private func logInfo(_ message: String) {
Logger.i(tag: "StrategyManager", message: message)
}
private func logError(_ message: String, error: Error? = nil) {
Logger.e(tag: "StrategyManager", message: message)
}
// MARK: - SDK Management
internal func addAdsSdk(_ adsSdk: GuruAdsSdk) {
supportedAdsSdk[adsSdk.adPlatform] = adsSdk
}
private func getAdsSdk(adPlatform: AdPlatform) -> GuruAdsSdk? {
return supportedAdsSdk[adPlatform]
}
// MARK: - Interstitial Engine Methods
private func createDefaultInterstitialAdEngine(
id: Int,
config: DefaultInterstitialConfig
) -> DefaultInterstitialAdEngine? {
if let adSdk = getAdsSdk(adPlatform: AdPlatform.fromName(config.adPlatform))?.obtainInterstitialAd(
adConfig: AdConfig(
engineId: id,
adUnitId: config.adUnitId,
adAmazonSlotId: config.adAmazonSlotId
)
) {
return DefaultInterstitialAdEngine(viewController: viewController, id: id, ad: adSdk)
} else {
return nil;
}
}
private func createMabInterstitialEngine(
id: Int,
faUnitId: String,
config: MabInterstitialConfig
) -> MabInterstitialEngine {
return MabInterstitialEngine(viewController: viewController, id: id, faUnitId: faUnitId, mabConfig: config) {
[weak self] adPlatform, adConfig in
self?.getAdsSdk(adPlatform: adPlatform)?.obtainInterstitialAd(adConfig: adConfig)
}
}
func getInterstitialAdEngine(id: Int) -> InterstitialAdEngine? {
return interstitialAdEngines[id]
}
func createInterstitialAdEngine(adEngineConfig: AdEngineConfig) -> InterstitialAdEngine? {
// Potential hashcode collision
let id = adEngineConfig.hashCode()
logInfo("createInterstitialAdEngine: \(id)")
let engine:InterstitialAdEngine? = getInterstitialAdEngine(id: id) ?? {
logInfo("config parsed!, with strategy \(InterstitialStrategy.default.rawValue)")
switch adEngineConfig.strategy {
case InterstitialStrategy.default.rawValue:
guard let config = try? DefaultInterstitialConfig(adEngineConfig.config) else {
return nil;
}
return createDefaultInterstitialAdEngine(
id: id,
config: config
)
case InterstitialStrategy.mab.rawValue:
guard let config = try? MabInterstitialConfig(adEngineConfig.config) else {
return nil;
}
return createMabInterstitialEngine(id: id, faUnitId: adEngineConfig.faUnitId, config: config)
default:
// todo forward compatible
guard let config = try? DefaultInterstitialConfig(adEngineConfig.config) else {
return nil;
}
return createDefaultInterstitialAdEngine(
id: id,
config:config
)
}
}()
if let engine = engine {
interstitialAdEngines[id] = engine
}
return engine
}
// MARK: - Rewarded Engine Methods
private func createDefaultRewardedAdEngine(
id: Int,
config: DefaultRewardedConfig
) -> DefaultRewardedAdEngine? {
if let adSdk = getAdsSdk(adPlatform: AdPlatform.fromName(config.adPlatform))?.obtainRewardedAd(
adConfig: AdConfig(
engineId: id,
adUnitId: config.adUnitId,
adAmazonSlotId: config.adAmazonSlotId
)
) {
return DefaultRewardedAdEngine(viewController: viewController, id: id, ad: adSdk)
} else {
return nil;
}
}
private func createMabRewardedEngine(
id: Int,
faUnitId: String,
config: MabRewardedConfig
) -> MabRewardedEngine {
return MabRewardedEngine(viewController: viewController, id: id, faUnitId: faUnitId, mabConfig: config) {
[weak self] adPlatform, adConfig in
self?.getAdsSdk(adPlatform: adPlatform)?.obtainRewardedAd(adConfig: adConfig)
}
}
func getRewardedAdEngine(id: Int) -> RewardedAdEngine? {
return rewardedAdEngines[id]
}
func createRewardedAdEngine(adEngineConfig: AdEngineConfig) -> RewardedAdEngine? {
let id = adEngineConfig.hashCode()
logInfo("createRewardedAdEngine: \(id)")
let engine = getRewardedAdEngine(id: id) ?? {
switch adEngineConfig.strategy {
case RewardedStrategy.default.rawValue:
guard let config = try? DefaultRewardedConfig(adEngineConfig.config) else {
return nil;
}
return createDefaultRewardedAdEngine(
id: id,
config: config
)
case RewardedStrategy.mab.rawValue:
guard let config = try? MabRewardedConfig(adEngineConfig.config) else {
return nil;
}
return createMabRewardedEngine(id: id, faUnitId: adEngineConfig.faUnitId, config: config)
default:
guard let config = try? DefaultRewardedConfig(adEngineConfig.config) else {
return nil;
}
return createDefaultRewardedAdEngine(
id: id,
config: config
)
}
}()
if let engine = engine {
rewardedAdEngines[id] = engine
}
return engine
}
// MARK: - Banner Engine Methods
private func createDefaultBannerAdEngine(
id: Int,
config: DefaultBannerConfig
) -> DefaultBannerAdEngine? {
if let adSdk = getAdsSdk(adPlatform: AdPlatform.fromName(config.adPlatform))?.obtainBannerAd(
adConfig: AdConfig(
engineId: id,
adUnitId: config.adUnitId,
adAmazonSlotId: config.adAmazonSlotId
)
) {
return DefaultBannerAdEngine(viewController: viewController, id: id, ad: adSdk)
} else {
return nil;
}
}
func getBannerAdEngine(id: Int) -> BannerAdEngine? {
return bannerAdEngines[id]
}
func createBannerAdEngine(adEngineConfig: AdEngineConfig) -> BannerAdEngine? {
let id = adEngineConfig.hashCode()
logInfo("createBannerAdEngine: \(id)")
let engine = getBannerAdEngine(id: id) ?? {
switch adEngineConfig.strategy {
case BannerStrategy.default.rawValue:
guard let config = try? DefaultBannerConfig(adEngineConfig.config) else {
return nil;
}
return createDefaultBannerAdEngine(
id: id,
config: config
)
default:
guard let config = try? DefaultBannerConfig(adEngineConfig.config) else {
return nil;
}
return createDefaultBannerAdEngine(
id: id,
config: config
)
}
}()
if let engine = engine {
bannerAdEngines[id] = engine
}
return engine
}
// MARK: - MRec Engine Methods
private func createDefaultMRecAdEngine(
id: Int,
config: DefaultMRecConfig
) -> DefaultMRecAdEngine? {
if let adSdk = getAdsSdk(adPlatform: AdPlatform.fromName(config.adPlatform))?.obtainMRecAd(
adConfig: AdConfig(
engineId: id,
adUnitId: config.adUnitId,
adAmazonSlotId: config.adAmazonSlotId
)
) {
return DefaultMRecAdEngine(viewController: viewController, id: id, ad: adSdk)
} else {
return nil;
}
}
func getMRecAdEngine(id: Int) -> MRecAdEngine? {
return mrecAdEngines[id]
}
func createMRecAdEngine(adEngineConfig: AdEngineConfig) -> MRecAdEngine? {
let id = adEngineConfig.hashCode()
logInfo("createMRecAdEngine: \(id)")
let engine = getMRecAdEngine(id: id) ?? {
switch adEngineConfig.strategy {
case BannerStrategy.default.rawValue:
guard let config = try? DefaultMRecConfig(adEngineConfig.config) else {
return nil;
}
return createDefaultMRecAdEngine(
id: id,
config: config
)
default:
guard let config = try? DefaultMRecConfig(adEngineConfig.config) else {
return nil;
}
return createDefaultMRecAdEngine(
id: id,
config: config
)
}
}()
if let engine = engine {
mrecAdEngines[id] = engine
}
return engine
}
func dispatchOrientationUpdate(orientation: ScreenOrientation) {
let body: (Any) throws -> Void = { it in
(it as? OrientationAware)?.onOrientationUpdate(orientation: orientation)
}
try? interstitialAdEngines.values.forEach(body)
try? bannerAdEngines.values.forEach(body)
try? rewardedAdEngines.values.forEach(body)
try? mrecAdEngines.values.forEach(body)
}
// for testing only!
func clearCachedEngines() {
interstitialAdEngines.removeAll()
bannerAdEngines.removeAll()
rewardedAdEngines.removeAll()
mrecAdEngines.removeAll()
}
}
// MARK: - Hashable Extension for AdEngineConfig
extension AdEngineConfig: Hashable {
func hashCode() -> Int {
return hashValue
}
public static func == (lhs: AdEngineConfig, rhs: AdEngineConfig) -> Bool {
return lhs.strategy == rhs.strategy
&& (lhs.config as NSDictionary).isEqual(to: rhs.config)
}
public func hash(into hasher: inout Hasher) {
hasher.combine(strategy)
for (key, value) in config {
hasher.combine(key)
if let hashableValue = value as? (any Hashable) {
hasher.combine(hashableValue)
}
}
}
}
// MARK: - Hashable Extensions
extension Int {
func hashCode() -> Int {
return self
}
}
extension String {
func hashCode() -> Int {
var h: Int = 0
for c in self.unicodeScalars {
h = 31 &* h &+ Int(c.value)
}
return h
}
}

View File

@ -0,0 +1,101 @@
// RelayAdEngine.swift
// Base class for all ad engines with state machine
// Corresponds to RelayAdEngine.kt in Android implementation
import Foundation
import UIKit
public struct EventParams {
public let ad: FusionAd?
public let reward: FusionReward?
public let displayFailedInfo: FusionError?
public let loadFailedInfo: LoadFailedInfo?
public let id: Int
public init(
ad: FusionAd? = nil,
reward: FusionReward? = nil,
displayFailedInfo: FusionError? = nil,
loadFailedInfo: LoadFailedInfo? = nil,
id: Int = -1
) {
self.ad = ad
self.reward = reward
self.displayFailedInfo = displayFailedInfo
self.loadFailedInfo = loadFailedInfo
self.id = id
}
}
protocol OrientationAware {
func onOrientationUpdate(orientation: ScreenOrientation)
}
public class RelayAdEngine: StateMachine {
public let id: Int
public let adType: AdType
public var listener: FusionAdListener?
public var supportedAdPlatforms: Set<AdPlatform> {
fatalError("Subclass must implement")
}
public init(viewController: UIViewController, id: Int, adType: AdType, strategyName: String) {
self.id = id
self.adType = adType
super.init(name: "\(adType.name)-\(strategyName)-\(id)")
}
// MARK: - Logging Methods
public func logDebug(_ message: String) {
Logger.d(tag: name, message: message)
}
public func logInfo(_ message: String) {
Logger.i(tag: name, message: message)
}
public func logWarn(_ message: String) {
Logger.w(tag: name, message: message)
}
public func logError(_ message: String) {
Logger.e(tag: name, message: message)
}
// MARK: - Listener Callback Methods
public func adLoaded(ad: FusionAd) {
listener?.onAdLoaded(ad: ad)
}
public func adDisplayed(ad: FusionAd) {
listener?.onAdDisplayed(ad: ad)
}
public func adHidden(ad: FusionAd) {
listener?.onAdHidden(ad: ad)
}
public func adClicked(ad: FusionAd) {
listener?.onAdClicked(ad: ad)
}
public func adLoadFailed(loadFailedInfo: LoadFailedInfo) {
listener?.onAdLoadFailed(loadFailedInfo: loadFailedInfo)
}
public func adDisplayFailed(ad: FusionAd, error: FusionError?) {
listener?.onAdDisplayFailed(ad: ad, error: error)
}
public func adRevenuePaid(ad: FusionAd) {
listener?.onAdRevenuePaid(ad: ad)
}
public func adUserRewarded(ad: FusionAd, reward: FusionReward?) {
listener?.onUserRewarded(ad: ad, reward: reward)
}
}

View File

@ -0,0 +1,299 @@
// StrategyConfig.swift
// Defines configuration structures for ad strategies
// Corresponds to StrategyConfig.kt in Android implementation
import Foundation
// MARK: - Strategy Types
public enum InterstitialStrategy: Int {
case `default` = 0
case mab = 1
}
public enum RewardedStrategy: Int {
case `default` = 0
case mab = 1
}
public enum BannerStrategy: Int {
case `default` = 0
}
protocol StrategyConfig: Decodable {}
// MARK: - Default Config Classes
public struct DefaultInterstitialConfig: Codable, StrategyConfig {
public let adPlatform: String
public let adUnitId: String
public let adAmazonSlotId: String?
public init(adPlatform: String, adUnitId: String, adAmazonSlotId: String? = nil) {
self.adPlatform = adPlatform
self.adUnitId = adUnitId
self.adAmazonSlotId = adAmazonSlotId
}
private enum CodingKeys : String, CodingKey {
case adPlatform = "ad_platform",
adUnitId = "ad_unit_id",
adAmazonSlotId = "ad_amz_slot_id"
}
}
public struct DefaultRewardedConfig: Codable, StrategyConfig {
public let adPlatform: String
public let adUnitId: String
public let adAmazonSlotId: String?
public init(adPlatform: String, adUnitId: String, adAmazonSlotId: String? = nil) {
self.adPlatform = adPlatform
self.adUnitId = adUnitId
self.adAmazonSlotId = adAmazonSlotId
}
private enum CodingKeys : String, CodingKey {
case adPlatform = "ad_platform",
adUnitId = "ad_unit_id",
adAmazonSlotId = "ad_amz_slot_id"
}
}
public struct DefaultBannerConfig: Codable, StrategyConfig {
public let adPlatform: String
public let adUnitId: String
public let adAmazonSlotId: String?
public init(adPlatform: String, adUnitId: String, adAmazonSlotId: String? = nil) {
self.adPlatform = adPlatform
self.adUnitId = adUnitId
self.adAmazonSlotId = adAmazonSlotId
}
private enum CodingKeys : String, CodingKey {
case adPlatform = "ad_platform",
adUnitId = "ad_unit_id",
adAmazonSlotId = "ad_amz_slot_id"
}
}
public struct DefaultMRecConfig: Codable, StrategyConfig {
public let adPlatform: String
public let adUnitId: String
public let adAmazonSlotId: String?
public init(adPlatform: String, adUnitId: String, adAmazonSlotId: String? = nil) {
self.adPlatform = adPlatform
self.adUnitId = adUnitId
self.adAmazonSlotId = adAmazonSlotId
}
private enum CodingKeys : String, CodingKey {
case adPlatform = "ad_platform",
adUnitId = "ad_unit_id",
adAmazonSlotId = "ad_amz_slot_id"
}
}
public struct MabRewardedConfig: Codable, StrategyConfig {
public let aggregatorConfigs: [AggregatorConfig]
public let showTimeoutInSecond: Double?
init(aggregatorConfigs: [AggregatorConfig], showTimeoutInSecond: Double?) {
self.aggregatorConfigs = aggregatorConfigs
self.showTimeoutInSecond = showTimeoutInSecond
}
private enum CodingKeys : String, CodingKey {
case aggregatorConfigs = "aggregator_configs",
showTimeoutInSecond = "show_timeout_second"
}
}
public struct MabInterstitialConfig: Codable, StrategyConfig {
public let aggregatorConfigs: [AggregatorConfig]
public let showTimeoutInSecond: Double?
init(aggregatorConfigs: [AggregatorConfig], showTimeoutInSecond: Double?) {
self.aggregatorConfigs = aggregatorConfigs
self.showTimeoutInSecond = showTimeoutInSecond
}
private enum CodingKeys : String, CodingKey {
case aggregatorConfigs = "aggregator_configs",
showTimeoutInSecond = "show_timeout_second"
}
}
public struct AggregatorConfig: Codable {
public let aggregatorId: String
public let adPlatform: String
public let raiseRate: Double
public let selfRaiseRate: Double?
public let cacheExpireInSecond: Double
public let priority: Int
public let adUnitIds: [AdUnitSpec]
public let loadTimeoutInSecond: Double?
init(aggregatorId: String, adPlatform: String, raiseRate: Double, selfRaiseRate: Double?, cacheExpireInSecond: Double, priority: Int, adUnitIds: [AdUnitSpec], loadTimeoutInSecond: Double?) {
self.aggregatorId = aggregatorId
self.adPlatform = adPlatform
self.raiseRate = raiseRate
self.selfRaiseRate = selfRaiseRate
self.cacheExpireInSecond = cacheExpireInSecond
self.priority = priority
self.adUnitIds = adUnitIds
self.loadTimeoutInSecond = loadTimeoutInSecond
}
private enum CodingKeys : String, CodingKey {
case aggregatorId = "aggregator_id",
adPlatform = "ad_platform",
raiseRate = "raise_rate",
selfRaiseRate = "self_raise_rate",
cacheExpireInSecond = "cache_expire_second",
priority = "priority",
adUnitIds = "ad_unit_ids",
loadTimeoutInSecond = "load_timeout_second"
}
}
public struct AdUnitSpec: Codable {
public let adUnitId: String
public let adAmazonSlotId: String?
public let floorEcpm: Double
public var floor: Double {
return floorEcpm / 1000
}
init(adUnitId: String, adAmazonSlotId: String?, floorEcpm: Double = 0.0) {
self.adUnitId = adUnitId
self.adAmazonSlotId = adAmazonSlotId
self.floorEcpm = floorEcpm
}
private enum CodingKeys : String, CodingKey {
case adUnitId = "ad_unit_id",
adAmazonSlotId = "ad_amz_slot_id",
floorEcpm = "floor_ecpm"
}
}
// MARK: - AdEngineConfig
public struct AdEngineConfig: Codable {
public let strategy: Int
public let faUnitId: String
public let config: [String: Any]
public init(strategy: Int, faUnitId: String, config: [String: Any]) {
self.strategy = strategy
self.faUnitId = faUnitId
self.config = config
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(strategy, forKey: .strategy)
try container.encode(faUnitId, forKey: .faUnitId)
try container.encode(JSONCodable(config), forKey: .config)
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
strategy = try container.decode(Int.self, forKey: .strategy)
faUnitId = try container.decode(String?.self, forKey: .faUnitId) ?? "default"
config = try container.decode(JSONCodable.self, forKey: .config).value as! Dictionary
}
private enum CodingKeys: String, CodingKey {
case strategy = "strategy",
config = "config",
faUnitId = "fa_unit_id"
}
}
private struct JSONCodable: Codable {
let value: Any
init(_ value: Any) {
self.value = value
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
self.value = NSNull()
} else if let bool = try? container.decode(Bool.self) {
self.value = bool
} else if let int = try? container.decode(Int.self) {
self.value = int
} else if let uint = try? container.decode(UInt.self) {
self.value = uint
} else if let double = try? container.decode(Double.self) {
self.value = double
} else if let string = try? container.decode(String.self) {
self.value = string
} else if let array = try? container.decode([JSONCodable].self) {
self.value = array.map { $0.value }
} else if let dictionary = try? container.decode([String: JSONCodable].self) {
self.value = dictionary.mapValues { $0.value }
} else {
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "AnyCodable cannot decode value"
)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self.value {
case is NSNull:
try container.encodeNil()
case let bool as Bool:
try container.encode(bool)
case let int as Int:
try container.encode(int)
case let uint as UInt:
try container.encode(uint)
case let double as Double:
try container.encode(double)
case let string as String:
try container.encode(string)
case let array as [Any]:
try container.encode(array.map { JSONCodable($0) })
case let dictionary as [String: Any]:
try container.encode(dictionary.mapValues { JSONCodable($0) })
default:
let context = EncodingError.Context(
codingPath: container.codingPath,
debugDescription: "AnyCodable cannot encode value \(self.value)"
)
throw EncodingError.invalidValue(self.value, context)
}
}
}
extension StrategyConfig
{
init<Key: Hashable>(_ dict: [Key: Any]) throws
{
do {
let data = try JSONSerialization.data(withJSONObject: dict, options: [])
self = try JSONDecoder().decode(Self.self, from: data)
} catch {
Logger.e(tag: "StrategyConfig", message: "strategy config deserialize to \(Self.self) fail, data is \(dict)")
throw error;
}
}
}

View File

@ -0,0 +1,170 @@
// BannerAdEngine.swift
// Base class for banner ad engines
// Corresponds to BannerAdEngine.kt in Android implementation
import Foundation
import UIKit
public class BannerAdEngine: RelayAdEngine {
private(set) var idleState: Idle!
private(set) var loadingState: Loading!
private(set) var activeState: Active!
private(set) var showingState: Showing!
public private(set) var hiddenState: Hidden!
public var currentStateIdentifier: AdState.Identifier {
return (currentState as? AdState)?.stateId ?? AdState.Identifier(name: currentState?.name ?? "unknown")
}
public override init(viewController: UIViewController, id: Int, adType: AdType, strategyName: String) {
super.init(viewController: viewController, id: id, adType: adType, strategyName: strategyName)
}
public func initialize() {
idleState = createIdleState()
loadingState = createLoadingState()
activeState = createActiveState()
showingState = createShowingState()
hiddenState = createHiddenState()
addState(idleState)
addState(loadingState)
addState(activeState)
addState(showingState, parent: activeState)
addState(hiddenState, parent: activeState)
setInitialState(idleState)
start()
}
// Methods to be overridden by subclasses
public class AdState: State {
public let stateId: Identifier
public init(name: String, stateId: Identifier) {
self.stateId = stateId
super.init()
}
public override var name: String {
return stateId.name
}
public class Identifier: Equatable {
public let name: String
public init(name: String) {
self.name = name
}
// Pre-defined identifiers
public static let IDLE = Identifier(name: "idle")
public static let LOADING = Identifier(name: "loading")
public static let ACTIVE = Identifier(name: "active")
public static let SHOWING = Identifier(name: "showing")
public static let HIDDEN = Identifier(name: "hidden")
public static func == (lhs: Identifier, rhs: Identifier) -> Bool {
return lhs.name == rhs.name
}
}
}
public class Idle: AdState {
public init() {
super.init(name: "[IDLE]", stateId: Identifier.IDLE)
}
}
public class Loading: AdState {
public init() {
super.init(name: "[LOADING]", stateId: Identifier.LOADING)
}
}
public class Active: AdState {
public init() {
super.init(name: "[ACTIVE]", stateId: Identifier.ACTIVE)
}
}
public class Showing: AdState {
public init() {
super.init(name: "[SHOWING]", stateId: Identifier.SHOWING)
}
}
public class Hidden: AdState {
public init() {
super.init(name: "[HIDDEN]", stateId: Identifier.HIDDEN)
}
}
// Factory methods for creating the states
open func createIdleState() -> Idle {
return Idle()
}
open func createLoadingState() -> Loading {
return Loading()
}
open func createActiveState() -> Active {
return Active()
}
open func createShowingState() -> Showing {
return Showing()
}
open func createHiddenState() -> Hidden {
return Hidden()
}
// Abstract methods to be implemented by subclasses
internal func requestLoad() {
fatalError("Subclass must implement")
}
internal func requestShow(_ request: BannerShowRequest? = nil) {
fatalError("Subclass must implement")
}
internal func requestHide() {
fatalError("Subclass must implement")
}
internal func requestDestroy() {
fatalError("Subclass must implement")
}
// Public API methods
public func show(_ request: BannerShowRequest? = nil) -> Bool {
if isCurrent(activeState) || isCurrent(hiddenState) {
requestShow(request)
return true
}
return false
}
public func hide() -> Bool {
if isCurrent(showingState) {
requestHide()
return true
}
return false
}
public func load() -> Bool {
if isCurrent(idleState) {
requestLoad()
return true
}
return false
}
public func destroy() -> Bool {
requestDestroy()
return true
}
}

View File

@ -0,0 +1,428 @@
// DefaultBannerAdEngine.swift
// Default implementation of BannerAdEngine
// Corresponds to DefaultBannerAdEngine.kt in Android implementation
import Foundation
import UIKit
public class DefaultBannerAdEngine: BannerAdEngine, OrientationAware {
private let ad: GuruBannerAd
// Constants
private enum Action {
static let LOAD = 1
static let SHOW = 2
static let HIDE = 3
static let DESTROY = 4
static let RETRY = 5
}
private enum Event {
static let ON_LOADED = 100
static let ON_LOAD_FAILED = 101
static let ON_DISPLAYED = 102
static let ON_DISPLAY_FAILED = 103
static let ON_CLICK = 104
static let ON_HIDDEN = 105
static let ON_USER_REWARDED = 106
static let REVENUE_PAID = 107
static let LOAD_TIMEOUT = 108
static let SHOW_TIMEOUT = 109
}
private static let LOAD_TIMEOUT_MILLIS: TimeInterval = 120000 / 1000.0
private static let SHOW_TIMEOUT_MILLIS: TimeInterval = 120000 / 1000.0
public override var supportedAdPlatforms: Set<AdPlatform> {
return [ad.adPlatform]
}
public init(viewController: UIViewController, id: Int, ad: GuruBannerAd) {
self.ad = ad
super.init(viewController: viewController, id: id, adType: .Banner, strategyName: ad.adPlatform.name)
setupAdListener()
initialize()
}
private func setupAdListener() {
ad.listener = FusionAdListenerImpl { [weak self] event in
guard let self = self else { return }
switch event {
case .onAdLoaded(let ad):
self.sendMessage(what: Event.ON_LOADED, obj: EventParams(ad: ad))
case .onAdDisplayed(let ad):
// Not used for banner ads, but handled for completeness
break
case .onAdHidden(let ad):
// Not used for banner ads, but handled for completeness
break
case .onUserRewarded(let ad, let reward):
// Not used for banner ads, but handled for completeness
break
case .onAdClicked(let ad):
self.sendMessage(what: Event.ON_CLICK, obj: EventParams(ad: ad))
case .onAdLoadFailed(let loadFailedInfo):
self.sendMessage(what: Event.ON_LOAD_FAILED, obj: EventParams(loadFailedInfo: loadFailedInfo))
case .onAdDisplayFailed(let ad, let error):
self.sendMessage(what: Event.ON_DISPLAY_FAILED, obj: EventParams(ad: ad, displayFailedInfo: error))
case .onAdRevenuePaid(let ad):
self.sendMessage(what: Event.REVENUE_PAID, obj: EventParams(ad: ad))
}
}
}
// MARK: - State Classes
public override func createIdleState() -> Idle {
return IdleImpl(self)
}
public override func createLoadingState() -> Loading {
return LoadingImpl(self)
}
public override func createActiveState() -> Active {
return ActiveImpl(self)
}
public override func createShowingState() -> Showing {
return ShowingImpl(self)
}
public override func createHiddenState() -> Hidden {
return HiddenImpl(self)
}
// MARK: - Request Methods
override internal func requestLoad() {
sendMessage(what: Action.LOAD)
}
override internal func requestShow(_ request: BannerShowRequest? = nil) {
sendMessage(what: Action.SHOW, obj: request)
}
override internal func requestHide() {
sendMessage(what: Action.HIDE)
}
override internal func requestDestroy() {
sendMessage(what: Action.DESTROY)
}
// MARK: - Helper Methods
private func handleUnhandledMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.DESTROY:
ad.destroy()
transitionTo(idleState)
return true
default:
return false
}
}
// MARK: - State Implementations
class IdleImpl: Idle {
private let engine: DefaultBannerAdEngine
init(_ engine: DefaultBannerAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.LOAD:
if engine.ad.load() {
engine.transitionTo(engine.loadingState)
} else {
engine.adLoadFailed(loadFailedInfo: LoadFailedInfo(
engineId: engine.id,
adPlatform: engine.ad.adPlatform,
adType: engine.adType,
adUnitId: engine.ad.adUnitId,
error: LoadAdRequestError()
))
}
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
class LoadingImpl: Loading {
private var retryCount = 0
private let engine: DefaultBannerAdEngine
init(_ engine: DefaultBannerAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
retryCount = 0
// Set load timeout
engine.sendMessageDelayed(what: Event.LOAD_TIMEOUT, delayMillis: Int(DefaultBannerAdEngine.LOAD_TIMEOUT_MILLIS * 1000))
}
public override func exit(to: IState?) {
engine.removeMessages(what: Event.LOAD_TIMEOUT)
engine.removeMessages(what: Action.RETRY)
retryCount = 0
super.exit(to: to)
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.RETRY:
retryCount += 1
if !engine.ad.load() {
engine.sendMessage(what: Event.ON_LOAD_FAILED, obj: EventParams(
loadFailedInfo: LoadFailedInfo(
engineId: engine.id,
adPlatform: engine.ad.adPlatform,
adType: engine.adType,
adUnitId: engine.ad.adUnitId,
error: LoadAdRequestError()
)
))
} else {
engine.sendMessageDelayed(what: Event.LOAD_TIMEOUT, delayMillis: Int(DefaultBannerAdEngine.LOAD_TIMEOUT_MILLIS * 1000))
}
return true
case Action.LOAD:
engine.logWarn("Already loading! Ignore load request")
return true
case Event.LOAD_TIMEOUT:
engine.removeMessages(what: Action.RETRY)
engine.sendMessage(what: Action.RETRY)
return true
case Event.ON_LOADED:
if let eventParams = msg.obj as? EventParams, eventParams.ad != nil {
engine.transitionTo(engine.hiddenState, params: eventParams)
}
engine.deferMessage(msg)
return true
case Event.ON_LOAD_FAILED:
let params = msg.obj as? EventParams
engine.adLoadFailed(loadFailedInfo: params?.loadFailedInfo ?? LoadFailedInfo(
engineId: engine.id,
adPlatform: engine.ad.adPlatform,
adType: engine.adType,
adUnitId: engine.ad.adUnitId,
error: LoadAdRequestError()
))
engine.transitionTo(engine.idleState)
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
class ActiveImpl: Active {
private let engine: DefaultBannerAdEngine
init(_ engine: DefaultBannerAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Event.ON_CLICK:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adClicked(ad: ad)
}
return true
case Event.REVENUE_PAID:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adRevenuePaid(ad: ad)
}
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
class ShowingImpl: Showing {
private let engine: DefaultBannerAdEngine
init(_ engine: DefaultBannerAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
if let eventParams = params as? EventParams, let ad = eventParams.ad {
engine.adDisplayed(ad: ad)
}
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.HIDE:
if engine.ad.hide() {
engine.transitionTo(engine.hiddenState)
} else {
engine.logWarn("Failed to hide banner")
}
return true
case Action.SHOW:
engine.logWarn("Already showing! Ignore show request")
return true
case Event.ON_DISPLAY_FAILED:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adDisplayFailed(ad: ad, error: eventParams.displayFailedInfo)
}
engine.transitionTo(engine.hiddenState)
engine.requestLoad()
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
class HiddenImpl: Hidden {
private let engine: DefaultBannerAdEngine
init(_ engine: DefaultBannerAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
if let eventParams = params as? EventParams, let ad = eventParams.ad {
engine.adLoaded(ad: ad)
}
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.SHOW:
let request = msg.obj as? BannerShowRequest
if engine.ad.show(request) {
engine.transitionTo(engine.showingState)
} else {
engine.logWarn("Failed to show banner")
// no FusionAd here
// if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
// engine.adDisplayFailed(ad: ad, error: ShowAdRequestError())
// }
if(engine.ad.load()) {
engine.transitionTo(engine.loadingState)
}
}
return true
case Action.HIDE:
engine.logWarn("Already hidden! Ignore hide request")
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
func onOrientationUpdate(orientation: ScreenOrientation) {
ad.updateOrientation(orientation: orientation)
}
}
// Helper class to implement the FusionAdListener protocol
private class FusionAdListenerImpl: FusionAdListener {
enum Event {
case onAdLoaded(ad: FusionAd)
case onAdDisplayed(ad: FusionAd)
case onAdHidden(ad: FusionAd)
case onUserRewarded(ad: FusionAd, reward: FusionReward?)
case onAdClicked(ad: FusionAd)
case onAdLoadFailed(loadFailedInfo: LoadFailedInfo)
case onAdDisplayFailed(ad: FusionAd, error: FusionError?)
case onAdRevenuePaid(ad: FusionAd)
}
private let handler: (Event) -> Void
init(handler: @escaping (Event) -> Void) {
self.handler = handler
}
func onAdLoaded(ad: FusionAd) {
handler(.onAdLoaded(ad: ad))
}
func onAdDisplayed(ad: FusionAd) {
handler(.onAdDisplayed(ad: ad))
}
func onAdHidden(ad: FusionAd) {
handler(.onAdHidden(ad: ad))
}
func onUserRewarded(ad: FusionAd, reward: FusionReward?) {
handler(.onUserRewarded(ad: ad, reward: reward))
}
func onAdClicked(ad: FusionAd) {
handler(.onAdClicked(ad: ad))
}
func onAdLoadFailed(loadFailedInfo: LoadFailedInfo) {
handler(.onAdLoadFailed(loadFailedInfo: loadFailedInfo))
}
func onAdDisplayFailed(ad: FusionAd, error: FusionError?) {
handler(.onAdDisplayFailed(ad: ad, error: error))
}
func onAdRevenuePaid(ad: FusionAd) {
handler(.onAdRevenuePaid(ad: ad))
}
}

View File

@ -0,0 +1,383 @@
// DefaultInterstitialAdEngine.swift
// Default implementation of InterstitialAdEngine
// Corresponds to DefaultInterstitialAdEngine.kt in Android implementation
import Foundation
import UIKit
public class DefaultInterstitialAdEngine: InterstitialAdEngine {
private let ad: GuruInterstitialAd
// Constants
private enum Action {
static let LOAD = 1
static let SHOW = 2
static let DESTROY = 3
static let RETRY = 4
}
private enum Event {
static let ON_LOADED = 100
static let ON_LOAD_FAILED = 101
static let ON_DISPLAYED = 102
static let ON_DISPLAY_FAILED = 103
static let ON_CLICK = 104
static let ON_HIDDEN = 105
static let ON_USER_REWARDED = 106
static let REVENUE_PAID = 107
static let LOAD_TIMEOUT = 108
static let SHOW_TIMEOUT = 109
}
private static let LOAD_TIMEOUT_MILLIS: Int = 120_000
private static let SHOW_TIMEOUT_MILLIS: Int = 120_000
public override var supportedAdPlatforms: Set<AdPlatform> {
return [ad.adPlatform]
}
public init(viewController: UIViewController, id: Int, ad: GuruInterstitialAd) {
self.ad = ad
super.init(viewController: viewController, id: id, adType: .Interstitial, strategyName: ad.adPlatform.name)
setupAdListener()
initialize()
}
private func setupAdListener() {
ad.listener = FusionAdListenerImpl { [weak self] event in
guard let self = self else { return }
switch event {
case .onAdLoaded(let ad):
self.sendMessage(what: Event.ON_LOADED, obj: EventParams(ad: ad))
case .onAdDisplayed(let ad):
self.sendMessage(what: Event.ON_DISPLAYED, obj: EventParams(ad: ad))
case .onAdHidden(let ad):
self.sendMessage(what: Event.ON_HIDDEN, obj: EventParams(ad: ad))
case .onUserRewarded(let ad, let reward):
// Not used for interstitial ads, but handled for completeness
break
case .onAdClicked(let ad):
self.sendMessage(what: Event.ON_CLICK, obj: EventParams(ad: ad))
case .onAdLoadFailed(let loadFailedInfo):
self.sendMessage(what: Event.ON_LOAD_FAILED, obj: EventParams(loadFailedInfo: loadFailedInfo))
case .onAdDisplayFailed(let ad, let error):
self.sendMessage(what: Event.ON_DISPLAY_FAILED, obj: EventParams(ad: ad, displayFailedInfo: error))
case .onAdRevenuePaid(let ad):
self.sendMessage(what: Event.REVENUE_PAID, obj: EventParams(ad: ad))
}
}
}
// MARK: - State Classes
public override func createIdleState() -> Idle {
return IdleImpl(self)
}
public override func createLoadingState() -> Loading {
return LoadingImpl(self)
}
public override func createLoadedState() -> Loaded {
return LoadedImpl(self)
}
public override func createShowingState() -> Showing {
return ShowingImpl(self)
}
// MARK: - Request Methods
override internal func requestLoad() {
sendMessage(what: Action.LOAD)
}
override internal func requestShow(request: InterstitialShowRequest?) {
sendMessage(what: Action.SHOW, obj: request)
}
override internal func requestDestroy() {
sendMessage(what: Action.DESTROY)
}
// MARK: - Helper Methods
private func handleUnhandledMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.DESTROY:
ad.destroy()
transitionTo(idleState)
return true
default:
return false
}
}
// MARK: - State Implementations
private class IdleImpl: Idle {
private let engine: DefaultInterstitialAdEngine
init(_ engine: DefaultInterstitialAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.LOAD:
if engine.ad.load() {
engine.transitionTo(engine.loadingState)
} else {
engine.adLoadFailed(loadFailedInfo: LoadFailedInfo(
engineId: engine.id,
adPlatform: engine.ad.adPlatform,
adType: engine.adType,
adUnitId: engine.ad.adUnitId,
error: LoadAdRequestError()
))
}
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
class LoadingImpl: Loading {
private var retryCount = 0
private let engine: DefaultInterstitialAdEngine
init(_ engine: DefaultInterstitialAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
retryCount = 0
// Set load timeout
engine.sendMessageDelayed(what: Event.LOAD_TIMEOUT, delayMillis: DefaultInterstitialAdEngine.LOAD_TIMEOUT_MILLIS)
}
public override func exit(to: IState?) {
engine.removeMessages(what: Event.LOAD_TIMEOUT)
engine.removeMessages(what: Action.RETRY)
retryCount = 0
super.exit(to: to)
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.RETRY:
retryCount += 1
if !engine.ad.load() {
engine.sendMessage(what: Event.ON_LOAD_FAILED, obj: EventParams(
loadFailedInfo: LoadFailedInfo(
engineId: engine.id,
adPlatform: engine.ad.adPlatform,
adType: engine.adType,
adUnitId: engine.ad.adUnitId,
error: LoadAdRequestError()
)
))
} else {
engine.sendMessageDelayed(what: Event.LOAD_TIMEOUT, delayMillis: DefaultInterstitialAdEngine.LOAD_TIMEOUT_MILLIS)
}
return true
case Action.LOAD:
engine.logWarn("Already loading! Ignore load request")
return true
case Event.LOAD_TIMEOUT:
engine.removeMessages(what: Action.RETRY)
engine.sendMessage(what: Action.RETRY)
return true
case Event.ON_LOADED:
if let eventParams = msg.obj as? EventParams, eventParams.ad != nil {
engine.transitionTo(engine.loadedState, params: eventParams)
}
engine.deferMessage(msg)
return true
case Event.ON_LOAD_FAILED:
let params = msg.obj as? EventParams
engine.adLoadFailed(loadFailedInfo: params?.loadFailedInfo ?? LoadFailedInfo(
engineId: engine.id,
adPlatform: engine.ad.adPlatform,
adType: engine.adType,
adUnitId: engine.ad.adUnitId,
error: LoadAdRequestError()
))
engine.transitionTo(engine.idleState)
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
class LoadedImpl: Loaded {
private let engine: DefaultInterstitialAdEngine
init(_ engine: DefaultInterstitialAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
if let eventParams = params as? EventParams, let ad = eventParams.ad {
engine.adLoaded(ad: ad)
}
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.SHOW:
let request = msg.obj as? InterstitialShowRequest
if engine.ad.show(request) {
engine.transitionTo(engine.showingState)
} else {
engine.logWarn("Failed to show interstitial ad")
// no FusionAd here
// engine.adDisplayFailed(ad: engine.ad, error: ShowAdRequestError())
if(engine.ad.load()) {
engine.transitionTo(engine.loadingState)
}
}
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
class ShowingImpl: Showing {
private let engine: DefaultInterstitialAdEngine
init(_ engine: DefaultInterstitialAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
engine.sendMessageDelayed(what: Event.SHOW_TIMEOUT, delayed: .milliseconds(SHOW_TIMEOUT_MILLIS))
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Event.ON_DISPLAYED:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adDisplayed(ad: ad)
}
engine.removeMessages(what: Event.SHOW_TIMEOUT)
return true
case Event.ON_CLICK:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adClicked(ad: ad)
}
return true
case Event.ON_HIDDEN:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adHidden(ad: ad)
engine.transitionTo(engine.idleState)
}
return true
case Event.REVENUE_PAID:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adRevenuePaid(ad: ad)
}
return true
case Event.ON_DISPLAY_FAILED:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adDisplayFailed(ad: ad, error: eventParams.displayFailedInfo)
}
engine.transitionTo(engine.idleState)
engine.requestLoad()
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
}
// Helper class to implement the FusionAdListener protocol
private class FusionAdListenerImpl: FusionAdListener {
enum Event {
case onAdLoaded(ad: FusionAd)
case onAdDisplayed(ad: FusionAd)
case onAdHidden(ad: FusionAd)
case onUserRewarded(ad: FusionAd, reward: FusionReward?)
case onAdClicked(ad: FusionAd)
case onAdLoadFailed(loadFailedInfo: LoadFailedInfo)
case onAdDisplayFailed(ad: FusionAd, error: FusionError?)
case onAdRevenuePaid(ad: FusionAd)
}
private let handler: (Event) -> Void
init(handler: @escaping (Event) -> Void) {
self.handler = handler
}
func onAdLoaded(ad: FusionAd) {
handler(.onAdLoaded(ad: ad))
}
func onAdDisplayed(ad: FusionAd) {
handler(.onAdDisplayed(ad: ad))
}
func onAdHidden(ad: FusionAd) {
handler(.onAdHidden(ad: ad))
}
func onUserRewarded(ad: FusionAd, reward: FusionReward?) {
handler(.onUserRewarded(ad: ad, reward: reward))
}
func onAdClicked(ad: FusionAd) {
handler(.onAdClicked(ad: ad))
}
func onAdLoadFailed(loadFailedInfo: LoadFailedInfo) {
handler(.onAdLoadFailed(loadFailedInfo: loadFailedInfo))
}
func onAdDisplayFailed(ad: FusionAd, error: FusionError?) {
handler(.onAdDisplayFailed(ad: ad, error: error))
}
func onAdRevenuePaid(ad: FusionAd) {
handler(.onAdRevenuePaid(ad: ad))
}
}

View File

@ -0,0 +1,144 @@
// InterstitialAdEngine.swift
// Base class for interstitial ad engines
// Corresponds to InterstitialAdEngine.kt in Android implementation
import Foundation
import UIKit
public class InterstitialAdEngine: RelayAdEngine {
private(set) var idleState: Idle!
private(set) var loadingState: Loading!
private(set) var loadedState: Loaded!
private(set) var showingState: Showing!
public var currentStateIdentifier: AdState.Identifier {
return (currentState as? AdState)?.stateId ?? AdState.Identifier(name: currentState?.name ?? "unknown")
}
public override init(viewController: UIViewController, id: Int, adType: AdType, strategyName: String) {
super.init(viewController: viewController, id: id, adType: .Interstitial, strategyName: strategyName)
}
public func initialize() {
idleState = createIdleState()
loadingState = createLoadingState()
loadedState = createLoadedState()
showingState = createShowingState()
addState(idleState)
addState(loadingState)
addState(loadedState)
addState(showingState)
setInitialState(idleState)
start()
}
// State classes
public class AdState: State {
public let stateId: Identifier
public init(name: String, stateId: Identifier) {
self.stateId = stateId
super.init()
}
public override var name: String {
return stateId.name
}
public class Identifier: Equatable {
public let name: String
public init(name: String) {
self.name = name
}
// Pre-defined identifiers
public static let IDLE = Identifier(name: "idle")
public static let LOADING = Identifier(name: "loading")
public static let LOADED = Identifier(name: "loaded")
public static let SHOWING = Identifier(name: "showing")
public static func == (lhs: Identifier, rhs: Identifier) -> Bool {
return lhs.name == rhs.name
}
}
}
public class Idle: AdState {
public init() {
super.init(name: "[IDLE]", stateId: Identifier.IDLE)
}
}
public class Loading: AdState {
public init() {
super.init(name: "[LOADING]", stateId: Identifier.LOADING)
}
}
public class Loaded: AdState {
public init() {
super.init(name: "[LOADED]", stateId: Identifier.LOADED)
}
}
public class Showing: AdState {
public init() {
super.init(name: "[SHOWING]", stateId: Identifier.SHOWING)
}
}
// Factory methods for creating the states
open func createIdleState() -> Idle {
return Idle()
}
open func createLoadingState() -> Loading {
return Loading()
}
open func createLoadedState() -> Loaded {
return Loaded()
}
open func createShowingState() -> Showing {
return Showing()
}
// Abstract methods to be implemented by subclasses
internal func requestLoad() {
fatalError("Subclass must implement")
}
internal func requestShow(request: InterstitialShowRequest?) {
fatalError("Subclass must implement")
}
internal func requestDestroy() {
fatalError("Subclass must implement")
}
// Public API methods
public func show(_ request: InterstitialShowRequest? = nil) -> Bool {
if isCurrent(loadedState) {
requestShow(request: request)
return true
}
return false
}
public func load() -> Bool {
if isCurrent(idleState) {
requestLoad()
return true
}
return false
}
public func destroy() -> Bool {
requestDestroy()
return true
}
}

View File

@ -0,0 +1,424 @@
// DefaultMRecAdEngine.swift
// Default implementation of MRecAdEngine
// Corresponds to DefaultMRecAdEngine.kt in Android implementation
import Foundation
import UIKit
public class DefaultMRecAdEngine: MRecAdEngine {
private let ad: GuruMRecAd
private var currentShowRequest: MRecShowRequest?
// Constants
private enum Action {
static let LOAD = 1
static let SHOW = 2
static let HIDE = 3
static let DESTROY = 4
static let RETRY = 5
}
private enum Event {
static let ON_LOADED = 100
static let ON_LOAD_FAILED = 101
static let ON_DISPLAYED = 102
static let ON_DISPLAY_FAILED = 103
static let ON_CLICK = 104
static let ON_HIDDEN = 105
static let ON_USER_REWARDED = 106
static let REVENUE_PAID = 107
static let LOAD_TIMEOUT = 108
static let SHOW_TIMEOUT = 109
}
private static let LOAD_TIMEOUT_MILLIS: TimeInterval = 120000 / 1000.0
private static let SHOW_TIMEOUT_MILLIS: TimeInterval = 120000 / 1000.0
public override var supportedAdPlatforms: Set<AdPlatform> {
return [ad.adPlatform]
}
public init(viewController: UIViewController, id: Int, ad: GuruMRecAd) {
self.ad = ad
super.init(viewController: viewController, id: id, adType: .MRec, strategyName: ad.adPlatform.name)
setupAdListener()
initialize()
}
private func setupAdListener() {
ad.listener = FusionAdListenerImpl { [weak self] event in
guard let self = self else { return }
switch event {
case .onAdLoaded(let ad):
self.sendMessage(what: Event.ON_LOADED, obj: EventParams(ad: ad))
case .onAdDisplayed(let ad):
// Not used for MREC ads, but handled for completeness
break
case .onAdHidden(let ad):
// Not used for MREC ads, but handled for completeness
break
case .onUserRewarded(let ad, let reward):
// Not used for MREC ads, but handled for completeness
break
case .onAdClicked(let ad):
self.sendMessage(what: Event.ON_CLICK, obj: EventParams(ad: ad))
case .onAdLoadFailed(let loadFailedInfo):
self.sendMessage(what: Event.ON_LOAD_FAILED, obj: EventParams(loadFailedInfo: loadFailedInfo))
case .onAdDisplayFailed(let ad, let error):
self.sendMessage(what: Event.ON_DISPLAY_FAILED, obj: EventParams(ad: ad, displayFailedInfo: error))
case .onAdRevenuePaid(let ad):
self.sendMessage(what: Event.REVENUE_PAID, obj: EventParams(ad: ad))
}
}
}
// MARK: - State Classes
public override func createIdleState() -> Idle {
return IdleImpl(self)
}
public override func createLoadingState() -> Loading {
return LoadingImpl(self)
}
public override func createActiveState() -> Active {
return ActiveImpl(self)
}
public override func createShowingState() -> Showing {
return ShowingImpl(self)
}
public override func createHiddenState() -> Hidden {
return HiddenImpl(self)
}
// MARK: - Request Methods
override internal func requestLoad() {
sendMessage(what: Action.LOAD)
}
override internal func requestShow(_ request: MRecShowRequest? = nil) {
currentShowRequest = request
sendMessage(what: Action.SHOW, obj: request)
}
override internal func requestHide() {
sendMessage(what: Action.HIDE)
}
override internal func requestDestroy() {
sendMessage(what: Action.DESTROY)
}
// MARK: - Helper Methods
private func handleUnhandledMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.DESTROY:
ad.destroy()
transitionTo(idleState)
return true
default:
return false
}
}
// MARK: - State Implementations
class IdleImpl: Idle {
private let engine: DefaultMRecAdEngine
init(_ engine: DefaultMRecAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.LOAD:
if engine.ad.load() {
engine.transitionTo(engine.loadingState)
} else {
engine.adLoadFailed(loadFailedInfo: LoadFailedInfo(
engineId: engine.id,
adPlatform: engine.ad.adPlatform,
adType: engine.adType,
adUnitId: engine.ad.adUnitId,
error: LoadAdRequestError()
))
}
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
class LoadingImpl: Loading {
private var retryCount = 0
private let engine: DefaultMRecAdEngine
init(_ engine: DefaultMRecAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
retryCount = 0
// Set load timeout
engine.sendMessageDelayed(what: Event.LOAD_TIMEOUT, delayMillis: Int(DefaultMRecAdEngine.LOAD_TIMEOUT_MILLIS * 1000))
}
public override func exit(to: IState?) {
engine.removeMessages(what: Event.LOAD_TIMEOUT)
engine.removeMessages(what: Action.RETRY)
retryCount = 0
super.exit(to: to)
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.RETRY:
retryCount += 1
if !engine.ad.load() {
engine.sendMessage(what: Event.ON_LOAD_FAILED, obj: EventParams(
loadFailedInfo: LoadFailedInfo(
engineId: engine.id,
adPlatform: engine.ad.adPlatform,
adType: engine.adType,
adUnitId: engine.ad.adUnitId,
error: LoadAdRequestError()
)
))
} else {
engine.sendMessageDelayed(what: Event.LOAD_TIMEOUT, delayMillis: Int(DefaultMRecAdEngine.LOAD_TIMEOUT_MILLIS * 1000))
}
return true
case Action.LOAD:
engine.logWarn("Already loading! Ignore load request")
return true
case Event.LOAD_TIMEOUT:
engine.removeMessages(what: Action.RETRY)
engine.sendMessage(what: Action.RETRY)
return true
case Event.ON_LOADED:
if let eventParams = msg.obj as? EventParams, eventParams.ad != nil {
engine.transitionTo(engine.hiddenState, params: eventParams)
}
engine.deferMessage(msg)
return true
case Event.ON_LOAD_FAILED:
let params = msg.obj as? EventParams
engine.adLoadFailed(loadFailedInfo: params?.loadFailedInfo ?? LoadFailedInfo(
engineId: engine.id,
adPlatform: engine.ad.adPlatform,
adType: engine.adType,
adUnitId: engine.ad.adUnitId,
error: LoadAdRequestError()
))
engine.transitionTo(engine.idleState)
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
class ActiveImpl: Active {
private let engine: DefaultMRecAdEngine
init(_ engine: DefaultMRecAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Event.ON_CLICK:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adClicked(ad: ad)
}
return true
case Event.REVENUE_PAID:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adRevenuePaid(ad: ad)
}
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
class ShowingImpl: Showing {
private let engine: DefaultMRecAdEngine
init(_ engine: DefaultMRecAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
if let eventParams = params as? EventParams, let ad = eventParams.ad {
engine.adDisplayed(ad: ad)
}
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.HIDE:
if engine.ad.hide() {
engine.transitionTo(engine.hiddenState)
} else {
engine.logWarn("Failed to hide MREC")
}
return true
case Action.SHOW:
engine.logWarn("Already showing! Ignore show request")
return true
case Event.ON_DISPLAY_FAILED:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adDisplayFailed(ad: ad, error: eventParams.displayFailedInfo)
}
engine.transitionTo(engine.hiddenState)
engine.requestLoad()
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
class HiddenImpl: Hidden {
private let engine: DefaultMRecAdEngine
init(_ engine: DefaultMRecAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
if let eventParams = params as? EventParams, let ad = eventParams.ad {
engine.adLoaded(ad: ad)
}
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.SHOW:
let request = msg.obj as? MRecShowRequest
if engine.ad.show(request: request) {
engine.transitionTo(engine.showingState)
} else {
engine.logWarn("Failed to show MREC")
// no FusionAd here
// engine.adDisplayFailed(ad: engine.ad, error: ShowAdRequestError())
if(engine.load()) {
engine.transitionTo(engine.loadingState)
}
}
return true
case Action.HIDE:
engine.logWarn("Already hidden! Ignore hide request")
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
}
// Helper class to implement the FusionAdListener protocol
private class FusionAdListenerImpl: FusionAdListener {
enum Event {
case onAdLoaded(ad: FusionAd)
case onAdDisplayed(ad: FusionAd)
case onAdHidden(ad: FusionAd)
case onUserRewarded(ad: FusionAd, reward: FusionReward?)
case onAdClicked(ad: FusionAd)
case onAdLoadFailed(loadFailedInfo: LoadFailedInfo)
case onAdDisplayFailed(ad: FusionAd, error: FusionError?)
case onAdRevenuePaid(ad: FusionAd)
}
private let handler: (Event) -> Void
init(handler: @escaping (Event) -> Void) {
self.handler = handler
}
func onAdLoaded(ad: FusionAd) {
handler(.onAdLoaded(ad: ad))
}
func onAdDisplayed(ad: FusionAd) {
handler(.onAdDisplayed(ad: ad))
}
func onAdHidden(ad: FusionAd) {
handler(.onAdHidden(ad: ad))
}
func onUserRewarded(ad: FusionAd, reward: FusionReward?) {
handler(.onUserRewarded(ad: ad, reward: reward))
}
func onAdClicked(ad: FusionAd) {
handler(.onAdClicked(ad: ad))
}
func onAdLoadFailed(loadFailedInfo: LoadFailedInfo) {
handler(.onAdLoadFailed(loadFailedInfo: loadFailedInfo))
}
func onAdDisplayFailed(ad: FusionAd, error: FusionError?) {
handler(.onAdDisplayFailed(ad: ad, error: error))
}
func onAdRevenuePaid(ad: FusionAd) {
handler(.onAdRevenuePaid(ad: ad))
}
}

View File

@ -0,0 +1,173 @@
// MRecAdEngine.swift
// Base class for MREC ad engines
// Corresponds to MRecAdEngine.kt in Android implementation
import Foundation
import UIKit
public class MRecAdEngine: RelayAdEngine {
private(set) var idleState: Idle!
private(set) var loadingState: Loading!
private(set) var activeState: Active!
private(set) var showingState: Showing!
private(set) var hiddenState: Hidden!
public var currentStateIdentifier: AdState.Identifier {
return (currentState as? AdState)?.stateId ?? AdState.Identifier(name: currentState?.name ?? "unknown")
}
public override init(viewController: UIViewController, id: Int, adType: AdType, strategyName: String) {
super.init(viewController: viewController, id: id, adType: .MRec, strategyName: strategyName)
}
public func initialize() {
idleState = createIdleState()
loadingState = createLoadingState()
activeState = createActiveState()
showingState = createShowingState()
hiddenState = createHiddenState()
addState(idleState)
addState(loadingState)
addState(activeState)
addState(showingState, parent: activeState)
addState(hiddenState, parent: activeState)
setInitialState(idleState)
start()
}
// State classes
public class AdState: State {
public let stateId: Identifier
public init(name: String, stateId: Identifier) {
self.stateId = stateId
self.displayName = name
super.init()
}
private var displayName: String;
public override var name: String {
return displayName
}
public class Identifier: Equatable {
public let name: String
public init(name: String) {
self.name = name
}
// Pre-defined identifiers
public static let IDLE = Identifier(name: "idle")
public static let LOADING = Identifier(name: "loading")
public static let ACTIVE = Identifier(name: "active")
public static let SHOWING = Identifier(name: "showing")
public static let HIDDEN = Identifier(name: "hidden")
public static func == (lhs: Identifier, rhs: Identifier) -> Bool {
return lhs.name == rhs.name
}
}
}
public class Idle: AdState {
public init() {
super.init(name: "[IDLE]", stateId: Identifier.IDLE)
}
}
public class Loading: AdState {
public init() {
super.init(name: "[LOADING]", stateId: Identifier.LOADING)
}
}
public class Active: AdState {
public init() {
super.init(name: "[ACTIVE]", stateId: Identifier.ACTIVE)
}
}
public class Showing: AdState {
public init() {
super.init(name: "[SHOWING]", stateId: Identifier.SHOWING)
}
}
public class Hidden: AdState {
public init() {
super.init(name: "[HIDDEN]", stateId: Identifier.HIDDEN)
}
}
// Factory methods for creating the states
open func createIdleState() -> Idle {
return Idle()
}
open func createLoadingState() -> Loading {
return Loading()
}
open func createActiveState() -> Active {
return Active()
}
open func createShowingState() -> Showing {
return Showing()
}
open func createHiddenState() -> Hidden {
return Hidden()
}
// Abstract methods to be implemented by subclasses
internal func requestLoad() {
fatalError("Subclass must implement")
}
internal func requestShow(_ request: MRecShowRequest? = nil) {
fatalError("Subclass must implement")
}
internal func requestHide() {
fatalError("Subclass must implement")
}
internal func requestDestroy() {
fatalError("Subclass must implement")
}
// Public API methods
public func show(request: MRecShowRequest? = nil) -> Bool {
if isCurrent(activeState) || isCurrent(hiddenState) {
requestShow(request)
return true
}
return false
}
public func hide() -> Bool {
if isCurrent(showingState) {
requestHide()
return true
}
return false
}
public func load() -> Bool {
if isCurrent(idleState) {
requestLoad()
return true
}
return false
}
public func destroy() -> Bool {
requestDestroy()
return true
}
}

View File

@ -0,0 +1,390 @@
// DefaultRewardedAdEngine.swift
// Default implementation of RewardedAdEngine
// Corresponds to DefaultRewardedAdEngine.kt in Android implementation
import Foundation
import UIKit
public class DefaultRewardedAdEngine: RewardedAdEngine {
private let ad: GuruRewardedAd
// Constants
private enum Action {
static let LOAD = 1
static let SHOW = 2
static let DESTROY = 3
static let RETRY = 4
}
private enum Event {
static let ON_LOADED = 100
static let ON_LOAD_FAILED = 101
static let ON_DISPLAYED = 102
static let ON_DISPLAY_FAILED = 103
static let ON_CLICK = 104
static let ON_HIDDEN = 105
static let ON_USER_REWARDED = 106
static let REVENUE_PAID = 107
static let LOAD_TIMEOUT = 108
static let SHOW_TIMEOUT = 109
}
private static let LOAD_TIMEOUT_MILLIS: Int = 120000
private static let SHOW_TIMEOUT_MILLIS: Int = 120000
public override var supportedAdPlatforms: Set<AdPlatform> {
return [ad.adPlatform]
}
public init(viewController: UIViewController, id: Int, ad: GuruRewardedAd) {
self.ad = ad
super.init(viewController: viewController, id: id, adType: .Rewarded, strategyName: ad.adPlatform.name)
setupAdListener()
initialize()
}
private func setupAdListener() {
ad.listener = FusionAdListenerImpl { [weak self] event in
guard let self = self else { return }
switch event {
case .onAdLoaded(let ad):
self.sendMessage(what: Event.ON_LOADED, obj: EventParams(ad: ad))
case .onAdDisplayed(let ad):
self.sendMessage(what: Event.ON_DISPLAYED, obj: EventParams(ad: ad))
case .onAdHidden(let ad):
self.sendMessage(what: Event.ON_HIDDEN, obj: EventParams(ad: ad))
case .onUserRewarded(let ad, let reward):
self.sendMessage(what: Event.ON_USER_REWARDED, obj: EventParams(ad: ad, reward: reward))
case .onAdClicked(let ad):
self.sendMessage(what: Event.ON_CLICK, obj: EventParams(ad: ad))
case .onAdLoadFailed(let loadFailedInfo):
self.sendMessage(what: Event.ON_LOAD_FAILED, obj: EventParams(loadFailedInfo: loadFailedInfo))
case .onAdDisplayFailed(let ad, let error):
self.sendMessage(what: Event.ON_DISPLAY_FAILED, obj: EventParams(ad: ad, displayFailedInfo: error))
case .onAdRevenuePaid(let ad):
self.sendMessage(what: Event.REVENUE_PAID, obj: EventParams(ad: ad))
}
}
}
// MARK: - State Classes
public override func createIdleState() -> Idle {
return IdleImpl(self)
}
public override func createLoadingState() -> Loading {
return LoadingImpl(self)
}
public override func createLoadedState() -> Loaded {
return LoadedImpl(self)
}
public override func createShowingState() -> Showing {
return ShowingImpl(self)
}
// MARK: - Request Methods
override internal func requestLoad() {
sendMessage(what: Action.LOAD)
}
override internal func requestShow(_ request: RewardedShowRequest? = nil) {
sendMessage(what: Action.SHOW, obj: request)
}
override internal func requestDestroy() {
sendMessage(what: Action.DESTROY)
}
// MARK: - Helper Methods
private func handleUnhandledMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.DESTROY:
ad.destroy()
transitionTo(idleState)
return true
default:
return false
}
}
// MARK: - State Implementations
class IdleImpl: Idle {
private let engine: DefaultRewardedAdEngine
init(_ engine: DefaultRewardedAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.LOAD:
if engine.ad.load() {
engine.transitionTo(engine.loadingState)
} else {
engine.adLoadFailed(loadFailedInfo: LoadFailedInfo(
engineId: engine.id,
adPlatform: engine.ad.adPlatform,
adType: engine.adType,
adUnitId: engine.ad.adUnitId,
error: LoadAdRequestError()
))
}
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
class LoadingImpl: Loading {
private var retryCount = 0
private let engine: DefaultRewardedAdEngine
init(_ engine: DefaultRewardedAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
retryCount = 0
// Set load timeout
engine.sendMessageDelayed(what: Event.LOAD_TIMEOUT, delayMillis: DefaultRewardedAdEngine.LOAD_TIMEOUT_MILLIS)
}
public override func exit(to: IState?) {
engine.removeMessages(what: Event.LOAD_TIMEOUT)
engine.removeMessages(what: Action.RETRY)
retryCount = 0
super.exit(to: to)
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.RETRY:
retryCount += 1
if !engine.ad.load() {
engine.sendMessage(what: Event.ON_LOAD_FAILED, obj: EventParams(
loadFailedInfo: LoadFailedInfo(
engineId: engine.id,
adPlatform: engine.ad.adPlatform,
adType: engine.adType,
adUnitId: engine.ad.adUnitId,
error: LoadAdRequestError()
)
))
} else {
engine.sendMessageDelayed(what: Event.LOAD_TIMEOUT, delayMillis: DefaultRewardedAdEngine.LOAD_TIMEOUT_MILLIS)
}
return true
case Action.LOAD:
engine.logWarn("Already loading! Ignore load request")
return true
case Event.LOAD_TIMEOUT:
engine.removeMessages(what: Action.RETRY)
engine.sendMessage(what: Action.RETRY)
return true
case Event.ON_LOADED:
if let eventParams = msg.obj as? EventParams, eventParams.ad != nil {
engine.transitionTo(engine.loadedState, params: eventParams)
}
engine.deferMessage(msg)
return true
case Event.ON_LOAD_FAILED:
let params = msg.obj as? EventParams
engine.adLoadFailed(loadFailedInfo: params?.loadFailedInfo ?? LoadFailedInfo(
engineId: engine.id,
adPlatform: engine.ad.adPlatform,
adType: engine.adType,
adUnitId: engine.ad.adUnitId,
error: LoadAdRequestError()
))
engine.transitionTo(engine.idleState)
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
class LoadedImpl: Loaded {
private let engine: DefaultRewardedAdEngine
init(_ engine: DefaultRewardedAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
if let eventParams = params as? EventParams, let ad = eventParams.ad {
engine.adLoaded(ad: ad)
}
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Action.SHOW:
let request = msg.obj as? RewardedShowRequest
if engine.ad.show(request) {
engine.transitionTo(engine.showingState)
} else {
engine.logWarn("Failed to show rewarded ad")
// no FusionAd here
// engine.adDisplayFailed(ad: engine.ad, error: ShowAdRequestError())
if(engine.ad.load()) {
engine.transitionTo(engine.loadingState)
}
}
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
class ShowingImpl: Showing {
private let engine: DefaultRewardedAdEngine
init(_ engine: DefaultRewardedAdEngine) {
self.engine = engine
super.init()
}
public override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
engine.sendMessageDelayed(what: Event.SHOW_TIMEOUT, delayMillis: SHOW_TIMEOUT_MILLIS)
}
public override func processMessage(_ msg: Message) -> Bool {
switch msg.what {
case Event.ON_DISPLAYED:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adDisplayed(ad: ad)
}
engine.removeMessages(what: Event.SHOW_TIMEOUT)
return true
case Event.ON_CLICK:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adClicked(ad: ad)
}
return true
case Event.ON_HIDDEN:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adHidden(ad: ad)
engine.transitionTo(engine.idleState)
}
return true
case Event.ON_USER_REWARDED:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adUserRewarded(ad: ad, reward: eventParams.reward)
}
return true
case Event.REVENUE_PAID:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adRevenuePaid(ad: ad)
}
return true
case Event.ON_DISPLAY_FAILED:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adDisplayFailed(ad: ad, error: eventParams.displayFailedInfo)
}
engine.transitionTo(engine.idleState)
engine.requestLoad()
return true
default:
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
}
}
// Helper class to implement the FusionAdListener protocol
private class FusionAdListenerImpl: FusionAdListener {
enum Event {
case onAdLoaded(ad: FusionAd)
case onAdDisplayed(ad: FusionAd)
case onAdHidden(ad: FusionAd)
case onUserRewarded(ad: FusionAd, reward: FusionReward?)
case onAdClicked(ad: FusionAd)
case onAdLoadFailed(loadFailedInfo: LoadFailedInfo)
case onAdDisplayFailed(ad: FusionAd, error: FusionError?)
case onAdRevenuePaid(ad: FusionAd)
}
private let handler: (Event) -> Void
init(handler: @escaping (Event) -> Void) {
self.handler = handler
}
func onAdLoaded(ad: FusionAd) {
handler(.onAdLoaded(ad: ad))
}
func onAdDisplayed(ad: FusionAd) {
handler(.onAdDisplayed(ad: ad))
}
func onAdHidden(ad: FusionAd) {
handler(.onAdHidden(ad: ad))
}
func onUserRewarded(ad: FusionAd, reward: FusionReward?) {
handler(.onUserRewarded(ad: ad, reward: reward))
}
func onAdClicked(ad: FusionAd) {
handler(.onAdClicked(ad: ad))
}
func onAdLoadFailed(loadFailedInfo: LoadFailedInfo) {
handler(.onAdLoadFailed(loadFailedInfo: loadFailedInfo))
}
func onAdDisplayFailed(ad: FusionAd, error: FusionError?) {
handler(.onAdDisplayFailed(ad: ad, error: error))
}
func onAdRevenuePaid(ad: FusionAd) {
handler(.onAdRevenuePaid(ad: ad))
}
}

View File

@ -0,0 +1,144 @@
// RewardedAdEngine.swift
// Base class for rewarded ad engines
// Corresponds to RewardedAdEngine.kt in Android implementation
import Foundation
import UIKit
public class RewardedAdEngine: RelayAdEngine {
private(set) var idleState: Idle!
private(set) var loadingState: Loading!
private(set) var loadedState: Loaded!
private(set) var showingState: Showing!
public var currentStateIdentifier: AdState.Identifier {
return (currentState as? AdState)?.stateId ?? AdState.Identifier(name: currentState?.name ?? "unknown")
}
public override init(viewController: UIViewController, id: Int, adType: AdType, strategyName: String) {
super.init(viewController: viewController, id: id, adType: .Rewarded, strategyName: strategyName)
}
public func initialize() {
idleState = createIdleState()
loadingState = createLoadingState()
loadedState = createLoadedState()
showingState = createShowingState()
addState(idleState)
addState(loadingState)
addState(loadedState)
addState(showingState)
setInitialState(idleState)
start()
}
// State classes
public class AdState: State {
public let stateId: Identifier
public init(name: String, stateId: Identifier) {
self.stateId = stateId
super.init()
}
public override var name: String {
return stateId.name
}
public class Identifier: Equatable {
public let name: String
public init(name: String) {
self.name = name
}
// Pre-defined identifiers
public static let IDLE = Identifier(name: "idle")
public static let LOADING = Identifier(name: "loading")
public static let LOADED = Identifier(name: "loaded")
public static let SHOWING = Identifier(name: "showing")
public static func == (lhs: Identifier, rhs: Identifier) -> Bool {
return lhs.name == rhs.name
}
}
}
public class Idle: AdState {
public init() {
super.init(name: "[IDLE]", stateId: Identifier.IDLE)
}
}
public class Loading: AdState {
public init() {
super.init(name: "[LOADING]", stateId: Identifier.LOADING)
}
}
public class Loaded: AdState {
public init() {
super.init(name: "[LOADED]", stateId: Identifier.LOADED)
}
}
public class Showing: AdState {
public init() {
super.init(name: "[SHOWING]", stateId: Identifier.SHOWING)
}
}
// Factory methods for creating the states
open func createIdleState() -> Idle {
return Idle()
}
open func createLoadingState() -> Loading {
return Loading()
}
open func createLoadedState() -> Loaded {
return Loaded()
}
open func createShowingState() -> Showing {
return Showing()
}
// Abstract methods to be implemented by subclasses
internal func requestLoad() {
fatalError("Subclass must implement")
}
internal func requestShow(_ request: RewardedShowRequest? = nil) {
fatalError("Subclass must implement")
}
internal func requestDestroy() {
fatalError("Subclass must implement")
}
// Public API methods
public func show(_ request: RewardedShowRequest? = nil) -> Bool {
if isCurrent(loadedState) {
requestShow(request)
return true
}
return false
}
public func load() -> Bool {
if isCurrent(idleState) {
requestLoad()
return true
}
return false
}
public func destroy() -> Bool {
requestDestroy()
return true
}
}

View File

@ -0,0 +1,97 @@
//
// MabAnalyticsEvents.swift
// Pods
//
// Created by 250102 on 2025/5/10.
//
//
internal class MabAnalyticEvents {
// 访
static let shared = MabAnalyticEvents()
//
private init() {}
static func faLoad(
faUnitId: String,
adType: AdType,
aggregatorId: String,
adUnitId: String,
requestReason: RequestReason?
) {
var params: [String: Any] = [:]
params["fa_unitid"] = faUnitId
params["aggregator_id"] = aggregatorId
if let requestReason = requestReason {
params["req_type"] = requestReason.statsName
}
params["ad_format"] = adType.label
params["ad_unit_name"] = shrink(adUnitId)
AnalyticsEvents.shared.onEvent(name: "fa_load", param: params)
}
static func faLoaded(
faUnitId: String,
adType: AdType,
adSource: String?,
aggregatorId: String,
adUnitId: String,
durationInMillis: Int64?
) {
guard let durationInMillis = durationInMillis else {
return
}
AnalyticsEvents.shared.onEvent(
name: "fa_loaded",
pairs:
("fa_unitid", faUnitId),
("aggregator_id", aggregatorId),
("ad_format", adType.label),
("duration", durationInMillis),
("ad_unit_name", shrink(adUnitId)),
("ad_source", (adSource ?? "unknown"))
)
}
static func faFailed(
faUnitId: String,
adType: AdType,
aggregatorId: String,
adUnitId: String,
durationInMillis: Int64?,
errorCode: Int
) {
guard let durationInMillis = durationInMillis else {
return
}
AnalyticsEvents.shared.onEvent(
name: "fa_failed",
pairs:
("fa_unitid", faUnitId),
("aggregator_id", aggregatorId),
("ad_format", adType.label),
("duration", durationInMillis),
("ad_unit_name", shrink(adUnitId)),
("error_code", errorCode)
)
}
/**
* adUnitId便
*/
private static func shrink(_ adUnitId: String) -> String {
// shrink a adUnitId from AdMob to a shorter form,
// for example, ca-app-pub-2436733915645843/9966308712
// shrink("ca-app-pub-2436733915645843/9966308712") == "9966308712"
if adUnitId.hasPrefix("ca-app-pub-") && adUnitId.count == 38 && adUnitId[adUnitId.index(adUnitId.startIndex, offsetBy: 27)] == "/" {
return String(adUnitId.suffix(10))
}
// no need to shrink
return adUnitId
}
}

View File

@ -0,0 +1,544 @@
import Foundation
// AggregatorBid
private struct AggregatorBid {
let config: AggregatorConfig
/**
*
* show revenue * selfRaise bid
* true[floor]revenue
*/
let useSelfRaise: Bool
/**
* bidnil使winnersrevenue, see [MabWinners.maxRevenue]
*
*/
let floor: Double?
/**
*
*/
let reason: RequestReason?
init(config: AggregatorConfig, useSelfRaise: Bool = false, floor: Double? = nil, reason: RequestReason? = nil) {
self.config = config
self.useSelfRaise = useSelfRaise
self.floor = floor
self.reason = reason
}
var raiseRate: Double {
return useSelfRaise ? config.selfRaiseRate ?? config.raiseRate : config.raiseRate
}
}
//
extension AggregatorConfig {
var deferRetryUntilShown: Bool {
return adPlatform == AdPlatform.adMob.name
}
}
/**
* 广
*/
private class MabWinners<T: MabBidder> {
private var winners: [String: T] = [:]
private let onEvict: ((T) -> Void)?
init(onEvict: ((T) -> Void)? = nil) {
self.onEvict = onEvict
}
/**
*
*/
func evictCache() {
let expiredKeys = winners.filter { (_, bidder) in
if bidder.isExpired != false {
onEvict?(bidder)
return true
}
return false
}.map { $0.key }
for key in expiredKeys {
winners.removeValue(forKey: key)
}
}
/**
* 广
*/
func add(bidder: T) {
evictCache()
winners[bidder.aggregatorId] = bidder
}
/**
* 广
*/
func contains(aggregatorId: String) -> Bool {
evictCache()
return winners[aggregatorId] != nil
}
/**
* 广广0
*/
func maxRevenue() -> Double {
evictCache()
if winners.isEmpty {
return 0.0
}
return winners.values.map { $0.revenue }.max() ?? 0.0
}
/**
* 广
*/
func peekMaxRevenue() -> T? {
evictCache()
return winners.values.max(by: {
if($0.revenue == $1.revenue) {
//
return $0.config.priority > $1.config.priority
}
return $0.revenue < $1.revenue
})
}
/**
* 广
*/
func pollMaxRevenue() -> T? {
evictCache()
guard let entry = peekMaxRevenue() else {
return nil
}
return winners.removeValue(forKey: entry.aggregatorId)
}
/**
* 广
*/
func isEmpty() -> Bool {
evictCache()
return winners.isEmpty
}
/**
* 广
*/
func clear() {
winners.removeAll()
}
func buildStateLog() -> String {
var result = "winners["
for winner in winners {
result += "\(winner.key)(\(winner.value.revenue)),"
}
result += "]"
return result
}
}
/**
* loadAdMobloadloadloadload广show
* -> "每次广告show完此聚合重新获得一次load机会不能累加"
*/
private class RetryGate {
// SwiftAtomicInteger使
private class AtomicInteger {
private var value: Int
init(_ initialValue: Int = 0) {
self.value = initialValue
}
func get() -> Int {
return value
}
func set(_ newValue: Int) {
value = newValue
}
func incrementAndGet() -> Int {
value += 1
return value
}
}
private var version = AtomicInteger() // ad show
private var lastUsed: [String: AtomicInteger] = [:]
func onAdShown() {
_ = version.incrementAndGet()
}
/**
* showretryshow
*/
func canRetry(aggregatorId: String) -> Bool {
return getLastUsed(aggregatorId: aggregatorId).get() < version.get()
}
/**
*
*/
func markRetryUsed(aggregatorId: String) {
getLastUsed(aggregatorId: aggregatorId).set(version.get())
}
private func getLastUsed(aggregatorId: String) -> AtomicInteger {
if let lastUsed = lastUsed[aggregatorId] {
return lastUsed
} else {
let atomicInteger = AtomicInteger(-1)
lastUsed[aggregatorId] = atomicInteger
return atomicInteger
}
}
}
private func selectAdUnitSpec(
aggregatorConfig: AggregatorConfig,
floor: Double,
selfRaise: Bool = false
) -> AdUnitSpec? {
let adUnitIds = aggregatorConfig.adUnitIds
if adUnitIds.isEmpty {
return nil
}
let targetFloor = floor * (selfRaise ? aggregatorConfig.selfRaiseRate ?? aggregatorConfig.raiseRate : aggregatorConfig.raiseRate)
let firstGtIndex = adUnitIds.firstIndex { $0.floor >= targetFloor }
if firstGtIndex == nil {
return adUnitIds.last
}
if firstGtIndex == 0 {
return adUnitIds.first
}
// targetFloor
let index = firstGtIndex!
let nearestIndex = (abs(targetFloor - adUnitIds[index].floor) <= abs(adUnitIds[index - 1].floor - targetFloor)) ? index : index - 1
let selection = adUnitIds[nearestIndex]
return selection
}
enum RequestReason: String {
case INIT = "init"
case FAIL = "fail"
case SHOW = "show"
case EXPIRATION = "exp"
var statsName: String {
return rawValue
}
}
protocol MabBidder {
var id: Int { get }
var config: AggregatorConfig { get }
var isExpired: Bool? { get }
var revenue: Double { get }
func load(reason: RequestReason?) -> Bool
func destroy()
}
extension MabBidder {
var aggregatorId: String {
return config.aggregatorId
}
}
//
class MabBidderUtils {
static func adjustRevenue(ad: FusionAd, floor: Double) -> Double {
switch ad.adPlatform {
case .max:
return ad.revenue
case .ironSource:
return max(ad.revenue, floor)
case .adMob:
return max(ad.revenue, floor)
default:
return max(ad.revenue, floor)
}
}
}
class MabAuctioneer<T: MabBidder> {
enum RequestResult {
case success
case busy
case noAvailableSource
}
private let tag: String
private let faUnitId: String
private let priorityAggregators: [AggregatorConfig]
private var candidatesQueue: [AggregatorBid] = []
private let bidderFactory: (AggregatorConfig, AdUnitSpec) -> T?
private var bidding: T?
private var bidders: [String: T] = [:]
private let winners: MabWinners<T>
private let retryGate = RetryGate()
var hasWinner: Bool {
return !winners.isEmpty()
}
init(
engineId: Int,
faUnitId: String,
aggregatorConfigs: [AggregatorConfig],
bidderFactory: @escaping (AggregatorConfig, AdUnitSpec) -> T?
) {
self.tag = "MabAuctioneer@\(engineId)"
self.faUnitId = faUnitId
self.priorityAggregators = aggregatorConfigs.sorted(by: { $0.priority < $1.priority })
self.bidderFactory = bidderFactory
self.winners = MabWinners<T>()
}
private func logI(_ message: String) {
Logger.i(tag: tag, message: message)
}
private func logD(_ message: String) {
Logger.d(tag: tag, message: message)
}
private func logW(_ message: String) {
Logger.w(tag: tag, message: message)
}
private func logE(_ message: String) {
Logger.e(tag: tag, message: message)
}
private func logState() {
logI("mab state: \(buildStateLog())")
}
private func obtainBidder(
config: AggregatorConfig,
adUnitSpec: AdUnitSpec
) -> T? {
if let bidder = bidders[adUnitSpec.adUnitId] {
return bidder
} else {
if let newBidder = bidderFactory(config, adUnitSpec) {
bidders[adUnitSpec.adUnitId] = newBidder
return newBidder
}
return nil
}
}
func requestNext() -> RequestResult {
logI("requestNext")
let result = internalRequestNext()
logI("requestNext result \(result)")
logState()
return result
}
private func internalRequestNext() -> RequestResult {
if bidding != nil {
return .busy
}
while !candidatesQueue.isEmpty {
guard let bid = candidatesQueue.first else {
break
}
candidatesQueue.removeFirst()
if winners.contains(aggregatorId: bid.config.aggregatorId) {
// bid
continue
}
guard let adUnitSpec = selectAdUnitSpec(
aggregatorConfig: bid.config,
floor: bid.floor ?? winners.maxRevenue(),
selfRaise: bid.useSelfRaise
) else {
continue
}
logD("selected \(adUnitSpec.adUnitId) with \(adUnitSpec.floorEcpm)")
if let bidder = obtainBidder(config: bid.config, adUnitSpec: adUnitSpec) {
if bidder.load(reason: bid.reason) {
bidding = bidder
return .success
}
}
}
return .noAvailableSource
}
func refillCandidatesQueue(reason: RequestReason? = nil) {
logI("refilling candidates")
logState()
for aggregator in priorityAggregators {
fillCandidate(AggregatorBid(config: aggregator, useSelfRaise: false, reason: reason))
}
logI("candidates refilled")
logState()
}
private func fillCandidate(_ bid: AggregatorBid) {
let aggregator = bid.config
//
if winners.contains(aggregatorId: aggregator.aggregatorId) {
logD("refilling - \(aggregator.aggregatorId) filled, skip")
return
}
//
if let currentBidding = bidding, currentBidding.aggregatorId == aggregator.aggregatorId {
logW("refilling - \(aggregator.aggregatorId) is loading, skip")
return
}
// show
if aggregator.deferRetryUntilShown {
if !retryGate.canRetry(aggregatorId: aggregator.aggregatorId) {
logD("refilling - \(aggregator.aggregatorId) deferred until next shown")
return
}
retryGate.markRetryUsed(aggregatorId: aggregator.aggregatorId)
}
logD("refilling - \(aggregator.aggregatorId) added to queue")
candidatesQueue.append(bid)
}
private func onCacheEvict(_ bidder: T) {
// candidatesQueue.append(AggregatorBid(config: bidder.config, reason: .EXPIRATION))
}
func reset() {
logI("reset")
for bidder in bidders.values {
bidder.destroy()
}
bidding?.destroy()
bidding = nil
winners.clear()
bidders.removeAll()
candidatesQueue.removeAll()
refillCandidatesQueue()
}
func onShown(winner: T) {
logI("winner shown, \(winner)")
logState()
retryGate.onAdShown()
// hidden
candidatesQueue.removeAll()
//
if winner.config.selfRaiseRate != nil {
// show
candidatesQueue.append(
AggregatorBid(
config: winner.config,
useSelfRaise: true,
floor: winner.revenue,
reason: .SHOW
)
)
}
}
func onLoaded(bidderId: Int) {
logI("onLoaded \(bidderId)")
if let bidding = bidding, bidding.id == bidderId {
winners.add(bidder: bidding)
self.bidding = nil
} else {
logW("onLoaded bidderId mismatch \(String(describing: bidding?.id)) != \(bidderId)")
}
logState()
}
func onLoadFailed(bidderId: Int) {
logI("onLoadFailed \(bidderId)")
if let bidder = bidding, bidder.id == bidderId {
bidding = nil
} else {
logW("onLoadFailed bidderId mismatch \(String(describing: bidding?.id)) != \(bidderId)")
}
logState()
}
func pollMaxRevenue() -> T? {
return winners.pollMaxRevenue()
}
func peekMaxRevenue() -> T? {
return winners.peekMaxRevenue()
}
func evictCache() {
winners.evictCache()
}
private func buildStateLog() -> String {
var result = "faUnitId(\(faUnitId)), "
result += winners.buildStateLog()
result += ", "
result += "bidding["
if let bidding = bidding {
result += "\(bidding)"
} else {
result += "null"
}
result += "], "
result += "candidates["
for bid in candidatesQueue {
result += "\(bid.config.aggregatorId)("
result += "\(bid.raiseRate)"
if bid.useSelfRaise {
result += " self-raise"
}
result += "), "
}
result += "]"
return result
}
}

View File

@ -0,0 +1,784 @@
//
// MabInterstitialEngine.swift
// Pods
//
// Created by 250102 on 2025/5/10.
//
import Foundation
//
typealias InterstitialAdObtainer = (_ adPlatform: AdPlatform, _ adConfig: AdConfig) -> GuruInterstitialAd?
// InterstitialBidder
private class InterstitialBidder: CustomStringConvertible, MabBidder, FusionAdListener {
//
private static let idPool = AtomicInteger(0)
static let TAG = "InterstitialBidder"
//
private let engine: InterstitialAdEngine
var config: AggregatorConfig
private let ad: GuruInterstitialAd
private let adUnitSpec: AdUnitSpec
private let faUnitId: String
var id: Int
var revenue: Double = 0.0
private var fusionAd: FusionAd?
private var loadedTimeInMillis: Int64?
private var loaded: Bool {
return loadedTimeInMillis != nil
}
var isExpired: Bool? {
get {
// 0
if config.cacheExpireInSecond == 0.0 {
return false
}
guard let loadedTime = loadedTimeInMillis else {
return nil
}
return (SystemClock.elapsedRealtime() - loadedTime) > Int64(config.cacheExpireInSecond * 1000)
}
}
var isFilled: Bool {
return loaded && !(isExpired ?? true)
}
func getFusionAd() -> FusionAd? {
return fusionAd
}
//
private let loadTimeOutToken = UUID()
private let cacheExpireToken = UUID()
private var loadStartTime: Int64?
//
init(
engine: InterstitialAdEngine,
config: AggregatorConfig,
ad: GuruInterstitialAd,
adUnitSpec: AdUnitSpec,
faUnitId: String
) {
self.engine = engine
self.config = config
self.ad = ad
self.adUnitSpec = adUnitSpec
self.faUnitId = faUnitId
self.id = InterstitialBidder.idPool.incrementAndGet()
ad.listener = self
}
private func consumeLoadDuration() -> Int64? {
guard let start = loadStartTime else {
return nil
}
loadStartTime = nil
return SystemClock.elapsedRealtime() - start
}
private var handler: StateMachine {
return engine
}
func load(reason: RequestReason?) -> Bool {
MabAnalyticEvents.faLoad(faUnitId: faUnitId, adType: engine.adType, aggregatorId: config.aggregatorId, adUnitId: adUnitSpec.adUnitId, requestReason: reason)
let result = ad.load()
if result {
handler.removeCallbacksAndMessages(cacheExpireToken)
loadedTimeInMillis = nil
loadStartTime = SystemClock.elapsedRealtime()
let timeout: Int64
if let configTimeout = config.loadTimeoutInSecond {
timeout = Int64(configTimeout * 1000)
} else {
timeout = MabInterstitialEngine.BIDDING_LOAD_TIMEOUT_MILLIS
}
handler.postDelayed(runnable: { [weak self] in self?.performLoadTimeout() }, token: loadTimeOutToken, delayed: .milliseconds(Int(timeout)))
}
return result
}
func show(request: InterstitialShowRequest?) -> Bool {
let result = ad.show(request)
if result {
handler.removeCallbacksAndMessages(cacheExpireToken)
loadedTimeInMillis = nil
}
return result
}
func destroy() {
handler.removeCallbacksAndMessages(loadTimeOutToken)
handler.removeCallbacksAndMessages(cacheExpireToken)
revenue = 0.0
ad.destroy()
fusionAd = nil
loadedTimeInMillis = nil
loadStartTime = nil
}
// MARK: - FusionAdListener
func onAdLoaded(ad: FusionAd) {
logLoaded(ad)
handler.removeCallbacksAndMessages(loadTimeOutToken)
fusionAd = ad
loadedTimeInMillis = SystemClock.elapsedRealtime()
revenue = MabBidderUtils.adjustRevenue(ad: ad, floor: adUnitSpec.floor)
engine.sendMessage(what: MabInterstitialEngine.EVENT_ON_LOADED, obj: EventParams(ad: ad, id: id))
handler.removeCallbacksAndMessages(cacheExpireToken)
let cacheTimeInMillis = Int64(config.cacheExpireInSecond * 1000)
if cacheTimeInMillis > 0 {
handler.postDelayed(runnable: { [weak self] in self?.performCacheExpired() }, token: cacheExpireToken, delayed: .milliseconds(Int(cacheTimeInMillis + 50)))
}
}
func onAdDisplayed(ad: FusionAd) {
engine.sendMessage(what: MabInterstitialEngine.EVENT_ON_DISPLAYED, obj: EventParams(ad: ad, id: id))
}
func onAdHidden(ad: FusionAd) {
engine.sendMessage(what: MabInterstitialEngine.EVENT_ON_HIDDEN, obj: EventParams(ad: ad, id: id))
}
func onAdClicked(ad: FusionAd) {
engine.sendMessage(what: MabInterstitialEngine.EVENT_ON_CLICK, obj: EventParams(ad: ad, id: id))
}
func onAdLoadFailed(loadFailedInfo: LoadFailedInfo) {
let errorCode = loadFailedInfo.error?.errorCode ?? FusionErrorCodes.UNKNOWN.code
logLoadFailed(errorCode)
loadedTimeInMillis = nil
handler.removeCallbacksAndMessages(loadTimeOutToken)
engine.sendMessage(what:
MabInterstitialEngine.EVENT_ON_LOAD_FAILED,
obj: EventParams(loadFailedInfo: loadFailedInfo, id: id)
)
}
func onAdDisplayFailed(ad: FusionAd, error: FusionError?) {
engine.sendMessage(
what: MabInterstitialEngine.EVENT_ON_DISPLAY_FAILED,
obj: EventParams(ad: ad, displayFailedInfo: error, id: id)
)
}
func onAdRevenuePaid(ad: FusionAd) {
engine.sendMessage(what: MabInterstitialEngine.EVENT_REVENUE_PAID, obj: EventParams(ad: ad, id: id))
}
// MARK: -
private func performLoadTimeout() {
ad.destroy()
onAdLoadFailed(
loadFailedInfo: LoadFailedInfo(
engineId: ad.engineId,
adPlatform: ad.adPlatform,
adType: engine.adType,
adUnitId: ad.adUnitId,
error: LoadAdTimeoutError()
)
)
}
private func performCacheExpired() {
engine.sendMessage(what: MabInterstitialEngine.EVENT_CACHE_EXPIRED, obj: EventParams(id: id))
}
private func logLoaded(_ fusionAd: FusionAd) {
guard let duration = consumeLoadDuration() else {
Logger.w(tag: InterstitialBidder.TAG, message: "logLoaded but duration is nil, ignored")
return
}
MabAnalyticEvents.faLoaded(
faUnitId: faUnitId,
adType: engine.adType,
adSource: fusionAd.networkName,
aggregatorId: config.aggregatorId,
adUnitId: ad.adUnitId,
durationInMillis: duration
)
}
private func logLoadFailed(_ errorCode: Int) {
guard let duration = consumeLoadDuration() else {
Logger.w(tag: InterstitialBidder.TAG, message: "logLoadFailed but duration is nil, ignored")
return
}
MabAnalyticEvents.faFailed(
faUnitId: faUnitId,
adType: engine.adType,
aggregatorId: config.aggregatorId,
adUnitId: ad.adUnitId,
durationInMillis: duration,
errorCode: errorCode
)
}
var description: String {
return "InterstitialBidder[\(aggregatorId)(\(adUnitSpec.adUnitId))]"
}
}
// MabInterstitialEngine
internal class MabInterstitialEngine: InterstitialAdEngine {
// MARK: -
static let ACTION_LOAD = 1
static let ACTION_SHOW = 2
static let ACTION_DESTROY = 4
static let ACTION_RETRY = 5
static let EVENT_ON_LOADED = 100
static let EVENT_ON_LOAD_FAILED = 101
static let EVENT_ON_DISPLAYED = 102
static let EVENT_ON_DISPLAY_FAILED = 103
static let EVENT_ON_CLICK = 104
static let EVENT_ON_HIDDEN = 105
static let EVENT_REVENUE_PAID = 107
static let EVENT_SHOW_TIMEOUT = 109
static let EVENT_CACHE_EXPIRED = 110
static let BIDDING_LOAD_TIMEOUT_MILLIS: Int64 = 60_000
static let SHOW_TIMEOUT_MILLIS: Int64 = 120_000
// MARK: -
private let faUnitId: String
private let mabConfig: MabInterstitialConfig
private let adObtainer: InterstitialAdObtainer
private let auctioneer: MabAuctioneer<InterstitialBidder>
// MARK: -
init(
viewController: UIViewController,
id: Int,
faUnitId: String,
mabConfig: MabInterstitialConfig,
adObtainer: @escaping InterstitialAdObtainer
) {
self.mabConfig = mabConfig
self.adObtainer = adObtainer
self.faUnitId = faUnitId
//
weak var futureSelf: MabInterstitialEngine? = nil
auctioneer = MabAuctioneer<InterstitialBidder>(
engineId: id,
faUnitId: faUnitId,
aggregatorConfigs: mabConfig.aggregatorConfigs,
bidderFactory: { config, adUnitSpec in
guard let self = futureSelf else { return nil }
return self.createBidder(config: config, adUnitSpec: adUnitSpec)
}
)
super.init(viewController: viewController, id: id, adType: AdType.Interstitial, strategyName: "Mab")
futureSelf = self
}
public override func createIdleState() -> Idle {
return IdleImpl(self)
}
public override func createLoadingState() -> Loading {
return LoadingImpl(self)
}
public override func createLoadedState() -> Loaded {
return LoadedImpl(self)
}
public override func createShowingState() -> Showing {
return ShowingImpl(self)
}
// MARK: -
private func createBidder(
config: AggregatorConfig,
adUnitSpec: AdUnitSpec
) -> InterstitialBidder? {
let adAmzId = adUnitSpec.adAmazonSlotId
let adPlatform = AdPlatform.fromName(config.adPlatform)
guard let ad = adObtainer(
adPlatform,
AdConfig(engineId: id, adUnitId: adUnitSpec.adUnitId, adAmazonSlotId: adAmzId, requireDisableAutoRetries: true)
) else {
return nil
}
return InterstitialBidder(
engine: self,
config: config,
ad: ad,
adUnitSpec: adUnitSpec,
faUnitId: faUnitId
)
}
private func handleUnhandledMessage(_ msg: Message?) -> Bool {
guard let what = msg?.what else { return false }
switch what {
case MabInterstitialEngine.ACTION_LOAD:
logWarn("ACTION_LOAD on \(String(describing: currentState?.name))")
adLoadFailed(
loadFailedInfo: LoadFailedInfo(
engineId: id,
adPlatform: AdPlatform.fusion,
adType: adType,
adUnitId: "mab_interstitial",
error: LoadAdRequestError(message: "unhandled ACTION_LOAD, current state is \(String(describing: currentState?.name))")
)
)
return true
case MabInterstitialEngine.ACTION_SHOW:
adDisplayFailed(ad: InvalidFusionAd.interstitial(engineId: id), error: ShowAdRequestError())
return true
case MabInterstitialEngine.ACTION_DESTROY:
logWarn("Destroy may not supported in a Mab Engine, may lead memory leakages if you are trying to destroy one.")
transitionTo(idleState)
// destroy all ads
return true
case MabInterstitialEngine.EVENT_REVENUE_PAID:
logWarn("revenue paid event unhandled!")
case MabInterstitialEngine.EVENT_ON_LOADED:
logWarn("event EVENT_ON_LOADED unhandled!")
case MabInterstitialEngine.EVENT_ON_LOAD_FAILED:
logWarn("event EVENT_ON_LOAD_FAILED unhandled!")
default:
break
}
return false
}
private func reset() {
auctioneer.reset()
removeMessages(what: MabInterstitialEngine.EVENT_SHOW_TIMEOUT)
}
// MARK: - InterstitialAdEngine
override func requestLoad() {
sendMessage(what: MabInterstitialEngine.ACTION_LOAD)
}
override func requestShow(request: InterstitialShowRequest?) {
sendMessage(what: MabInterstitialEngine.ACTION_SHOW, obj: request)
}
override func requestDestroy() {
sendMessage(what: MabInterstitialEngine.ACTION_DESTROY)
}
override var supportedAdPlatforms: Set<AdPlatform> {
return [AdPlatform.fusion]
}
// MARK: -
class IdleImpl: Idle {
public let engine: MabInterstitialEngine
init(_ engine: MabInterstitialEngine) {
self.engine = engine
}
override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
self.engine.reset()
}
override func exit(to: IState?) {
super.exit(to: to)
}
override func processMessage(_ msg: Message) -> Bool {
let what = msg.what
if what == MabInterstitialEngine.ACTION_LOAD {
let result = engine.auctioneer.requestNext()
if result != MabAuctioneer.RequestResult.noAvailableSource {
engine.transitionTo(engine.loadingState)
return true
} else {
engine.logError("No available source")
engine.adLoadFailed(
loadFailedInfo: LoadFailedInfo(
engineId: engine.id,
adPlatform: AdPlatform.fusion,
adType: engine.adType,
adUnitId: "mab_interstitial",
error: LoadAdNotAvailableError()
)
)
return true
}
}
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
override var name: String {
return super.name
}
}
class LoadingImpl: Loading {
public let engine: MabInterstitialEngine
init(_ engine: MabInterstitialEngine) {
self.engine = engine
}
private var retryCount = 0
override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
}
override func exit(to: IState?) {
super.exit(to: to)
}
override func processMessage(_ msg: Message) -> Bool {
let what = msg.what
switch what {
case MabInterstitialEngine.ACTION_RETRY:
retryCount += 1
engine.transitionTo(engine.idleState)
engine.requestLoad()
return true
case MabInterstitialEngine.ACTION_LOAD:
engine.logWarn("Already loading! Ignore load request")
return true
case MabInterstitialEngine.EVENT_ON_LOADED:
if let eventParams = msg.obj as? EventParams {
if eventParams.ad != nil {
engine.auctioneer.onLoaded(bidderId: eventParams.id)
engine.auctioneer.requestNext()
if engine.auctioneer.hasWinner {
engine.transitionTo(engine.loadedState, params: eventParams)
}
}
}
return true
case MabInterstitialEngine.EVENT_ON_LOAD_FAILED:
guard let eventParams = msg.obj as? EventParams else {
return true
}
engine.auctioneer.onLoadFailed(bidderId: eventParams.id)
let result = engine.auctioneer.requestNext()
if result == MabAuctioneer.RequestResult.noAvailableSource {
let failedInfo = eventParams.loadFailedInfo ?? LoadFailedInfo(
engineId: engine.id,
adPlatform: AdPlatform.fusion,
adType: engine.adType,
adUnitId: "mab_interstitial",
error: LoadAdNotAvailableError()
)
engine.adLoadFailed(loadFailedInfo: failedInfo)
let delay = min(max(pow(2.0, Double(retryCount)) as Double, 10), 300)
engine.sendMessageDelayed(what: MabInterstitialEngine.ACTION_RETRY, delayMillis: Int(delay * 1000))
}
return true
default:
break
}
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
class LoadedImpl: Loaded {
public let engine: MabInterstitialEngine
init(_ engine: MabInterstitialEngine) {
self.engine = engine
}
override func enter(from: IState?, params: Any?) {
if let params = params as? EventParams, let ad = params.ad {
self.engine.adLoaded(ad: ad)
} else {
self.engine.logWarn("Loaded state enter with invalid params")
self.engine.transitionTo(self.engine.idleState)
}
super.enter(from: from, params: params)
}
override func exit(to: IState?) {
super.exit(to: to)
}
override func processMessage(_ msg: Message) -> Bool {
let what = msg.what
switch what {
case MabInterstitialEngine.ACTION_SHOW:
let showRequest = msg.obj as? InterstitialShowRequest
var showResult: Bool? = nil
var finalWinner: InterstitialBidder? = nil
while true {
guard let winner = engine.auctioneer.pollMaxRevenue() else {
break
}
if !winner.isFilled {
continue
}
showResult = winner.show(request: showRequest)
if showResult != true {
continue
}
finalWinner = winner
break
}
if let winner = finalWinner {
engine.auctioneer.onShown(winner: winner)
engine.transitionTo(engine.showingState)
} else {
let errorMessage = showResult == nil
? "requestShow while NO winner in candidates"
: "Show Ad returns false"
engine.adDisplayFailed(
ad: InvalidFusionAd.interstitial(engineId: engine.id),
error: ShowAdRequestError(message: errorMessage)
)
let result = engine.auctioneer.requestNext()
if result != MabAuctioneer.RequestResult.noAvailableSource {
engine.transitionTo(engine.loadingState)
} else {
engine.transitionTo(engine.idleState)
engine.requestLoad()
}
}
return true
case MabInterstitialEngine.EVENT_ON_LOADED:
if let eventParams = msg.obj as? EventParams, eventParams.ad != nil {
engine.auctioneer.onLoaded(bidderId: eventParams.id)
engine.auctioneer.requestNext()
}
return true
case MabInterstitialEngine.EVENT_ON_LOAD_FAILED:
processLoadFailed(msg)
return true
case MabInterstitialEngine.EVENT_CACHE_EXPIRED:
engine.auctioneer.evictCache()
if !engine.auctioneer.hasWinner {
engine.transitionTo(engine.idleState)
engine.requestLoad()
}
return true
default:
break
}
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
private func processLoadFailed(_ msg: Message) {
guard let eventParams = msg.obj as? EventParams else {
return
}
engine.auctioneer.onLoadFailed(bidderId: eventParams.id)
engine.auctioneer.requestNext()
}
}
class ShowingImpl: Showing {
public let engine: MabInterstitialEngine
init(_ engine: MabInterstitialEngine) {
self.engine = engine
}
override func enter(from: IState?, params: Any?) {
let timeout: Int64
if let configTimeout = engine.mabConfig.showTimeoutInSecond {
timeout = Int64(configTimeout * 1000)
} else {
timeout = MabInterstitialEngine.SHOW_TIMEOUT_MILLIS
}
engine.sendMessageDelayed(what: MabInterstitialEngine.EVENT_SHOW_TIMEOUT, delayed: .milliseconds(Int(timeout)))
super.enter(from: from, params: params)
}
override func exit(to: IState?) {
self.engine.removeMessages(what: MabInterstitialEngine.EVENT_SHOW_TIMEOUT)
super.exit(to: to)
}
override func processMessage(_ msg: Message) -> Bool {
let what = msg.what
switch what {
case MabInterstitialEngine.EVENT_ON_DISPLAYED:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adDisplayed(ad: ad)
} else {
engine.logWarn("Showing state enter with invalid params")
}
engine.removeMessages(what: MabInterstitialEngine.EVENT_SHOW_TIMEOUT)
return true
case MabInterstitialEngine.EVENT_ON_CLICK:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adClicked(ad: ad)
} else {
engine.logWarn("Showing state enter with invalid params")
}
engine.removeMessages(what: MabInterstitialEngine.EVENT_SHOW_TIMEOUT)
return true
case MabInterstitialEngine.EVENT_ON_HIDDEN:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adHidden(ad: ad)
} else {
engine.logWarn("Showing state enter with invalid params")
}
processRequestAfterConsumed()
return true
case MabInterstitialEngine.EVENT_ON_DISPLAY_FAILED:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adDisplayFailed(ad: ad, error: eventParams.displayFailedInfo)
} else {
engine.logWarn("Showing state enter with invalid params")
}
processRequestAfterConsumed()
return true
case MabInterstitialEngine.EVENT_SHOW_TIMEOUT:
engine.adDisplayFailed(ad: InvalidFusionAd.interstitial(engineId: engine.id), error: ShowAdTimeoutError())
processRequestAfterConsumed()
return true
case MabInterstitialEngine.EVENT_REVENUE_PAID:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adRevenuePaid(ad: ad)
} else {
engine.logWarn("revenue paid event with invalid params")
}
return true
case MabInterstitialEngine.EVENT_ON_LOADED:
if let eventParams = msg.obj as? EventParams {
engine.auctioneer.onLoaded(bidderId: eventParams.id)
}
return true
case MabInterstitialEngine.EVENT_ON_LOAD_FAILED:
if let eventParams = msg.obj as? EventParams {
engine.auctioneer.onLoadFailed(bidderId: eventParams.id)
}
return true
default:
break
}
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
/**
*
* priority
* requestNext
*/
private func processRequestAfterConsumed() {
engine.auctioneer.refillCandidatesQueue()
let result = engine.auctioneer.requestNext()
if let winner = engine.auctioneer.peekMaxRevenue() {
engine.transitionTo(
engine.loadedState,
params: EventParams(ad: winner.getFusionAd())
)
} else if result != MabAuctioneer.RequestResult.noAvailableSource {
engine.transitionTo(engine.loadingState)
} else {
engine.transitionTo(engine.idleState)
engine.requestLoad()
}
}
}
}
private class AtomicInteger {
private var value: Int
init(_ initialValue: Int) {
self.value = initialValue
}
func get() -> Int {
return value
}
func incrementAndGet() -> Int {
value += 1
return value
}
}
private class SystemClock {
static func elapsedRealtime() -> Int64 {
return Int64(ProcessInfo.processInfo.systemUptime * 1000)
}
static func elapsedRealtimeNanos() -> Int64 {
return Int64(ProcessInfo.processInfo.systemUptime * 1_000_000_000)
}
}

View File

@ -0,0 +1,796 @@
//
// MabRewardedEngine.swift
// Pods
//
// Created by 250102 on 2025/5/10.
//
import Foundation
//
typealias RewardedAdObtainer = (_ adPlatform: AdPlatform, _ adConfig: AdConfig) -> GuruRewardedAd?
// RewardedBidder
private class RewardedBidder: CustomStringConvertible, MabBidder, FusionAdListener {
//
private static let idPool = AtomicInteger(0)
static let TAG = "RewardedBidder"
//
private let engine: RewardedAdEngine
var config: AggregatorConfig
private let ad: GuruRewardedAd
private let adUnitSpec: AdUnitSpec
private let faUnitId: String
var id: Int
var revenue: Double = 0.0
private var fusionAd: FusionAd?
private var loadedTimeInMillis: Int64?
private var loaded: Bool {
return loadedTimeInMillis != nil
}
var isExpired: Bool? {
get {
// 0
if config.cacheExpireInSecond == 0.0 {
return false
}
guard let loadedTime = loadedTimeInMillis else {
return nil
}
return (SystemClock.elapsedRealtime() - loadedTime) > Int64(config.cacheExpireInSecond * 1000)
}
}
var isFilled: Bool {
return loaded && !(isExpired ?? true)
}
func getFusionAd() -> FusionAd? {
return fusionAd
}
//
private let loadTimeOutToken = UUID()
private let cacheExpireToken = UUID()
private var loadStartTime: Int64?
//
init(
engine: RewardedAdEngine,
config: AggregatorConfig,
ad: GuruRewardedAd,
adUnitSpec: AdUnitSpec,
faUnitId: String
) {
self.engine = engine
self.config = config
self.ad = ad
self.adUnitSpec = adUnitSpec
self.faUnitId = faUnitId
self.id = RewardedBidder.idPool.incrementAndGet()
ad.listener = self
}
private func consumeLoadDuration() -> Int64? {
guard let start = loadStartTime else {
return nil
}
loadStartTime = nil
return SystemClock.elapsedRealtime() - start
}
private var handler: StateMachine {
return engine
}
func load(reason: RequestReason?) -> Bool {
MabAnalyticEvents.faLoad(faUnitId: faUnitId, adType: engine.adType, aggregatorId: config.aggregatorId, adUnitId: adUnitSpec.adUnitId, requestReason: reason)
let result = ad.load()
if result {
handler.removeCallbacksAndMessages(cacheExpireToken)
loadedTimeInMillis = nil
loadStartTime = SystemClock.elapsedRealtime()
let timeout: Int64
if let configTimeout = config.loadTimeoutInSecond {
timeout = Int64(configTimeout * 1000)
} else {
timeout = MabRewardedEngine.BIDDING_LOAD_TIMEOUT_MILLIS
}
handler.postDelayed(runnable: { [weak self] in self?.performLoadTimeout() }, token: loadTimeOutToken, delayed: .milliseconds(Int(timeout)))
}
return result
}
func show(request: RewardedShowRequest?) -> Bool {
let result = ad.show(request)
if result {
handler.removeCallbacksAndMessages(cacheExpireToken)
loadedTimeInMillis = nil
}
return result
}
func destroy() {
handler.removeCallbacksAndMessages(loadTimeOutToken)
handler.removeCallbacksAndMessages(cacheExpireToken)
revenue = 0.0
ad.destroy()
fusionAd = nil
loadedTimeInMillis = nil
loadStartTime = nil
}
// MARK: - FusionAdListener
func onAdLoaded(ad: FusionAd) {
logLoaded(ad)
handler.removeCallbacksAndMessages(loadTimeOutToken)
fusionAd = ad
loadedTimeInMillis = SystemClock.elapsedRealtime()
revenue = MabBidderUtils.adjustRevenue(ad: ad, floor: adUnitSpec.floor)
engine.sendMessage(what: MabRewardedEngine.EVENT_ON_LOADED, obj: EventParams(ad: ad, id: id))
handler.removeCallbacksAndMessages(cacheExpireToken)
let cacheTimeInMillis = Int64(config.cacheExpireInSecond * 1000)
if cacheTimeInMillis > 0 {
handler.postDelayed(runnable: { [weak self] in self?.performCacheExpired() }, token: cacheExpireToken, delayed: .milliseconds(Int(cacheTimeInMillis + 50)))
}
}
func onAdDisplayed(ad: FusionAd) {
engine.sendMessage(what: MabRewardedEngine.EVENT_ON_DISPLAYED, obj: EventParams(ad: ad, id: id))
}
func onAdHidden(ad: FusionAd) {
engine.sendMessage(what: MabRewardedEngine.EVENT_ON_HIDDEN, obj: EventParams(ad: ad, id: id))
}
func onAdClicked(ad: FusionAd) {
engine.sendMessage(what: MabRewardedEngine.EVENT_ON_CLICK, obj: EventParams(ad: ad, id: id))
}
func onAdLoadFailed(loadFailedInfo: LoadFailedInfo) {
let errorCode = loadFailedInfo.error?.errorCode ?? FusionErrorCodes.UNKNOWN.code
logLoadFailed(errorCode)
loadedTimeInMillis = nil
handler.removeCallbacksAndMessages(loadTimeOutToken)
engine.sendMessage(what:
MabRewardedEngine.EVENT_ON_LOAD_FAILED,
obj: EventParams(loadFailedInfo: loadFailedInfo, id: id)
)
}
func onAdDisplayFailed(ad: FusionAd, error: FusionError?) {
engine.sendMessage(
what: MabRewardedEngine.EVENT_ON_DISPLAY_FAILED,
obj: EventParams(ad: ad, displayFailedInfo: error, id: id)
)
}
func onAdRevenuePaid(ad: FusionAd) {
engine.sendMessage(what: MabRewardedEngine.EVENT_REVENUE_PAID, obj: EventParams(ad: ad, id: id))
}
func onUserRewarded(ad: FusionAd, reward: (any FusionReward)?) {
engine.sendMessage(what: MabRewardedEngine.EVENT_USER_REWARDED, obj: EventParams(ad: ad, reward: reward))
}
// MARK: -
private func performLoadTimeout() {
ad.destroy()
onAdLoadFailed(
loadFailedInfo: LoadFailedInfo(
engineId: ad.engineId,
adPlatform: ad.adPlatform,
adType: engine.adType,
adUnitId: ad.adUnitId,
error: LoadAdTimeoutError()
)
)
}
private func performCacheExpired() {
engine.sendMessage(what: MabRewardedEngine.EVENT_CACHE_EXPIRED, obj: EventParams(id: id))
}
private func logLoaded(_ fusionAd: FusionAd) {
guard let duration = consumeLoadDuration() else {
Logger.w(tag: RewardedBidder.TAG, message: "logLoaded but duration is nil, ignored")
return
}
MabAnalyticEvents.faLoaded(
faUnitId: faUnitId,
adType: engine.adType,
adSource: fusionAd.networkName,
aggregatorId: config.aggregatorId,
adUnitId: ad.adUnitId,
durationInMillis: duration
)
}
private func logLoadFailed(_ errorCode: Int) {
guard let duration = consumeLoadDuration() else {
Logger.w(tag: RewardedBidder.TAG, message: "logLoadFailed but duration is nil, ignored")
return
}
MabAnalyticEvents.faFailed(
faUnitId: faUnitId,
adType: engine.adType,
aggregatorId: config.aggregatorId,
adUnitId: ad.adUnitId,
durationInMillis: duration,
errorCode: errorCode
)
}
var description: String {
return "RewardedBidder[\(aggregatorId)(\(adUnitSpec.adUnitId))]"
}
}
// MabRewardedEngine
internal class MabRewardedEngine: RewardedAdEngine {
// MARK: -
static let ACTION_LOAD = 1
static let ACTION_SHOW = 2
static let ACTION_DESTROY = 4
static let ACTION_RETRY = 5
static let EVENT_ON_LOADED = 100
static let EVENT_ON_LOAD_FAILED = 101
static let EVENT_ON_DISPLAYED = 102
static let EVENT_ON_DISPLAY_FAILED = 103
static let EVENT_ON_CLICK = 104
static let EVENT_ON_HIDDEN = 105
static let EVENT_USER_REWARDED = 106
static let EVENT_REVENUE_PAID = 107
static let EVENT_SHOW_TIMEOUT = 109
static let EVENT_CACHE_EXPIRED = 110
static let BIDDING_LOAD_TIMEOUT_MILLIS: Int64 = 60_000
static let SHOW_TIMEOUT_MILLIS: Int64 = 120_000
// MARK: -
private let faUnitId: String
private let mabConfig: MabRewardedConfig
private let adObtainer: RewardedAdObtainer
private let auctioneer: MabAuctioneer<RewardedBidder>
// MARK: -
init(
viewController: UIViewController,
id: Int,
faUnitId: String,
mabConfig: MabRewardedConfig,
adObtainer: @escaping RewardedAdObtainer
) {
self.mabConfig = mabConfig
self.adObtainer = adObtainer
self.faUnitId = faUnitId
//
weak var futureSelf: MabRewardedEngine? = nil
auctioneer = MabAuctioneer<RewardedBidder>(
engineId: id,
faUnitId: faUnitId,
aggregatorConfigs: mabConfig.aggregatorConfigs,
bidderFactory: { config, adUnitSpec in
guard let self = futureSelf else { return nil }
return self.createBidder(config: config, adUnitSpec: adUnitSpec)
}
)
super.init(viewController: viewController, id: id, adType: AdType.Rewarded, strategyName: "Mab")
futureSelf = self
}
public override func createIdleState() -> Idle {
return IdleImpl(self)
}
public override func createLoadingState() -> Loading {
return LoadingImpl(self)
}
public override func createLoadedState() -> Loaded {
return LoadedImpl(self)
}
public override func createShowingState() -> Showing {
return ShowingImpl(self)
}
// MARK: -
private func createBidder(
config: AggregatorConfig,
adUnitSpec: AdUnitSpec
) -> RewardedBidder? {
let adAmzId = adUnitSpec.adAmazonSlotId
let adPlatform = AdPlatform.fromName(config.adPlatform)
guard let ad = adObtainer(
adPlatform,
AdConfig(engineId: id, adUnitId: adUnitSpec.adUnitId, adAmazonSlotId: adAmzId, requireDisableAutoRetries: true)
) else {
return nil
}
return RewardedBidder(
engine: self,
config: config,
ad: ad,
adUnitSpec: adUnitSpec,
faUnitId: faUnitId
)
}
private func handleUnhandledMessage(_ msg: Message?) -> Bool {
guard let what = msg?.what else { return false }
switch what {
case MabRewardedEngine.ACTION_LOAD:
logWarn("ACTION_LOAD on \(String(describing: currentState?.name))")
adLoadFailed(
loadFailedInfo: LoadFailedInfo(
engineId: id,
adPlatform: AdPlatform.fusion,
adType: adType,
adUnitId: "mab_rewarded",
error: LoadAdRequestError(message: "unhandled ACTION_LOAD, current state is \(String(describing: currentState?.name))")
)
)
return true
case MabRewardedEngine.ACTION_SHOW:
adDisplayFailed(ad: InvalidFusionAd.rewarded(engineId: id), error: ShowAdRequestError())
return true
case MabRewardedEngine.ACTION_DESTROY:
logWarn("Destroy may not supported in a Mab Engine, may lead memory leakages if you are trying to destroy one.")
transitionTo(idleState)
// destroy all ads
return true
case MabRewardedEngine.EVENT_REVENUE_PAID:
logWarn("revenue paid event unhandled!")
case MabRewardedEngine.EVENT_ON_LOADED:
logWarn("event EVENT_ON_LOADED unhandled!")
case MabRewardedEngine.EVENT_ON_LOAD_FAILED:
logWarn("event EVENT_ON_LOAD_FAILED unhandled!")
default:
break
}
return false
}
private func reset() {
auctioneer.reset()
removeMessages(what: MabRewardedEngine.EVENT_SHOW_TIMEOUT)
}
// MARK: - RewardedAdEngine
override func requestLoad() {
sendMessage(what: MabRewardedEngine.ACTION_LOAD)
}
override func requestShow(_ request: RewardedShowRequest?) {
sendMessage(what: MabRewardedEngine.ACTION_SHOW, obj: request)
}
override func requestDestroy() {
sendMessage(what: MabRewardedEngine.ACTION_DESTROY)
}
override var supportedAdPlatforms: Set<AdPlatform> {
return [AdPlatform.fusion]
}
// MARK: -
class IdleImpl: Idle {
public let engine: MabRewardedEngine
init(_ engine: MabRewardedEngine) {
self.engine = engine
}
override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
self.engine.reset()
}
override func exit(to: IState?) {
super.exit(to: to)
}
override func processMessage(_ msg: Message) -> Bool {
let what = msg.what
if what == MabRewardedEngine.ACTION_LOAD {
let result = engine.auctioneer.requestNext()
if result != MabAuctioneer.RequestResult.noAvailableSource {
engine.transitionTo(engine.loadingState)
return true
} else {
engine.logError("No available source")
engine.adLoadFailed(
loadFailedInfo: LoadFailedInfo(
engineId: engine.id,
adPlatform: AdPlatform.fusion,
adType: engine.adType,
adUnitId: "mab_rewarded",
error: LoadAdNotAvailableError()
)
)
return true
}
}
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
override var name: String {
return super.name
}
}
class LoadingImpl: Loading {
public let engine: MabRewardedEngine
init(_ engine: MabRewardedEngine) {
self.engine = engine
}
private var retryCount = 0
override func enter(from: IState?, params: Any?) {
super.enter(from: from, params: params)
}
override func exit(to: IState?) {
super.exit(to: to)
}
override func processMessage(_ msg: Message) -> Bool {
let what = msg.what
switch what {
case MabRewardedEngine.ACTION_RETRY:
retryCount += 1
engine.transitionTo(engine.idleState)
engine.requestLoad()
return true
case MabRewardedEngine.ACTION_LOAD:
engine.logWarn("Already loading! Ignore load request")
return true
case MabRewardedEngine.EVENT_ON_LOADED:
if let eventParams = msg.obj as? EventParams {
if eventParams.ad != nil {
engine.auctioneer.onLoaded(bidderId: eventParams.id)
engine.auctioneer.requestNext()
if engine.auctioneer.hasWinner {
engine.transitionTo(engine.loadedState, params: eventParams)
}
}
}
return true
case MabRewardedEngine.EVENT_ON_LOAD_FAILED:
guard let eventParams = msg.obj as? EventParams else {
return true
}
engine.auctioneer.onLoadFailed(bidderId: eventParams.id)
let result = engine.auctioneer.requestNext()
if result == MabAuctioneer.RequestResult.noAvailableSource {
let failedInfo = eventParams.loadFailedInfo ?? LoadFailedInfo(
engineId: engine.id,
adPlatform: AdPlatform.fusion,
adType: engine.adType,
adUnitId: "mab_rewarded",
error: LoadAdNotAvailableError()
)
engine.adLoadFailed(loadFailedInfo: failedInfo)
let delay = min(max(pow(2.0, Double(retryCount)) as Double, 10), 300)
engine.sendMessageDelayed(what: MabRewardedEngine.ACTION_RETRY, delayMillis: Int(delay * 1000))
}
return true
default:
break
}
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
}
class LoadedImpl: Loaded {
public let engine: MabRewardedEngine
init(_ engine: MabRewardedEngine) {
self.engine = engine
}
override func enter(from: IState?, params: Any?) {
if let params = params as? EventParams, let ad = params.ad {
self.engine.adLoaded(ad: ad)
} else {
self.engine.logWarn("Loaded state enter with invalid params")
self.engine.transitionTo(self.engine.idleState)
}
super.enter(from: from, params: params)
}
override func exit(to: IState?) {
super.exit(to: to)
}
override func processMessage(_ msg: Message) -> Bool {
let what = msg.what
switch what {
case MabRewardedEngine.ACTION_SHOW:
let showRequest = msg.obj as? RewardedShowRequest
var showResult: Bool? = nil
var finalWinner: RewardedBidder? = nil
while true {
guard let winner = engine.auctioneer.pollMaxRevenue() else {
break
}
if !winner.isFilled {
continue
}
showResult = winner.show(request: showRequest)
if showResult != true {
continue
}
finalWinner = winner
break
}
if let winner = finalWinner {
engine.auctioneer.onShown(winner: winner)
engine.transitionTo(engine.showingState)
} else {
let errorMessage = showResult == nil
? "requestShow while NO winner in candidates"
: "Show Ad returns false"
engine.adDisplayFailed(
ad: InvalidFusionAd.rewarded(engineId: engine.id),
error: ShowAdRequestError(message: errorMessage)
)
let result = engine.auctioneer.requestNext()
if result != MabAuctioneer.RequestResult.noAvailableSource {
engine.transitionTo(engine.loadingState)
} else {
engine.transitionTo(engine.idleState)
engine.requestLoad()
}
}
return true
case MabRewardedEngine.EVENT_ON_LOADED:
if let eventParams = msg.obj as? EventParams, eventParams.ad != nil {
engine.auctioneer.onLoaded(bidderId: eventParams.id)
engine.auctioneer.requestNext()
}
return true
case MabRewardedEngine.EVENT_ON_LOAD_FAILED:
processLoadFailed(msg)
return true
case MabRewardedEngine.EVENT_CACHE_EXPIRED:
engine.auctioneer.evictCache()
if !engine.auctioneer.hasWinner {
engine.transitionTo(engine.idleState)
engine.requestLoad()
}
return true
default:
break
}
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
private func processLoadFailed(_ msg: Message) {
guard let eventParams = msg.obj as? EventParams else {
return
}
engine.auctioneer.onLoadFailed(bidderId: eventParams.id)
engine.auctioneer.requestNext()
}
}
class ShowingImpl: Showing {
public let engine: MabRewardedEngine
init(_ engine: MabRewardedEngine) {
self.engine = engine
}
override func enter(from: IState?, params: Any?) {
let timeout: Int64
if let configTimeout = engine.mabConfig.showTimeoutInSecond {
timeout = Int64(configTimeout * 1000)
} else {
timeout = MabRewardedEngine.SHOW_TIMEOUT_MILLIS
}
engine.sendMessageDelayed(what: MabRewardedEngine.EVENT_SHOW_TIMEOUT, delayed: .milliseconds(Int(timeout)))
super.enter(from: from, params: params)
}
override func exit(to: IState?) {
self.engine.removeMessages(what: MabRewardedEngine.EVENT_SHOW_TIMEOUT)
super.exit(to: to)
}
override func processMessage(_ msg: Message) -> Bool {
let what = msg.what
switch what {
case MabRewardedEngine.EVENT_ON_DISPLAYED:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adDisplayed(ad: ad)
} else {
engine.logWarn("Showing state enter with invalid params")
}
engine.removeMessages(what: MabRewardedEngine.EVENT_SHOW_TIMEOUT)
return true
case MabRewardedEngine.EVENT_ON_CLICK:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adClicked(ad: ad)
} else {
engine.logWarn("Showing state enter with invalid params")
}
engine.removeMessages(what: MabRewardedEngine.EVENT_SHOW_TIMEOUT)
return true
case MabRewardedEngine.EVENT_ON_HIDDEN:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adHidden(ad: ad)
} else {
engine.logWarn("Showing state enter with invalid params")
}
processRequestAfterConsumed()
return true
case MabRewardedEngine.EVENT_ON_DISPLAY_FAILED:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adDisplayFailed(ad: ad, error: eventParams.displayFailedInfo)
} else {
engine.logWarn("Showing state enter with invalid params")
}
processRequestAfterConsumed()
return true
case MabRewardedEngine.EVENT_SHOW_TIMEOUT:
engine.adDisplayFailed(ad: InvalidFusionAd.rewarded(engineId: engine.id), error: ShowAdTimeoutError())
processRequestAfterConsumed()
return true
case MabRewardedEngine.EVENT_USER_REWARDED:
guard let rewarded = (msg.obj as? EventParams)?.reward, let ad = (msg.obj as? EventParams)?.ad else {
return true
}
engine.adUserRewarded(ad: ad, reward: rewarded)
return true
case MabRewardedEngine.EVENT_REVENUE_PAID:
if let eventParams = msg.obj as? EventParams, let ad = eventParams.ad {
engine.adRevenuePaid(ad: ad)
} else {
engine.logWarn("revenue paid event with invalid params")
}
return true
case MabRewardedEngine.EVENT_ON_LOADED:
if let eventParams = msg.obj as? EventParams {
engine.auctioneer.onLoaded(bidderId: eventParams.id)
}
return true
case MabRewardedEngine.EVENT_ON_LOAD_FAILED:
if let eventParams = msg.obj as? EventParams {
engine.auctioneer.onLoadFailed(bidderId: eventParams.id)
}
return true
default:
break
}
return engine.handleUnhandledMessage(msg) || super.processMessage(msg)
}
/**
*
* priority
* requestNext
*/
private func processRequestAfterConsumed() {
engine.auctioneer.refillCandidatesQueue()
let result = engine.auctioneer.requestNext()
if let winner = engine.auctioneer.peekMaxRevenue() {
engine.transitionTo(
engine.loadedState,
params: EventParams(ad: winner.getFusionAd())
)
} else if result != MabAuctioneer.RequestResult.noAvailableSource {
engine.transitionTo(engine.loadingState)
} else {
engine.transitionTo(engine.idleState)
engine.requestLoad()
}
}
}
}
private class AtomicInteger {
private var value: Int
init(_ initialValue: Int) {
self.value = initialValue
}
func get() -> Int {
return value
}
func incrementAndGet() -> Int {
value += 1
return value
}
}
private class SystemClock {
static func elapsedRealtime() -> Int64 {
return Int64(ProcessInfo.processInfo.systemUptime * 1000)
}
static func elapsedRealtimeNanos() -> Int64 {
return Int64(ProcessInfo.processInfo.systemUptime * 1_000_000_000)
}
}

View File

@ -0,0 +1,140 @@
//
// GuruIronSourceAdsSdk.swift
// Pods
//
// Created by 250102 on 2025/5/6.
//
import IronSource
public class GuruIronSourceAdsSdk: GuruAdsSdk {
private static var instance: GuruIronSourceAdsSdk?
public var adPlatform: AdPlatform {
return AdPlatform.ironSource
}
private var initialized = false
private static var interstitialAds: [String: GuruInterstitialAd] = [:]
private static var rewardedAds: [String: GuruRewardedAd] = [:]
private static var bannerAds: [String: GuruBannerAd] = [:]
private static var mrecAds: [String: GuruMRecAd] = [:]
private func logDebug(_ message: String) {
Logger.d(tag: "GuruIronSourceAdsSdk", message: message)
}
private func logInfo(_ message: String) {
Logger.i(tag: "GuruIronSourceAdsSdk", message: message)
}
private func logWarn(_ message: String) {
Logger.w(tag: "GuruIronSourceAdsSdk", message: message)
}
private func logError(_ message: String) {
Logger.e(tag: "GuruIronSourceAdsSdk", message: message)
}
private let viewController: UIViewController
private let impressionDataDispatcher = GuruIronSourceImpressionDataDispatcher()
private init(viewController: UIViewController) {
self.viewController = viewController
}
public static func obtain(viewController: UIViewController) -> GuruAdsSdk {
if let existingInstance = instance {
return existingInstance
}
let newInstance = GuruIronSourceAdsSdk(viewController: viewController)
instance = newInstance
return newInstance
}
public func initialize(adsProfile: AdsProfile) async -> Bool {
guard !initialized else {
logWarn("SDK already initialized! Ignoring.")
return false
}
if(adsProfile.ironSourceSdkKey?.isEmpty != false) {
logError("ironSourceSdkKey is empty, unable to initialize")
return false;
}
initialized = true
if(adsProfile.debugMode) {
IronSource.setMetaDataWithKey("is_test_suite", value: "enable")
ISIntegrationHelper.validateIntegration()
}
IronSource.add(impressionDataDispatcher)
return await withCheckedContinuation(isolation: MainActor.shared) { continuation in
let requestBuilder = LPMInitRequestBuilder(appKey: adsProfile.ironSourceSdkKey!)
.withUserId(adsProfile.userId)
let initRequest = requestBuilder.build()
LevelPlay.initWith(initRequest)
{ config, error in
if let error = error {
self.logError("initialize failed, \(error)")
continuation.resume(returning: false)
} else {
continuation.resume(returning: true)
}
}
}
}
public func obtainInterstitialAd(adConfig: AdConfig) -> GuruInterstitialAd {
if let existingAd = Self.interstitialAds[adConfig.cacheKey] {
return existingAd
}
let newAd = GuruIronSourceInterstitialAd(viewController: viewController, adConfig: adConfig)
Self.interstitialAds[adConfig.cacheKey] = newAd
return newAd
}
public func obtainRewardedAd(adConfig: AdConfig) -> GuruRewardedAd {
if let existingAd = Self.rewardedAds[adConfig.cacheKey] {
return existingAd
}
let newAd = GuruIronSourceRewardedAd(viewController: viewController, adConfig: adConfig)
Self.rewardedAds[adConfig.cacheKey] = newAd
return newAd
}
public func obtainBannerAd(adConfig: AdConfig) -> GuruBannerAd {
if let existingAd = Self.bannerAds[adConfig.cacheKey] {
return existingAd
}
let newAd = GuruIronSourceBannerAd(viewController: viewController, adConfig: adConfig)
Self.bannerAds[adConfig.cacheKey] = newAd
return newAd
}
public func obtainMRecAd(adConfig: AdConfig) -> GuruMRecAd {
if let existingAd = Self.mrecAds[adConfig.cacheKey] {
return existingAd
}
let newAd = GuruIronSourceMRecAd(viewController: viewController, adConfig: adConfig)
Self.mrecAds[adConfig.cacheKey] = newAd
return newAd
}
public func processCrossAction(action: AdsCrossAction) -> Bool {
return false
}
}

View File

@ -0,0 +1,124 @@
//
// GuruIronSourceBannerAd.swift
// Pods
//
// Created by 250102 on 2025/5/7.
//
import IronSource
class GuruIronSourceBannerView : GuruBannerView {
func view() -> UIView {
return bannerAdView
}
func setPlacementName(placement: String) {
bannerAdView.setPlacementName(placement)
}
func onShow() {
bannerAdView.isHidden = false
bannerAdView.resumeAutoRefresh()
}
func onHide() {
bannerAdView.isHidden = true
bannerAdView.pauseAutoRefresh()
}
public var bannerAdView: LPMBannerAdView
public init(_ banner: LPMBannerAdView) {
self.bannerAdView = banner
}
}
class GuruIronSourceBannerAd : GuruBannerAd, LPMBannerAdViewDelegate, ISImpressionDataDelegate {
private let viewController: UIViewController
private let adConfig: AdConfig
private var bannerAdCoordinator: GuruBannerCoordinator<GuruIronSourceBannerView>?
private var bannerAd: LPMBannerAdView? { return bannerAdCoordinator?.bannerAd.bannerAdView }
private var contentView: UIView?
private var currentUid2Token: String = ""
private var contentId: Int {
return self.adConfig.engineId
}
private let impressionListenerRegistrar: GuruIronSourceImpressionDataListenerRegistrar?
public init(viewController: UIViewController, adConfig: AdConfig, impressionListenerRegistrar: GuruIronSourceImpressionDataListenerRegistrar? = nil) {
self.viewController = viewController
self.adConfig = adConfig
self.impressionListenerRegistrar = impressionListenerRegistrar
super.init(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: AdPlatform.ironSource)
}
override func load() -> Bool {
let bannerAd = LPMBannerAdView(adUnitId: adConfig.adUnitId)
bannerAd.loadAd(with: self.viewController)
bannerAd.setDelegate(self)
impressionListenerRegistrar?.unregister(listener: self)
impressionListenerRegistrar?.register(adUnitId: adUnitId, listener: self)
bannerAd.setAdSize(LPMAdSize.banner())
bannerAd.pauseAutoRefresh()
bannerAdCoordinator = GuruBannerCoordinator(viewController: viewController, bannerAd: GuruIronSourceBannerView(bannerAd), contentId: contentId)
return true
}
public override func show(_ request: BannerShowRequest? = nil) -> Bool {
return bannerAdCoordinator?.show() ?? false
}
public override func hide() -> Bool {
return bannerAdCoordinator?.hide() ?? false
}
override public func destroy() -> Bool {
bannerAd?.destroy()
let _ = bannerAdCoordinator?.destroy()
bannerAdCoordinator = nil
impressionListenerRegistrar?.unregister(listener: self)
return true
}
override public func updateOrientation(orientation: ScreenOrientation) -> Bool {
return bannerAdCoordinator?.updateOrientation(orientation: orientation) ?? false
}
func didLoadAd(with adInfo: LPMAdInfo) {
adLoaded(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didFailToLoadAd(withAdUnitId adUnitId: String, error: any Error) {
adLoadFailed(loadFailedInfo: LoadFailedInfo(engineId: engineId, adPlatform: adPlatform, adType: adType, adUnitId: adUnitId, error: SwiftFusionError(error)))
}
func didClickAd(with adInfo: LPMAdInfo) {
adClicked(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didExpandAd(with adInfo: LPMAdInfo) {
adBannerExpanded(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didDisplayAd(with adInfo: LPMAdInfo) {
adDisplayed(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didCollapseAd(with adInfo: LPMAdInfo) {
adBannerCollapsed(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didFailToDisplayAd(with adInfo: LPMAdInfo, error: any Error) {
adDisplayFailed(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo), error: SwiftFusionError(error))
}
func didLeaveApp(with adInfo: LPMAdInfo) {
}
func impressionDataDidSucceed(_ impressionData: ISImpressionData!) {
adRevenuePaid(ad: IronSourceImpressedFusionAd(engineId: engineId, adType: adType, impressionData: impressionData))
}
}

View File

@ -0,0 +1,88 @@
//
// GuruIronSourceImpressionDataDispatcher.swift
// Pods
//
// Created by 250102 on 2025/5/12.
//
import IronSource
internal protocol GuruIronSourceImpressionDataListenerRegistrar {
func register(adUnitId: String, listener: ISImpressionDataDelegate)
func unregister(listener: ISImpressionDataDelegate)
}
internal class GuruIronSourceImpressionDataDispatcher : NSObject, ISImpressionDataDelegate,
GuruIronSourceImpressionDataListenerRegistrar {
func impressionDataDidSucceed(_ impressionData: ISImpressionData?) {
guard let data = impressionData else { return }
dispatchImpressionData(data: data)
}
private var listeners: [(String, any ISImpressionDataDelegate)] = []
private func dispatchImpressionData(data: ISImpressionData) {
listeners.forEach {
let adUnitId = $0.0
let listener = $0.1
if (adUnitId == data.mediation_ad_unit_id) {
listener.impressionDataDidSucceed(data)
}
}
}
func register(
adUnitId: String,
listener: ISImpressionDataDelegate
) {
listeners.append((adUnitId, listener))
}
func unregister(listener: ISImpressionDataDelegate) {
listeners.removeAll { $0.1 === listener }
}
}
internal class IronSourceImpressedFusionAd: FusionAd {
private let impressionData: ISImpressionData
init(engineId: Int, adType: AdType, impressionData: ISImpressionData) {
self.impressionData = impressionData
super.init(engineId: engineId, adType: adType)
}
override public var adPlatform: AdPlatform {
return AdPlatform.ironSource
}
override public var adUnitId: String? {
return impressionData.mediation_ad_unit_id
}
override public var revenue: Double {
return impressionData.revenue?.doubleValue ?? 0.0
}
override public var waterfallName: String? {
return impressionData.auction_id
}
override public var placement: String? {
return impressionData.placement
}
override public var networkName: String? {
return impressionData.ad_network
}
override public var networkPlacement: String? {
return nil
}
override public var creativeId: String? {
return impressionData.creative_id
}
override public var adReviewCreativeId: String? {
return nil
}
}

View File

@ -0,0 +1,80 @@
//
// GuruIronSourceInterstitialAd.swift
// Pods
//
// Created by 250102 on 2025/5/6.
//
import IronSource
class GuruIronSourceInterstitialAd: GuruInterstitialAd, LPMInterstitialAdDelegate, ISImpressionDataDelegate {
private let viewController: UIViewController
private let adConfig: AdConfig
private var ad: LPMInterstitialAd?
private let impressionListenerRegistrar: GuruIronSourceImpressionDataListenerRegistrar?
public init(viewController: UIViewController, adConfig: AdConfig, impressionListenerRegistrar: GuruIronSourceImpressionDataListenerRegistrar? = nil) {
self.viewController = viewController
self.adConfig = adConfig
self.impressionListenerRegistrar = impressionListenerRegistrar
super.init(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: AdPlatform.ironSource)
}
override func load() -> Bool {
logInfo("request load")
self.ad = LPMInterstitialAd(adUnitId: adConfig.adUnitId)
self.ad?.setDelegate(self)
impressionListenerRegistrar?.unregister(listener: self)
impressionListenerRegistrar?.register(adUnitId: adUnitId, listener: self)
self.ad?.loadAd()
return true
}
override func show(_ request: InterstitialShowRequest? = nil) -> Bool {
logInfo("request show")
guard let ad = ad, ad.isAdReady() else {
return false
}
ad.showAd(viewController: viewController, placementName: request?.placement)
return true
}
override func destroy() -> Bool {
self.ad = nil
impressionListenerRegistrar?.unregister(listener: self)
return true
}
func didLoadAd(with adInfo: LPMAdInfo) {
logInfo("loaded")
adLoaded(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didFailToLoadAd(withAdUnitId adUnitId: String, error: any Error) {
logError("load failed, \(error.code) - \(error.localizedDescription)")
adLoadFailed(loadFailedInfo: LoadFailedInfo(engineId: engineId, adPlatform: adPlatform, adType: adType, adUnitId: adUnitId, error: SwiftFusionError(error)))
}
func didDisplayAd(with adInfo: LPMAdInfo) {
logInfo("displayed")
adDisplayed(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didFailToDisplayAd(with adInfo: LPMAdInfo, error: any Error) {
logError("displayed failed, \(error.code) - \(error.localizedDescription)")
adDisplayFailed(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo), error: SwiftFusionError(error))
}
func didClickAd(with adInfo: LPMAdInfo) {
adClicked(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didCloseAd(with adInfo: LPMAdInfo) {
adHidden(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func impressionDataDidSucceed(_ impressionData: ISImpressionData!) {
logInfo("revenue paid")
adRevenuePaid(ad: IronSourceImpressedFusionAd(engineId: engineId, adType: adType, impressionData: impressionData))
}
}

View File

@ -0,0 +1,167 @@
//
// GuruIronSourceBannerAd.swift
// Pods
//
// Created by 250102 on 2025/5/7.
//
import IronSource
class GuruIronSourceMRecAd : GuruMRecAd, LPMBannerAdViewDelegate, ISImpressionDataDelegate {
private let viewController: UIViewController
private let adConfig: AdConfig
private var mrecAd: LPMBannerAdView?
private var contentView: UIView?
private var currentUid2Token: String = ""
private var horizontalOffset: CGFloat = 0.0
private var verticalOffset: CGFloat = 0.0
private var orientation: ScreenOrientation = .portrait
private var contentId: Int {
return self.adConfig.engineId
}
private let impressionListenerRegistrar: GuruIronSourceImpressionDataListenerRegistrar?
public init(viewController: UIViewController, adConfig: AdConfig, impressionListenerRegistrar: GuruIronSourceImpressionDataListenerRegistrar? = nil) {
self.viewController = viewController
self.adConfig = adConfig
self.impressionListenerRegistrar = impressionListenerRegistrar
super.init(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: AdPlatform.ironSource)
}
override func load() -> Bool {
self.mrecAd = LPMBannerAdView(adUnitId: adConfig.adUnitId)
self.mrecAd?.loadAd(with: self.viewController)
self.mrecAd?.setDelegate(self)
impressionListenerRegistrar?.unregister(listener: self)
impressionListenerRegistrar?.register(adUnitId: adUnitId, listener: self)
self.mrecAd?.setAdSize(LPMAdSize.mediumRectangle())
self.mrecAd?.pauseAutoRefresh()
return true
}
public override func show(request: MRecShowRequest? = nil) -> Bool {
let placement = request?.placement
let newHorizontalOffset = request?.horizontalOffset
let newVerticalOffset = request?.verticalOffset
var shouldReattach = false
// Check if position or offset has changed
if let offset = newVerticalOffset, offset != Float(verticalOffset) {
verticalOffset = CGFloat(offset)
shouldReattach = true
}
if let offset = newHorizontalOffset, offset != Float(horizontalOffset) {
horizontalOffset = CGFloat(offset)
shouldReattach = true
}
// If we already have a view and parameters changed, remove it
if let contentView = contentView, shouldReattach {
logInfo("Parameters changed! Re-attaching view!")
contentView.removeFromSuperview()
self.contentView = nil
}
// Create new content view if needed
if contentView == nil {
let container = UIView()
container.tag = contentId
viewController.view.addSubview(container)
container.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
container.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor),
container.trailingAnchor.constraint(equalTo: viewController.view.trailingAnchor),
container.topAnchor.constraint(equalTo: viewController.view.topAnchor),
container.bottomAnchor.constraint(equalTo: viewController.view.bottomAnchor)
])
contentView = container
guard let mrecAd = mrecAd else { return false }
// Standard MREC size is 300x250
let mrecWidth: CGFloat = 300
let mrecHeight: CGFloat = 250
mrecAd.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(mrecAd)
NSLayoutConstraint.activate([
mrecAd.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: horizontalOffset),
mrecAd.topAnchor.constraint(equalTo: container.topAnchor, constant: verticalOffset),
mrecAd.widthAnchor.constraint(equalToConstant: mrecWidth),
mrecAd.heightAnchor.constraint(equalToConstant: mrecHeight)
])
mrecAd.isHidden = false
mrecAd.resumeAutoRefresh()
} else {
mrecAd?.resumeAutoRefresh()
mrecAd?.isHidden = false
}
return true
}
public override func hide() -> Bool {
guard let _ = viewController.view.viewWithTag(contentId) else {
return true
}
if let mrecAd = mrecAd, !mrecAd.isHidden {
mrecAd.pauseAutoRefresh()
mrecAd.isHidden = true
}
return true
}
override public func destroy() -> Bool{
mrecAd?.removeFromSuperview()
mrecAd = nil
contentView?.removeFromSuperview()
contentView = nil
impressionListenerRegistrar?.unregister(listener: self)
return true
}
func didLoadAd(with adInfo: LPMAdInfo) {
adLoaded(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didFailToLoadAd(withAdUnitId adUnitId: String, error: any Error) {
adLoadFailed(loadFailedInfo: LoadFailedInfo(engineId: engineId, adPlatform: adPlatform, adType: adType, adUnitId: adUnitId, error: SwiftFusionError(error)))
}
func didClickAd(with adInfo: LPMAdInfo) {
adClicked(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didExpandAd(with adInfo: LPMAdInfo) {
adBannerExpanded(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didDisplayAd(with adInfo: LPMAdInfo) {
adDisplayed(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didCollapseAd(with adInfo: LPMAdInfo) {
adBannerCollapsed(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didFailToDisplayAd(with adInfo: LPMAdInfo, error: any Error) {
adDisplayFailed(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo), error: SwiftFusionError(error))
}
func impressionDataDidSucceed(_ impressionData: ISImpressionData!) {
adRevenuePaid(ad: IronSourceImpressedFusionAd(engineId: engineId, adType: adType, impressionData: impressionData))
}
}

View File

@ -0,0 +1,86 @@
//
// GuruIronSourceRewardedAd.swift
// Pods
//
// Created by 250102 on 2025/5/7.
//
import IronSource
class GuruIronSourceRewardedAd: GuruRewardedAd, LPMRewardedAdDelegate, ISImpressionDataDelegate {
private let viewController: UIViewController
private let adConfig: AdConfig
private var ad: LPMRewardedAd?
private let impressionListenerRegistrar: GuruIronSourceImpressionDataListenerRegistrar?
public init(viewController: UIViewController, adConfig: AdConfig, impressionListenerRegistrar: GuruIronSourceImpressionDataListenerRegistrar? = nil) {
self.viewController = viewController
self.adConfig = adConfig
self.impressionListenerRegistrar = impressionListenerRegistrar
super.init(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: AdPlatform.ironSource)
}
override func load() -> Bool {
logInfo("request load")
self.ad = LPMRewardedAd(adUnitId: adConfig.adUnitId)
self.ad?.setDelegate(self)
impressionListenerRegistrar?.unregister(listener: self)
impressionListenerRegistrar?.register(adUnitId: adUnitId, listener: self)
self.ad?.loadAd()
return true
}
override func show(_ request: RewardedShowRequest? = nil) -> Bool {
logInfo("request show")
guard let ad = ad, ad.isAdReady() else {
return false
}
ad.showAd(viewController: viewController, placementName: request?.placement)
return true
}
override func destroy() -> Bool {
self.ad = nil
impressionListenerRegistrar?.unregister(listener: self)
return true
}
func didLoadAd(with adInfo: LPMAdInfo) {
logInfo("loaded")
adLoaded(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didFailToLoadAd(withAdUnitId adUnitId: String, error: any Error) {
logError("load failed, \(error.code) - \(error.localizedDescription)")
adLoadFailed(loadFailedInfo: LoadFailedInfo(engineId: engineId, adPlatform: adPlatform, adType: adType, adUnitId: adUnitId, error: SwiftFusionError(error)))
}
func didDisplayAd(with adInfo: LPMAdInfo) {
logInfo("displayed")
adDisplayed(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didFailToDisplayAd(with adInfo: LPMAdInfo, error: any Error) {
logError("displayed failed, \(error.code) - \(error.localizedDescription)")
adDisplayFailed(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo), error: SwiftFusionError(error))
}
func didClickAd(with adInfo: LPMAdInfo) {
adClicked(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didCloseAd(with adInfo: LPMAdInfo) {
adHidden(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo))
}
func didRewardAd(with adInfo: LPMAdInfo, reward: LPMReward) {
logInfo("reward user")
adUserRewarded(ad: IronSourceFusionAd(engineId: engineId, adType: adType, ad: adInfo), reward: IronSourceFusionReward(reward))
}
func impressionDataDidSucceed(_ impressionData: ISImpressionData!) {
logInfo("revenue paid")
adRevenuePaid(ad: IronSourceImpressedFusionAd(engineId: engineId, adType: adType, impressionData: impressionData))
}
}

View File

@ -0,0 +1,52 @@
//
// GuruIronSourceAd.swift
// Pods
//
// Created by 250102 on 2025/5/6.
//
import IronSource
class IronSourceFusionAd: FusionAd {
private let originAd: LPMAdInfo
public init(engineId: Int, adType: AdType, ad: LPMAdInfo) {
originAd = ad
super.init(engineId: engineId, adType: adType)
}
override public var adPlatform: AdPlatform {
return AdPlatform.ironSource
}
override public var adUnitId: String? {
return originAd.adUnitId
}
override public var revenue: Double {
return originAd.revenue.doubleValue
}
override public var waterfallName: String? {
return originAd.auctionId
}
override public var placement: String? {
return originAd.placementName
}
override public var networkName: String? {
return originAd.adNetwork
}
override public var networkPlacement: String? {
return nil
}
override public var creativeId: String? {
return originAd.creativeId
}
override public var adReviewCreativeId: String? {
return nil
}
}

View File

@ -0,0 +1,18 @@
//
// IronSourceFusionReward.swift
// Pods
//
// Created by 250102 on 2025/5/7.
//
import IronSource
class IronSourceFusionReward : FusionReward {
var rewardAmount: Int {
return reward.amount
}
private let reward: LPMReward
public init(_ reward: LPMReward) {
self.reward = reward
}
}

View File

@ -0,0 +1,240 @@
// GuruMaxAdsSdk.swift
// Main SDK class for MAX integration
// Corresponds to GuruMaxAdsSdk.kt in Android implementation
import UIKit
import AppLovinSDK
import DTBiOSSDK
import GuruConsent
import FBAudienceNetwork
import OpenWrapSDK
import InMobiSDK
import TradPlusAds
import MobileFuseSDK
public class GuruMaxAdsSdk: GuruAdsSdk {
private static var instance: GuruMaxAdsSdk?
private var initialized = false
private static var interstitialAds: [String: GuruMaxInterstitialAd] = [:]
private static var rewardedAds: [String: GuruMaxRewardedAd] = [:]
private static var bannerAds: [String: GuruMaxBannerAd] = [:]
private static var mrecAds: [String: GuruMaxMRecAd] = [:]
private let viewController: UIViewController
private init(viewController: UIViewController) {
self.viewController = viewController
}
public static func obtain(viewController: UIViewController) -> GuruAdsSdk {
if let existingInstance = instance {
return existingInstance
}
let newInstance = GuruMaxAdsSdk(viewController: viewController)
instance = newInstance
return newInstance
}
private func logDebug(_ message: String) {
Logger.d(tag: "GuruMaxAdsSdk", message: message)
}
private func logInfo(_ message: String) {
Logger.i(tag: "GuruMaxAdsSdk", message: message)
}
private func logWarn(_ message: String) {
Logger.w(tag: "GuruMaxAdsSdk", message: message)
}
public var adPlatform: AdPlatform {
return AdPlatform.max
}
public func initialize(adsProfile: AdsProfile) async -> Bool {
return await internalInitialize(adsProfile: adsProfile)
// return true
}
private func internalInitialize(adsProfile: AdsProfile) async -> Bool {
guard !initialized else {
logWarn("SDK already initialized! Ignoring.")
return false
}
guard !adsProfile.maxSdkKey.isEmpty else {
logWarn("maxSdkKey is empty")
return false
}
initialized = true
// Initialize Amazon SDK if needed
if let amazonAppId = adsProfile.amazonAppId, !amazonAppId.isEmpty {
// In a real implementation, you would initialize Amazon SDK here
logInfo("Would initialize Amazon SDK with ID: \(amazonAppId)")
}
// Configure PubMatic if needed
if let pubmaticStoreUrl = adsProfile.pubmaticStoreUrl, !pubmaticStoreUrl.isEmpty {
// In a real implementation, you would configure PubMatic here
logInfo("Would configure PubMatic with store URL: \(pubmaticStoreUrl)")
}
let amazonAppId = adsProfile.amazonAppId ?? ""
if (!amazonAppId.isEmpty) {
DTBAds.sharedInstance().setAppKey(amazonAppId)
DTBAds.sharedInstance().setAdNetworkInfo(DTBAdNetworkInfo.init(networkName: DTBADNETWORK_MAX))
DTBAds.sharedInstance().mraidCustomVersions = ["1.0", "2.0", "3.0"]
DTBAds.sharedInstance().mraidPolicy = CUSTOM_MRAID
DTBAds.sharedInstance().useGeoLocation = true
if (adsProfile.debugMode) {
DTBAds.sharedInstance().setLogLevel(DTBLogLevelAll)
}
// DTBAds.sharedInstance().testMode = true
}
// Update UID2 token if available
if let uid2Token = adsProfile.uid2Token {
updateUid2Token(uid2Token)
}
if(!(adsProfile.pubmaticStoreUrl?.isEmpty ?? true)){
let appInfo = POBApplicationInfo()
appInfo.storeURL = URL(string: adsProfile.pubmaticStoreUrl!)!
OpenWrapSDK.setApplicationInfo(appInfo)
}
let maxSdkKey = adsProfile.maxSdkKey
NSLog("===> [\(ALMediationProviderMAX)]maxSdkKey: \(maxSdkKey)")
let initConfig = ALSdkInitializationConfiguration(sdkKey: maxSdkKey) { builder in
builder.mediationProvider = ALMediationProviderMAX
let segments = adsProfile.segments.segments
if(!segments.isEmpty) {
builder.segmentCollection = MASegmentCollection { segmentsBuilder in
for seg in segments {
if (seg.key <= 0 || seg.values.isEmpty) {
continue
}
segmentsBuilder.add(MASegment(key: NSNumber(value: seg.key), values: seg.values.map {
NSNumber(value: $0)
}))
NSLog("segment.key:\(seg.key) \(seg.values)")
}
}
}
}
// Initialize the AppLovin SDK
let sdk = ALSdk.shared()
// Set user ID if available
if !adsProfile.userId.isEmpty {
sdk.settings.userIdentifier = adsProfile.userId
logDebug("Set userId \(adsProfile.userId) success")
} else {
logDebug("userId is null")
}
// Set custom parameters
sdk.settings.isVerboseLoggingEnabled = adsProfile.debugMode
sdk.settings.isMuted = true
sdk.settings.setExtraParameterForKey("enable_black_screen_fixes", value: "true")
return await withCheckedContinuation(isolation: MainActor.shared) { continuation in
// Initialize AppLovin SDK
do {
sdk.initialize(with: initConfig){ [weak self] configuration in
guard let self = self else {
continuation.resume(returning: false)
return
}
self.logDebug("AppLovinSdk initialized successfully")
// Initialize TP Creative Key if needed
if !adsProfile.tpCreativeKey.isEmpty {
DispatchQueue.main.async {
self.logWarn("tpCreativeKey \(adsProfile.tpCreativeKey)")
// In a real implementation, you would initialize GuardManager here
}
}
continuation.resume(returning: true)
}
} catch {
continuation.resume(returning: false)
}
}
}
private func updateUid2Token(_ uid2Token: String?) {
guard let uid2Token = uid2Token, !uid2Token.isEmpty else { return }
// In a real implementation, you would set the UID2 token to various ad networks
logDebug("Set uid2Token success \(uid2Token)")
// Define UID2 API constant
let UID2_API = "uidapi.com"
// Update for InMobi
let jsonObject = ["uidapi.com": uid2Token]
// In a real implementation, you would use this with InMobiSdk
// Update for PubMatic
// In a real implementation, you would use this with PubMatic SDK
// Update for TradPlus
// In a real implementation, you would use this with TradPlus SDK
// Update for MobileFuse
// In a real implementation, you would use this with MobileFuse SDK
}
public func obtainInterstitialAd(adConfig: AdConfig) -> GuruInterstitialAd {
if let existingAd = Self.interstitialAds[adConfig.cacheKey] {
return existingAd
}
let newAd = GuruMaxInterstitialAd(viewController: viewController, adConfig: adConfig)
Self.interstitialAds[adConfig.cacheKey] = newAd
return newAd
}
public func obtainRewardedAd(adConfig: AdConfig) -> GuruRewardedAd {
if let existingAd = Self.rewardedAds[adConfig.cacheKey] {
return existingAd
}
let newAd = GuruMaxRewardedAd(viewController: viewController, adConfig: adConfig)
Self.rewardedAds[adConfig.cacheKey] = newAd
return newAd
}
public func obtainBannerAd(adConfig: AdConfig) -> GuruBannerAd {
if let existingAd = Self.bannerAds[adConfig.cacheKey] {
return existingAd
}
let newAd = GuruMaxBannerAd(viewController: viewController, adConfig: adConfig)
Self.bannerAds[adConfig.cacheKey] = newAd
return newAd
}
public func obtainMRecAd(adConfig: AdConfig) -> GuruMRecAd {
if let existingAd = Self.mrecAds[adConfig.cacheKey] {
return existingAd
}
let newAd = GuruMaxMRecAd(viewController: viewController, adConfig: adConfig)
Self.mrecAds[adConfig.cacheKey] = newAd
return newAd
}
public func processCrossAction(action: AdsCrossAction) -> Bool {
// Implement cross-platform actions as needed
return true
}
}

View File

@ -0,0 +1,173 @@
// GuruMaxBannerAd.swift
// Implementation for MAX banner ads
// Corresponds to GuruMaxBannerAd.kt in Android implementation
import Foundation
import UIKit
import AppLovinSDK
class GuruMaxBannerView : GuruBannerView {
func view() -> UIView {
return bannerAdView
}
func setPlacementName(placement: String) {
bannerAdView.placement = placement
}
func onShow() {
bannerAdView.isHidden = false
bannerAdView.startAutoRefresh()
}
func onHide() {
bannerAdView.setExtraParameterForKey("allow_pause_auto_refresh_immediately", value: "true")
bannerAdView.isHidden = true
bannerAdView.stopAutoRefresh()
}
public var bannerAdView: MAAdView
public init(_ banner: MAAdView) {
self.bannerAdView = banner
}
}
public class GuruMaxBannerAd: GuruBannerAd, MAAdViewAdDelegate, MAAdRevenueDelegate {
private let viewController: UIViewController
private let adConfig: AdConfig
private var bannerAd: MAAdView? { return bannerAdCoordinator?.bannerAd.bannerAdView}
private var bannerAdCoordinator: GuruBannerCoordinator<GuruMaxBannerView>?
private var contentView: UIView?
private var currentUid2Token: String = ""
private var contentId: Int {
return self.adConfig.engineId
}
public init(viewController: UIViewController, adConfig: AdConfig) {
self.viewController = viewController
self.adConfig = adConfig
super.init(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: AdPlatform.max)
}
override public func load() -> Bool {
logInfo("request load")
let bannerAd = MAAdView(adUnitIdentifier: adConfig.adUnitId, adFormat: MAAdFormat.banner)
bannerAd.delegate = self
bannerAd.revenueDelegate = self
bannerAd.setExtraParameterForKey("allow_pause_auto_refresh_immediately", value: "true")
if let uid2Token = AdsHelper.getUid2Token() {
bannerAd.setExtraParameterForKey("uid2_token", value: uid2Token)
currentUid2Token = uid2Token
}
bannerAd.stopAutoRefresh()
if let amazonSlotId = adConfig.adAmazonSlotId, !amazonSlotId.isEmpty {
// In a real implementation, you would load Amazon ads here
logDebug("Would load Amazon banner ad with slot ID: \(amazonSlotId)")
// Simulate direct load for now
bannerAd.loadAd()
} else {
bannerAd.loadAd()
}
self.bannerAdCoordinator = GuruBannerCoordinator(viewController: viewController, bannerAd: GuruMaxBannerView(bannerAd), contentId: contentId)
return true
}
public override func show(_ request: BannerShowRequest? = nil) -> Bool {
logInfo("request show")
let result = self.bannerAdCoordinator?.show(request)
// Update UID2 token if needed
if let uid2Token = AdsHelper.getUid2Token(), !uid2Token.isEmpty, uid2Token != currentUid2Token {
bannerAd?.setExtraParameterForKey("uid2_token", value: uid2Token)
currentUid2Token = uid2Token
logInfo("BannerAd refresh uid2_token \(currentUid2Token)")
}
return result ?? false
}
public override func hide() -> Bool {
logInfo("request hide")
return bannerAdCoordinator?.hide() ?? false
}
override public func destroy() -> Bool {
logInfo("request destroy")
bannerAd?.delegate = nil
bannerAd?.revenueDelegate = nil
let _ = bannerAdCoordinator?.destroy()
bannerAdCoordinator = nil
return true
}
override public func updateOrientation(orientation: ScreenOrientation) -> Bool {
logInfo("update orientation to \(orientation)")
return bannerAdCoordinator?.updateOrientation(orientation: orientation) ?? false
}
// MARK: - MAXAdViewAdDelegate methods
public func didLoad(_ ad: MAAd) {
logInfo("loaded from \(ad.networkName)")
listener?.onAdLoaded(ad: MaxFusionAd(engineId: engineId, adType: AdType.Banner, ad: ad))
}
public func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) {
logError("load failed, \(error.code) - \(error.message)")
let loadFailedInfo = LoadFailedInfo(
engineId: engineId,
adPlatform: AdPlatform.max,
adType: AdType.Banner,
adUnitId: adUnitIdentifier,
error: MaxFusionError(error)
)
listener?.onAdLoadFailed(loadFailedInfo: loadFailedInfo)
}
public func didDisplay(_ ad: MAAd) {
listener?.onAdDisplayed(ad: MaxFusionAd(engineId: engineId, adType: AdType.Banner, ad: ad))
}
public func didHide(_ ad: MAAd) {
listener?.onAdHidden(ad: MaxFusionAd(engineId: engineId, adType: AdType.Banner, ad: ad))
}
public func didClick(_ ad: MAAd) {
listener?.onAdClicked(ad: MaxFusionAd(engineId: engineId, adType: AdType.Banner, ad: ad))
}
public func didFail(toDisplay ad: MAAd, withError error: MAError) {
logError("display failed, \(error.code) - \(error.message)")
listener?.onAdDisplayFailed(
ad: MaxFusionAd(engineId: engineId, adType: AdType.Banner, ad: ad),
error: MaxFusionError(error)
)
}
public func didExpand(_ ad: MAAd) {
// In a real implementation, you would call adBannerExpanded
}
public func didCollapse(_ ad: MAAd) {
// In a real implementation, you would call adBannerCollapsed
}
// MARK: - MAXAdRevenueDelegate methods
public func didPayRevenue(for ad: MAAd) {
logInfo("revenue paid")
listener?.onAdRevenuePaid(ad: MaxFusionAd(engineId: engineId, adType: AdType.Banner, ad: ad))
}
}

View File

@ -0,0 +1,123 @@
// GuruMaxInterstitialAd.swift
// Implementation for MAX interstitial ads
// Corresponds to GuruMaxInterstitialAd.kt in Android implementation
import Foundation
import UIKit
import AppLovinSDK
public class GuruMaxInterstitialAd: GuruInterstitialAd, MAAdDelegate, MAAdRevenueDelegate {
private let viewController: UIViewController
private let adConfig: AdConfig
private var interstitialAd: MAInterstitialAd?
private var isFirstLoad = true
public init(viewController: UIViewController, adConfig: AdConfig) {
self.viewController = viewController
self.adConfig = adConfig
super.init(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: AdPlatform.max)
}
public override func logDebug(_ message: String) {
Logger.d(tag: "MaxInterstitialAd[\(adConfig.adUnitId)]", message: message)
}
public override func logWarn(_ message: String) {
Logger.w(tag: "MaxInterstitialAd[\(adConfig.adUnitId)]", message: message)
}
public override func load() -> Bool {
Logger.i(tag: "MaxInterstitialAd[\(adConfig.adUnitId)]", message: "starting load")
destroy()
interstitialAd = MAInterstitialAd(adUnitIdentifier: adConfig.adUnitId)
interstitialAd?.delegate = self
interstitialAd?.revenueDelegate = self
// Set UID2 token if available
if let uid2Token = AdsHelper.getUid2Token() {
interstitialAd?.setExtraParameterForKey("uid2_token", value: uid2Token)
}
interstitialAd?.setExtraParameterForKey("disable_auto_retries", value: self.adConfig.requireDisableAutoRetries ? "true":"false")
if let amazonSlotId = adConfig.adAmazonSlotId, !amazonSlotId.isEmpty, isFirstLoad {
isFirstLoad = false
// In a real implementation, you would load Amazon ads here
logDebug("Would load Amazon interstitial ad with slot ID: \(amazonSlotId)")
// Simulate direct load for now
interstitialAd?.load()
} else {
logDebug("Loading interstitial ad \(adConfig.adUnitId)")
interstitialAd?.load()
}
return true
}
public override func show(_ request: InterstitialShowRequest? = nil) -> Bool {
guard let ad = interstitialAd, ad.isReady else {
logDebug("Show result: false - ad not ready")
return false
}
ad.show(forPlacement: request?.placement)
logDebug("Show result: true")
return true
}
public override func destroy() -> Bool {
interstitialAd?.delegate = nil
interstitialAd?.revenueDelegate = nil
interstitialAd = nil
return true
}
// MARK: - MAAdDelegate methods
public func didLoad(_ ad: MAAd) {
Logger.i(tag: "MaxInterstitialAd[\(adConfig.adUnitId)]", message: "loaded")
listener?.onAdLoaded(ad: MaxFusionAd(engineId: engineId, adType: AdType.Interstitial, ad: ad))
}
public func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) {
logError("load failed, \(error.code) - \(error.message)")
let loadFailedInfo = LoadFailedInfo(
engineId: engineId,
adPlatform: AdPlatform.max,
adType: AdType.Interstitial,
adUnitId: adUnitIdentifier,
error: MaxFusionError(error)
)
listener?.onAdLoadFailed(loadFailedInfo: loadFailedInfo)
}
public func didDisplay(_ ad: MAAd) {
logInfo("displayed")
listener?.onAdDisplayed(ad: MaxFusionAd(engineId: engineId, adType: AdType.Interstitial, ad: ad))
}
public func didHide(_ ad: MAAd) {
listener?.onAdHidden(ad: MaxFusionAd(engineId: engineId, adType: AdType.Interstitial, ad: ad))
}
public func didClick(_ ad: MAAd) {
listener?.onAdClicked(ad: MaxFusionAd(engineId: engineId, adType: AdType.Interstitial, ad: ad))
}
public func didFail(toDisplay ad: MAAd, withError error: MAError) {
logError("displayed failed, \(error.code) - \(error.message)")
listener?.onAdDisplayFailed(
ad: MaxFusionAd(engineId: engineId, adType: AdType.Interstitial, ad: ad),
error: MaxFusionError(error)
)
}
// MARK: - MAAdRevenueDelegate methods
public func didPayRevenue(for ad: MAAd) {
logInfo("revenue paid")
listener?.onAdRevenuePaid(ad: MaxFusionAd(engineId: engineId, adType: AdType.Interstitial, ad: ad))
}
}

View File

@ -0,0 +1,239 @@
// GuruMaxMRecAd.swift
// Implementation for MAX MREC ads
// Corresponds to GuruMaxMRecAd.kt in Android implementation
import Foundation
import UIKit
import AppLovinSDK
public class GuruMaxMRecAd: GuruMRecAd, MAAdViewAdDelegate, MAAdRevenueDelegate {
private let viewController: UIViewController
private let adConfig: AdConfig
private var mrecAd: MAAdView?
private var contentView: UIView?
private var currentUid2Token: String = ""
private var horizontalOffset: CGFloat = 0.0
private var verticalOffset: CGFloat = 0.0
private var orientation: ScreenOrientation = .portrait
private var contentId: Int {
return self.adConfig.engineId
}
public init(viewController: UIViewController, adConfig: AdConfig) {
self.viewController = viewController
self.adConfig = adConfig
super.init(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: AdPlatform.max)
}
public override func logDebug(_ message: String) {
Logger.d(tag: "MaxMRecAd[\(adConfig.adUnitId)]", message: message)
}
public override func logInfo(_ message: String) {
Logger.i(tag: "MaxMRecAd[\(adConfig.adUnitId)]", message: message)
}
public override func logWarn(_ message: String) {
Logger.w(tag: "MaxMRecAd[\(adConfig.adUnitId)]", message: message)
}
public override func load() -> Bool {
mrecAd = MAAdView(adUnitIdentifier: adConfig.adUnitId, adFormat: MAAdFormat.mrec)
guard let mrecAd = mrecAd else { return false }
mrecAd.delegate = self
mrecAd.revenueDelegate = self
mrecAd.setExtraParameterForKey("allow_pause_auto_refresh_immediately", value: "true")
if let uid2Token = AdsHelper.getUid2Token() {
mrecAd.setExtraParameterForKey("uid2_token", value: uid2Token)
currentUid2Token = uid2Token
}
mrecAd.stopAutoRefresh()
if let amazonSlotId = adConfig.adAmazonSlotId, !amazonSlotId.isEmpty {
// In a real implementation, you would load Amazon ads here
logDebug("Would load Amazon MREC ad with slot ID: \(amazonSlotId)")
// Simulate direct load for now
mrecAd.loadAd()
} else {
mrecAd.loadAd()
}
return true
}
public override func show(request: MRecShowRequest? = nil) -> Bool {
let placement = request?.placement
let newHorizontalOffset = request?.horizontalOffset
let newVerticalOffset = request?.verticalOffset
var shouldReattach = false
// Check if position or offset has changed
if let offset = newVerticalOffset, offset != Float(verticalOffset) {
verticalOffset = CGFloat(offset)
shouldReattach = true
}
if let offset = newHorizontalOffset, offset != Float(horizontalOffset) {
horizontalOffset = CGFloat(offset)
shouldReattach = true
}
// If we already have a view and parameters changed, remove it
if let contentView = contentView, shouldReattach {
logInfo("Parameters changed! Re-attaching view!")
contentView.removeFromSuperview()
self.contentView = nil
}
// Create new content view if needed
if contentView == nil {
let container = UIView()
container.tag = contentId
viewController.view.addSubview(container)
container.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
container.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor),
container.trailingAnchor.constraint(equalTo: viewController.view.trailingAnchor),
container.topAnchor.constraint(equalTo: viewController.view.topAnchor),
container.bottomAnchor.constraint(equalTo: viewController.view.bottomAnchor)
])
contentView = container
guard let mrecAd = mrecAd else { return false }
// Standard MREC size is 300x250
let mrecWidth: CGFloat = 300
let mrecHeight: CGFloat = 250
mrecAd.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(mrecAd)
NSLayoutConstraint.activate([
mrecAd.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: horizontalOffset),
mrecAd.topAnchor.constraint(equalTo: container.topAnchor, constant: verticalOffset),
mrecAd.widthAnchor.constraint(equalToConstant: mrecWidth),
mrecAd.heightAnchor.constraint(equalToConstant: mrecHeight)
])
mrecAd.isHidden = false
mrecAd.startAutoRefresh()
} else {
mrecAd?.startAutoRefresh()
mrecAd?.isHidden = false
}
// Update UID2 token if needed
if let uid2Token = AdsHelper.getUid2Token(), !uid2Token.isEmpty, uid2Token != currentUid2Token {
mrecAd?.setExtraParameterForKey("uid2_token", value: uid2Token)
currentUid2Token = uid2Token
logInfo("MRec refresh uid2_token \(currentUid2Token)")
}
return true
}
public override func hide() -> Bool {
guard let _ = viewController.view.viewWithTag(contentId) else {
return true
}
if let mrecAd = mrecAd, !mrecAd.isHidden {
mrecAd.setExtraParameterForKey("allow_pause_auto_refresh_immediately", value: "true")
mrecAd.stopAutoRefresh()
mrecAd.isHidden = true
}
return true
}
override public func destroy() -> Bool{
mrecAd?.delegate = nil
mrecAd?.revenueDelegate = nil
mrecAd?.removeFromSuperview()
mrecAd = nil
contentView?.removeFromSuperview()
contentView = nil
return true
}
override public func updateOrientation(orientation: ScreenOrientation) -> Bool {
if let _ = viewController.view.viewWithTag(contentId), self.orientation != orientation {
let isVisible = !(mrecAd?.isHidden ?? true)
mrecAd?.setExtraParameterForKey("allow_pause_auto_refresh_immediately", value: "true")
mrecAd?.stopAutoRefresh()
mrecAd?.isHidden = true
contentView?.removeFromSuperview()
contentView = nil
if isVisible {
return show()
}
}
self.orientation = orientation
return true
}
// MARK: - MAAdViewAdDelegate methods
public func didLoad(_ ad: MAAd) {
listener?.onAdLoaded(ad: MaxFusionAd(engineId: engineId, adType: AdType.MRec, ad: ad))
}
public func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) {
let loadFailedInfo = LoadFailedInfo(
engineId: engineId,
adPlatform: AdPlatform.max,
adType: AdType.MRec,
adUnitId: adUnitIdentifier,
error: MaxFusionError(error)
)
listener?.onAdLoadFailed(loadFailedInfo: loadFailedInfo)
}
public func didDisplay(_ ad: MAAd) {
listener?.onAdDisplayed(ad: MaxFusionAd(engineId: engineId, adType: AdType.MRec, ad: ad))
}
public func didHide(_ ad: MAAd) {
listener?.onAdHidden(ad: MaxFusionAd(engineId: engineId, adType: AdType.MRec, ad: ad))
}
public func didClick(_ ad: MAAd) {
listener?.onAdClicked(ad: MaxFusionAd(engineId: engineId, adType: AdType.MRec, ad: ad))
}
public func didFail(toDisplay ad: MAAd, withError error: MAError) {
listener?.onAdDisplayFailed(
ad: MaxFusionAd(engineId: engineId, adType: AdType.MRec, ad: ad),
error: MaxFusionError(error)
)
}
public func didExpand(_ ad: MAAd) {
// In a real implementation, you would call adBannerExpanded
}
public func didCollapse(_ ad: MAAd) {
// In a real implementation, you would call adBannerCollapsed
}
// MARK: - MAAdRevenueDelegate methods
public func didPayRevenue(for ad: MAAd) {
listener?.onAdRevenuePaid(ad: MaxFusionAd(engineId: engineId, adType: AdType.MRec, ad: ad))
}
}

View File

@ -0,0 +1,130 @@
// GuruMaxRewardedAd.swift
// Implementation for MAX rewarded ads
// Corresponds to GuruMaxRewardedAd.kt in Android implementation
import Foundation
import UIKit
import AppLovinSDK
public class GuruMaxRewardedAd: GuruRewardedAd, MARewardedAdDelegate, MAAdRevenueDelegate {
private let viewController: UIViewController
private let adConfig: AdConfig
private var rewardedAd: MARewardedAd?
private var isFirstLoad = true
public init(viewController: UIViewController, adConfig: AdConfig) {
self.viewController = viewController
self.adConfig = adConfig
super.init(engineId: adConfig.engineId, adUnitId: adConfig.adUnitId, adPlatform: AdPlatform.max)
}
public override func load() -> Bool {
logInfo("request load")
destroy()
rewardedAd = MARewardedAd.shared(withAdUnitIdentifier: adConfig.adUnitId)
rewardedAd?.delegate = self
rewardedAd?.revenueDelegate = self
// Set UID2 token if available
if let uid2Token = AdsHelper.getUid2Token() {
rewardedAd?.setExtraParameterForKey("uid2_token", value: uid2Token)
}
rewardedAd?.setExtraParameterForKey("disable_auto_retries", value: self.adConfig.requireDisableAutoRetries ? "true":"false")
if let amazonSlotId = adConfig.adAmazonSlotId, !amazonSlotId.isEmpty, isFirstLoad {
isFirstLoad = false
// In a real implementation, you would load Amazon ads here
logWarn("Would load Amazon rewarded ad with slot ID: \(amazonSlotId)")
// Simulate direct load for now
rewardedAd?.load()
} else {
rewardedAd?.load()
}
return true
}
public override func show(_ request: RewardedShowRequest? = nil) -> Bool {
logInfo("request show")
guard let ad = rewardedAd, ad.isReady else {
logDebug("Show result: false - ad not ready")
return false
}
ad.show(forPlacement: request?.placement)
logDebug("Show result: true")
return true
}
public override func destroy() -> Bool {
rewardedAd?.delegate = nil
rewardedAd?.revenueDelegate = nil
rewardedAd = nil
return true
}
// MARK: - MARewardedAdDelegate methods
public func didLoad(_ ad: MAAd) {
logInfo("loaded")
listener?.onAdLoaded(ad: MaxFusionAd(engineId: engineId, adType: AdType.Rewarded, ad: ad))
}
public func didFail(toDisplay ad: MAAd, withError error: MAError) {
logError("displayed failed, \(error.code) - \(error.message)")
listener?.onAdDisplayFailed(ad: MaxFusionAd(engineId: engineId, adType: AdType.Rewarded, ad: ad), error: MaxFusionError(error))
}
public func didFailToLoadAd(forAdUnitIdentifier adUnitIdentifier: String, withError error: MAError) {
logError("load failed, \(error.code) - \(error.message)")
let loadFailedInfo = LoadFailedInfo(
engineId: engineId,
adPlatform: AdPlatform.max,
adType: AdType.Rewarded,
adUnitId: adUnitIdentifier,
error: MaxFusionError(error)
)
listener?.onAdLoadFailed(loadFailedInfo: loadFailedInfo)
}
public func didDisplay(_ ad: MAAd) {
logInfo("displayed")
listener?.onAdDisplayed(ad: MaxFusionAd(engineId: engineId, adType: AdType.Rewarded, ad: ad))
}
public func didHide(_ ad: MAAd) {
listener?.onAdHidden(ad: MaxFusionAd(engineId: engineId, adType: AdType.Rewarded, ad: ad))
}
public func didClick(_ ad: MAAd) {
listener?.onAdClicked(ad: MaxFusionAd(engineId: engineId, adType: AdType.Rewarded, ad: ad))
}
public func didStartRewardedVideo(for ad: MAAd) {
// Additional callback if needed
}
public func didCompleteRewardedVideo(for ad: MAAd) {
// Additional callback if needed
}
public func didRewardUser(for ad: MAAd, with reward: MAReward) {
logInfo("reward user")
listener?.onUserRewarded(
ad: MaxFusionAd(engineId: engineId, adType: AdType.Rewarded, ad: ad),
reward: MaxFusionReward(reward)
)
}
// MARK: - MAAdRevenueDelegate methods
public func didPayRevenue(for ad: MAAd) {
logInfo("revenue paid")
listener?.onAdRevenuePaid(ad: MaxFusionAd(engineId: engineId, adType: AdType.Rewarded, ad: ad))
}
}

View File

@ -0,0 +1,52 @@
// MaxFusionAd.swift
// Adapts MaxAd to FusionAd interface
// Corresponds to MaxFusionAd.kt in Android implementation
import Foundation
import AppLovinSDK
public class MaxFusionAd: FusionAd {
private let originAd: MAAd
public init(engineId: Int, adType: AdType, ad: MAAd) {
originAd = ad
super.init(engineId: engineId, adType: adType)
}
override public var adPlatform: AdPlatform {
return AdPlatform.max
}
override public var adUnitId: String? {
return originAd.adUnitIdentifier
}
override public var revenue: Double {
return originAd.revenue
}
override public var waterfallName: String? {
return originAd.waterfall.name
}
override public var placement: String? {
return originAd.placement
}
override public var networkName: String? {
return originAd.networkName
}
override public var networkPlacement: String? {
return originAd.networkPlacement
}
override public var creativeId: String? {
return originAd.creativeIdentifier
}
override public var adReviewCreativeId: String? {
return originAd.adReviewCreativeIdentifier
}
}

View File

@ -0,0 +1,34 @@
// MaxFusionError.swift
// Adapts MAX errors to FusionError interface
// Corresponds to MaxFusionError.kt in Android implementation
import Foundation
import AppLovinSDK
public class MaxFusionError: FusionError {
private let maxError: MAError
public init(_ maxError: MAError) {
self.maxError = maxError
}
public var cause: Error? {
return nil
}
public var info: String? {
return nil
}
public var errorCode: Int {
return maxError.code.rawValue
}
public var message: String {
return maxError.message
}
public var waterfallName: String? {
return maxError.waterfall?.name ?? "unknown"
}
}

View File

@ -0,0 +1,26 @@
// MaxFusionReward.swift
// Adapts MAX reward to FusionReward interface
// Corresponds to MaxFusionReward.kt in Android implementation
import Foundation
import AppLovinSDK
public class MaxFusionReward: FusionReward {
public var rewardAmount: Int {
return maxReward.amount
}
private let maxReward: MAReward
public init(_ reward: MAReward) {
self.maxReward = reward
}
public var amount: Int {
return maxReward.amount
}
public var type: String {
return maxReward.label
}
}

View File

@ -0,0 +1,45 @@
// AdsHelper.swift
// Helper utilities for ad management
// Corresponds to AdsHelper.kt in Android implementation
import Foundation
import AppLovinSDK
public class AdsHelper {
private static var uid2Token: String?
public static func setUid2Token(_ token: String?) -> Bool {
guard let token = token, !token.isEmpty else {
return false
}
uid2Token = token
return true
}
public static func getUid2Token() -> String? {
return uid2Token
}
public static func toAdParams(ad: FusionAd, pairs: [String: Any] = [:]) -> [String: Any] {
var params: [String: Any] = [
"engine_id": ad.engineId,
"ad_platform": ad.adPlatform.name,
"ad_revenue": ad.revenue,
"ad_format": ad.format ?? "unknown",
"ad_source": ad.networkName ?? "unknown",
"ad_unit_name": ad.adUnitId ?? "unknown",
"ad_creative_id": ad.creativeId ?? "unknown",
"ad_placement": ad.placement ?? "unknown",
"ad_network_name": ad.networkName ?? "unknown",
"ad_network_placement": ad.networkPlacement ?? "unknown",
"review_creative_id": ad.adReviewCreativeId ?? "unknown"
]
// Add additional pairs
for (key, value) in pairs {
params[key] = value
}
return params
}
}

View File

@ -0,0 +1,49 @@
//
// MabAnalyticsEvents.swift
// Pods
//
// Created by 250102 on 2025/5/10.
//
// AnalyticsEvents
internal class AnalyticsEvents {
//
static let shared = AnalyticsEvents()
//
private init() {}
// 使NSHashTable
private var handlers = NSHashTable<AnyObject>.weakObjects()
func registerHandler(handler: AnalyticsEventHandler) {
handlers.add(handler as AnyObject)
}
func unregisterHandler(handler: AnalyticsEventHandler) {
for existingHandler in handlers.allObjects {
if let existing = existingHandler as? AnalyticsEventHandler,
existing === handler as AnyObject {
handlers.remove(existingHandler)
break
}
}
}
func onEvent(name: String, param: [String: Any]? = nil) {
for case let handler as AnalyticsEventHandler in handlers.allObjects {
handler.onEvent(name: name, param: param)
}
}
func onEvent(name: String, pairs: (String, any Any)...) {
let paramMap = Dictionary(uniqueKeysWithValues: pairs)
onEvent(name: name, param: paramMap)
}
}
// AnalyticsEventHandler - Hashable
public protocol AnalyticsEventHandler: AnyObject {
func onEvent(name: String, param: [String: Any]?)
}

View File

@ -0,0 +1,169 @@
//
// GuruBannerCoordinator.swift
// Pods
//
// Created by 250102 on 2025/5/9.
//
protocol GuruBannerView {
func view() -> UIView
func setPlacementName(placement: String)
func onShow();
func onHide();
}
class GuruBannerCoordinator<T: GuruBannerView> {
private let viewController: UIViewController
public private(set) var bannerAd: T
private var bannerPosition: BannerPosition { return lastShowRequest?.position ?? BannerPosition.bottom }
private var bannerOffset: CGFloat { return CGFloat(lastShowRequest?.offset ?? 0.0) }
private var lastShowRequest: BannerShowRequest? = nil
public private(set) var orientation: ScreenOrientation = .portrait
public init(viewController: UIViewController, bannerAd: T, contentId: Int) {
self.viewController = viewController
self.bannerAd = bannerAd
}
public func logInfo(_ message: String) {
Logger.i(tag: "GuruBannerCoordinator", message: "\(message)")
}
public func logWarn(_ message: String) {
Logger.w(tag: "GuruBannerCoordinator", message: "\(message)")
}
public func show(_ request: BannerShowRequest? = nil) -> Bool {
var shouldReattach = false
// Check if position or offset has changed
if let newOffset = request?.offset, newOffset != Float(bannerOffset) {
shouldReattach = true
}
if let newPosition = request?.position, newPosition != bannerPosition {
shouldReattach = true
}
lastShowRequest = request
guard let container = viewController.viewIfLoaded else {
logWarn("viewController.view is nil, returns false")
return false
}
let bannerAdView = bannerAd.view()
var attached = container.subviews.contains(bannerAdView)
// If we already have a view and parameters changed, remove it
if shouldReattach || !attached {
logInfo("Parameters changed! Re-attaching view!")
if(attached) {
bannerAdView.removeFromSuperview()
}
let isTablet = UIDevice.current.userInterfaceIdiom == .pad
let bannerHeight: CGFloat = isTablet ? 90 : 50
let screenWidth = min(viewController.view.bounds.width, viewController.view.bounds.height)
container.addSubview(bannerAdView)
bannerAdView.translatesAutoresizingMaskIntoConstraints = false
logInfo("BannerAd show!!! bannerSize: \(screenWidth)x\(bannerHeight)")
if let placement = lastShowRequest?.placement {
bannerAd.setPlacementName(placement: placement)
}
// Configure banner position and rotation
var transform = CGAffineTransform.identity
switch bannerPosition {
case .start:
transform = CGAffineTransform(rotationAngle: .pi / 2)
NSLayoutConstraint.activate([
bannerAdView.leadingAnchor.constraint(equalTo: container.safeAreaLayoutGuide.leadingAnchor, constant: bannerOffset),
bannerAdView.centerYAnchor.constraint(equalTo: container.centerYAnchor),
bannerAdView.widthAnchor.constraint(equalToConstant: screenWidth),
bannerAdView.heightAnchor.constraint(equalToConstant: bannerHeight)
])
case .end:
transform = CGAffineTransform(rotationAngle: -(.pi / 2))
NSLayoutConstraint.activate([
bannerAdView.trailingAnchor.constraint(equalTo: container.safeAreaLayoutGuide.trailingAnchor, constant: -bannerOffset),
bannerAdView.centerYAnchor.constraint(equalTo: container.centerYAnchor),
bannerAdView.widthAnchor.constraint(equalToConstant: screenWidth),
bannerAdView.heightAnchor.constraint(equalToConstant: bannerHeight)
])
case .top:
transform = CGAffineTransform(rotationAngle: 0)
NSLayoutConstraint.activate([
bannerAdView.centerXAnchor.constraint(equalTo: container.centerXAnchor),
bannerAdView.topAnchor.constraint(equalTo: container.safeAreaLayoutGuide.topAnchor, constant: bannerOffset),
bannerAdView.heightAnchor.constraint(equalToConstant: bannerHeight),
bannerAdView.widthAnchor.constraint(equalToConstant: screenWidth),
])
default:
transform = CGAffineTransform(rotationAngle: 0)
NSLayoutConstraint.activate([
bannerAdView.centerXAnchor.constraint(equalTo: container.centerXAnchor),
bannerAdView.bottomAnchor.constraint(equalTo: container.safeAreaLayoutGuide.bottomAnchor, constant: -bannerOffset),
bannerAdView.heightAnchor.constraint(equalToConstant: bannerHeight),
bannerAdView.widthAnchor.constraint(equalToConstant: screenWidth)
])
}
bannerAdView.transform = transform
bannerAdView.isHidden = false
bannerAd.onShow()
} else {
bannerAd.view().isHidden = false
bannerAd.onShow()
}
if let placement = request?.placement {
bannerAd.setPlacementName(placement: placement)
}
return true
}
public func hide() -> Bool {
guard let _ = viewController.view.superview?.contains(bannerAd.view()) else {
return true
}
bannerAd.view().isHidden = true
bannerAd.onHide()
return true
}
public func destroy() -> Bool {
bannerAd.view().removeFromSuperview()
return true
}
public func updateOrientation(orientation: ScreenOrientation) -> Bool {
if let _ = viewController.view.superview?.contains(bannerAd.view()), self.orientation != orientation {
let isVisible = !bannerAd.view().isHidden
bannerAd.onHide()
bannerAd.view().isHidden = true
if isVisible {
return show()
}
}
self.orientation = orientation
return true
}
}

View File

@ -0,0 +1,143 @@
//
// GuruBannerCoordinator.swift
// Pods
//
// Created by 250102 on 2025/5/9.
//
protocol GuruMRecView {
func view() -> UIView
func onShow();
func onHide();
}
class GuruMRecCoordinator {
private let viewController: UIViewController
private var mrecAd: GuruMRecView
private var contentView: UIView?
private var currentUid2Token: String = ""
private var horizontalOffset: CGFloat = 0.0
private var verticalOffset: CGFloat = 0.0
private var orientation: ScreenOrientation = .portrait
private let contentId: Int
public init(viewController: UIViewController, mrecAd: GuruMRecView, contentId: Int) {
self.viewController = viewController
self.mrecAd = mrecAd
self.contentId = contentId
}
public func logInfo(_ message: String) {
Logger.i(tag: "GuruBannerCoordinator", message: "\(message)")
}
public func show(request: MRecShowRequest? = nil) -> Bool {
let placement = request?.placement
let newHorizontalOffset = request?.horizontalOffset
let newVerticalOffset = request?.verticalOffset
var shouldReattach = false
// Check if position or offset has changed
if let offset = newVerticalOffset, offset != Float(verticalOffset) {
verticalOffset = CGFloat(offset)
shouldReattach = true
}
if let offset = newHorizontalOffset, offset != Float(horizontalOffset) {
horizontalOffset = CGFloat(offset)
shouldReattach = true
}
// If we already have a view and parameters changed, remove it
if let contentView = contentView, shouldReattach {
logInfo("Parameters changed! Re-attaching view!")
contentView.removeFromSuperview()
self.contentView = nil
}
let mrecAdView = mrecAd.view()
// Create new content view if needed
if contentView == nil {
let container = UIView()
container.tag = contentId
viewController.view.addSubview(container)
container.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
container.leadingAnchor.constraint(equalTo: viewController.view.leadingAnchor),
container.trailingAnchor.constraint(equalTo: viewController.view.trailingAnchor),
container.topAnchor.constraint(equalTo: viewController.view.topAnchor),
container.bottomAnchor.constraint(equalTo: viewController.view.bottomAnchor)
])
contentView = container
// Standard MREC size is 300x250
let mrecWidth: CGFloat = 300
let mrecHeight: CGFloat = 250
mrecAdView.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(mrecAdView)
NSLayoutConstraint.activate([
mrecAdView.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: horizontalOffset),
mrecAdView.topAnchor.constraint(equalTo: container.topAnchor, constant: verticalOffset),
mrecAdView.widthAnchor.constraint(equalToConstant: mrecWidth),
mrecAdView.heightAnchor.constraint(equalToConstant: mrecHeight)
])
mrecAdView.isHidden = false
mrecAd.onShow()
} else {
mrecAd.onShow()
mrecAd.view().isHidden = false
}
return true
}
public func hide() -> Bool {
guard let _ = viewController.view.viewWithTag(contentId) else {
return true
}
if !mrecAd.view().isHidden {
mrecAd.onHide()
mrecAd.view().isHidden = true
}
return true
}
public func destroy() -> Bool{
mrecAd.view().removeFromSuperview()
contentView?.removeFromSuperview()
contentView = nil
return true
}
public func updateOrientation(orientation: ScreenOrientation) -> Bool {
if let _ = viewController.view.viewWithTag(contentId), self.orientation != orientation {
let isVisible = !mrecAd.view().isHidden
mrecAd.onHide()
mrecAd.view().isHidden = true
contentView?.removeFromSuperview()
contentView = nil
if isVisible {
return show()
}
}
self.orientation = orientation
return true
}
}

View File

@ -0,0 +1,35 @@
// MathUtils.swift
// Mathematical utility functions
// Corresponds to MathUtils.kt in Android implementation
import Foundation
public class MathUtils {
private static let _fibonacciArray: [Int] = [
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946,
17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578,
5702887, 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296,
433494437, 701408733, 1134903170, 1836311903, 2971215073
]
public static func fibonacci(_ n: Int, offset: Int = 0) -> Int64 {
let len = _fibonacciArray.count
let idx = n + offset
if idx < len {
return Int64(_fibonacciArray[idx])
}
var n1 = Int64(_fibonacciArray[len - 2])
var n2 = Int64(_fibonacciArray[len - 1])
var sum: Int64 = 0
for _ in len..<idx {
sum = n1 + n2
n1 = n2
n2 = sum
}
return sum
}
}

View File

@ -0,0 +1,34 @@
//
// SwiftFusionError.swift
// Pods
//
// Created by 250102 on 2025/5/9.
//
public class SwiftFusionError: FusionError {
private let causeValue: Error
public init(_ cause: Error) {
self.causeValue = cause
}
public var cause: Error? {
return causeValue
}
public var info: String? {
return nil
}
public var errorCode: Int {
return causeValue.code
}
public var message: String {
return causeValue.localizedDescription
}
public var waterfallName: String? {
return nil
}
}

View File

@ -0,0 +1,304 @@
//
// FileLogHandler.swift
// Pods
//
// Created by 250102 on 2025/5/21.
//
import Foundation
import os.log
/// rotatablerobustefficient
public class FileLogHandler {
// MARK: -
public enum LogLevel: Int, Comparable {
case verbose = 0
case debug = 1
case info = 2
case warning = 3
case error = 4
case fatal = 5
public static func < (lhs: LogLevel, rhs: LogLevel) -> Bool {
return lhs.rawValue < rhs.rawValue
}
public var prefix: String {
switch self {
case .verbose: return "V"
case .debug: return "D"
case .info: return "I"
case .warning: return "W"
case .error: return "E"
case .fatal: return "WTF"
}
}
}
// MARK: -
public struct RotationConfig {
/// 10MB
public var maxFileSize: UInt64
/// 5
public var maxBackupCount: Int
public init(maxFileSize: UInt64 = 10_485_760, // 10MB
maxBackupCount: Int = 5) {
self.maxFileSize = maxFileSize
self.maxBackupCount = maxBackupCount
}
}
// MARK: -
private let fileURL: URL
private let rotationConfig: RotationConfig
private let dateFormatter: DateFormatter
private let minimumLogLevel: LogLevel
private let queue: DispatchQueue
private let fileManager: FileManager
private var fileHandle: FileHandle?
// MARK: -
/// FileLogHandler
/// - :
/// - directory:
/// - filename:
/// - rotationConfig: rotation
/// - minimumLogLevel:
public init(directory: String? = nil,
filename: String = "app.log",
rotationConfig: RotationConfig = RotationConfig(),
minimumLogLevel: LogLevel = .debug) {
self.rotationConfig = rotationConfig
self.minimumLogLevel = minimumLogLevel
//
self.dateFormatter = DateFormatter()
self.dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
// 线
self.queue = DispatchQueue(label: "com.fileloghandler.queue", qos: .utility)
// 使
self.fileManager = FileManager.default
//
let baseDirectory: URL
if let directory = directory {
baseDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("Logs", isDirectory: true).appendingPathComponent(directory, isDirectory: true)
} else {
//
#if os(macOS)
let cacheDir = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
#else
let cacheDir = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!
#endif
baseDirectory = cacheDir.appendingPathComponent("Logs", isDirectory: true)
}
//
let logsDirectory = baseDirectory
try? fileManager.createDirectory(at: logsDirectory, withIntermediateDirectories: true, attributes: nil)
// URL
self.fileURL = logsDirectory.appendingPathComponent(filename)
//
self.openLogFile()
}
deinit {
try? fileHandle?.close()
}
// MARK: -
/// 使
/// - :
/// - message:
/// - level:
/// - file:
/// - function:
/// - line:
public func log(_ tag: String, _ message: String,
level: LogLevel = .info) {
guard level >= minimumLogLevel else { return }
queue.async { [weak self] in
guard let self = self else { return }
//
let timestamp = self.dateFormatter.string(from: Date())
let pid = ProcessInfo.processInfo.processIdentifier
let logMessage = "\(timestamp) \(pid) \(level.prefix)/\(tag): \(message)\n"
//
self.writeToFile(logMessage)
// rotate
self.checkSizeRotation()
}
}
/// rotate
public func rotateLogFile() {
queue.async { [weak self] in
self?.performRotation()
}
}
// MARK: -
private func openLogFile() {
do {
//
if !fileManager.fileExists(atPath: fileURL.path) {
try "".write(to: fileURL, atomically: true, encoding: .utf8)
}
//
fileHandle = try FileHandle(forWritingTo: fileURL)
//
fileHandle?.seekToEndOfFile()
} catch {
print("打开日志文件时出错: \(error)")
}
}
private func writeToFile(_ message: String) {
guard let data = message.data(using: .utf8) else { return }
do {
if fileHandle == nil {
// fileHandlenil
openLogFile()
}
if #available(iOS 13.4, macOS 10.15.4, watchOS 6.4, tvOS 13.4, *) {
try fileHandle?.write(contentsOf: data)
} else {
fileHandle?.write(data)
}
} catch {
print("写入日志文件时出错: \(error)")
//
try? fileHandle?.close()
fileHandle = nil
}
}
private func checkSizeRotation() {
do {
let attributes = try fileManager.attributesOfItem(atPath: fileURL.path)
let fileSize = attributes[.size] as? UInt64 ?? 0
if fileSize >= rotationConfig.maxFileSize {
performRotation()
}
} catch {
print("检查文件大小时出错: \(error)")
}
}
private func performRotation() {
do {
//
try fileHandle?.close()
fileHandle = nil
//
let directory = fileURL.deletingLastPathComponent()
let filename = fileURL.lastPathComponent
//
let baseFilename = filename
let pattern = "^\(baseFilename)\\.(\\d+)$"
let regex = try NSRegularExpression(pattern: pattern, options: [])
let logFiles = try fileManager.contentsOfDirectory(atPath: directory.path)
.filter { filename in
let range = NSRange(location: 0, length: filename.utf16.count)
return regex.firstMatch(in: filename, options: [], range: range) != nil
}
.sorted { file1, file2 in
//
let range1 = NSRange(location: 0, length: file1.utf16.count)
let range2 = NSRange(location: 0, length: file2.utf16.count)
guard let match1 = regex.firstMatch(in: file1, options: [], range: range1),
let match2 = regex.firstMatch(in: file2, options: [], range: range2) else {
return false
}
let numberRange1 = match1.range(at: 1)
let numberRange2 = match2.range(at: 1)
let number1 = strToInt(file1.utf16.dropFirst(baseFilename.utf16.count + 1).prefix(numberRange1.length)) ?? 0
let number2 = strToInt(file2.utf16.dropFirst(baseFilename.utf16.count + 1).prefix(numberRange2.length)) ?? 0
return number1 < number2
}
//
if logFiles.count >= rotationConfig.maxBackupCount {
for i in (rotationConfig.maxBackupCount - 1)..<logFiles.count {
try fileManager.removeItem(at: directory.appendingPathComponent(logFiles[i]))
}
}
//
for i in (0..<logFiles.count).reversed() {
let file = logFiles[i]
let range = NSRange(location: 0, length: file.utf16.count)
guard let match = regex.firstMatch(in: file, options: [], range: range) else {
continue
}
let numberRange = match.range(at: 1)
guard let number = strToInt(file.utf16.dropFirst(baseFilename.utf16.count + 1).prefix(numberRange.length)) else {
continue
}
if number < rotationConfig.maxBackupCount - 1 {
let oldPath = directory.appendingPathComponent(file)
let newPath = directory.appendingPathComponent("\(baseFilename).\(number + 1)")
try fileManager.moveItem(at: oldPath, to: newPath)
} else {
try fileManager.removeItem(at: directory.appendingPathComponent(file))
}
}
//
let newBackupPath = directory.appendingPathComponent("\(baseFilename).1")
try fileManager.moveItem(at: fileURL, to: newBackupPath)
//
openLogFile()
// rotation
let timestamp = dateFormatter.string(from: Date())
let rotationMessage = "[\(timestamp)] [I] [FileLogHandler] Rotated\n"
writeToFile(rotationMessage)
} catch {
print("rotate日志文件时出错: \(error)")
// 使rotate
openLogFile()
}
}
private func strToInt(_ number: Substring.UTF16View) -> Int? {
guard let numStr = String(number) else {
return nil
}
return Int(numStr)
}
}

View File

@ -0,0 +1,37 @@
// Formatter.swift
// Log formatting utilities
// Corresponds to Formatter.kt in Android implementation
import Foundation
public class MainFormatter {
private let dateFormatter: DateFormatter
public init() {
dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyyMMdd'T'HHmmss"
dateFormatter.locale = Locale.current
}
public func format(level: Logger.LogLevel, tag: String, message: String) -> String {
let formatTime = dateFormatter.string(from: Date())
let levelString: String
switch level {
case .verbose:
levelString = "V"
case .debug:
levelString = "D"
case .info:
levelString = "I"
case .warn:
levelString = "W"
case .error:
levelString = "E"
case .none:
levelString = "N"
}
return "\(formatTime) \(levelString)/\(tag): \(message)"
}
}

View File

@ -0,0 +1,69 @@
// Logger.swift
// Utility class for logging
// Provides similar functionality to Android's Logger
import Foundation
public class Logger {
public enum LogLevel: Int {
case verbose = 0
case debug = 1
case info = 2
case warn = 3
case error = 4
case none = 5
}
private static let fileLogger = FileLogHandler(
directory: "guru_app",
filename: "fusion-ads.log",
rotationConfig: FileLogHandler.RotationConfig(maxFileSize: 5 * 1024 * 1024, maxBackupCount: 2),
minimumLogLevel: .verbose
)
private static var logLevel: LogLevel = .info
public static func setLogLevel(_ level: LogLevel) {
logLevel = level
}
// Verbose log
public static func v(tag: String, message: String) {
if logLevel.rawValue <= LogLevel.verbose.rawValue {
print("V/\(tag): \(message)")
fileLogger.log(tag, message, level: FileLogHandler.LogLevel.verbose)
}
}
// Debug log
public static func d(tag: String, message: String) {
if logLevel.rawValue <= LogLevel.debug.rawValue {
print("D/\(tag): \(message)")
fileLogger.log(tag, message, level: FileLogHandler.LogLevel.debug)
}
}
// Info log
public static func i(tag: String, message: String) {
if logLevel.rawValue <= LogLevel.info.rawValue {
print("I/\(tag): \(message)")
fileLogger.log(tag, message, level: FileLogHandler.LogLevel.info)
}
}
// Warning log
public static func w(tag: String, message: String) {
if logLevel.rawValue <= LogLevel.warn.rawValue {
print("W/\(tag): \(message)")
fileLogger.log(tag, message, level: FileLogHandler.LogLevel.warning)
}
}
// Error log
public static func e(tag: String, message: String) {
if logLevel.rawValue <= LogLevel.error.rawValue {
print("E/\(tag): \(message)")
fileLogger.log(tag, message, level: FileLogHandler.LogLevel.error)
}
}
}

View File

@ -0,0 +1,57 @@
// SystemProperties.swift
// System property utilities
// Corresponds to SystemProperties.kt in Android implementation
import Foundation
import UIKit
public class SystemProperties {
private static let TAG = "SystemProperties"
// Read system property from UserDefaults
public static func read(propName: String) -> String {
let value = UserDefaults.standard.string(forKey: propName) ?? ""
Logger.i(tag: TAG, message: "read System Property: \(propName)=\(value)")
return value
}
// Write system property to UserDefaults
public static func write(propName: String, value: String) {
UserDefaults.standard.set(value, forKey: propName)
UserDefaults.standard.synchronize()
Logger.i(tag: TAG, message: "write System Property: \(propName)=\(value)")
}
// Get device information
public static func getDeviceInfo() -> [String: String] {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
return [
"model": identifier,
"os_version": UIDevice.current.systemVersion,
"device_name": UIDevice.current.name,
"device_model": UIDevice.current.model,
"system_name": UIDevice.current.systemName
]
}
// Get app information
public static func getAppInfo() -> [String: String] {
let bundle = Bundle.main
let appVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown"
let buildNumber = bundle.infoDictionary?["CFBundleVersion"] as? String ?? "Unknown"
let bundleId = bundle.bundleIdentifier ?? "Unknown"
return [
"app_version": appVersion,
"build_number": buildNumber,
"bundle_id": bundleId
]
}
}

View File

@ -0,0 +1,28 @@
// IState.swift
// The interface for implementing states in a StateMachine
// Ported from Android implementation
import Foundation
/// Protocol defining the interface for implementing states in a state machine
public protocol IState {
/// Called when a state is entered
/// - Parameters:
/// - from: State we are coming from
/// - params: Optional parameters for state transition
func enter(from: IState?, params: Any?)
/// Called when a state is exited
/// - Parameter to: State we are transitioning to
func exit(to: IState?)
/// Process a message sent to the state machine
/// - Parameter msg: The message to process
/// - Returns: HANDLED if processing has completed or NOT_HANDLED if the message wasn't processed
func processMessage(_ msg: Message) -> Bool
/// Name of State for debugging purposes
/// - Returns: name of state
var name: String { get }
}

View File

@ -0,0 +1,17 @@
// IStateMonitor.swift
// Protocol for monitoring state changes in a StateMachine
// Ported from Android implementation
import Foundation
/// Protocol for monitoring state changes in a StateMachine
public protocol IStateMonitor {
/// Called when a state transition occurs
/// - Parameters:
/// - stateMachine: The state machine that changed state
/// - state: The new state
/// - lastState: The previous state
/// - params: Optional parameters associated with the transition
func onStateChanged(stateMachine: StateMachine, state: IState, lastState: IState?, params: Any?)
}

View File

@ -0,0 +1,30 @@
// Message.swift
// Message class for State Machine communication
// Equivalent to Android's Message class
import Foundation
/// Message class for state machine communication
public class Message {
/// The message identifier
public let what: Int
/// First argument value
public let arg1: Int
/// Second argument value
public let arg2: Int
/// Object associated with this message
public let obj: Any?
public let callback: (() -> Void)?
init(what: Int, arg1: Int = 0, arg2: Int = 0, obj: Any? = nil, callback: (() -> Void)? = nil) {
self.what = what
self.arg1 = arg1
self.arg2 = arg2
self.obj = obj
self.callback = callback
}
}

View File

@ -0,0 +1,43 @@
// State.swift
// The base class for implementing states in a StateMachine
// Ported from Android implementation
import Foundation
/// Base class for implementing states in a StateMachine
open class State: IState {
/// Initialize a new state
public init() {}
/// Called when a state is entered
/// - Parameters:
/// - from: State we are coming from
/// - params: Optional parameters for state transition
open func enter(from: IState?, params: Any?) {
// Default implementation does nothing
}
/// Called when a state is exited
/// - Parameter to: State we are transitioning to
open func exit(to: IState?) {
// Default implementation does nothing
}
/// Process a message sent to the state machine
/// - Parameter msg: The message to process
/// - Returns: HANDLED if processing has completed or NOT_HANDLED if the message wasn't processed
open func processMessage(_ msg: Message) -> Bool {
return false
}
/// Name of State for debugging purposes
/// This default implementation returns the class name
open var name: String {
let className = String(describing: type(of: self))
if let lastDotIndex = className.lastIndex(of: ".") {
return String(className[className.index(after: lastDotIndex)...])
}
return className
}
}

View File

@ -0,0 +1,566 @@
// StateMachine.swift
// Core state machine implementation
// Ported from Android implementation
import Foundation
private struct PendingWorkItem {
let id: UUID
weak var work: DispatchWorkItem?
let what: Int
let obj: Any?
}
/// A hierarchical state machine for managing states and transitions
public class StateMachine {
// MARK: - Constants
/// Message.what value when quitting
private static let SM_QUIT_CMD = -1
/// Message.what value when initializing
private static let SM_INIT_CMD = -2
// MARK: - Properties
/// Name of the state machine, used for logging
let name: String
/// Current state of the state machine
private(set) var currentState: IState?
/// Initial state for the state machine
private var initialState: IState?
/// Destination state for transitions
private var destState: IState?
private var params: Any?
/// Parent-child state relationships
private var stateInfos = [String: StateInfo]()
/// Monitors state changes
private var monitors = [IStateMonitor]()
/// Processing queue
private let queue = DispatchQueue.main
/// Queue for deferred messages
private var deferredMessages = [Message]()
/// Flag to indicate if the state machine has quit
private var hasQuit = false
/// The current message being processed
private var currentMessage: Message?
/// Debug flag
private let isDebugEnabled = false
private var pendingWorks: [PendingWorkItem] = []
// MARK: - Nested Types
/// Halting state entered when transitionToHaltingState is called
private class HaltingState: State {
private weak var stateMachine: StateMachine?
init(stateMachine: StateMachine) {
self.stateMachine = stateMachine
super.init()
}
override func processMessage(_ msg: Message) -> Bool {
stateMachine?.haltedProcessMessage(msg)
return true
}
}
/// Quitting state entered when a valid quit message is handled
private class QuittingState: State {
override func processMessage(_ msg: Message) -> Bool {
return false
}
}
public func isCurrent(_ state: IState) -> Bool {
return (currentState as? State) === (state as? State)
}
/// Information about a state and its relationships
private class StateInfo {
let state: IState
var parentStateInfo: StateInfo?
var active = false
init(state: IState) {
self.state = state
}
}
// MARK: - Lifecycle
/// Initialize a state machine with a name
/// - Parameter name: Name of the state machine
public init(name: String) {
self.name = name
}
// MARK: - Public Methods
/// Add a new state to the state machine
/// - Parameters:
/// - state: The state to add
/// - parent: Optional parent state
public func addState(_ state: IState, parent: IState? = nil) {
let stateInfo = StateInfo(state: state)
stateInfo.active = false
if let parent = parent {
// Look up the parent state info
if let parentStateInfo = getStateInfo(parent) {
stateInfo.parentStateInfo = parentStateInfo
} else {
log("StateMachine: parent state not found: \(parent.name)")
}
}
stateInfo.active = false
stateInfo.parentStateInfo = parent != nil ? getStateInfo(parent!) : nil
stateInfos[state.name] = stateInfo
}
/// Set the initial state of the state machine
/// - Parameter state: The initial state
public func setInitialState(_ state: IState) {
initialState = state
}
/// Add a monitor for state changes
/// - Parameter monitor: The monitor to add
public func addStateMonitor(_ monitor: IStateMonitor) {
monitors.append(monitor)
}
/// Remove a monitor for state changes
/// - Parameter monitor: The monitor to remove
public func removeStateMonitor(_ monitor: IStateMonitor) {
if let index = monitors.firstIndex(where: { $0 as AnyObject === monitor as AnyObject }) {
monitors.remove(at: index)
}
}
/// Start the state machine
public func start() {
guard !hasQuit, let initialState = initialState else {
log("StateMachine: start called without an initial state or after quitting")
return
}
queue.async { [weak self] in
guard let self = self else { return }
self.setupInitialState()
}
}
/// Send a message to the state machine
/// - Parameter message: The message to send
public func sendMessage(_ message: Message) {
sendMessageDelayed(message, delayed: .milliseconds(0))
}
public func sendMessageDelayed(_ message: Message, delayed: DispatchTimeInterval) {
if hasQuit {
log("StateMachine: sendMessage called after quitting")
return
}
let pendingItemId = UUID()
var work: DispatchWorkItem?
work = DispatchWorkItem() {
[weak self] in
guard let self = self, !(work?.isCancelled ?? true) else {
self?.pendingWorks.removeAll {
item in
return pendingItemId == item.id
}
return
}
if(message.callback != nil) {
// messagecallbackcallbackAndroid`Handler.post(runnable)``Handler.postDelayed(runnable, delay)`
message.callback?()
} else {
self.processMessage(message)
}
self.pendingWorks.removeAll {
item in
return pendingItemId == item.id
}
}
if delayed == DispatchTimeInterval.milliseconds(0) {
queue.async(execute: work!)
} else {
pendingWorks.append(PendingWorkItem(id: pendingItemId, work: work, what: message.what, obj: message.obj))
queue.asyncAfter(deadline: .now() + delayed, execute: work!)
}
}
public func sendMessage(what: Int, obj: Any? = nil) {
sendMessage(Message(what: what, obj: obj))
}
public func sendMessageDelayed(what: Int, delayMillis: Int, obj: Any? = nil) {
sendMessageDelayed(Message(what: what, obj: obj), delayed: .milliseconds(delayMillis))
}
public func sendMessageDelayed(what: Int, delayed: DispatchTimeInterval, obj: Any? = nil) {
sendMessageDelayed(Message(what: what, obj: obj), delayed: delayed)
}
public func removeMessages(what: Int) {
if hasQuit {
log("StateMachine: removeMessages called after quitting")
return
}
pendingWorks.filter {
pendingWorkItem in
return pendingWorkItem.work == nil || pendingWorkItem.what == what
}.forEach {
pendingWorkItem in
if pendingWorkItem.work != nil && !(pendingWorkItem.work?.isCancelled ?? true) {
pendingWorkItem.work?.cancel()
}
}
}
private func clearPendingWorks() {
pendingWorks.forEach {
pendingWorkItem in
if pendingWorkItem.work != nil && !(pendingWorkItem.work?.isCancelled ?? true) {
pendingWorkItem.work?.cancel()
}
}
pendingWorks.removeAll()
}
public func post(runnable: @escaping () -> Void, token: Any? = nil) {
postDelayed(runnable: runnable, token: token, delayed: .microseconds(0))
}
public func postDelayed(runnable: @escaping () -> Void, token: Any? = nil, delayed: DispatchTimeInterval) {
sendMessageDelayed(Message(what: 0, obj: token, callback: runnable), delayed: delayed)
}
private func anyEquals(_ x : Any?, _ y : Any?) -> Bool {
guard let x = x as? AnyHashable,
let y = y as? AnyHashable else { return false }
return x == y
}
public func removeCallbacksAndMessages(_ token: Any? = nil) {
if(token == nil) {
clearPendingWorks()
}
pendingWorks.removeAll {
pendingWorkItem in
let remove = anyEquals(pendingWorkItem.obj, token)
if(remove && !(pendingWorkItem.work?.isCancelled ?? true)) {
pendingWorkItem.work?.cancel()
}
return remove
}
}
/// Send a message to the front of the queue
/// - Parameter message: The message to send
public func sendMessageAtFrontOfQueue(_ message: Message) {
if hasQuit {
log("StateMachine: sendMessageAtFrontOfQueue called after quitting")
return
}
queue.async(flags: .barrier) { [weak self] in
guard let self = self else { return }
self.processMessage(message)
}
}
/// Transition to a new state
/// - Parameters:
/// - state: The state to transition to
/// - params: Optional parameters for the transition
public func transitionTo(_ state: IState, params: Any? = nil) {
if hasQuit {
log("StateMachine: transitionTo called after quitting")
return
}
guard let stateInfo = getStateInfo(state) else {
log("StateMachine: transitionTo called with unrecognized state: \(state.name)")
return
}
destState = state
self.params = params
}
/// Transition to the halting state
public func transitionToHaltingState() {
let haltingState = HaltingState(stateMachine: self)
addState(haltingState)
transitionTo(haltingState)
}
/// Quit the state machine
public func quit() {
if hasQuit {
log("StateMachine: quit called after already quitting")
return
}
let quittingState = QuittingState()
addState(quittingState)
transitionTo(quittingState)
queue.async(flags: .barrier) { [weak self] in
guard let self = self else { return }
self.cleanupAfterQuitting()
}
}
/// Defer a message to be processed after the next state transition
/// - Parameter message: The message to defer
public func deferMessage(_ message: Message) {
if hasQuit {
log("StateMachine: deferMessage called after quitting")
return
}
deferredMessages.append(message)
}
// MARK: - public Methods
/// Called for any message received after transitioning to halting state
/// - Parameter message: The message received
open func haltedProcessMessage(_ message: Message) {
// Default implementation does nothing
}
/// Called once after transitioning to halting state
open func onHalting() {
// Default implementation does nothing
}
/// Called once after quitting
open func onQuitting() {
// Default implementation does nothing
}
/// Called when a message is not handled by any state
/// - Parameter message: The unhandled message
open func unhandledMessage(_ message: Message) {
// Default implementation does nothing
}
// MARK: - Private Methods
/// Get the StateInfo for a state
/// - Parameter state: The state to get info for
/// - Returns: StateInfo object or nil if not found
private func getStateInfo(_ state: IState) -> StateInfo? {
return stateInfos[state.name]
}
/// Setup the initial state
private func setupInitialState() {
guard let initialState = initialState else { return }
currentState = initialState
// Find all parent states of the initial state
var parentStates = [IState]()
var stateInfo = getStateInfo(initialState)
while let info = stateInfo?.parentStateInfo {
parentStates.insert(info.state, at: 0)
stateInfo = info
}
// Enter parent states from top to bottom
for state in parentStates {
state.enter(from: nil, params: nil)
}
// Enter initial state
initialState.enter(from: nil, params: nil)
// Process any deferred messages
processDeferredMessages()
}
/// Process a message through the state hierarchy
/// - Parameter message: The message to process
private func processMessage(_ message: Message) {
if hasQuit {
return
}
currentMessage = message
if message.what == StateMachine.SM_QUIT_CMD {
transitionTo(QuittingState())
return
}
var state: IState? = currentState
var handled = false
// Try to handle the message starting with the current state and going up the hierarchy
while let currentState = state, !handled {
handled = currentState.processMessage(message)
if !handled {
state = getStateInfo(currentState)?.parentStateInfo?.state
}
}
if !handled {
unhandledMessage(message)
}
performStateTransitions(message)
currentMessage = nil
}
/// Perform any pending state transitions
/// - Parameter message: The message that triggered potential transitions
private func performStateTransitions(_ message: Message) {
if let destState = destState {
let orgState = currentState
// Find common ancestor state
let (commonAncestor, statesToExit, statesToEnter) = findCommonAncestor(currentState!, destState)
// Exit current states up to common ancestor
for state in statesToExit {
state.exit(to: destState)
}
// Enter new states from common ancestor down to destination
for state in statesToEnter {
state.enter(from: orgState, params: params)
}
// Update current state
currentState = destState
// Process deferred messages
processDeferredMessages()
// Notify monitors
notifyMonitors(destState, lastState: orgState, params: message.obj)
// Reset destination state
self.destState = nil
self.params = nil
// Check if we're entering a special state
if destState is QuittingState {
onQuitting()
cleanupAfterQuitting()
} else if destState is HaltingState {
onHalting()
}
}
}
/// Find the common ancestor of two states and determine which states to exit and enter
/// - Parameters:
/// - currentState: The current state
/// - destState: The destination state
/// - Returns: Tuple containing common ancestor, states to exit, and states to enter
private func findCommonAncestor(_ currentState: IState, _ destState: IState) -> (IState?, [IState], [IState]) {
var commonAncestor: IState? = nil
var statesToExit = [IState]()
var statesToEnter = [IState]()
// Build path from current state to root
var currentPath = [IState]()
var currentStateInfo = getStateInfo(currentState)
while let info = currentStateInfo {
currentPath.insert(info.state, at: 0)
currentStateInfo = info.parentStateInfo
}
// Build path from destination state to root
var destPath = [IState]()
var destStateInfo = getStateInfo(destState)
while let info = destStateInfo {
destPath.insert(info.state, at: 0)
destStateInfo = info.parentStateInfo
}
// Find common ancestor
var commonIndex = 0
while commonIndex < min(currentPath.count, destPath.count) && currentPath[commonIndex].name == destPath[commonIndex].name {
commonAncestor = currentPath[commonIndex]
commonIndex += 1
}
// States to exit - from current state up to (but not including) common ancestor
statesToExit = Array(currentPath.reversed().prefix(currentPath.count - commonIndex))
// States to enter - from one below common ancestor down to destination state
statesToEnter = Array(destPath.suffix(destPath.count - commonIndex))
return (commonAncestor, statesToExit, statesToEnter)
}
/// Process all deferred messages
private func processDeferredMessages() {
guard !deferredMessages.isEmpty else { return }
let messages = deferredMessages
deferredMessages.removeAll()
for message in messages {
sendMessageAtFrontOfQueue(message)
}
}
/// Notify all monitors of a state change
/// - Parameters:
/// - newState: The new state
/// - lastState: The previous state
/// - params: Optional parameters
private func notifyMonitors(_ newState: IState, lastState: IState?, params: Any?) {
for monitor in monitors {
monitor.onStateChanged(stateMachine: self, state: newState, lastState: lastState, params: params)
}
}
/// Clean up resources after quitting
private func cleanupAfterQuitting() {
hasQuit = true
currentState = nil
initialState = nil
destState = nil
deferredMessages.removeAll()
stateInfos.removeAll()
monitors.removeAll()
clearPendingWorks()
}
/// Log a message if debugging is enabled
/// - Parameter message: The message to log
private func log(_ message: String) {
if isDebugEnabled {
print("\(name): \(message)")
}
}
}

19
LICENSE Normal file
View File

@ -0,0 +1,19 @@
Copyright (c) 2025 Haoyi <haoyi.zhang@castbox.fm>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

29
README.md Normal file
View File

@ -0,0 +1,29 @@
# FusionAds
[![CI Status](https://img.shields.io/travis/Haoyi/FusionAds.svg?style=flat)](https://travis-ci.org/Haoyi/FusionAds)
[![Version](https://img.shields.io/cocoapods/v/FusionAds.svg?style=flat)](https://cocoapods.org/pods/FusionAds)
[![License](https://img.shields.io/cocoapods/l/FusionAds.svg?style=flat)](https://cocoapods.org/pods/FusionAds)
[![Platform](https://img.shields.io/cocoapods/p/FusionAds.svg?style=flat)](https://cocoapods.org/pods/FusionAds)
## Example
To run the example project, clone the repo, and run `pod install` from the Example directory first.
## Requirements
## Installation
FusionAds is available through [CocoaPods](https://cocoapods.org). To install
it, simply add the following line to your Podfile:
```ruby
pod 'FusionAds'
```
## Author
Haoyi, haoyi.zhang@castbox.fm
## License
FusionAds is available under the MIT license. See the LICENSE file for more info.