AOP (面向切面编程)

1. AOP简介

AOP为Aspect Oriented Programming的缩写:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。


2. iOS中的AOP

在iOS中实现AOP的核心技术是Runtime,使用Runtime的Method Swizzling黑魔法,我们可以移花接木,在运行时将方法的具体实现添油加醋、偷梁换柱。也就是说,AOP做一些与主业务无关的事,不影响主业务逻辑。

例如埋点,常见的三种埋点,页面进入次数、页面停留时间、点击事件,都可以通过运行时方法替换技术来插入埋点方法,具体实现方法,先写一个运行时替换方法。例如以下分类实现埋点操作

替换的方法如下

+(void)hookClass:(Class)classObject fromSelector:(SEL)fromSelector toSelector:(SEL)toSelector{

 Classclass = classObject;

 // 得到被替换类的实例方法

 MethodfromMethod = class_getInstanceMethod(class, fromSelector);

 // 得到替换类的实例方法

 MethodtoMethod = class_getInstanceMethod(class, toSelector);


 //返回失败,表示交换方法已经存在

 if(class_addMethod(class, fromSelector, method_getImplementation(toMethod), method_getTypeEncoding(fromMethod))) {


 class_replaceMethod(class, toSelector, method_getImplementation(fromMethod), method_getTypeEncoding(toMethod));

    }else{

 //函数直接进行IMP指针交换以实现方法交换

 method_exchangeImplementations(fromMethod, toMethod);

}

------页面进入次数、页面停留时间

@implementationUIViewController (Points)

+(void)initialize{

 staticdispatch_once_tonceToken;

 dispatch_once(&onceToken, ^{

 SELfromWillAppearSelector = @selector(viewWillAppear:);

 SELtoWillAppearSelector = @selector(hook_viewWillAppear:);

        [LHHookhookClass:selffromSelector:fromWillAppearSelector toSelector:toWillAppearSelector];


 SELfromillDisappear = @selector(viewWillDisappear:);

 SELtoillDisappear= @selector(hook_viewWillDisAppear:);

        [LHHookhookClass:selffromSelector:fromillDisappear toSelector:toillDisappear];

 });

}

-(void)hook_viewWillAppear:(BOOL)animated{

 // 进来的时间 根据具体的业务去加时间的统计

         [selfcomeIn];

 NSLog(@"__%@___%s",NSStringFromClass(self.class), __func__);


       [selfhook_viewWillAppear:animated];

}

-(void)hook_viewWillDisAppear:(BOOL)animated{

 // 出去的时间 统计方法根据具体的业务加

     [selfcomeOut];

//    NSLog(@"_____%s",__func__);

    [selfhook_viewWillDisAppear:animated];

}

-------点击事件

@implementationUIButton (TouchInside)

+(void)initialize{

 staticdispatch_once_tonceToken;

 dispatch_once(&onceToken, ^{

 SELfromSelector = @selector(sendAction:to:forEvent:);

 SELtoSelector = @selector(hook_sendAction:to:forEvent:);

        [LHHookhookClass:selffromSelector:fromSelector toSelector:toSelector];

    });

}

-(void)hook_sendAction:(SEL)action to:(id)target forEvent:(UIEvent*)event{

    [selfinsertAction:action to:target forEvent:event];

    [selfhook_sendAction:action to:target forEvent:event];


}

//对此方法进行相关埋点操作

-(void)insertAction:(SEL)action to:(id)target forEvent:(UIEvent*)event{

 NSString* actionName = NSStringFromSelector(action);//目标方法

 NSString* targetName = NSStringFromClass([target class]);//目标类

}

3. iOS中的AOP开发,直接使用Runtime方法交换开发的风险有哪些?

 Runtime不仅能够进行方法的交换,还能够在运行时Objective-C特性(比如类、成员函数、继承)的增删改查操作。

(1)需要在+load方法进行交换,  其他时候交换,难以保证另一个线程中不会同时调用被交换的方法,从而导致程序的崩溃。

(2)被交换必须是当前类的方法,不能是父类的方法,直接把父类的实现拷贝过来不会起作用。父类的方法必须是在调用的时候使用,而不是方法交换的时候使用。

(3)方法交换命名冲突,可能导致方法交换失败。

4.Aspects-安全的方法交换库(Runtime的消息转发机制来实现方法交换)

   将所有的方法调用都指到_objc_msgForward函数调用上,也就利用了forwardInvocation进行转发,最后通过NSInvocation调用来实现方法交换。

// 核心方法 1.Hook forwardInvocation 到自己的方法

// 2.交换原方法的实现为_objc_msgForward 使其直接进入消息转发模式

 核心代码

staticvoidaspect_prepareClassAndHookSelector(NSObject*self, SELselector, NSError**error) {

 NSCParameterAssert(selector);

 // 1  swizzling forwardInvocation

 Classklass = aspect_hookClass(self, error);


 // // 被 hook 的 selector

 MethodtargetMethod = class_getInstanceMethod(klass, selector);

 IMPtargetMethodIMP = method_getImplementation(targetMethod);

 // 判断需要被Hook的方法是否应指向 _objc_msgForward 进入消息转发模式

 if(!aspect_isMsgForwardIMP(targetMethodIMP)) {

 // Make a method alias for the existing method implementation, it not already copied.

 // 让一个新的子类方法名指向原先方法的实现,处理回调

 constchar*typeEncoding = method_getTypeEncoding(targetMethod);

 SELaliasSelector = aspect_aliasForSelector(selector);

 if(![klass instancesRespondToSelector:aliasSelector]) {

 __unusedBOOLaddedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);

 NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);

        }


 // We use forwardInvocation to hook in.

 // 把 selector 指向 _objc_msgForward 函数

 // 用 _objc_msgForward 函数指针代替 selector 的 imp,然后执行这个 imp  进入消息转发模式

 class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);

 AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));

    }

}

