This guide covers how to add the MSP iOS SDK to your app, including how to initialize SDK, load Ad and display ads.
The guide also provides some best pratices which helps you avoid common pitfalls and achieve best performance.
System requirements
| Requirement | Version |
|---|---|
| iOS deployment target | 15.0+ |
| Xcode | 15.0+ |
| Swift | 5.0+ |
| CocoaPods | 1.12+ |
The MSP SDK is distributed via CocoaPods. Add the core pod and any adapter pods for the ad networks you want to monetize with.
Podfile
platform :ios, '15.0'
target 'YourApp' do
use_frameworks!
# Core SDK (required)
pod 'MSPCore', '{LATEST_VERSION}', :modular_headers => true
# Ad network adapters (add only those you need)
pod 'MSPGoogleAdapter', '{LATEST_VERSION}', :modular_headers => true
pod 'MSPFacebookAdapter', '{LATEST_VERSION}', :modular_headers => true
pod 'MSPNovaAdapter', '{LATEST_VERSION}', :modular_headers => true
pod 'MSPMolocoAdapter', '{LATEST_VERSION}', :modular_headers => true
pod 'MSPApplovinMaxAdapter', '{LATEST_VERSION}', :modular_headers => true
endRun the install command:
pod installOpen the generated .xcworkspace file — do not use the .xcodeproj directly.
Add the usage description key. The system displays this string in the ATT permission prompt:
<key>NSUserTrackingUsageDescription</key>
<string>This identifier will be used to deliver personalized ads to you.</string>Network-specific Info.plist entries (GAD identifier, SKAdNetwork IDs) are covered per-network in the Mediation Networks Guide.
Initialize the MSP SDK as early as possible — ideally in application(_:didFinishLaunchingWithOptions:). The SDK uses the initialization window to prefetch ad configurations and warm up adapter networks.
AppDelegate.swift
import AppTrackingTransparency
import MSPCore
import MSPiOSCore
// Import adapter modules — see Mediation Networks Guide for each network's import name
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
// Declare adapter managers for the networks you want to activate
private let adNetworkManagers: [AdNetworkManager] = [
GoogleManager(),
FacebookManager(),
NovaManager(),
MolocoManager(),
ApplovinMaxManager(),
]
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
initializeMSP()
// Request ATT permission after a brief delay so your UI is visible
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
if #available(iOS 14, *) {
ATTrackingManager.requestTrackingAuthorization { _ in }
}
}
return true
}
private func initializeMSP() {
// Optional: pass a publisher-provided user identifier for frequency capping
MSP.shared.ppid = "your-user-id"
// Build initialization parameters
let initParams = InitializationParametersImp(
prebidAPIKey: "YOUR_PREBID_API_KEY",
sourceApp: "YOUR_SOURCE_APP",
orgId: 12345, // Your org ID from the MSP dashboard
appId: 67890 // Your app ID from the MSP dashboard
)
// Additional network-specific init parameters
initParams.params = [
InitializationParametersCustomKeys.MOLOCO_APP_KEY: "xxxxx-v9C8uNYV5riG7Vwu",
InitializationParametersCustomKeys.APPLOVIN_SDK_KEY: "6KrA5SQHFTBpGDUU4FeLIZG...",
]
// Initialize
MSP.shared.initMSP(
initParams: initParams,
sdkInitListener: self, // optional — set to nil if not needed
adNetworkManagers: adNetworkManagers
)
}
}
// MARK: - MSPInitListener (optional)
extension AppDelegate: MSPInitListener {
func onComplete(status: MSPInitStatus, message: String) {
print("[MSP] Initialization complete. Status: \(status), message: \(message)")
}
}Key initialization parameters
| Parameter | Type | Description |
|---|---|---|
prebidAPIKey |
String |
API key issued by Particle inc. for your app |
sourceApp |
String |
App bundle identifier or source tag |
orgId |
Int64 |
Organization ID provisioned from Particle inc. |
appId |
Int64 |
App ID provisioned from Particle inc. |
params |
[String: Any]? |
Additional network-specific initialization parameters |
Optional MSP.shared properties
| Property | Default | Description |
|---|---|---|
ppid |
nil |
Publisher-provided user ID |
The pattern for every ad format is the same:
- Create an
MSPAdLoaderand anAdRequest. - Call
loadAd(placementId:adListener:adRequest:). - In
onAdLoaded, callloader.getAd(placementId:)to retrieve the ad object. - Present the Ad(for interstitial Ad) or attach the ad to the App view hierarchy.
Call MSP.shared.notifyLoss API when:
- MSP Ad loses the auction, or
- MSP SDK does not fill while other bidder wins
public func notifyLoss(winnerBidderName: String, winnerPrice: Float, ad: MSPAd?, requestId: String?)
winnerBidderName: name of the winning bidder other than MSPwinnderPrice: Ad price of the winning bid other than MSPad: MSP Ad that loses the auction.(passnilfor the No fill case)requestId: Provided byonAdLoadedandonErrorcallback parameterloadInfo[MSPConstants.LOAD_INFO_KEY_REQUEST_ID]ofloadAdAPI
import MSPCore
import MSPiOSCore
class BannerViewController: UIViewController {
private let loader = MSPAdLoader()
private var bannerContainerView: UIView?
private func loadBanner() {
let size = AdSize(width: 320, height: 50)
let request = AdRequest(
customParams: [:],
geo: nil,
context: nil,
adaptiveBannerSize: nil,
adSize: size,
placementId: "YOUR_BANNER_PLACEMENT_ID",
adFormat: .banner
)
loader.loadAd(placementId: "YOUR_BANNER_PLACEMENT_ID", adListener: self, adRequest: request)
}
}
extension BannerViewController: AdListener {
func onAdLoaded(placementId: String, loadInfo: [String: Any]) {
guard let bannerAd = loader.getAd(placementId: placementId) as? BannerAd else { return }
let adView = bannerAd.adView
adView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(adView)
NSLayoutConstraint.activate([
adView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
adView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
adView.widthAnchor.constraint(equalToConstant: 320),
adView.heightAnchor.constraint(equalToConstant: 50),
])
}
func onError(msg: String, loadInfo: [String: Any]) {
print("[MSP] Banner error: \(msg)")
}
func onAdImpression(ad: MSPAd) {}
func onAdClick(ad: MSPAd) {}
func onAdDismissed(ad: MSPAd) {}
func getRootViewController() -> UIViewController? { self }
}Standard banner sizes
| Size | Width | Height |
|---|---|---|
| Banner | 320 | 50 |
| Medium rectangle | 300 | 250 |
| Leaderboard | 728 | 90 |
Load the interstitial before you need to show it. Display it at a natural transition point — level completion, article end, etc.
import MSPCore
import MSPiOSCore
class InterstitialViewController: UIViewController {
private let loader = MSPAdLoader()
private func loadInterstitial() {
let request = AdRequest(
customParams: [:],
geo: nil,
context: nil,
adaptiveBannerSize: nil,
adSize: nil,
placementId: "YOUR_INTERSTITIAL_PLACEMENT_ID",
adFormat: .interstitial
)
loader.loadAd(placementId: "YOUR_INTERSTITIAL_PLACEMENT_ID", adListener: self, adRequest: request)
}
private func showInterstitialIfReady() {
guard let interstitialAd = loader.getAd(placementId: "YOUR_INTERSTITIAL_PLACEMENT_ID") as? InterstitialAd else {
loadInterstitial() // not ready yet — start loading
return
}
interstitialAd.show(rootViewController: self)
}
}
extension InterstitialViewController: AdListener {
func onAdLoaded(placementId: String, loadInfo: [String: Any]) {
print("[MSP] Interstitial ready")
showInterstitialIfReady()
}
func onError(msg: String, loadInfo: [String: Any]) {
print("[MSP] Interstitial error: \(msg)")
}
func onAdDismissed(ad: MSPAd) {
print("[MSP] Interstitial Ads is dismissed: \(msg)")
}
func onAdImpression(ad: MSPAd) {}
func onAdClick(ad: MSPAd) {}
func getRootViewController() -> UIViewController? { self }
}Rewarded ads are full-screen placements that grant a reward — coins, lives, premium content access — after the user completes the ad experience. The reward is delivered via onAdRewardReceived.
import MSPCore
import MSPiOSCore
class RewardedViewController: UIViewController {
private let loader = MSPAdLoader()
private func loadRewardedAd() {
let request = AdRequest(
customParams: [:],
geo: nil,
context: nil,
adaptiveBannerSize: nil,
adSize: nil,
placementId: "YOUR_REWARDED_PLACEMENT_ID",
adFormat: .rewarded
)
loader.loadAd(placementId: "YOUR_REWARDED_PLACEMENT_ID", adListener: self, adRequest: request)
}
@IBAction func userTappedWatchAd(_ sender: UIButton) {
guard let rewardedAd = loader.getAd(placementId: "YOUR_REWARDED_PLACEMENT_ID") as? RewardedAd else {
print("[MSP] Rewarded ad not ready yet")
return
}
rewardedAd.show(rootViewController: self)
}
}
extension RewardedViewController: AdListener {
// Override this to grant the reward to the user
func onAdRewardReceived(ad: MSPAd) {
guard let rewardedAd = ad as? RewardedAd else { return }
let reward = rewardedAd.reward
print("[MSP] Reward: \(reward.amount) \(reward.type)")
// Grant the reward to the user here
}
func onAdLoaded(placementId: String, loadInfo: [String: Any]) {}
func onError(msg: String, loadInfo: [String: Any]) {}
func onAdDismissed(ad: MSPAd) {}
func onAdImpression(ad: MSPAd) {}
func onAdClick(ad: MSPAd) {}
func getRootViewController() -> UIViewController? { self }
}Native ads return a NativeAd object you render with your own layout. Implement MSPNativeAdContainer to wire up your UI elements, then pass it to NativeAdView.
import MSPCore
import MSPiOSCore
class NativeViewController: UIViewController {
private let loader = MSPAdLoader()
private func loadNativeAd() {
let request = AdRequest(
customParams: [:],
geo: nil,
context: nil,
adaptiveBannerSize: nil,
adSize: nil,
placementId: "YOUR_NATIVE_PLACEMENT_ID",
adFormat: .native
)
loader.loadAd(placementId: "YOUR_NATIVE_PLACEMENT_ID", adListener: self, adRequest: request)
}
}
extension NativeViewController: AdListener {
func onAdLoaded(placementId: String, loadInfo: [String: Any]) {
guard let nativeAd = loader.getAd(placementId: placementId) as? NativeAd else { return }
DispatchQueue.main.async {
let container = MyNativeAdContainer(frame: CGRect(x: 0, y: 0, width: 300, height: 250))
let nativeAdView = NativeAdView(nativeAd: nativeAd, nativeAdContainer: container)
nativeAdView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(nativeAdView)
NSLayoutConstraint.activate([
nativeAdView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
nativeAdView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
nativeAdView.widthAnchor.constraint(equalToConstant: 300),
])
}
}
func onError(msg: String, loadInfo: [String: Any]) {}
func onAdImpression(ad: MSPAd) {}
func onAdClick(ad: MSPAd) {}
func onAdDismissed(ad: MSPAd) {}
func getRootViewController() -> UIViewController? { self }
}Implement MSPNativeAdContainer to provide the UI elements the SDK will populate:
class MyNativeAdContainer: UIView, MSPNativeAdContainer {
private let titleLabel = UILabel()
private let bodyLabel = UILabel()
private let advertiserLabel = UILabel()
private let ctaButton = UIButton()
private let mediaView = UIView()
private let iconView = UIImageView()
func getTitle() -> UILabel? { titleLabel }
func getbody() -> UILabel? { bodyLabel }
func getAdvertiser() -> UILabel? { advertiserLabel }
func getCallToAction() -> UIButton? { ctaButton }
func getMedia() -> UIView? { mediaView }
func getIcon() -> UIImageView? { iconView }
func getCustomClickableViews() -> [UIView]? { nil }
}.multi_format allows both native and banner ads to fill the same placement. In onAdLoaded, check which type was returned and render accordingly.
import MSPCore
import MSPiOSCore
class MultiFormatViewController: UIViewController {
private let loader = MSPAdLoader()
private func loadAd() {
let request = AdRequest(
customParams: [:],
geo: nil,
context: nil,
adaptiveBannerSize: nil,
adSize: nil,
placementId: "YOUR_MULTI_FORMAT_PLACEMENT_ID",
adFormat: .multi_format
)
loader.loadAd(placementId: "YOUR_MULTI_FORMAT_PLACEMENT_ID", adListener: self, adRequest: request)
}
}
extension MultiFormatViewController: AdListener {
func onAdLoaded(placementId: String, loadInfo: [String: Any]) {
let ad = loader.getAd(placementId: placementId)
DispatchQueue.main.async {
if let nativeAd = ad as? NativeAd {
let container = MyNativeAdContainer(frame: CGRect(x: 0, y: 0, width: 300, height: 250))
let nativeAdView = NativeAdView(nativeAd: nativeAd, nativeAdContainer: container)
nativeAdView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(nativeAdView)
NSLayoutConstraint.activate([
nativeAdView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
nativeAdView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
nativeAdView.widthAnchor.constraint(equalToConstant: 300),
])
} else if let bannerAd = ad as? BannerAd {
let adView = bannerAd.adView
adView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(adView)
NSLayoutConstraint.activate([
adView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
adView.centerYAnchor.constraint(equalTo: self.view.centerYAnchor),
adView.widthAnchor.constraint(equalToConstant: 320),
adView.heightAnchor.constraint(equalToConstant: 50),
])
}
}
}
func onError(msg: String, loadInfo: [String: Any]) {}
func onAdImpression(ad: MSPAd) {}
func onAdClick(ad: MSPAd) {}
func onAdDismissed(ad: MSPAd) {}
func getRootViewController() -> UIViewController? { self }
}Network-specific Info.plist entries required for each adapter are listed below.
| Pod | Ad Network | Formats |
|---|---|---|
MSPGoogleAdapter |
Google Ad Manager / AdMob | Banner, Interstitial, Rewarded, Native |
MSPFacebookAdapter |
Meta Audience Network | Banner, Interstitial, Rewarded, Native |
MSPNovaAdapter |
Nova | Banner, Interstitial, Native, Rewarded |
MSPMolocoAdapter |
Moloco | Banner, Native, Interstitial, Rewarded |
MSPApplovinMaxAdapter |
AppLovin/Max | Banner, Native, Interstitial, Rewarded |
Info.plist — GAD identifier
Add your app's GAD identifier from the Google AdMob dashboard:
<key>GADApplicationIdentifier</key>
<string>ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX</string>Info.plist — SKAdNetwork
<dict>
<key>SKAdNetworkIdentifier</key>
<string>cstr6suwn9.skadnetwork</string>
</dict>Info.plist — SKAdNetwork
<dict>
<key>SKAdNetworkIdentifier</key>
<string>v9wttpbfk9.skadnetwork</string>
</dict>
<dict>
<key>SKAdNetworkIdentifier</key>
<string>n38lu8286q.skadnetwork</string>
</dict>Pass your Moloco App Key via params during initialization:
initParams.params = [
InitializationParametersCustomKeys.MOLOCO_APP_KEY: "YOUR_MOLOCO_APP_KEY"
]Pass your AppLovin SDK Key via params during initialization:
initParams.params = [
InitializationParametersCustomKeys.APPLOVIN_SDK_KEY: "6KrA5SQHFTBpGDUU4FeLIZG..."
]The auction takes time. Suggest Call loadAd before the moment you need to show the ad:
- Banners: Load when the view controller loads (
viewDidLoad). - Interstitials: Load at app launch or immediately after the previous interstitial is dismissed.
- Rewarded: Load at app launch and again in
onAdDismissed.
When an ad is no longer needed, call destroy() to release adapter resources and prevent memory leaks:
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
currentAd?.destroy()
currentAd = nil
}Keep a strong reference to your MSPAdLoader instance for the lifetime of the placement. The AdListener is held weakly by the loader — your view controller or delegate object must remain alive until onAdLoaded or onError fires.
No ads filling
- Verify the placement IDs match what is Provisioned by Particles inc.
- Confirm
initMSPcompletes beforeloadAdis called. - Enable verbose logging during development:
MSPLogger.shared.setLogLevel(level: MSPLogger.DEBUG).
onAdLoaded fires but getAd returns nil
getAd removes the ad from cache. Call it exactly once per load cycle and hold the returned ad strongly until you are done with it.
ATT prompt not appearing
ATTrackingManager.requestTrackingAuthorization must be called from the main thread after your root view controller is visible. The 1-second delay in the sample above handles the most common case; adjust as needed for your presentation flow.
Rewarded ad shows but reward is never granted
onAdRewardReceived fires only if the user watches the ad to completion. Do not grant rewards based on onAdDismissed alone.
Prebid SDK which is introduced by by MSP SDK will read UserDefaults (iOS) or SharedPreferences (Android) key IABUSPrivacy_String for US Privacy signal. Prebid SDK CCPA doc
Prebid SDK which is introduced by by MSP SDK will read UserDefaults (iOS) or SharedPreferences (Android) keys IABTCF_gdprApplies and IABTCF_TCString for GDPR privacy signals. Predbid SDK GDPR doc
Set your COPPA signal through init parameter isAgeRestrictedUser