Integration Guide
Complete guide to integrating the BidMachine Plus iOS SDK. Code samples are provided in Swift and Objective-C — use the language switch on each block.
BidMachine Plus iOS SDK
BidMachine Plus exposes two demand paths through one API:
| Mode | Description |
|---|---|
| AdNetwork | BidMachine runs as a header-bidding demand source — on its own, or plugged into a third-party mediation |
| Mediation | BidMachine acts as the mediation platform itself, running the auction across your demand |
The same ad unit classes work in both modes, selected at initialization:
| Swift | Objective-C |
|---|---|
BannerAd | BMBannerAd |
InterstitialAd | BMInterstitialAd |
RewardedAd | BMRewardedAd |
This page covers SDK installation, initialization, and ad loading. Two installation paths are available: automated via AI coding agents, or manual setup with CocoaPods or Swift Package Manager.
Automated Integration with AI Coding Agents
The BidMachine Plus SDK Agents bundle ships a portable integration skill for Claude Code, Codex, and Gemini. Install it for your runtime:
- Claude Code
- Gemini CLI
- npx (any agent)
/plugin marketplace add bidmachine/bidmachine-sdk-agents
/plugin install bidmachine-sdk-agents@bidmachine
gemini extensions install https://github.com/bidmachine/bidmachine-sdk-agents
npx skills add bidmachine/bidmachine-sdk-agents
Then ask your agent to integrate BidMachine Plus — for example:
Integrate BidMachine Plus into my iOS app with interstitial, rewarded, and banner ads.
The bundled skill drives the integration: it adds the SDK dependency, initializes in AdNetwork or Mediation mode, wires ad units, and sets privacy flags (GDPR, CCPA, ATT), preserving any existing ad setup.
Manual Installation
Requires iOS 13.0+ and Xcode 16.4+. Import the module as import BidMachinePlus (Swift) or @import BidMachinePlus; (Objective-C).
CocoaPods
The Static subspec is the default and injects the -ObjC linker flag automatically.
platform :ios, '13.0'
use_frameworks!
target 'YourApp' do
pod 'BidMachinePlus' # default (Static)
# pod 'BidMachinePlus/Static'
# pod 'BidMachinePlus/Dynamic'
end
Swift Package Manager
- File → Add Package Dependencies
- URL:
https://github.com/bidmachine/BidMachinePlus-SPM - Pick version
0.1.0or a range, and add theBidMachinePlusproduct - Add
-ObjCto Build Settings → Other Linker Flags
.package(url: "https://github.com/bidmachine/BidMachinePlus-SPM", from: "0.1.0")
// product: .product(name: "BidMachinePlus", package: "BidMachinePlus-SPM")
Info.plist Configuration
Allow arbitrary loads so ad creatives can render:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Insert the SKAdNetworkItems array — the full list of network IDs is published with the SDK.
If you collect IDFA, add an ATT usage description:
<key>NSUserTrackingUsageDescription</key>
<string>$(APP_NAME) needs your advertising identifier to deliver personalized ads.</string>
Initialization
Call BidMachine.instance(appKey:) once at app startup to get the SDK instance, then call initialize(config:completion:). Keep a reference to the instance — you'll pass it to every ad unit constructor. The same app key always returns the same instance. The completion fires on the main queue.
BidMachine Plus initializes in one of two modes, selected by the integrationType you pass to the builder.
AdNetwork Mode
BidMachine runs as a header-bidding ad network. When it plugs into a third-party mediation, name that platform with the mediator setter so it can compete in the waterfall.
- Swift
- Objective-C
let sdk = BidMachine.instance(appKey: "YOUR_APP_KEY")
let config = InitializationConfigBuilder(integrationType: .adNetwork)
.with(mediator: MediatorName.admob) // or MediatorName.levelPlay, MediatorName.max, a custom String
.with(loggingEnabled: true) // remove before release
.with(testModeEnabled: true) // remove before release
.build()
sdk.initialize(config: config) { status, error in
if let error { print("init failed: \(error.message)") }
}
@import BidMachinePlus;
BidMachine *sdk = [BidMachine instanceWithAppKey:@"YOUR_APP_KEY"];
BMInitializationConfigBuilder *builder =
[[BMInitializationConfigBuilder alloc] initWithIntegrationType:BMIntegrationTypeAdNetwork];
[builder withMediator:BMMediatorNameAdMob]; // or BMMediatorNameLevelPlay, BMMediatorNameMax, a custom NSString
[builder withLoggingEnabled:YES]; // remove before release
[builder withTestModeEnabled:YES]; // remove before release
BMInitializationConfig *config = [builder build];
[sdk initializeWithConfig:config completion:^(BMInitializationStatus * _Nullable status, BMError * _Nullable error) {
if (error) { NSLog(@"init failed: %@", error.message); }
}];
MediatorName.admob / BMMediatorNameAdMob ("admob"), .levelPlay / BMMediatorNameLevelPlay ("level_play"), and .max / BMMediatorNameMax ("max") are predefined string constants; the mediator setter also accepts any custom string.
Mediation Mode
BidMachine acts as the mediation platform itself, running the auction across your demand. Pass the mediation type — there's no third-party mediator to declare:
- Swift
- Objective-C
let config = InitializationConfigBuilder(integrationType: .mediation)
.with(loggingEnabled: true) // remove before release
.with(testModeEnabled: true) // remove before release
.build()
BidMachine.instance(appKey: "YOUR_APP_KEY").initialize(config: config) { status, error in }
BMInitializationConfigBuilder *builder =
[[BMInitializationConfigBuilder alloc] initWithIntegrationType:BMIntegrationTypeMediation];
[builder withLoggingEnabled:YES]; // remove before release
[builder withTestModeEnabled:YES]; // remove before release
BMInitializationConfig *config = [builder build];
BidMachine *sdk = [BidMachine instanceWithAppKey:@"YOUR_APP_KEY"];
[sdk initializeWithConfig:config completion:^(BMInitializationStatus * _Nullable status, BMError * _Nullable error) {}];
In both modes the ad unit classes expose notifyWin / notifyLoss for reporting the outcome of a waterfall round — see Mediation.
Loading against a server-side bid? See S2S Bidding for
load(bidPayload:).
Ad Integration
All ad lifecycle callbacks deliver an AdInfo / BMAdInfo describing the winning ad:
| Property | Type (Swift / Obj-C) | Description |
|---|---|---|
placementId | String / NSString * | Placement ID from the BidMachine dashboard |
price | Double / double | eCPM ÷ 1000 (e.g. 0.005 = $5 CPM) |
precision | RevenuePrecision | One of .estimated, .exact, .unknown |
info | [String: String] / NSDictionary | Network metadata. Known keys: networkName, dsp, ecpm |
Read the winning network via adInfo.info["networkName"], and the per-impression revenue via adInfo.price.
adInfo.precision describes how reliable that price is:
| Swift | Obj-C | Meaning |
|---|---|---|
.exact | RevenuePrecisionExact | Real-time auction price — trust for reporting |
.estimated | RevenuePrecisionEstimated | Historical-data estimate — treat as approximate |
.unknown | RevenuePrecisionUnknown | Confidence cannot be determined |
All delegate callbacks fire on the main thread, so you can update UI from them directly. On every delegate, only didLoadAd and didFailToLoadAd are required — the rest are optional.
Interstitial Ads
Full-screen ads. Create once, load, then show when ready. Reload from didDismiss to keep an ad ready for the next show.
- Swift
- Objective-C
class MainViewController: UIViewController, InterstitialDelegate {
private var interstitialAd: InterstitialAd!
private func createInterstitialAd() {
interstitialAd = InterstitialAd(sdk, placementId: "YOUR_PLACEMENT_ID")
interstitialAd.delegate = self
interstitialAd.load()
}
private func showInterstitialAd() {
if interstitialAd.isLoaded {
interstitialAd.show(from: self)
} else {
print("Interstitial ad not ready yet")
}
}
// InterstitialDelegate
func didLoadAd(adInfo: AdInfo) {
print("Interstitial loaded from \(adInfo.info["networkName"] ?? "?")")
}
func didFailToLoadAd(adInfo: AdInfo?, error: BidMachineError) {
print("Interstitial load failed: \(error.message)")
}
func didShowAd(adInfo: AdInfo) {}
func didFailToShowAd(adInfo: AdInfo?, error: BidMachineError) {
print("Interstitial show failed: \(error.message)")
}
func didClick(adInfo: AdInfo) {}
func didDismiss(adInfo: AdInfo) {
interstitialAd.load() // reload for next show
}
func didExpire(adInfo: AdInfo) {}
func didPayRevenue(adInfo: AdInfo) {
print("Interstitial revenue: \(adInfo.price) from \(adInfo.info["networkName"] ?? "?")")
}
}
@interface MainViewController () <InterstitialDelegate>
@property (nonatomic, strong) BMInterstitialAd *interstitialAd;
@end
@implementation MainViewController
- (void)createInterstitialAd {
self.interstitialAd = [[BMInterstitialAd alloc] init:self.sdk placementId:@"YOUR_PLACEMENT_ID"];
self.interstitialAd.delegate = self;
[self.interstitialAd load];
}
- (void)showInterstitialAd {
if (self.interstitialAd.isLoaded) {
[self.interstitialAd showFrom:self];
} else {
NSLog(@"Interstitial ad not ready yet");
}
}
#pragma mark - InterstitialDelegate
- (void)didLoadAdWithAdInfo:(BMAdInfo *)adInfo {
NSLog(@"Interstitial loaded from %@", adInfo.info[@"networkName"]);
}
- (void)didFailToLoadAdWithAdInfo:(BMAdInfo *)adInfo error:(BMError *)error {
NSLog(@"Interstitial load failed: %@", error.message);
}
- (void)didShowAdWithAdInfo:(BMAdInfo *)adInfo {}
- (void)didFailToShowAdWithAdInfo:(BMAdInfo *)adInfo error:(BMError *)error {
NSLog(@"Interstitial show failed: %@", error.message);
}
- (void)didClickWithAdInfo:(BMAdInfo *)adInfo {}
- (void)didDismissWithAdInfo:(BMAdInfo *)adInfo {
[self.interstitialAd load]; // reload for next show
}
- (void)didExpireWithAdInfo:(BMAdInfo *)adInfo {}
- (void)didPayRevenueWithAdInfo:(BMAdInfo *)adInfo {
NSLog(@"Interstitial revenue: %f from %@", adInfo.price, adInfo.info[@"networkName"]);
}
@end
| Callback | Description |
|---|---|
didLoadAd | Ad loaded and ready to show |
didFailToLoadAd | Ad failed to load |
didShowAd | Ad is displayed full-screen |
didFailToShowAd | Ad failed to display |
didClick | User tapped the ad |
didDismiss | User dismissed the ad |
didExpire | Ad expired before being shown |
didPayRevenue | Billable impression recorded |
InterstitialAd and RewardedAd have no destroy() method — to release a fullscreen ad, drop your reference to it. (BannerAd does expose destroy().)
Rewarded Ads
Same lifecycle as interstitial, plus a didReceiveReward callback when the user earns a reward. RewardedDelegate inherits from InterstitialDelegate.
- Swift
- Objective-C
class MainViewController: UIViewController, RewardedDelegate {
private var rewardedAd: RewardedAd!
private func createRewardedAd() {
rewardedAd = RewardedAd(sdk, placementId: "YOUR_PLACEMENT_ID")
rewardedAd.delegate = self
rewardedAd.load()
}
private func showRewardedAd() {
if rewardedAd.isLoaded {
rewardedAd.show(from: self)
}
}
// RewardedDelegate
func didLoadAd(adInfo: AdInfo) {}
func didReceiveReward(adInfo: AdInfo, reward: Reward?) {
print("Reward earned: \(reward?.amount ?? 0) \(reward?.label ?? "")")
// grant reward to the user
}
func didFailToLoadAd(adInfo: AdInfo?, error: BidMachineError) {
print("Rewarded load failed: \(error.message)")
}
func didDismiss(adInfo: AdInfo) {
rewardedAd.load() // reload for next show
}
func didPayRevenue(adInfo: AdInfo) {
print("Rewarded revenue: \(adInfo.price) from \(adInfo.info["networkName"] ?? "?")")
}
}
@interface MainViewController () <RewardedDelegate>
@property (nonatomic, strong) BMRewardedAd *rewardedAd;
@end
@implementation MainViewController
- (void)createRewardedAd {
self.rewardedAd = [[BMRewardedAd alloc] init:self.sdk placementId:@"YOUR_PLACEMENT_ID"];
self.rewardedAd.delegate = self;
[self.rewardedAd load];
}
- (void)showRewardedAd {
if (self.rewardedAd.isLoaded) {
[self.rewardedAd showFrom:self];
}
}
#pragma mark - RewardedDelegate
- (void)didLoadAdWithAdInfo:(BMAdInfo *)adInfo {}
- (void)didReceiveRewardWithAdInfo:(BMAdInfo *)adInfo reward:(BMReward *)reward {
NSLog(@"Reward earned: %ld %@", (long)reward.amount, reward.label);
// grant reward to the user
}
- (void)didFailToLoadAdWithAdInfo:(BMAdInfo *)adInfo error:(BMError *)error {
NSLog(@"Rewarded load failed: %@", error.message);
}
- (void)didDismissWithAdInfo:(BMAdInfo *)adInfo {
[self.rewardedAd load]; // reload for next show
}
- (void)didPayRevenueWithAdInfo:(BMAdInfo *)adInfo {
NSLog(@"Rewarded revenue: %f from %@", adInfo.price, adInfo.info[@"networkName"]);
}
@end
didReceiveReward adds to the interstitial callback set. The Reward / BMReward object exposes label: String and amount: Int.
Banner Ads
BannerAd / BMBannerAd is a UIView. Add it to your own layout, or call show(at:) to anchor it to a screen edge. It is single-shot — it does not auto-refresh.
| Size (Swift) | Obj-C | Dimensions | Use case |
|---|---|---|---|
.banner | BMBannerAdSize.banner | 320 × 50 | Standard banner |
.leaderboard | BMBannerAdSize.leaderboard | 728 × 90 | Tablets |
.mrec | BMBannerAdSize.mrec | 300 × 250 | Medium rectangle |
.adaptive(width:maxHeight:) | +adaptiveWithWidth:maxHeight: | custom | Fluid layout |
Use BannerAdSize.getMaxAdaptiveHeight(width:) (+getMaxAdaptiveHeightWithWidth: in Obj-C) to calculate maxHeight for adaptive banners. width and maxHeight are UInt32.
Manual Banner
Reserve a host UIView, then attach the loaded banner to it inside didLoadAd:
- Swift
- Objective-C
class MainViewController: UIViewController, BannerDelegate {
@IBOutlet private var adContainer: UIView!
private var bannerAd: BannerAd!
private func createBannerAd() {
bannerAd = BannerAd(sdk, placementId: "YOUR_PLACEMENT_ID", size: .banner)
bannerAd.delegate = self
bannerAd.load()
}
deinit { bannerAd?.destroy() }
// BannerDelegate
func didLoadAd(adInfo: AdInfo) {
adContainer.addSubview(bannerAd)
}
func didFailToLoadAd(adInfo: AdInfo?, error: BidMachineError) {
print("Banner load failed: \(error.message)")
}
func didShowAd(adInfo: AdInfo) {}
func didFailToShowAd(adInfo: AdInfo?, error: BidMachineError) {}
func didClick(adInfo: AdInfo) {}
func didExpire(adInfo: AdInfo) {}
func didPayRevenue(adInfo: AdInfo) {
print("Banner revenue: \(adInfo.price) from \(adInfo.info["networkName"] ?? "?")")
}
}
@interface MainViewController () <BannerDelegate>
@property (nonatomic, strong) IBOutlet UIView *adContainer;
@property (nonatomic, strong) BMBannerAd *bannerAd;
@end
@implementation MainViewController
- (void)createBannerAd {
self.bannerAd = [[BMBannerAd alloc] init:self.sdk
placementId:@"YOUR_PLACEMENT_ID"
size:BMBannerAdSize.banner];
self.bannerAd.delegate = self;
[self.bannerAd load];
}
- (void)dealloc { [self.bannerAd destroy]; }
#pragma mark - BannerDelegate
- (void)didLoadAdWithAdInfo:(BMAdInfo *)adInfo {
[self.adContainer addSubview:self.bannerAd];
}
- (void)didFailToLoadAdWithAdInfo:(BMAdInfo *)adInfo error:(BMError *)error {
NSLog(@"Banner load failed: %@", error.message);
}
- (void)didShowAdWithAdInfo:(BMAdInfo *)adInfo {}
- (void)didFailToShowAdWithAdInfo:(BMAdInfo *)adInfo error:(BMError *)error {}
- (void)didClickWithAdInfo:(BMAdInfo *)adInfo {}
- (void)didExpireWithAdInfo:(BMAdInfo *)adInfo {}
- (void)didPayRevenueWithAdInfo:(BMAdInfo *)adInfo {
NSLog(@"Banner revenue: %f from %@", adInfo.price, adInfo.info[@"networkName"]);
}
@end
| Callback | Description |
|---|---|
didLoadAd | Ad loaded and ready to attach to a superview |
didFailToLoadAd | Ad failed to load |
didShowAd | Ad visible on screen, impression tracked |
didFailToShowAd | Ad failed to display |
didClick | User tapped the ad |
didExpire | Ad expired before being shown |
didPayRevenue | Billable impression recorded |
Positioned Banner
Call show(at:) on a loaded banner to anchor it to one of the edges below. The SDK resolves the host view internally (the key window's rootViewController, with a top-presented-VC fallback) — no host view is required at the call site. Call hide() to remove it.
| Position | Swift | Obj-C |
|---|---|---|
| Top | .horizontalTop | BMBannerPositionHorizontalTop |
| Bottom | .horizontalBottom | BMBannerPositionHorizontalBottom |
| Left | .verticalLeft | BMBannerPositionVerticalLeft |
| Right | .verticalRight | BMBannerPositionVerticalRight |
- Swift
- Objective-C
// In didLoadAd:
bannerAd.show(at: .horizontalBottom) // position defaults to .horizontalBottom
// Later:
bannerAd.hide() // detaches the view; show(at:) can re-display it
bannerAd.destroy()
// In didLoadAdWithAdInfo::
[self.bannerAd showAt:BMBannerPositionHorizontalBottom];
// Later:
[self.bannerAd hide]; // detaches the view; showAt: can re-display it
[self.bannerAd destroy];
.verticalLeft / .verticalRight rotate the banner 90° into a side strip (non-adaptive sizes only — adaptive banners fall back to a horizontal-bottom layout). hide() only detaches the view; it does not call destroy().
Publisher Extras
Attach arbitrary key → value pairs forwarded with the bid request. The API has the same shape at two levels:
| Scope | Where to call | Applies to |
|---|---|---|
| SDK-wide | sdk.addExtra | Every auction from this instance |
| Per ad unit | <AdUnit>.addExtra | Only that placement's auctions |
Passing nil as the value removes the key. At load time per-ad-unit extras are merged on top of the SDK-wide values — per-ad keys win on collision.
SDK-wide
- Swift
- Objective-C
sdk.addExtra("whale", forKey: "user_segment")
let all: [String: String] = sdk.extras
sdk.addExtra(nil, forKey: "user_segment") // remove
[sdk addExtra:@"whale" forKey:@"user_segment"];
NSDictionary<NSString *, NSString *> *all = sdk.extras;
[sdk addExtra:nil forKey:@"user_segment"]; // remove
Per ad unit
Available on BannerAd, InterstitialAd, and RewardedAd, with the same addExtra(_:forKey:) signature as the SDK-wide call:
- Swift
- Objective-C
interstitialAd.addExtra("level_complete", forKey: "placement_context")
let adExtras: [String: String] = interstitialAd.extras
[self.interstitialAd addExtra:@"level_complete" forKey:@"placement_context"];
NSDictionary<NSString *, NSString *> *adExtras = self.interstitialAd.extras;