如何使用:

+ (id<AspectToken>)aspect_hookSelector:(SEL)selector

                      withOptions:(AspectOptions)options

                       usingBlock:(id)block

                            error:(NSError**)error;

/// Adds a block of code before/instead/after the current `selector` for a specific instance.

- (id<AspectToken>)aspect_hookSelector:(SEL)selector

                      withOptions:(AspectOptions)options

                       usingBlock:(id)block

                            error:(NSError**)error;

举例说明下:

例子一:所有的UIViewController进行AOP操作,替换刚刚的方法

        [UIViewControlleraspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionInsteadusingBlock:^(id<AspectInfo> info) {

 NSString*className = NSStringFromClass([[info instance] class]);

 NSLog(@"aspect-->%@",className);

} error:NULL];

        [UIViewControlleraspect_hookSelector:@selector(viewWillDisappear:) withOptions:AspectPositionAfterusingBlock:^(id<AspectInfo> info) {

 NSString*className = NSStringFromClass([[info instance] class]);

 NSLog(@"aspect-->%@",className);

} error:NULL];

例子二:对于实例方法

    - (void)hello:(NSString*)str{

 NSLog(@"——————hello__%@",str);

}

+(void)load{

//    AspectPositionAfter   = 0,            /// Called after the original implementation (default)

//    AspectPositionInstead = 1,            /// Will replace the original implementation.

//    AspectPositionBefore  = 2,            /// Called before the original implementation.

//

//    AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.

 //   AspectInfo 被勾的对象

    [selfaspect_hookSelector:@selector(hello:) withOptions:AspectPositionBeforeusingBlock:^(id<AspectInfo> info,NSString*str) {


 //调用的实例对象

 idinstance = info.instance;

 NSString*className = NSStringFromClass([instance class]);

 NSLog(@"aspect-->%@--hello1",className);

 //原始的方法

 idinvocation = info.originalInvocation;

 NSLog(@"原始的方法:%@",invocation);

 //参数

 idarguments = info.arguments;

 NSLog(@"参数:%@",arguments);

 //原始的方法,再次调用

[invocation invoke];


 //监控方法的参数值

 NSLog(@"方法参数值:%@",str);

} error:NULL];

}

+ (void)aps_exchangeInstanceMethodWithSelfClass:(Class)selfClass

                           originalSelector:(SEL)originalSelector

                           swizzledSelector:(SEL)swizzledSelector {


[selfClass aspect_hookSelector:originalSelector withOptions:AspectPositionInsteadusingBlock:^(id<AspectInfo> info,idobj) {



 idinstance = info.instance;


 NSMethodSignature*signature = [instance methodSignatureForSelector:swizzledSelector];


 NSInvocation*invocationNew = [NSInvocationinvocationWithMethodSignature:signature];

[invocationNew setTarget:instance];

 // 设置要执行的selector, 与[invocation setArgument:@selector(test:) atIndex:1]

[invocationNew setSelector:swizzledSelector];

 ////        设置参数

 for(NSUIntegeridx = 0; idx < info.arguments.count; idx++) {

 idobj = info.arguments[idx];

 //把参数赋值给block调用

[invocationNew setArgument:&obj atIndex:idx+2];

        }

[invocationNew invoke];


} error:NULL];

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,552评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,666评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,519评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,180评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,205评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,344评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,781评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,449评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,635评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,467评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,515评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,217评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,775评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,851评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,084评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,637评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,204评论 2 341

推荐阅读更多精彩内容