5

聊聊 AOP 模式与 ObjC 对它的实现

 3 years ago
source link: http://www.swiftcafe.io/post/objc-aop
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

聊聊 AOP 模式与 ObjC 对它的实现

聊聊 AOP 模式与 ObjC 对它的实现

swift 发布于 2021年09月10日 AOP 简述

在聊具体实现之前,我们先从设计层面介绍一下 AOP 的由来,以及它解决了什么问题。 了解这个模式后,其实它可以用到任何的语言实现中。 AOP 的全称叫做 Aspect-oriented programming,维基百科上面有对它的完整解释:https://en.wikipedia.org/wiki/Aspect-oriented_programming

按照字面翻译过来,它叫做面向方面编程。是不是听起来怪怪的,不好理解。 下面我们就用最简单的语言来描述一下。 所谓 AOP 其实就是给你的程序提供一个可拆卸的组件化能力。 这么说可能还不够直观,咱们直接上代码。

比如你的 APP 需要用到事件统计功能,这个场景应该大多数伙伴都会遇到过。 无论你是用 UMeng, Google Analytics, 还是其他的统计平台等等, 你应该都会写过类似的代码:

- (void)viewDidLoad {

[super viewDidLoad];
[Logger log:@"View Did Load"];

UILabel *label = [[UILabel alloc] init];
[self.view addSubview:label];

}

上面这段代码会在试图控制器开始加载的时候,用 Logger 类记录一个统计事件。 这些统计相关的代码是我们为了完成我们记录 APP 运行数据这个逻辑的。 其实 viewDidLoad 方法本身的逻辑并不是为了完成统计,而是进行一些初始化操作, 比如这里初始化了 UILabel。

这就导致了一个设计上的瑕疵, 数据统计的代码和我们实际的业务逻辑代码混杂在一起了。 加入过了一段时间后, 你再回过头来要改业务逻辑代码的时候,你看到 Logger 的统计代码混杂在一起,就必须要花点时间将他们的含义区分出来。

随着业务逻辑代码不断增多,类似的混杂也会越来越多,这样的耦合势必会增加维护的成本。正式因为这个原因,所以诞生了 AOP 模式。 AOP 其实就是在不影响程序整体功能的情况下,将 Logger 这样的逻辑,从主业务逻辑中抽离出来的能力。

简单来说, 有了 AOP 之后, 我们的业务逻辑代码就变成了这样:

- (void)viewDidLoad {

[super viewDidLoad];
UILabel *label = [[UILabel alloc] init];
[self.view addSubview:label];

}

这里不再会出现 Logger 的统计逻辑的代码,但是统计功能依然是生效的。 当然,不出现在主业务代码中,不代表统计代码就消失了。 而是用 AOP 模式 hook 到别的地方去了。

ObjC 对 AOP 的实现

对 AOP 做了一个基本的介绍后,咱们就来看看如何实现它吧, 这里只举了 ObjC 的例子,但 AOP 是一个模式,理论上大多数语言都可以实现这个模式。

ObjC 中实现 AOP 最直接的方法就是使用 runtime 中的 Method Swizzling。 我们在前面的文章中也对 Method Swizzling 做了介绍,大家可以参考:Method Swizzle 的一些注意事项

还回到我们之前的 viewDidLoad 方法中,前面我们提到使用 AOP 后, viewDidLoad 方法中就会只包含业务逻辑代码,而和主业务不相干的统计代码就被剥离出去了。 具体是怎么做到的呢,我们看下面的代码:

#import <objc/runtime.h>
#import "ViewController.h"
#import "Logger.h"

@interface AOPHelper : NSObject

+ (void) setup;

@end


@implementation AOPHelper

IMP originalViewDidLoadIMP;
void replacedViewDidLoad() {

originalViewDidLoadIMP();
[Logger log:@"View Did Load"];

}


+ (void)setup {

originalViewDidLoadIMP = class_getMethodImplementation([ViewController class], @selector(viewDidLoad));
Method originalViewDidLoad = class_getInstanceMethod([ViewController class], @selector(viewDidLoad));
method_setImplementation(originalViewDidLoad, (IMP) replacedViewDidLoad);


}

@end

AOPHelper 所做的工作就是 hook 我们主业务的逻辑, 首先通过 class_getMethodImplementation 得到 viewDidLoad 原始的实现, 也就是包含业务代码的那个方法实现。 然后通过 class_getInstanceMethod 和 method_setImplementation 两个方法用我们自己的实现 replacedViewDidLoad 替换掉原有的 viewDidLoad 实现。

再来看看我们自己的 replacedViewDidLoad:

void replacedViewDidLoad() {

originalViewDidLoadIMP();
[Logger log:@"View Did Load"];

}

这里面加入了 Logger 类的统计逻辑, 并且也通过 originalViewDidLoadIMP 调用了原有的业务逻辑实现。 originalViewDidLoadIMP 是通过前面 class_getMethodImplementation 方法取得的。

