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 文件。