开发背景
利用adobe air开发完游戏后,需要针对ios或者android平台进行支付、推送的sdk接入,本文可以用来彻底解决ios平台下delegate生命周期几个回调函数的调用,实现原生的推送、支付功能的接入
hook知识背景
(objc里的Method Swizzling,本节内容转自http://blog.csdn.net/yiyaaixuexi)
在Objective-C中调用一个方法,其实是向一个对象发送消息,查找消息的唯一依据是selector的名字。利用Objective-C的动态特性,可以实现在运行时偷换selector对应的方法实现,达到给方法挂钩的目的。
每个类都有一个方法列表,存放着selector的名字和方法实现的映射关系。IMP有点类似函数指针,指向具体的Method实现。
我们可以利用 method_exchangeImplementations 来交换2个方法中的IMP,
我们可以利用 class_replaceMethod 来修改类,
我们可以利用 method_setImplementation 来直接设置某个方法的IMP,
……
归根结底,都是偷换了selector的IMP,如下图所示:
实现思路
1、在MDChangeDelegateHelper类加载阶段就对adboe air的appdelegate类进行方法hook,确保在adobe air的appdelegate在创建前替换成我们新实现的类
2、需要3个SEL,
一个AppDelegate原SEL:oldSEL,
一个MDChangeDelegateHelper的默认SEL:defaultSEL,用于为原appdelegate添加默认的原oldSEL
一个MDChangeDelegateHelper的目标SEL:newSEL
方法替换思路:
3、替换后对应的sel关系为
oldSEL -- newMethod
newSEL -- oldMethod
所以当对应的appdelegate方法被调用时,
oldSEL找到了newMethod的实现,newMethod的实现在MDChangeDelegateHelper.m内的hookedxxx方法
在newMethold中调用了newSEL,newSEL指向oldMethod,实现了原流程的兼容
代码实现
// // MDChangeDelegateHelper.m // ChangeDelegateDemo // // Created by ashqal on 14-10-31. // Copyright (c) 2014年 moredoo. All rights reserved. // #import "MDChangeDelegateHelper.h" #import <UIKit/UIKit.h> #import <objc/runtime.h> void hookMethod(SEL oldSEL,SEL defaultSEL, SEL newSEL) { //CTAppController Class aClass = objc_getClass("CTAppController"); if ( aClass == 0 ) { NSLog(@"!!!!!!!!!!!!!did not find adobe class!"); return; } Class bClass = [MDChangeDelegateHelper class]; //把方法加给aClass class_addMethod(aClass, newSEL, class_getMethodImplementation(bClass, newSEL),nil); class_addMethod(aClass, oldSEL, class_getMethodImplementation(bClass, defaultSEL),nil); Method oldMethod = class_getInstanceMethod(aClass, oldSEL); assert(oldMethod); Method newMethod = class_getInstanceMethod(aClass, newSEL); assert(newMethod); method_exchangeImplementations(oldMethod, newMethod); } @implementation MDChangeDelegateHelper + (void)load { NSLog(@"MDChangeDelegateHelper load"); //[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(createStarterNotificationChecker:) //name:@"UIApplicationWillFinishLaunchingNotification" object:nil]; //[self changeDelegate]; hookMethod( @selector(application:didFinishLaunchingWithOptions:) ,@selector(defaultApplication:didFinishLaunchingWithOptions:) ,@selector(hookedApplication:didFinishLaunchingWithOptions:) ); hookMethod( @selector(application:handleOpenURL:) , @selector(defaultApplication:handleOpenURL:) , @selector(hookedApplication:handleOpenURL:) ); hookMethod(@selector(application:openURL:sourceApplication:annotation:) , @selector(defaultApplication:openURL:sourceApplication:annotation:) , @selector(hookedApplication:openURL:sourceApplication:annotation:)); hookMethod(@selector(application:supportedInterfaceOrientationsForWindow:) , @selector(defaultApplication:supportedInterfaceOrientationsForWindow:) , @selector(hookedApplication:supportedInterfaceOrientationsForWindow:) ); hookMethod(@selector(applicationDidBecomeActive:) , @selector(defaultApplicationDidBecomeActive:) , @selector(hookedApplicationDidBecomeActive:) ); hookMethod(@selector(init) , @selector(init) , @selector(hookedInit) ); } -(BOOL)hookedApplication:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)dic { NSLog(@"hooked didFinishLaunchingWithOptions"); [self hookedApplication:application didFinishLaunchingWithOptions:dic]; return YES; } -(id)hookedInit { NSLog(@"hooked init!!!"); return [self hookedInit]; } - (BOOL)hookedApplication:(UIApplication *)application handleOpenURL:(NSURL *)url { NSLog(@"hooked handleOpenURL"); [self hookedApplication:application handleOpenURL:url]; return YES; } - (BOOL)hookedApplication:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { NSLog(@"hooked openURL sourceApplication annotation"); [self hookedApplication:application openURL:url sourceApplication:sourceApplication annotation:annotation]; return YES; } - (NSUInteger) hookedApplication:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window { NSLog(@"hooked supportedInterfaceOrientationsForWindow"); [self hookedApplication:application supportedInterfaceOrientationsForWindow:window ]; return 0; } - (void)hookedApplicationDidBecomeActive:(UIApplication *)application { [self hookedApplicationDidBecomeActive:application]; NSLog(@"hooked applicationDidBecomeActive"); } - (BOOL)defaultApplication:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)dic{ return YES;} - (BOOL)defaultApplication:(UIApplication *)application handleOpenURL:(NSURL *)url{return YES;} - (BOOL)defaultApplication:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation{return YES;} - (NSUInteger) defaultApplication:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window{return 0;} - (void)defaultApplicationDidBecomeActive:(UIApplication *)application{} - (id)init { self = [super init]; if (self) { } return self; } @end
注意点
a) 在运行时nslog出adobe air的appdelegate名字为CTAppController,所以将此类作为替换对象
b) 在对init函数进行替换时发现CTAppController没有实现init函数,所以将默认的init函数做了基础实现,不然无法创建实例了