SPLMessageLogger can intercept and log all objc messages right to your console:
-> <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 is available through CocoaPods, to install it simply add the following line to your Podfile:
pod "SPLMessageLogger"
SPLMessageLogger can intercept and log all objc messages. It therefore defines the following interface on NSObject
@interface NSObject (SPLMessageLogger)
+ (instancetype)messageLogger;
@end
where the messageLogger records every message it receives and hooks into the runtime to intercept and log these recorded messages. Let's take a look at an example:
@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
To now intercept and log all messages to -[SPLView setCenter:atIndex:forObject:]
and -[UIView setCenter:]
, simply run
SPLView *firstMessageLogger = [SPLView messageLogger];
[firstMessageLogger setCenter:CGPointZero atIndex:0 forObject:nil];
UIView *secondMessageLogger = [UIView messageLogger];
[secondMessageLogger setCenter:CGPointZero];
At runtime, this can result in the following output
-> <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
For this library, I have written a custom trampoline (you can read about trampolines here) which can forward any objc message to a new selector.
IMP imp_implementationForwardingToSelector(SEL forwardingSelector, BOOL returnsAStructValue);
Here is an example
IMP forwardingImplementation = imp_implementationForwardingToSelector(@selector(setCenter:), NO);
class_addMethod([UIView class], @selector(thisSetCenterDoesNotExistYet:), forwardingImplementation, typeEndoding);
and suddenly every instance of UIView
responds to -[UIView thisSetCenterDoesNotExistYet:]
and forwards this message to -[UIView setCenter:]
. If you would like some more information about trampolines and maybe a blog post like Writing custom trampolines for beginners and all the pitfalls
, hit me up on Twitter.
+[NSObject messageLogger]
returns an instance of SPLMessageLoggerRecorder
which implements -[NSObject methodSignatureForSelector:]
and then uses -[NSObject forwardInvocation:]
(similar to what UIAppearance is using) as an entry point to hook into the runtime. The runtime hook is probably best explained with the example from above:
SPLView *firstMessageLogger = [SPLView messageLogger];
[firstMessageLogger setCenter:CGPointZero atIndex:0 forObject:nil];
Once firstMessageLogger
receives the setCenter:atIndex:forObject:
message, it does the following things:
- Store the original implementation of
-[SPLView setCenter:atIndex:forObject:]
at the new location-[SPLView __SPLMessageLogger_original_SPLView_setCenter:atIndex:forObject:]
- Replace the implementation of
-[SPLView setCenter:atIndex:forObject:]
with a new implementation obtained byimp_implementationForwardingToSelector
which forwards to__SPLMessageLogger_SPLView_setCenter:atIndex:forObject:
.
The trick here is that no instance every is responding to the forwarded -[SPLView __SPLMessageLogger_SPLView_setCenter:atIndex:forObject:]
. In these cases, the objc runtime is asking -[NSObject forwardingTargetForSelector:]
, which for this selector returns an instance of the actual SPLMessageLogger
class. This instance then receives the message __SPLMessageLogger_SPLView_setCenter:atIndex:forObject:
. It implements -[NSObject methodSignatureForSelector:]
and then uses -[NSObject forwardInvocation:]
to log the invocation and then forwards the invocation back to the original object to the original implementation:
-[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
This approach uses a custom trampoline written in raw assembly which is currently only available on i386, armv7, armv7s and arm64.
Oliver Letterer
- https://github.com/OliverLetterer
- http://twitter.com/oletterer
- [email protected]
SPLMessageLogger is available under the MIT license. See the LICENSE file for more info.