概括的来说,就是我们先将业务逻辑代码和统计代码分成两个组件, 然后在 AOPHelper 中将他们组合起来。 这样两个逻辑在代码层面各司其职,互不影响。 而在运行时,通过 AOP 的 hook 机制又将他们组合了起来。

额外的收益

使用了 AOP 之后,还会有一些额外的好处。 比如,如果你在哪一天想换一个统计平台, 那么你不需要到处改代码了, 只需要把统计层面的代码修改一下就可以。 再比如,你想打一个不带统计能力的安装包,只需要将 hook 的部分去掉,就自动去掉统计逻辑了。 当想用的时候,再把它装回来即可。 这个感觉就像是乐高积木一样,通过 AOP 可以把某些能力变成一个模块,即插即用。

刚才说的不带统计安装包的场景其实在我们的真是环境中还是存在的,比如一个开发团队在初期测试自己的 APP 功能的时候,就不希望测试时候的会话计入统计数据中。

当然,AOP 带来收益的同时,也会有一些损失,比如在写代码的时候就不那么直观了。 这就取决于你对项目整体设计的权衡了,是健壮性优先,还是开发便捷性优先。根据不同的个人风格,不同的项目规模各自权衡。

结语

AOP 作为一种设计模式,他能为需要这种设计的项目解决实际中的问题。 你的项目中是否也要考虑使用 AOP 呢,我的建议是对于稍大些并且后期有维护价值的项目就值得使用。

这里是这篇文章中的示例代码:https://github.com/swiftcafex/aop-sample 大家也可以参考。另外 ObjC 已经有对于 AOP 最佳实践的第三方库 Aspects, 可以不需要繁琐的手工调用 Method Swizzling。 后面我们会继续介绍 Aspects 这个库。

如果你觉得这篇文章有帮助,还可以关注微信公众号 swift-cafe,会有更多我的原创内容分享给你~

本站文章均为原创内容,如需转载请注明出处,谢谢。
qrcode.jpg 关注微信公众号
发现更多精彩
swift-cafe


Recommend

  • 80
    • www.10tiao.com 6 years ago
    • Cache

    What does the -ObjC linker flag do

    今天学(踩)到了一个 iOS 开发小知识(坑):如果静态库里定义了

  • 54
    • www.tuicool.com 4 years ago
    • Cache

    objc_msgSend&#39;s New Prototype

    Posted at 2019-10-11 12:09 |RSS feed (Full text feed) |Blog Index Previous article: Frida...

  • 20
    • 微信 mp.weixin.qq.com 4 years ago
    • Cache

    你真的了解objc_msgSend吗?

    说到objc_msgSend可能大家都知道这是runtime的核心代码,是用来发送消息的,但你真的了解objc_msgSend吗?你清楚objc_msgSend是如何完成自己的职责吗?你清楚它和arm处理器如何协作的吗?下面让我们一起进入objc_msgSend的世界。

  • 14
    • www.mikeash.com 3 years ago
    • Cache

    mikeash.com: objc_msgSend's New Prototype

    objc_msgSend's New Prototypemikeash.com: just this guy, you know? objc_msgSend's New Prototype by Mike Ash   Apple's new OSes are out. If you've l...

  • 14
    • chipengliu.github.io 3 years ago
    • Cache

    从汇编角度分析objc_msgSend的hook过程

    从汇编角度分析objc_msgSend的hook过程 2019-07-132020-10-24计算机基础 o...

  • 6
    • chipengliu.github.io 3 years ago
    • Cache

    objc-msg-arm64源码深入分析

    在 Objective-C 语言中,实例对象执行方法,而执行方法的过程也可以称为给实例对象发送消息。发送消息的过程执行在编译阶段会转化成对 objc_msgSend 函数的调用。本文将分析 objc_msgSend 汇编部分主要部分(fast path)。 文章...

  • 12

    深入理解Macho文件(二)- 消失的__OBJC段与新生的__DATA段 在上文中,我们提到了有个神秘的__OBJC段,Runtime的许多机制就是依赖于它。但是无论我怎么搜索网上相关的资料、苹果的官方文档,都发现找不到这个段了...

  • 11

    Dissecting objc_msgSend on ARM64mikeash.com: just this guy, you know? Friday Q&A 2017-06-30: Dissecting objc_msgSend on ARM64 by Mike Ash   We...

  • 8

    Let's Build objc_msgSendmikeash.com: just this guy, you know? Friday Q&A 2012-11-16: Let's Build objc_msgSend by Mike Ash   The objc_msg...

  • 4
    • club.autohome.com.cn 3 years ago
    • Cache

    星瑞半年,对它的爱大过对自己

    【精】星瑞半年,对它的爱大过对自己 楼主 来自: 广西 来宾 注册: 2018-...

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK