前言
因需要做一个广告的聚合包,聚合包里包含了各个广告平台的SDK,当需要时,把SDK引入,不需要时把SDK删除即可,不需要动逻辑代码。比如:当需要接入admob时,只需要把admob的库引入项目中即可,不需要另外写代码,当不用admob时,把admob的库删除,此时,也不用修改代码。
因此,实现中,就不能直接用admob的对象,需要通过反射去实现。
Java可以用反射做,object-c、swift可以用runtime机制去做。以下用object-c实现一种方式,供参考。
步骤
- 提供一个方法,检测当前类是否实现delegate,如果没实现,则注册一个
//RunTimeTools.m
+ (Protocol *)objc_allocateProtocol:(const char *)name className:(Class) cls {
// 检查该协议是否已经注册
if (![self class_conformsToProtocol:cls protocol:NSProtocolFromString([NSString stringWithUTF8String:name])]) {
Protocol *protocol = objc_allocateProtocol(name);
if (protocol == nil) {
protocol = objc_getProtocol(name);//NSProtocolFromString([NSString stringWithUTF8String:name]);
}
return protocol;
}
return nil;
}
//判断是否实现delegate
+ (BOOL)class_conformsToProtocol:(Class)class protocol:(Protocol *)protocol {
if ([class conformsToProtocol:(protocol)]) {
return YES;
}else{
return NO;
}
}
- 定义一个delegate的回调,如admob的请求广告事件(interstitialDidReceiveAd:)
typedef void (^InterstitialDidReceiveAdBlock)(__weak id slf, id ad);
- 在load方法里,获取delegate,并替换方法
//GADInterstitialDelegate是admob的delegate
Protocol *protocol = [RunTimeTools objc_allocateProtocol:"GADInterstitialDelegate"
className:[AdMobInterstitialAdTask class]];
//interstitialDidReceiveAd 是admob收到广告的回调
SEL selector = @selector(interstitialDidReceiveAd:);
SEL swizzledSelector = [FTAPRunTimeTools swizzledSelectorForSelector:selector];
struct objc_method_description methodDescription = protocol_getMethodDescription(protocol, selector, NO, YES);
InterstitialDidReceiveAdBlock undefinedBlockReceive2 = ^(id slf, id ad) {
NSLog(@"admob 请求成功1 %@ ", [ad valueForKey:@"adUnitID"]);
};
InterstitialDidReceiveAdBlock implementationBlockReceive2 = ^(id slf, id ad) {
NSLog(@"%@ admob 请求成功2 %@ ",FTAPLogHead , [ad valueForKey:@"adUnitID"]);
};
//动态注册
[RunTimeTools replaceImplementationOfSelector:selector withSelector:swizzledSelector forClass:clz withMethodDescription:methodDescription implementationBlock:implementationBlockReceive2 undefinedBlock:undefinedBlockReceive2];
- 动态注册方法
+ (void)replaceImplementationOfSelector:(SEL)selector withSelector:(SEL)swizzledSelector forClass:(Class)cls withMethodDescription:(struct objc_method_description)methodDescription implementationBlock:(id)implementationBlock undefinedBlock:(id)undefinedBlock {
if ([self instanceRespondsButDoesNotImplementSelector:selector class:cls]) {
NSLog(@" 替换方法失败 ");
return;
}
if ([cls instancesRespondToSelector:selector]) {
NSLog(@" 已经有方法实现了,使用implementationBlock");
return;
}
IMP implementation = imp_implementationWithBlock((id)([cls instancesRespondToSelector:selector] ? implementationBlock : undefinedBlock));
Method oldMethod = class_getInstanceMethod(cls, selector);
if (oldMethod) {
class_addMethod(cls, swizzledSelector, implementation, methodDescription.types);
Method newMethod = class_getInstanceMethod(cls, swizzledSelector);
method_exchangeImplementations(oldMethod, newMethod);
} else {
class_addMethod(cls, selector, implementation, methodDescription.types);
}
}
+ (BOOL)instanceRespondsButDoesNotImplementSelector:(SEL)selector class:(Class)cls {
if ([cls instancesRespondToSelector:selector]) {
unsigned int numMethods = 0;
Method *methods = class_copyMethodList(cls, &numMethods);
BOOL implementsSelector = NO;
for (int index = 0; index < numMethods; index++) {
SEL methodSelector = method_getName(methods[index]);
if (selector == methodSelector) {
implementsSelector = YES;
break;
}
}
free(methods);
if (!implementsSelector) {
return YES;
}
}
return NO;
}
核心原理,就是检测下是否存在方法的实现,如果没有就
class_addMethod
添加一个方法,如果存在就method_exchangeImplementations
替换下方法实现。
- 获取admob的对象
@property (nonatomic, strong) id interstitial;
Class adMobileAds = NSClassFromString(@"GADMobileAds");
self.interstitial = [admoInterstitial alloc];
- 设置广告id
if([self.interstitial respondsToSelector:@selector(initWithAdUnitID:)]){
[self.interstitial performSelector:@selector(initWithAdUnitID:) withObject:self.getAdID];
}
- 设置admob的delegate
[self.interstitial setValue:self forKey:@"delegate"];
- 获取admob的请求对象
Class admobRequest = NSClassFromString(@"GADRequest");
id request;
if ([admobRequest respondsToSelector:@selector(request)]) {
request = [admobRequest performSelector:@selector(request)];
}
- 请求广告
if ([self.interstitial respondsToSelector:@selector(loadRequest:)]) {
[self.interstitial performSelector:@selector(loadRequest:) withObject: request];
}
至此,利用runtime机制,实现了不明文引用admob的对象,进行请求广告。
利用此方法接入三方库,要特别注意三方库的版本,避免版本不一样,导致方法的变动。
另外,提供一个代码设置admob的测试账号方法:
//1. 获取admobDeviceID
- (NSString *) admobDeviceID
{
NSUUID* adid = [[ASIdentifierManager sharedManager] advertisingIdentifier];
const char *cStr = [adid.UUIDString UTF8String];
unsigned char digest[16];
CC_MD5( cStr, strlen(cStr), digest );
NSMutableString *output = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH * 2];
for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++){
[output appendFormat:@"%02x", digest[i]];
}
return output;
}
//2. 把当前设备标记为测试设备
[request setValue:@[[self admobDeviceID]] forKey:@"testDevices"];