AspectsPlus 1.2

AspectsPlus 1.2

测试已测试
语言语言 Obj-CObjective C
许可证 MIT
发布最新发布2017年12月

Zhao Yiqi维护。



  • 作者:
  • heroims

AspectsPlus   Build Status codecov Carthage compatible

AspectsPlus是一个基于Aspects的新实现。

我为Aspects添加了AspectsConfig

@interface AspectsConfig : NSObject

+ (instancetype)sharedAspectsConfig;

@property(nonatomic,strong)NSSet *customClassBlackList;
@property(nonatomic,strong)NSDictionary *customClassMethodBlackList;

/// key :class string NSString  value:method string NSSet
///
/// @{@"UIViewController":[NSSet setWithObjects:@"viewDidAppear:", nil]}
///
@property(nonatomic,strong)NSDictionary *onceHookClassMethodMap;

@property(nonatomic,strong)NSDictionary *onceHookWhiteListClassMethodMap;

/// Only run in instance method ,It's like Aspects default.  Default YES
@property(nonatomic,assign)BOOL instanceMethodOnceHook;

/// methodOnceHook > instanceMethodOnceHook
@property(nonatomic,assign)BOOL methodOnceHook;

/// if you want use unFindMethodToAdd,must be the block like ^(id instance,id argument1,id argument2,...){} or ^(id instance,...){}
@property(nonatomic,assign)BOOL unFindMethodToAdd;

@end

#define AspectsConfigInstance [AspectsConfig sharedAspectsConfig]

用法

AspectsConfigInstance.unFindMethodToAdd=YES;
AspectsConfigInstance.methodOnceHook=NO;
AspectsConfigInstance.instanceMethodOnceHook=NO;

Aspects扩展现有的NSObject方法

/// Adds a block of code before/instead/after the current `selector` for a specific class.
///
/// @param block Aspects replicates the type signature of the method being hooked.
/// The first parameter will be `id<AspectInfo>`, followed by all parameters of the method.
/// These parameters are optional and will be filled to match the block signature.
/// You can even use an empty block, or one that simple gets `id<AspectInfo>`.
///
/// @note Hooking static methods is not supported.
/// @return A token which allows to later deregister the aspect.
+ (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;

/// Deregister an aspect.
/// @return YES if deregistration is successful, otherwise NO.
id<AspectToken> aspect = ...;
[aspect remove];

添加方面返回一个类型的不可见令牌AspectToken,可用于再次取消注册。所有调用都是线程安全的。

Aspects使用Objective-C消息转发来挂钩消息。这将创建一些开销。不要向被频繁调用的方法添加方面。适用于那些每秒不会1000次被调用的视图/控制器代码。

Aspects调用并匹配块参数。 beverages without arguments are also supported. 第一个块参数的类型将是id<AspectInfo>

何时使用Aspects

面向方面编程(AOP)用于封装"横切"关注点。 这些是在您的系统中跨许多模块的横切的要求,因此不能使用常规面向对象编程进行封装。 一些这类需求示例

  • 每当用户在服务客户端上调用方法时,应检查安全性。
  • 每当用户与商店交互时,应根据他们的交互提出天才建议。
  • 所有调用都应记录。

如果我们使用常规OOP来实现上述要求,会遇到一些缺点

良好的面向对象设计原则认为一个类应该只有一个责任,然而添加额外的横切需求意味着一个类承担了其他责任。例如,你可能有一个StoreClient类,其目的是从在线商店进行购物。添加一些横切需求后,它可能还需要承担日志记录、安全和推荐等功能。这不是一个好主意,因为

  • 我们的StoreClient类现在更难理解和维护。
  • 这些横切需求在我们的应用中重复且分散。

AOP让我们可以模块化这些横切需求,并清晰地识别它们应该应用的所有位置。如上例所示,横切需求可以是技术导向的,也可以是业务导向的。

以下是一些具体的示例

方面可以用来仅对调试构建添加动态日志

[UIViewController aspect_hookSelector:@selector(viewWillAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo, BOOL animated) {
    NSLog(@"View Controller %@ will appear animated: %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, @"Calling testCallAndExecuteBlock must call testCall");
}

它对于调试来说非常有用。这里我很好奇何时点击手势改变了状态

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

另一个方便的使用场景是为你不拥有的类添加处理程序。我为此编写了PSPDFKit程序,我们需要在视图控制器被模态关闭时收到通知。包括UIKit视图控制器如MFMailComposeViewControllerUIImagePickerController。我们本可以为这些控制器创建子类,但这会产生大量不必要的代码。方面为此问题提供了一个更简单的解决方案

@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

调试

方面在堆栈跟踪中表现得很好,因此可以很容易地看出方法是否被挂钩

使用非void返回类型的方面

您可以使用调用对象来自定义返回值

    [PSPDFDrawView aspect_hookSelector:@selector(shouldProcessTouches:withEvent:) withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo> info, NSSet *touches, UIEvent *event) {
        // Call original implementation.
        BOOL processTouches;
        NSInvocation *invocation = info.originalInvocation;
        [invocation invoke];
        [invocation getReturnValue:&processTouches];

        if (processTouches) {
            processTouches = pspdf_stylusShouldProcessTouches(touches, event);
            [invocation setReturnValue:&processTouches];
        }
    } error:NULL];

安装

最简单的方法是使用pod "Aspects"

您还可以将两个文件Aspects.h/m添加到您的项目中。没有其他要求。

兼容性和限制

方面使用了一些运行时技巧来实现其功能。您可以将它们与常规的方法替换混合使用。

一个重要的限制是,对于基于类的挂钩,一个方法只能在子类层次中挂钩一次。见#2
对于挂钩的对象不适用。方面在这里创建了一个动态子类,并拥有完全控制权。

KVO(键值观察)在调用aspect_hookSelector:之后创建观察者时工作。如果反过来,可能崩溃。仍然在这里寻找解决方案 - 欢迎任何帮助。

由于ObjC运行时中的其他不美观的实现细节,返回联合体(包含结构体)的方法可能无法正常工作,除非此代码在arm64运行时上运行。

致谢

使用_objc_msgForward和部分NSInvocation参数选择的想法来自GitHub的绝佳项目ReactiveCocoa这篇文章解释了它在底层数据是如何工作的。

支持的iOS和SDK版本

  • 方面需要ARC。
  • 方面已测试与iOS 7+和OS X 10.7或更高版本的SDK一起使用。

许可证

MIT许可,版权(c)2014 Peter Steinberger,@steipete

发行说明

版本1.0

  • 允许挂钩不同的子类。
  • 一些小改动。