Files
wagic/adwhirl/AdWhirlSDK_iOS_3.1.1/AdWhirl/internal/AdWhirlView.m
2011-12-15 11:18:37 +00:00

1016 lines
33 KiB
Objective-C

/*
AdWhirlView.m
Copyright 2009 AdMob, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#import "AdWhirlView.h"
#import "AdWhirlView+.h"
#import "AdWhirlConfigStore.h"
#import "AdWhirlAdNetworkConfig.h"
#import "CJSONDeserializer.h"
#import "AdWhirlLog.h"
#import "AdWhirlAdNetworkAdapter.h"
#import "AdWhirlError.h"
#import "AdWhirlConfigStore.h"
#import "AWNetworkReachabilityWrapper.h"
#define kAdWhirlViewAdSubViewTag 1000
NSInteger adNetworkPriorityComparer(id a, id b, void *ctx) {
AdWhirlAdNetworkConfig *acfg = a, *bcfg = b;
if(acfg.priority < bcfg.priority)
return NSOrderedAscending;
else if(acfg.priority > bcfg.priority)
return NSOrderedDescending;
else
return NSOrderedSame;
}
@implementation AdWhirlView
#pragma mark Properties getters/setters
@synthesize delegate;
@synthesize config;
@synthesize prioritizedAdNetCfgs;
@synthesize currAdapter;
@synthesize lastAdapter;
@synthesize lastRequestTime;
@synthesize refreshTimer;
@synthesize lastError;
@synthesize showingModalView;
@synthesize configStore;
@synthesize rollOverReachability;
@synthesize testDarts;
- (void)setDelegate:(id <AdWhirlDelegate>)theDelegate {
[self willChangeValueForKey:@"delegate"];
delegate = theDelegate;
if (self.currAdapter) {
self.currAdapter.adWhirlDelegate = theDelegate;
}
if (self.lastAdapter) {
self.lastAdapter.adWhirlDelegate = theDelegate;
}
[self didChangeValueForKey:@"delegate"];
}
#pragma mark Life cycle methods
+ (AdWhirlView *)requestAdWhirlViewWithDelegate:(id<AdWhirlDelegate>)delegate {
if (![delegate respondsToSelector:
@selector(viewControllerForPresentingModalView)]) {
[NSException raise:@"AdWhirlIncompleteDelegateException"
format:@"AdWhirlDelegate must implement"
@" viewControllerForPresentingModalView"];
}
AdWhirlView *adView
= [[[AdWhirlView alloc] initWithDelegate:delegate] autorelease];
[adView startGetConfig]; // errors are communicated via delegate
return adView;
}
- (id)initWithDelegate:(id<AdWhirlDelegate>)d {
self = [super initWithFrame:kAdWhirlViewDefaultFrame];
if (self != nil) {
delegate = d;
self.backgroundColor = [UIColor clearColor];
// to prevent ugly artifacts if ad network banners are bigger than the
// default frame
self.clipsToBounds = YES;
showingModalView = NO;
appInactive = NO;
// default config store. Can be overridden for testing
self.configStore = [AdWhirlConfigStore sharedStore];
// get notified of app activity
NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter];
[notifCenter addObserver:self
selector:@selector(resignActive:)
name:UIApplicationWillResignActiveNotification
object:nil];
[notifCenter addObserver:self
selector:@selector(becomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
// remember pending ad requests, so we don't make more than one
// request per ad network at a time
pendingAdapters = [[NSMutableDictionary alloc] initWithCapacity:30];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[rollOverReachability setDelegate:nil];
[rollOverReachability release], rollOverReachability = nil;
delegate = nil;
[config removeDelegate:self];
[config release], config = nil;
[prioritizedAdNetCfgs release], prioritizedAdNetCfgs = nil;
totalPercent = 0.0;
requesting = NO;
currAdapter.adWhirlDelegate = nil, currAdapter.adWhirlView = nil;
[currAdapter release], currAdapter = nil;
lastAdapter.adWhirlDelegate = nil, lastAdapter.adWhirlView = nil;
[lastAdapter release], lastAdapter = nil;
[lastRequestTime release], lastRequestTime = nil;
[pendingAdapters release], pendingAdapters = nil;
if (refreshTimer != nil) {
[refreshTimer invalidate];
[refreshTimer release], refreshTimer = nil;
}
[lastError release], lastError = nil;
[super dealloc];
}
#pragma mark Config and setup methods
static id<AdWhirlDelegate> classAdWhirlDelegateForConfig = nil;
+ (void)startPreFetchingConfigurationDataWithDelegate:
(id<AdWhirlDelegate>)delegate {
if (classAdWhirlDelegateForConfig != nil) {
AWLogWarn(@"Called startPreFetchingConfig when another fetch is"
@" in progress");
return;
}
classAdWhirlDelegateForConfig = delegate;
[[AdWhirlConfigStore sharedStore] getConfig:[delegate adWhirlApplicationKey]
delegate:(id<AdWhirlConfigDelegate>)self];
}
+ (void)updateAdWhirlConfigWithDelegate:(id<AdWhirlDelegate>)delegate {
if (classAdWhirlDelegateForConfig != nil) {
AWLogWarn(@"Called updateConfig when another fetch is in progress");
return;
}
classAdWhirlDelegateForConfig = delegate;
[[AdWhirlConfigStore sharedStore]
fetchConfig:[delegate adWhirlApplicationKey]
delegate:(id<AdWhirlConfigDelegate>)self];
}
- (void)startGetConfig {
// Invalidate ad refresh timer as it may change with the new config
if (self.refreshTimer) {
[self.refreshTimer invalidate];
self.refreshTimer = nil;
}
configFetchAttempts = 0;
AdWhirlConfig *cfg = [configStore getConfig:[delegate adWhirlApplicationKey]
delegate:(id<AdWhirlConfigDelegate>)self];
self.config = cfg;
}
- (void)attemptFetchConfig {
AdWhirlConfig *cfg = [configStore
fetchConfig:[delegate adWhirlApplicationKey]
delegate:(id<AdWhirlConfigDelegate>)self];
if (cfg != nil) {
self.config = cfg;
}
}
- (void)updateAdWhirlConfig {
// Invalidate ad refresh timer as it may change with the new config
if (self.refreshTimer) {
[self.refreshTimer invalidate];
self.refreshTimer = nil;
}
// Request new config
AWLogDebug(@"======== Updating config ========");
configFetchAttempts = 0;
[self attemptFetchConfig];
}
#pragma mark Ads management private methods
- (void)buildPrioritizedAdNetCfgsAndMakeRequest {
NSMutableArray *freshNetCfgs = [[NSMutableArray alloc] init];
for (AdWhirlAdNetworkConfig *cfg in config.adNetworkConfigs) {
// do not add the ad network in rotation if there's already a stray
// pending ad request to this ad network (due to network outage or plain
// slow request)
NSNumber *netKey = [NSNumber numberWithInt:(int)cfg.networkType];
if ([pendingAdapters objectForKey:netKey] == nil) {
[freshNetCfgs addObject:cfg];
}
else {
AWLogDebug(@"Already has pending ad request for network type %d,"
@" not adding ad network config %@",
cfg.networkType, cfg);
}
}
[freshNetCfgs sortUsingFunction:adNetworkPriorityComparer context:nil];
totalPercent = 0.0;
for (AdWhirlAdNetworkConfig *cfg in freshNetCfgs) {
totalPercent += cfg.trafficPercentage;
}
self.prioritizedAdNetCfgs = freshNetCfgs;
[freshNetCfgs release];
[self makeAdRequest:YES];
}
static BOOL randSeeded = NO;
- (double)nextDart {
if (testDarts != nil) {
if (testDartIndex >= [testDarts count]) {
testDartIndex = 0;
}
NSNumber *nextDartNum = [testDarts objectAtIndex:testDartIndex];
double dart = [nextDartNum doubleValue];
if (dart >= totalPercent) {
dart = totalPercent - 0.001;
}
testDartIndex++;
return dart;
}
else {
if (!randSeeded) {
srandom(CFAbsoluteTimeGetCurrent());
randSeeded = YES;
}
return ((double)(random()-1)/RAND_MAX) * totalPercent;
}
}
- (AdWhirlAdNetworkConfig *)nextNetworkCfgByPercent {
if ([prioritizedAdNetCfgs count] == 0) {
return nil;
}
double dart = [self nextDart];
double tempTotal = 0.0;
AdWhirlAdNetworkConfig *result = nil;
for (AdWhirlAdNetworkConfig *network in prioritizedAdNetCfgs) {
result = network; // make sure there is always a network chosen
tempTotal += network.trafficPercentage;
if (dart < tempTotal) {
// this is the one to use.
break;
}
}
AWLogDebug(@">>>> By Percent chosen %@ (%@), dart %lf in %lf",
result.nid, result.networkName, dart, totalPercent);
return result;
}
- (AdWhirlAdNetworkConfig *)nextNetworkCfgByPriority {
if ([prioritizedAdNetCfgs count] == 0) {
return nil;
}
AdWhirlAdNetworkConfig *result = [prioritizedAdNetCfgs objectAtIndex:0];
AWLogDebug(@">>>> By Priority chosen %@ (%@)",
result.nid, result.networkName);
return result;
}
- (void)makeAdRequest:(BOOL)isFirstRequest {
if ([prioritizedAdNetCfgs count] == 0) {
// ran out of ad networks
[self notifyDelegateOfErrorWithCode:AdWhirlAdRequestNoMoreAdNetworks
description:@"No more ad networks to roll over"];
return;
}
if (showingModalView) {
AWLogDebug(@"Modal view is active, not going to request another ad");
return;
}
[self.rollOverReachability setDelegate:nil];
self.rollOverReachability = nil; // stop any roll over reachability checks
if (requesting) {
// it is OK to request a new one while another one is in progress
// the adapter callbacks from the old adapter will be ignored.
// User-initiated request ad will be blocked in requestFreshAd.
AWLogDebug(@"Already requesting ad, will request a new one.");
}
requesting = YES;
AdWhirlAdNetworkConfig *nextAdNetCfg = nil;
if (isFirstRequest && totalPercent > 0.0) {
nextAdNetCfg = [self nextNetworkCfgByPercent];
}
else {
nextAdNetCfg = [self nextNetworkCfgByPriority];
}
if (nextAdNetCfg == nil) {
[self notifyDelegateOfErrorWithCode:AdWhirlAdRequestNoMoreAdNetworks
description:@"No more ad networks to request"];
return;
}
AdWhirlAdNetworkAdapter *adapter =
[[nextAdNetCfg.adapterClass alloc] initWithAdWhirlDelegate:delegate
view:self
config:config
networkConfig:nextAdNetCfg];
// keep the last adapter around to catch stale ad network delegate calls
// during transitions
self.lastAdapter = self.currAdapter;
self.currAdapter = adapter;
[adapter release];
// take nextAdNetCfg out so we don't request again when we roll over
[prioritizedAdNetCfgs removeObject:nextAdNetCfg];
if (lastRequestTime) {
[lastRequestTime release];
}
lastRequestTime = [[NSDate date] retain];
// remember this pending request so we do not request again when we make
// new ad requests
NSNumber *netTypeKey = [NSNumber numberWithInt:(int)nextAdNetCfg.networkType];
[pendingAdapters setObject:currAdapter forKey:netTypeKey];
// If last adapter is of the same network type, make the last adapter stop
// being an ad network view delegate to prevent the last adapter from calling
// back to this AdWhirlView during the transition and afterwards.
// We should not do this for all adapters, because if the last adapter is
// still in progress, we need to know about it in the adapter callbacks.
// That the last adapter is the same type as the new adapter is possible only
// if the last ad request finished, i.e. called back to its adapters. There
// are cases, e.g. iAd, when the ad network may call back multiple times,
// because of internal refreshes.
if (self.lastAdapter.networkConfig.networkType ==
self.currAdapter.networkConfig.networkType) {
[self.lastAdapter stopBeingDelegate];
}
[currAdapter getAd];
}
- (BOOL)canRefresh {
return !(ignoreNewAdRequests
|| ignoreAutoRefreshTimer
|| appInactive
|| showingModalView);
}
- (void)timerRequestFreshAd {
if (![self canRefresh]) {
AWLogDebug(@"Not going to refresh due to flags, app not active or modal");
return;
}
if (lastRequestTime != nil) {
NSTimeInterval sinceLast = -[lastRequestTime timeIntervalSinceNow];
if (sinceLast <= kAWMinimumTimeBetweenFreshAdRequests) {
AWLogDebug(@"Ad refresh timer fired too soon after last ad request,"
@" ignoring");
return;
}
}
AWLogDebug(@"======== Refreshing ad due to timer ========");
[self buildPrioritizedAdNetCfgsAndMakeRequest];
}
#pragma mark Ads management public methods
- (void)requestFreshAd {
// only make request in main thread
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(requestFreshAd)
withObject:nil
waitUntilDone:NO];
return;
}
if (ignoreNewAdRequests) {
// don't request new ad
[self notifyDelegateOfErrorWithCode:AdWhirlAdRequestIgnoredError
description:@"ignoreNewAdRequests flag set"];
return;
}
if (requesting) {
// don't request if there's a request outstanding
[self notifyDelegateOfErrorWithCode:AdWhirlAdRequestInProgressError
description:@"Ad request already in progress"];
return;
}
if (showingModalView) {
// don't request if there's a modal view active
[self notifyDelegateOfErrorWithCode:AdWhirlAdRequestModalActiveError
description:@"Modal view active"];
return;
}
if (!config) {
[self notifyDelegateOfErrorWithCode:AdWhirlAdRequestNoConfigError
description:@"No ad configuration"];
return;
}
if (lastRequestTime != nil) {
NSTimeInterval sinceLast = -[lastRequestTime timeIntervalSinceNow];
if (sinceLast <= kAWMinimumTimeBetweenFreshAdRequests) {
NSString *desc
= [NSString stringWithFormat:
@"Requesting fresh ad too soon! It has been only %lfs. Minimum %lfs",
sinceLast, kAWMinimumTimeBetweenFreshAdRequests];
[self notifyDelegateOfErrorWithCode:AdWhirlAdRequestTooSoonError
description:desc];
return;
}
}
[self buildPrioritizedAdNetCfgsAndMakeRequest];
}
- (void)rollOver {
if (ignoreNewAdRequests) {
return;
}
// only make request in main thread
if (![NSThread isMainThread]) {
[self performSelectorOnMainThread:@selector(rollOver)
withObject:nil
waitUntilDone:NO];
return;
}
[self makeAdRequest:NO];
}
- (BOOL)adExists {
UIView *currAdView = [self viewWithTag:kAdWhirlViewAdSubViewTag];
return currAdView != nil;
}
- (NSString *)mostRecentNetworkName {
if (currAdapter == nil) return nil;
return currAdapter.networkConfig.networkName;
}
- (void)ignoreAutoRefreshTimer {
ignoreAutoRefreshTimer = YES;
}
- (void)doNotIgnoreAutoRefreshTimer {
ignoreAutoRefreshTimer = NO;
}
- (BOOL)isIgnoringAutoRefreshTimer {
return ignoreAutoRefreshTimer;
}
- (void)ignoreNewAdRequests {
ignoreNewAdRequests = YES;
}
- (void)doNotIgnoreNewAdRequests {
ignoreNewAdRequests = NO;
}
- (BOOL)isIgnoringNewAdRequests {
return ignoreNewAdRequests;
}
#pragma mark Stats reporting methods
- (void)metricPing:(NSURL *)endPointBaseURL
nid:(NSString *)nid
netType:(AdWhirlAdNetworkType)type {
// use config.appKey not from [delegate adWhirlApplicationKey] as delegate
// can be niled out at this point. Attempt at Issue #42 .
NSString *query
= [NSString stringWithFormat:
@"?appid=%@&nid=%@&type=%d&country_code=%@&appver=%d&client=1",
config.appKey,
nid,
type,
[[NSLocale currentLocale] localeIdentifier],
kAdWhirlAppVer];
NSURL *metURL = [NSURL URLWithString:query
relativeToURL:endPointBaseURL];
AWLogDebug(@"Sending metric ping to %@", metURL);
NSURLRequest *metRequest = [NSURLRequest requestWithURL:metURL];
[NSURLConnection connectionWithRequest:metRequest
delegate:nil]; // fire and forget
}
- (void)reportExImpression:(NSString *)nid netType:(AdWhirlAdNetworkType)type {
NSURL *baseURL = nil;
if ([delegate respondsToSelector:@selector(adWhirlImpMetricURL)]) {
baseURL = [delegate adWhirlImpMetricURL];
}
if (baseURL == nil) {
baseURL = [NSURL URLWithString:kAdWhirlDefaultImpMetricURL];
}
[self metricPing:baseURL nid:nid netType:type];
}
- (void)reportExClick:(NSString *)nid netType:(AdWhirlAdNetworkType)type {
NSURL *baseURL = nil;
if ([delegate respondsToSelector:@selector(adWhirlClickMetricURL)]) {
baseURL = [delegate adWhirlClickMetricURL];
}
if (baseURL == nil) {
baseURL = [NSURL URLWithString:kAdWhirlDefaultClickMetricURL];
}
[self metricPing:baseURL nid:nid netType:type];
}
#pragma mark UI methods
- (CGSize)actualAdSize {
if (currAdapter == nil || currAdapter.adNetworkView == nil)
return kAdWhirlViewDefaultSize;
return currAdapter.adNetworkView.frame.size;
}
- (void)rotateToOrientation:(UIInterfaceOrientation)orientation {
if (currAdapter == nil) return;
[currAdapter rotateToOrientation:orientation];
}
- (void)transitionToView:(UIView *)view {
UIView *currAdView = [self viewWithTag:kAdWhirlViewAdSubViewTag];
if (view == currAdView) {
AWLogDebug(@"ignoring ad transition to itself");
return; // no need to transition to itself
}
view.tag = kAdWhirlViewAdSubViewTag;
if (currAdView) {
// swap
currAdView.tag = 0;
AWBannerAnimationType animType;
if (config.bannerAnimationType == AWBannerAnimationTypeRandom) {
if (!randSeeded) {
srandom(CFAbsoluteTimeGetCurrent());
}
// range is 1 to 7, inclusive
animType = (random() % 7) + 1;
AWLogDebug(@"Animation type chosen by random is %d", animType);
}
else {
animType = config.bannerAnimationType;
}
if (![currAdapter isBannerAnimationOK:animType]) {
animType = AWBannerAnimationTypeNone;
}
if (animType == AWBannerAnimationTypeNone) {
[currAdView removeFromSuperview];
[self addSubview:view];
if ([delegate respondsToSelector:
@selector(adWhirlDidAnimateToNewAdIn:)]) {
// no animation, callback right away
[(NSObject *)delegate
performSelectorOnMainThread:@selector(adWhirlDidAnimateToNewAdIn:)
withObject:self
waitUntilDone:NO];
}
}
else {
switch (animType) {
case AWBannerAnimationTypeSlideFromLeft:
{
CGRect f = view.frame;
f.origin.x = -f.size.width;
view.frame = f;
[self addSubview:view];
break;
}
case AWBannerAnimationTypeSlideFromRight:
{
CGRect f = view.frame;
f.origin.x = self.frame.size.width;
view.frame = f;
[self addSubview:view];
break;
}
case AWBannerAnimationTypeFadeIn:
view.alpha = 0;
[self addSubview:view];
break;
default:
// no setup required for other animation types
break;
}
[currAdView retain]; // will be released when animation is done
AWLogDebug(@"Beginning AdWhirlAdTransition animation"
@" currAdView %x incoming %x", currAdView, view);
[UIView beginAnimations:@"AdWhirlAdTransition" context:currAdView];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:
@selector(newAdAnimationDidStopWithAnimationID:finished:context:)];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:1.0];
// cache has to set to NO because of VideoEgg
switch (animType) {
case AWBannerAnimationTypeFlipFromLeft:
[self addSubview:view];
[currAdView removeFromSuperview];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft
forView:self
cache:NO];
break;
case AWBannerAnimationTypeFlipFromRight:
[self addSubview:view];
[currAdView removeFromSuperview];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight
forView:self
cache:NO];
break;
case AWBannerAnimationTypeCurlUp:
[self addSubview:view];
[currAdView removeFromSuperview];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp
forView:self
cache:NO];
break;
case AWBannerAnimationTypeCurlDown:
[self addSubview:view];
[currAdView removeFromSuperview];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlDown
forView:self
cache:NO];
break;
case AWBannerAnimationTypeSlideFromLeft:
case AWBannerAnimationTypeSlideFromRight:
{
CGRect f = view.frame;
f.origin.x = 0;
view.frame = f;
break;
}
case AWBannerAnimationTypeFadeIn:
view.alpha = 1.0;
break;
default:
[self addSubview:view];
AWLogWarn(@"Unrecognized Animation type: %d", animType);
break;
}
[UIView commitAnimations];
}
}
else { // currAdView
// new
[self addSubview:view];
if ([delegate respondsToSelector:@selector(adWhirlDidAnimateToNewAdIn:)]) {
// no animation, callback right away
[(NSObject *)delegate
performSelectorOnMainThread:@selector(adWhirlDidAnimateToNewAdIn:)
withObject:self
waitUntilDone:NO];
}
}
}
- (void)replaceBannerViewWith:(UIView*)bannerView {
[self transitionToView:bannerView];
}
// Called at the end of the new ad animation; we use this opportunity to do
// memory management cleanup. See the comment in adDidLoad:.
- (void)newAdAnimationDidStopWithAnimationID:(NSString *)animationID
finished:(BOOL)finished
context:(void *)context
{
AWLogDebug(@"animation %@ finished %@ context %x",
animationID, finished? @"YES":@"NO", context);
UIView *adViewToRemove = (UIView *)context;
[adViewToRemove removeFromSuperview];
[adViewToRemove release]; // was retained before beginAnimations
lastAdapter.adWhirlDelegate = nil, lastAdapter.adWhirlView = nil;
self.lastAdapter = nil;
if ([delegate respondsToSelector:@selector(adWhirlDidAnimateToNewAdIn:)]) {
[delegate adWhirlDidAnimateToNewAdIn:self];
}
}
#pragma mark UIView touch methods
- (BOOL)_isEventATouch30:(UIEvent *)event {
if ([event respondsToSelector:@selector(type)]) {
return event.type == UIEventTypeTouches;
}
return YES; // UIEvent in 2.2.1 has no type property, so assume yes.
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
BOOL itsInside = [super pointInside:point withEvent:event];
if (itsInside && currAdapter != nil && lastNotifyAdapter != currAdapter
&& [self _isEventATouch30:event]
&& [currAdapter shouldSendExMetric]) {
lastNotifyAdapter = currAdapter;
[self reportExClick:currAdapter.networkConfig.nid
netType:currAdapter.networkConfig.networkType];
}
return itsInside;
}
#pragma mark UIView methods
- (void)willMoveToSuperview:(UIView *)newSuperview {
if (newSuperview == nil) {
[refreshTimer invalidate];
self.refreshTimer = nil;
}
}
#pragma mark Adapter callbacks
// Chores that are common to all adapter callbacks
- (void)adRequestReturnsForAdapter:(AdWhirlAdNetworkAdapter *)adapter {
// no longer pending. Need to retain and autorelease the adapter
// since the adapter may not be retained anywhere else other than the pending
// dict
NSNumber *netTypeKey
= [NSNumber numberWithInt:(int)adapter.networkConfig.networkType];
AdWhirlAdNetworkAdapter *pendingAdapter
= [pendingAdapters objectForKey:netTypeKey];
if (pendingAdapter != nil) {
if (pendingAdapter != adapter) {
// Possible if the ad refreshes itself and sends callbacks doing so, while
// a new ad of the same network is pending (e.g. iAd)
AWLogError(@"Stored pending adapter %@ for network type %@ is different"
@" from the one sending the adapter callback %@",
pendingAdapter,
netTypeKey,
adapter);
}
[[pendingAdapter retain] autorelease];
[pendingAdapters removeObjectForKey:netTypeKey];
}
}
- (void)adapter:(AdWhirlAdNetworkAdapter *)adapter
didReceiveAdView:(UIView *)view {
[self adRequestReturnsForAdapter:adapter];
if (adapter != currAdapter) {
AWLogDebug(@"Received didReceiveAdView from a stale adapter %@", adapter);
return;
}
AWLogDebug(@"Received ad from adapter (nid %@)", adapter.networkConfig.nid);
// UIView operations should be performed on main thread
[self performSelectorOnMainThread:@selector(transitionToView:)
withObject:view
waitUntilDone:NO];
requesting = NO;
// report impression and notify delegate
if ([adapter shouldSendExMetric]) {
[self reportExImpression:adapter.networkConfig.nid
netType:adapter.networkConfig.networkType];
}
if ([delegate respondsToSelector:@selector(adWhirlDidReceiveAd:)]) {
[delegate adWhirlDidReceiveAd:self];
}
}
- (void)adapter:(AdWhirlAdNetworkAdapter *)adapter didFailAd:(NSError *)error {
[self adRequestReturnsForAdapter:adapter];
if (adapter != currAdapter) {
AWLogDebug(@"Received didFailAd from a stale adapter %@: %@",
adapter, error);
return;
}
AWLogDebug(@"Failed to receive ad from adapter (nid %@): %@",
adapter.networkConfig.nid, error);
requesting = NO;
if ([prioritizedAdNetCfgs count] == 0) {
// we have run out of networks to try and need to error out.
[self notifyDelegateOfErrorWithCode:AdWhirlAdRequestNoMoreAdNetworks
description:@"No more ad networks to roll over"];
return;
}
// try to roll over, but before we do, check to see if the failure is because
// network has gotten unreachable. If so, don't roll over. Use www.google.com
// as test, assuming www.google.com itself is always up if there's network.
self.rollOverReachability
= [AWNetworkReachabilityWrapper reachabilityWithHostname:@"www.google.com"
callbackDelegate:self];
if (self.rollOverReachability == nil) {
[self notifyDelegateOfErrorWithCode:AdWhirlAdRequestNoNetworkError
description:@"Failed network reachability test"];
return;
}
if (![self.rollOverReachability scheduleInCurrentRunLoop]) {
[self notifyDelegateOfErrorWithCode:AdWhirlAdRequestNoNetworkError
description:@"Failed network reachability test"];
return;
}
}
- (void)adapterDidFinishAdRequest:(AdWhirlAdNetworkAdapter *)adapter {
[self adRequestReturnsForAdapter:adapter];
if (adapter != currAdapter) {
AWLogDebug(@"Received adapterDidFinishAdRequest from a stale adapter");
return;
}
// view is supplied via other mechanism (e.g. Generic Notification or Event)
requesting = NO;
// report impression. No need to notify delegate because delegate is notified
// via Generic Notification or event.
if ([adapter shouldSendExMetric]) {
[self reportExImpression:adapter.networkConfig.nid
netType:adapter.networkConfig.networkType];
}
}
#pragma mark AWNetworkReachabilityDelegate methods
- (void)reachabilityNotReachable:(AWNetworkReachabilityWrapper *)reach {
if (reach == self.rollOverReachability) {
[self.rollOverReachability setDelegate:nil];
self.rollOverReachability = nil; // release it and unschedule
[self notifyDelegateOfErrorWithCode:AdWhirlAdRequestNoNetworkError
description:@"No network connection for rollover"];
return;
}
AWLogWarn(@"Unrecognized reachability called not reachable %s:%d",
__FILE__, __LINE__);
}
- (void)reachabilityBecameReachable:(AWNetworkReachabilityWrapper *)reach {
if (reach == self.rollOverReachability) {
// not an error, just need to rollover
[lastError release], lastError = nil;
if ([delegate respondsToSelector:
@selector(adWhirlDidFailToReceiveAd:usingBackup:)]) {
[delegate adWhirlDidFailToReceiveAd:self usingBackup:YES];
}
[self.rollOverReachability setDelegate:nil];
self.rollOverReachability = nil; // release it and unschedule
[self rollOver];
return;
}
AWLogWarn(@"Unrecognized reachability called reachable %s:%d",
__FILE__, __LINE__);
}
#pragma mark AdWhirlConfigDelegate methods
+ (NSURL *)adWhirlConfigURL {
if (classAdWhirlDelegateForConfig != nil
&& [classAdWhirlDelegateForConfig respondsToSelector:
@selector(adWhirlConfigURL)]) {
return [classAdWhirlDelegateForConfig adWhirlConfigURL];
}
return nil;
}
+ (void)adWhirlConfigDidReceiveConfig:(AdWhirlConfig *)config {
AWLogDebug(@"Fetched Ad network config: %@", config);
if (classAdWhirlDelegateForConfig != nil
&& [classAdWhirlDelegateForConfig respondsToSelector:
@selector(adWhirlDidReceiveConfig:)]) {
[classAdWhirlDelegateForConfig adWhirlDidReceiveConfig:nil];
}
classAdWhirlDelegateForConfig = nil;
}
+ (void)adWhirlConfigDidFail:(AdWhirlConfig *)cfg error:(NSError *)error {
AWLogError(@"Failed pre-fetching AdWhirl config: %@", error);
classAdWhirlDelegateForConfig = nil;
}
- (void)adWhirlConfigDidReceiveConfig:(AdWhirlConfig *)cfg {
if (self.config != cfg) {
AWLogWarn(@"AdWhirlView: getting adWhirlConfigDidReceiveConfig callback"
@" from unknown AdWhirlConfig object");
return;
}
AWLogDebug(@"Fetched Ad network config: %@", cfg);
if ([delegate respondsToSelector:@selector(adWhirlDidReceiveConfig:)]) {
[delegate adWhirlDidReceiveConfig:self];
}
if (cfg.adsAreOff) {
if ([delegate respondsToSelector:
@selector(adWhirlReceivedNotificationAdsAreOff:)]) {
// to prevent self being freed before this returns, in case the
// delegate decides to release this
[self retain];
[delegate adWhirlReceivedNotificationAdsAreOff:self];
[self autorelease];
}
return;
}
// Perform ad network data structure build and request in main thread
// to avoid contention
[self performSelectorOnMainThread:
@selector(buildPrioritizedAdNetCfgsAndMakeRequest)
withObject:nil
waitUntilDone:NO];
// Setup recurring timer for ad refreshes, if required
if (config.refreshInterval > kAWMinimumTimeBetweenFreshAdRequests) {
self.refreshTimer
= [NSTimer scheduledTimerWithTimeInterval:config.refreshInterval
target:self
selector:@selector(timerRequestFreshAd)
userInfo:nil
repeats:YES];
}
}
- (void)adWhirlConfigDidFail:(AdWhirlConfig *)cfg error:(NSError *)error {
if (self.config != nil && self.config != cfg) {
// self.config could be nil if this is called before init is finished
AWLogWarn(@"AdWhirlView: getting adWhirlConfigDidFail callback from unknown"
@" AdWhirlConfig object");
return;
}
configFetchAttempts++;
if (configFetchAttempts < 3) {
// schedule in run loop to avoid recursive calls to this function
[self performSelectorOnMainThread:@selector(attemptFetchConfig)
withObject:self
waitUntilDone:NO];
}
else {
AWLogError(@"Failed fetching AdWhirl config: %@", error);
[self notifyDelegateOfError:error];
}
}
- (NSURL *)adWhirlConfigURL {
if ([delegate respondsToSelector:@selector(adWhirlConfigURL)]) {
return [delegate adWhirlConfigURL];
}
return nil;
}
#pragma mark Active status notification callbacks
- (void)resignActive:(NSNotification *)notification {
AWLogDebug(@"App become inactive, AdWhirlView will stop requesting ads");
appInactive = YES;
}
- (void)becomeActive:(NSNotification *)notification {
AWLogDebug(@"App become active, AdWhirlView will resume requesting ads");
appInactive = NO;
}
#pragma mark AdWhirlDelegate helper methods
- (void)notifyDelegateOfErrorWithCode:(NSInteger)errorCode
description:(NSString *)desc {
NSError *error = [[AdWhirlError alloc] initWithCode:errorCode
description:desc];
[self notifyDelegateOfError:error];
[error release];
}
- (void)notifyDelegateOfError:(NSError *)error {
[error retain];
[lastError release];
lastError = error;
if ([delegate respondsToSelector:
@selector(adWhirlDidFailToReceiveAd:usingBackup:)]) {
// to prevent self being freed before this returns, in case the
// delegate decides to release this
[self retain];
[delegate adWhirlDidFailToReceiveAd:self usingBackup:NO];
[self autorelease];
}
}
@end