OC 反射實現(runtime)集合Admob案例

前言

因需要做一個廣告的聚合包,聚合包裏包含了各個廣告平臺的SDK,當需要時,把SDK引入,不需要時把SDK刪除即可,不需要動邏輯代碼。比如:當需要接入admob時,只需要把admob的庫引入項目中即可,不需要另外寫代碼,當不用admob時,把admob的庫刪除,此時,也不用修改代碼。
因此,實現中,就不能直接用admob的對象,需要通過反射去實現。
Java可以用反射做,object-c、swift可以用runtime機制去做。以下用object-c實現一種方式,供參考。

步驟

  1. 提供一個方法,檢測當前類是否實現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;
    } 
}
  1. 定義一個delegate的回調,如admob的請求廣告事件(interstitialDidReceiveAd:)
typedef void (^InterstitialDidReceiveAdBlock)(__weak id slf, id ad);
  1. 在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];
  1. 動態註冊方法
+ (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替換下方法實現。

  1. 獲取admob的對象
@property (nonatomic, strong) id interstitial;
Class adMobileAds = NSClassFromString(@"GADMobileAds");
self.interstitial = [admoInterstitial alloc]; 
  1. 設置廣告id
if([self.interstitial respondsToSelector:@selector(initWithAdUnitID:)]){
        [self.interstitial performSelector:@selector(initWithAdUnitID:) withObject:self.getAdID];
  }
  1. 設置admob的delegate
[self.interstitial setValue:self forKey:@"delegate"];
  1. 獲取admob的請求對象
Class admobRequest = NSClassFromString(@"GADRequest");
    id request;
    if ([admobRequest respondsToSelector:@selector(request)]) {
         request = [admobRequest performSelector:@selector(request)];
    }
  1. 請求廣告
 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"];
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章