开发背景
利用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函数做了基础实现,不然无法创建实例了