SPLMessageLogger 0.3.0

SPLMessageLogger 0.3.0

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

Oliver Letterer 维护。



License Badge

SPLMessageLogger 可以截获并直接在控制台记录所有 obj-c 消息

-> <UINavigationBar:0x167a9960>: -[UIView setCenter:NSPoint: {384, 42}]
=> void
-> <UIView:0x167b7e40>: -[UIView setCenter:NSPoint: {384, 512}]
=> void
-> <UIImageView:0x167c56c0>: -[UIView setCenter:NSPoint: {0, 0}]
=> void

安装

SPLMessageLogger 通过 CocoaPods 提供,要安装它,只需将以下行添加到 Podfile 中即可

pod "SPLMessageLogger"

用法

SPLMessageLogger 可以截获并记录所有 obj-c 消息。因此,在 NSObject 上定义了以下接口

@interface NSObject (SPLMessageLogger)

+ (instancetype)messageLogger;

@end

其中,messageLogger 记录它收到的每条消息,并链接到运行时以截获并记录这些记录的消息。让我们看一个例子

@interface SPLView : UIView

- (CGPoint)setCenter:(CGPoint)center atIndex:(NSInteger)index forObject:(id)object;

@end

@implementation SPLView

- (CGPoint)setCenter:(CGPoint)center atIndex:(NSInteger)index forObject:(id)object
{
    [super setCenter:center];
    return self.center;
}

@end

现在,为了截获并记录到 -[SPLView setCenter:atIndex:forObject:]-[UIView setCenter:] 的所有消息,只需运行:

SPLView *firstMessageLogger = [SPLView messageLogger];
[firstMessageLogger setCenter:CGPointZero atIndex:0 forObject:nil];

UIView *secondMessageLogger = [UIView messageLogger];
[secondMessageLogger setCenter:CGPointZero];

在运行时,这可能导致以下输出

-> <SPLView:0x166c41f0>: -[SPLView setCenter:NSPoint: {0.5, 0.5} atIndex:5 forObject:<ICAppDelegate: 0x166b0570>]
--> <SPLView:0x166c41f0>: -[UIView setCenter:NSPoint: {0.5, 0.5}]
==> void
=> NSPoint: {0.5, 0.5}
-> <SPLView:0x166c41f0>: -[UIView setCenter:NSPoint: {0.5, 0.5}]
=> void
-> <UINavigationBar:0x167a9960>: -[UIView setCenter:NSPoint: {384, 42}]
=> void
-> <UIView:0x167b7e40>: -[UIView setCenter:NSPoint: {384, 512}]
=> void
-> <UIImageView:0x167c56c0>: -[UIView setCenter:NSPoint: {0, 0}]
=> void

它是如何工作的

消息转发

对于这个库,我编写了一个自定义的驰骋(你可以在这里了解驰骋这里),它可以将任何 objc 消息转发到新的选择器。

IMP imp_implementationForwardingToSelector(SEL forwardingSelector, BOOL returnsAStructValue);

以下是一个示例

IMP forwardingImplementation = imp_implementationForwardingToSelector(@selector(setCenter:), NO);
class_addMethod([UIView class], @selector(thisSetCenterDoesNotExistYet:), forwardingImplementation, typeEndoding);

突然,每个 UIView 的实例都响应该 -[UIView thisSetCenterDoesNotExistYet:] 并将其转发到 -[UIView setCenter:]。如果您想了解更多关于驰骋和弦三篇类似博客(如《为初学者编写自定义驰骋及其所有陷阱》),请在 Twitter 上联系我。

运行时黑客技术

+[NSObject messageLogger] 返回一个 SPLMessageLoggerRecorder 实例,该实例实现了 -[NSObject methodSignatureForSelector:] 并使用 -[NSObject forwardInvocation:](类似于 UIAppearance 使用的)作为进入运行时的起点。运行时挂钩最好以上述示例进行解释

SPLView *firstMessageLogger = [SPLView messageLogger];
[firstMessageLogger setCenter:CGPointZero atIndex:0 forObject:nil];

一旦 firstMessageLogger 收到 setCenter:atIndex:forObject: 消息,它会进行以下操作:

  • 在新的位置 -[SPLView __SPLMessageLogger_original_SPLView_setCenter:atIndex:forObject:] 存储原始的 -[SPLView setCenter:atIndex:forObject:] 实现
  • 使用 imp_implementationForwardingToSelector 获得的新的实现替换 -[SPLView setCenter:atIndex:forObject:] 实现,该实现转发到 __SPLMessageLogger_SPLView_setCenter:atIndex:forObject:

这里的技巧在于,没有一个实例对转发的方法 -[SPLView __SPLMessageLogger_SPLView_setCenter:atIndex:forObject:] 做出响应。在这些情况下,objc运行时会询问 -[NSObject forwardingTargetForSelector:],而这个选择器会返回实际 SPLMessageLogger 类的实例。这个实例随后会收到消息 __SPLMessageLogger_SPLView_setCenter:atIndex:forObject:。它实现了 -[NSObject methodSignatureForSelector:],然后使用 -[NSObject forwardInvocation:] 记录调用,并转发调用回原始对象的原实现

-[SPLView setCenter:] // gets forwarded
↓
-[SPLView __SPLMessageLogger_SPLView_setCenter:atIndex:forObject:] // no implementation is found
↓
-[SPLView forwardingTargetForSelector:] // return an instance of SPLMessageLogger
↓
-[SPLMessageLogger methodSignatureForSelector:] // lets the runtime construct an invocation
↓
-[SPLMessageLogger forwardInvocation:]
// 1) log the invocation
// 2) forward invocation
↓
--->    `-[SPLView __SPLMessageLogger_original_SPLView_setCenter:atIndex:forObject:]` // execute original implementation
<---    `-[SPLView __SPLMessageLogger_original_SPLView_setCenter:atIndex:forObject:]`
// 3) log return argument of invocation

限制

这种方法使用了一个使用原生汇编编写的自定义跳板(trampoline),目前仅在i386、armv7、armv7s和arm64架构上可用。

作者

Oliver Letterer

许可

SPLMessageLogger 在 MIT 许可证下可用。更多信息请参阅 LICENSE 文件。