I've been tasked with adding some instrumentation logic to an app to track the latency of various API calls. I'm struggling to come up with a clean, non side-effecting way to add timing instrumentation to methods that return RACSignal's (deferred execution API calls).
Considerations
- Using ReactiveCocoa @ 1.9.5 (can't upgrade at the moment)
- Using Parse-RACExtensions @ 0.0.2
- I'd prefer to set up timings at the ViewModel layer as opposed to modifying Parse-RACExtensions. This is because the VM has extra info I'd like to log, like query parameters, and I don't need every API call instrumented.
- Only record timings upon receipt of a
completed
event
- In the spirit of painless instrumentation, the burden on the caller should be as small as is practical
Attempted Solution
The only thing I've been able to come up with is to create a concrete RACSubscriber subclass that that handles the timer logic. Besides the nasty subclass, this obviously isn't ideal as it requires an explicit subscribe:
, which in turn requires a replay
on the source signal. Additionally, there is a burden on the caller as they have to at least refactor to get a temporary handle to the signal.
@interface SignalTimer : RACSubscriber
@property (nonatomic) NSDate *startDate;
@end
@implementation SignalTimer
- (void)didSubscribeWithDisposable:(RACDisposable *)disposable
{
[super didSubscribeWithDisposable:disposable];
self.startDate = [NSDate date];
}
- (void)sendCompleted
{
NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:self.startDate];
NSLog(@"Time elapsed: %f", duration);
[super sendCompleted];
}
@end
Usage would look like this:
- (RACSignal*)saveFoo:(Foo*)fooParseObj {
RACSignal *save = [[fooParseObj rac_save] replay]; // Don't forget replay!
[save subscribe:[[SignalTimer alloc] initWithName@"Saving the user's foo object"]];
return save;
}
Obviously, I'm not happy with this implementation.
Final Thoughts
Ideally, I'd like a chain-able method like this, but I wasn't sure how to accomplish it/if it was possible to handle a cold signal without nasty side-effects inside a category method (like calling replay
on the receiver).
[[[fooParseObj rac_save] logTimingsWithName:@"Saving the user's foo object"] subscribeNext:...];
Thoughts?