Aspects– iOS的AOP面向切面编程的库

简介

一个简洁高效的用于使iOS支持AOP面向切面编程的库.它可以帮助你在不改变一个类或类实例的代码的前提下,有效更改类的行为.比iOS传统的 AOP方法,更加简单高效.支持在方法执行的前/后或替代原方法执行.曾经是 PSPDFKit 的一部分,PSPDFKit,在Dropbox和Evernote中都有应用,现在单独单独开源出来给大家使用.

最新实例:点击下载

注: AOP是一种完全不同于OOP的设计模式.更多信息,可以参考这里: AOP 百度百科

快速入门

环境要求

  • ARC
  • iOS 7 + 或 OS X 10.7 +

安装

使用 CocoaPods 安装

pod "Aspects"

手动安装

把文件 Aspects.h/m 拖到工程中即可.

用法

应用场景

Aspects 用于支持AOP(面向切面编程)模式,用于部分解决OOP(面向对象)模式无法解决的特定问题.具体指的是那些在多个方法有交叉,无法或很难被有效归类的操作,比如:

  • 不论何时用户通过客户端获取服务器端数据,权限检查总是必须的.
  • 不论何时用户和市场交互,总应该更具用户的操作提供相应地购买参考或相关商品.
  • 所有需要日志记录的操作.

接口概述

Aspects 给 NSObject 扩展了下面的方法:

/// 为一个指定的类的某个方法执行前/替换/后,添加一段代码块.对这个类的所有对象都会起作用.
///
/// @param block  方法被添加钩子时,Aspectes会拷贝方法的签名信息.
/// 第一个参数将会是 `id<AspectInfo>`,余下的参数是此被调用的方法的参数.
/// 这些参数是可选的,并将被用于传递给block代码块对应位置的参数.
/// 你甚至使用一个没有任何参数或只有一个`id<AspectInfo>`参数的block代码块.
///
/// @注意 不支持给静态方法添加钩子.
/// @return 返回一个唯一值,用于取消此钩子.
+ (id<AspectToken>)aspect_hookSelector:(SEL)selector
                      withOptions:(AspectOptions)options
                       usingBlock:(id)block
                            error:(NSError **)error;

/// 为一个指定的对象的某个方法执行前/替换/后,添加一段代码块.只作用于当前对象.
 - (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; 
/// 撤销一个Aspect 钩子.
/// @return YES 撤销成功, 否则返回 NO. 
id<AspectToken> aspect = ...; 
[aspect remove];

所有的调用,都会是线程安全的.Aspects 使用了Objective-C 的消息转发机会,会有一定的性能消耗.所有对于过于频繁的调用,不建议使用 Aspects.Aspects更适用于视图/控制器相关的等每秒调用不超过1000次的代码.

代码示例

可以在调试应用时,使用Aspects动态添加日志记录功能.

[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
    NSLog(@"控制器 %@ 将要显示: %tu", aspectInfo.instance, animated);
} error:NULL];

使用它,分析功能的设置会很简单:

https://github.com/orta/ARAnalytics

你可以在你的测试用例中用它来检查某个方法是否被真正调用(当涉及到继承或类目扩展时,很容易发生某个父类/子类方法未按预期调用的情况):

- (void)testExample {
    TestClass *testClass = [TestClass new];
    TestClass *testClass2 = [TestClass new];

    __block BOOL testCallCalled = NO;
    [testClass aspect_hookSelector:@selector(testCall) withOptions:AspectPositionAfter usingBlock:^{
        testCallCalled = YES;
    } error:NULL];

    [testClass2 testCallAndExecuteBlock:^{
        [testClass testCall];
    } error:NULL];
    XCTAssertTrue(testCallCalled, @"调用testCallAndExecuteBlock 必须调用 testCall");
}

它对调试应用真的会提供很大的作用.这里我想要知道究竟何时轻击手势的状态发生变化(如果是某个你自定义的手势的子类,你可以重写setState:方法来达到类似的效果;但这里的真正目的是,捕捉所有的各类控件的轻击手势,以准确分析原因):

