Wednesday 18 August 2010

Logging in Objective-C with Blocks

I've been learning Objective-C in my spare time, focusing on coding for iOS devices in particular. I tend to use debugging as the last resort when writing code, so logging is important. I have found the lack of decent logging in iOS to be a problem.

Being used to logging on java and .Net, NSLog does seem very primitive, and one solution does appear to be cocoalumberjack. I will be investigating this and doing a post soon.

In the mean time I am experimenting with using blocks to give me the flexibility I need as a minimum. This was partly motivated as a learning exercise for closures in Objective-C

The basic Logger header looks like this:
typedef enum {
kError = 0,
kWarn,
kInfo,
kDebug,
} TRLLogLevel;

@interface TRLLog : NSObject

+(void)logWithBlock:(NSString* (^)(void))block withLevel:(TRLLogLevel)level;
+(void)setLogLevel:(TRLLogLevel)level;

+(void)error:(NSString* (^)(void))block;
+(void)warn:(NSString* (^)(void))block;
+(void)info:(NSString* (^)(void))block;
+(void)debug:(NSString* (^)(void))block;
@end

.. so a simple logger API then. The interesting part as far as I am concerned is this bit...
(NSString* (^)(void))

There is a full discussion on block syntax here, so I am not going to go into lots of detail. The example above is defining a block which takes no parameters and returns a string.

Inside the implementation, the block is used like a normal C function

+(void)logWithBlock:(NSString* (^)(void))block withLevel:(TRLLogLevel)level {

if (level > currentLogLevel)
return;

NSString *msg = block();
NSLog(@"%@",msg);
[msg release];
}

This means that any expensive operations inside the block are not called if the log level is not met. To use the logger, the code looks like this

CGPoint point = [recogniser translationInView:self.view];

[TRLLog debug:^{
return [[NSString alloc]initWithFormat:@"handlePan [%f, %f]",point.x,point.y];
}];

Note how because the block is a closure, it can use other variables that are in scope.