Sunday, May 23, 2010

MyLogger Class in Objective-C (1)

Objective-C is new for me. In the past weeks, I have been working on my first iPhone application. This has been quite interesting and rewarding experience. Since I have been doing development in in .Net for many years, I always compare Objective-C to .Net, or try to find out something similar in Objective-C like ones I have used in .Net. Recently, I found some classes in System.Diagnostics namespace are quite useful, especially Debug class. I can use it to print debug information in my classes and I can use indent feature to net messages in a nice indented structure. All the debug strings are displayed in Visual Studio's output panel. I don't not need to worry about all those debug print codes since in the released mode, they will not work.

In Xcode, there is similar feature available. It is NSLog(), which is a Cocoa foundation function.

This function can be used to print out debug messages in the same way as Debug.Print() in .Net. However, NSLog() is only a C-like function, therefore, there is no other features you can alter the behaviour of its output, as far I know about it. Therefore, I need a wrapper class for NSLog().

I found a very nice blog by Objectuser: A Single Class Logger in Objective C. This is very close to what I want, except for indention. Based on the class, I created my logger class: MyLogger. Here is the header of the class:

#define kLogLevelDebug 1
#define kLogLevelWarning 2
#define kLogLevelError 3
#define kLogLevelInfo 4
#define kLogLevelNone 100

@interface MyLogger : NSObject {
  int mLevel;
  NSString* mContext;

@property (nonatomic, assign) int level;
@property (nonatomic, retain) NSString* context;

+ (char) indentChar;
+ (void) setIndentChar:(char)aChar;
+ (NSString*) format;
+ (void) setFormat:(NSString*)aFormat;
+ (int) defaultLoggingLevel;
+ (void) setDefaultLoggingLevel:(int)defaultLevel;

- (id) initWithContext:(NSString*)loggingContext;
- (id) initWithContext:(NSString*)loggingContext logLevel:(int)intentLoggingLevel;
- (BOOL) levelEnabled:(int) intentLevel;
- (BOOL) debugEnabled;
- (BOOL) warningEnabled;
- (BOOL) errorEnabled;
- (BOOL) infoEnabled;
- (MyLogger*) indent:(BOOL)indent;
- (MyLogger*) debug:(NSString*)messageFormat, ...;
- (MyLogger*) warning:(NSString*)messageFormat, ...;
- (MyLogger*) error:(NSString*)messageFormat, ...;
- (MyLogger*) info:(NSString*)messageFormat, ...;

The header file describes all the class members, properties, and methods.


The main purpose of this class is for easy to use. Typically, you need to create an instance of MyLogger in a class's init(). Then the instance is ready to use. For example, here is a view controller class .h:

@class MyLogger;

@interface MyViewController : UITableViewController
    MyLogger* mLogger;

Notice that the class level mLogger is a private variable. There is no reason for outside to access to this mLogger instance. The following is an exmaple how this mLogger is used in the .m class:

#import "MyViewController.h"
#import "MyLogger.h"
@implementation MyViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
   if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
     // create logger with class name as context
     logger = [[MyLogger alloc] initWithContext:@"MyViewController"];
     // log a message and then indent
     [[logger info:@"initWithNibName:bundle:"] indent:YES];
     [logger info:@"object: %@", obj];

     // off indent and print msg
     [[logger indent:NO] info:@"initWithNibName:bundle: DONE"];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    [[mLogger debug:@"tableView:cellForRowAtIndexPath:"] indent:YES];
    [mLogger debug:@"indexPath: %@", indexPath];

    [[mLogger indent:NO] @"tableView:cellForRowAtIndexPath: DONE"];
- (void)dealloc {
  if ([logger retainCount] > 0 ) {
    [logger release];

I think it is very straightforward: creating logger in init and retain it, using it anywhere in the class, and releasing it at end of the class. I declare logger in the .m file to keep it as private data members in the class. Three is no need to let outside to access or set this instance.

Here is an example output:

[INFO] MyController - viewDidLoad
--[INFO] MyViewController - fetchedResultsController
----[INFO] MyViewController - managedObjectContext
------[INFO] MyViewController - creating new managedObjectContext
----[INFO] MyViewController - managedObjectContext DONE
----[INFO] MyViewController - getFetchRequestWithEntity:inContext:inContext:
----[INFO] MyViewController - getFetchRequestWithEntity:inContext:inContext: DONE
----[INFO] MyViewController - managedObjectContext
----[INFO] MyViewController - managedObjectContext DONE
--[INFO] MyViewController - fetchedResultsController DONE
[INFO] MyViewController - viewDidLoad DONE

My next blog will discuss some key points in MyLogger class.