[_singleTapGesture aspect_hookSelector:@selector(setState:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) {
    NSLog(@"%@: %@", aspectInfo.instance, aspectInfo.arguments);
} error:NULL];

下面是一个你监测一个模态显示的控制器何时消失的示例.通常,你也可以写一个子类,来实现相似的效果,但使用 Aspects 可以有效减小你的代码量:

@implementation UIViewController (DismissActionHook)

// Will add a dismiss action once the controller gets dismissed.
- (void)pspdf_addWillDismissAction:(void (^)(void))action {
    PSPDFAssert(action != NULL);

    [self aspect_hookSelector:@selector(viewWillDisappear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo) {
        if ([aspectInfo.instance isBeingDismissed]) {
            action();
        }
    } error:NULL];
}

@end

对调试的好处

Aspectes 会自动标记自己,所有很容易在调用栈中查看某个方法是否已经调用:

在返回值不为void的方法上使用 Aspects

你可以使用 NSInvocation 对象类自定义返回值:

    [PSPDFDrawView aspect_hookSelector:@selector(shouldProcessTouches:withEvent:) withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo> info, NSSet *touches, UIEvent *event) {
        // 调用方法原来的实现.
        BOOL processTouches;
        NSInvocation *invocation = info.originalInvocation;
        [invocation invoke];
        [invocation getReturnValue:&processTouches];

        if (processTouches) {
            processTouches = pspdf_stylusShouldProcessTouches(touches, event);
            [invocation setReturnValue:&processTouches];
        }
    } error:NULL];
兼容性与限制
  • 当应用于某个类时(使用类方法添加钩子),不能同时hook父类和子类的同一个方法;否则会引起循环调用问题.但是,当应用于某个类的示例时(使用实例方法添加钩子),不受此限制.
  • 使用KVO时,最好在 aspect_hookSelector: 调用之后添加观察者;否则可能会引起崩溃.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏coding...

Objective-C plist文件与KVC 的使用

plist文件是以类似xml形式构造数据,下面我们直接在xcode中创建完成一个plist文件, File-New-File-属性列表

562
来自专栏哈雷彗星撞地球

iOS下JS与OC互相调用(三)--MessageHandler

使用WKWebView的时候,如果想要实现JS调用OC方法,除了拦截URL之外,还有一种简单的方式。那就是利用WKWebView的新特性MessageHandl...

764
来自专栏梧雨北辰的开发录

iOS与JS的交互

iOS和JS的交互看似两个问题,其实要解决的问题只有一个,那就是JS如何调用native方法。因为查询文档我们就可以发现,在UIWebView中,native有...

2617
来自专栏wOw的Android小站

[Objective-C] KVC 和 KVO

KVC是一种用间接方式访问类的属性的机制。比如你要给一个类中的属性赋值或者取值,可以直接通过类和点运算符实现,当然也可以使用KVC。不过对于私有属性,点运算符就...

421
来自专栏java工会

java设计模式-单例模式

1676
来自专栏一“技”之长

iOS中动态更新补丁策略JSPatch运用基础一

        JSPatch是GitHub上一个开源的框架,其可以通过Objective-C的run-time机制动态的使用JavaScript调用与替换项目...

682
来自专栏高性能服务器开发

算法导论第十三章 红黑树(2)

这一章我觉得难点就在于删除修复,插入修复是比较容易想到的,然后我认为需要着重注意的地方都记录下来了,下面贴上自己写的基于C++模板的代码,有点长。...

732
来自专栏iOS技术杂谈

iOS网络——AFNetworking AFURLSessionManager源码解析

你要知道的NSURLSession都在这里 转载请注明出处 https://cloud.tencent.com/developer/user/1605429 本...

3447
来自专栏ShaoYL

ARC(Automatic Reference Counting )技术概述

36616
来自专栏陈满iOS

iOS·枚举变量在 未赋值/赋值为空 的情况下,默认值为0(即第一个枚举类型)

在调用的时候,代码欲从VC的字典数组 self.resource 中获取某字典 self.resource[indexPath.row] 并取出 type 的键...

1161

扫码关注云+社区