由 @steipete 开发的一款面向对象编程的简单、令人愉悦的库。
将 Aspects 视为是强化版的 method swizzling。它允许您根据类或实例添加现有方法的代码,同时考虑插入点,例如在之前、替换或之后。Aspects 自动处理调用 super,比常规的 method swizzling 好用。
这是稳定的,并且已被数百个应用程序使用,它是 PSPDFKit,这是一个 iOS PDF 框架,Dropbox 或 Evernote 等应用程序附带的一部分,现在我终于将其开源了。
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 消息传递机制来挂钩消息。这会创建一些开销。不要向频繁调用的方法添加 aspects。Aspects 主要用于每秒不会调用 1000 次的 view/controller 代码。
Aspects 调用并匹配块参数。还包括无参数的块。第一个块参数的类型为 id<AspectInfo>
。
面向对象编程 (AOP) 用于封装“横切”关注点。这些是需要跨越系统中的许多模块,因此无法使用正常面向对象编程进行封装的要求。以下是一些这些类型要求的一些示例
如果我们使用常规 OOP 实现上述要求,则会有一些不足
好的 OOP 声明一个类应该只有一个职责,但是添加额外的 横切 要求意味着承担其他职责的类。例如,您可能有一个 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 中使用它,我们要求当视图控制器以模态形式被 dismissed 时接收通知。这包括 MFMailComposeViewController
或 UIImagePickerController
等UIKit 视图控制器。我们本可以为这些控制器中的每一个都创建子类,但这将产生大量不必要的代码。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
Aspects 在堆栈跟踪中标识良好,因此可以很容易地看出是否为方法安装了钩子
您可以使用调用对象来自定义返回值
[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
。没有其他要求。
Aspects 使用大量运行时技巧来实现其功能。您可以将这些与常规的方法交换混合。
一个重要的限制是,对于基于类的钩子,方法只能在每个子类层次结构中安装一次。 见 #2 这不适用于被钩子安装的对象。Aspects 在这里创建了一个动态子类并完全控制系统。
KVO 在观察者在调用 aspect_hookSelector:
之后创建时有效。否则很可能崩溃。仍然在这里寻找解决方案 - 非常欢迎任何帮助。
由于 ObjC 运行时中丑陋的实现细节,如果代码运行在 arm64 运行时上,则可能无法正确处理返回联合体,其中还包含结构体的方法。
使用 _objc_msgForward
的想法以及 NSInvocation
论证选择的部分来自来自 GitHub 的高质量项目 ReactiveCocoa。 此文章 解释了它如何在工作底层运行。
MIT 许可,版权所有 (c) 2014 Peter Steinberger, [email protetced], @steipete
版本 1.4.1
版本 1.4.0
版本 1.3.1
版本 1.3.0
版本 1.2.0
版本 1.1.0
remove
对象来移除。版本 1.0.1
版本 1.0.0