Saturday, July 10, 2010

MyLogger Class in Objective-C (3)

In my previous log, I have discussed the class level methods and some of instance methods, which as overwrites of NSObject. MyLogger class has a list of instance methods, most of them in similar structure. Those methods are defined mainly to provide convenience APIs for usage.

MyLogger Instance Methods

This snap-shot is a list of instance methods:


Here is the partial codes in .h:

- (MyLogger*) indent:(BOOL)indent;

- (BOOL) levelEnabled:(MyLoggerLevel) intentLevel;
- (BOOL) infoEnabled;
- (BOOL) debugEnabled;
- (BOOL) warningEnabled;
- (BOOL) errorEnabled;

- (MyLogger*) debug:(NSString*)messageFormat, ...;
- (MyLogger*) warning:(NSString*)messageFormat, ...;
- (MyLogger*) error:(NSString*)messageFormat, ...;
- (MyLogger*) info:(NSString*)messageFormat, ...;


I group them into 3 sections. The first one is indent. The first group contains only one simple method. This method takes only flag as parameter: indent or outdent. The implementation is very simple: it sets the global static variable (int). This integer number is used to insert number indent chars.

static int gIndent = 0;
...
@implementation MyLogger {
  ...
- (MyLogger*) indent:(BOOL)indent {
  if (indent) {
    gIndent += gIndentChars;
  }
  else if (gIndent >= gIndentChars) {
    gIndent -= gIndentChars;
  }

  return self;
}


The implementation is straightforward. One thing I should mention is that I applied the Fluent Interface pattern in this class so that some methods can be chained together to simplify its usage. I enjoy this practice very much as you can see my examples. However, it is a very controversial issue in Objective-C. I posted a question to my SO. I got some experts insights.

The next group is to get logging level status. Method levelEnabled: is a generic method to check if an intend level (MyLoggerLevel enum type) is enabled or not, and others are convenient methods to check if info, debug, warning or error logging level is enabled or not.

- (BOOL) levelEnabled:(MyLoggerLevel) intentLevel {
  BOOL enabled = NO;
  if (isValidLevel(intentLevel)) {
    enabled = self.level <= intentLevel;
  }
  return enabled;
}

- (BOOL) infoEnabled {
  return [self levelEnabled:LogLevelInfo];
}
...

here only infoEnabled is there. Other three are in the similar way. One interesting and great feature of Objective-C is that it interoperates with C well. I defined a static C function isValidLevel(...). Within the function, Objective-C types are recognized. This feature brings great power and speed into Objective-C.

The main reason I mixed C function into MyLogger class is to define private methods. Objective-C class does not provide any way or directive for private methods. All the methods in a class are public. This simplifies the compile and run time performance, no need to verify function's accessibility. Static C function is a perfect candidate for defining private methods. You will see three C functions later.

The last group of methods provides APIs to log messages. Those methods use FI pattern and C functions, as well variable arguments. Here is the method of info:, simple and straightforward again:

- (MyLogger*) info:(NSString*)messageFormat, ... {
  if ([self infoEnabled]) {
    va_list args;
    /* Initializing arguments to store all values after messageFormat */
    va_start(args, messageFormat);
    logAt(self, LogLevelInfo, messageFormat, args);
    va_end(args);
  }
  return self;
}


var_start and va_end are C macros, and logAt(...) is my C function. In the beginning part of MyLogger.m, I have the following C functions:

static NSString* nameOfLevel(MyLoggerLevel intentLevel) {
  NSString* name = kLogLevelNameUnknown;
  switch (intentLevel) {
    case LogLevelDebug:
      name = kLogLevelNameDebug;
      break;
    case LogLevelWarning:
      name = kLogLevelNameWarning;
      break;
    case LogLevelError:
      name = kLogLevelNameError;
      break;
    case LogLevelInfo:
      name = kLogLevelNameInfo;
      break;
    default:
      break;
  }

  return name;
}

static void logAt(MyLogger* logger, MyLoggerLevel intentLevel,  NSString* messageFormat, va_list argList) {
  if ( [logger levelEnabled:intentLevel]) {
    NSString* s = [[NSString alloc] initWithFormat:messageFormat arguments:argList];
    NSString* indent = [NSString stringWithRepeatedChar:MyLogger.indentChar times:gIndent];
    NSLog(gFormat, indent, nameOfLevel(intentLevel), [logger context], s);
    [s release];
  }
}

static BOOL isValidLevel(MyLoggerLevel intentLevel)
{
  BOOL valid = NO;
  switch (intentLevel) {
    case LogLevelDebug:
    case LogLevelWarning:
    case LogLevelError:
    case LogLevelInfo:
      valid = YES;
      break;
    default:
      break;
  }
  return valid;
}


This almost concludes my posts on MyLogger class. I'll wrap it up in my next post with its usages and complete codes for downloading.

Reference

0 comments: