Wednesday, August 26, 2009

GSEvent Recording and Playback in 3.0

iPhoneOS has built-in API for application macro since 2.x. (Of course it was never documented.) (And the API was really changed in 3.0 to adopt for multiple recorders.)

For example, to record everything you've done during the for yourUIApplication:

recorder = [Recorder new];
[yourUIApplication _addRecorder:recorder];
[recorder release];

The command _addRecorder: adds the object to the application's event recorder array. You can remove your recorder at anytime with

[yourUIApplication _removeRecorder:recorder];

An event recorder must conform to the informal protocol

@protocol UIEventRecorder

Here, "event" is a GSEvent converted to a plist using GSEventCreatePlistRepresentation(). You can add the event to an array and save that array for later reference, e.g.

@interface Recorder : NSObject { NSMutableArray* eventList; } @end
@implementation Recorder
-(id)init { if ((self = [super init])) eventList = [NSMutableArray new]; return self; }
-(void)save { [eventList writeToFile:@"events.plist" atomically:YES]; }
-(void)recordApplicationEvent:(NSDictionary*)event { [eventList addObject:event]; }

Now say you want to play back the events you've previously collected. Just use this code:

NSArray* eventList = [NSArray arrayWithContentsOfFile:@"events.plist"];
float playbackRate = 1;
[app _playbackEvents:eventList atPlaybackRate:playbackRate messageWhenDone:target withSelector:@selector(done:)];

The higher the playback rate, the faster the system will run the events.

When the playback is finished, the "done:" selector will be called for "target". This selector must have a signature of


the "detail" argument contains exactly 1 key, "UIApplicationEventRecordingDeliveryTimeUserInfoKey", which points to an array of the time the corresponding event happened.

1 comment:

  1. This is very interesting; i'm trying to use this approach to help test my application, but i'm hitting a roadblock; calling the method to playback events causes an EXC_BAD_ACCESS error. My code looks like this, with eventList being retained beforehand:

    [[UIApplication sharedApplication] _playbackEvents:eventList atPlaybackRate:1.0 messageWhenDone:self withSelector:@selector(done:)];

    any ideas what the cause might be?