Logging and bookmarks with Objective-C and Swift

Overview

This article discusses the techniques I use for bookmarking code and adding log statements for both Objective-C and Swift.

ObjC_Swift_logging

Temporary Bookmarks

When I develop code I often find a need to temporarily reference different sections that I’m working on, either within the same file or in many different files.   Prior to Xcode 4 there was apparently a means to set and jump to bookmarks with keyboard shortcuts as mentioned in the article Killer Xcode Tips and Tricks – Tips 1 to 10 (see tips 7 and 8).

Starting with Xcode 4, there were a few workaround options for bookmarking as mentioned in the post How to bookmark code in XCode 4?

Breakpoints are a nice quick option which I use occasionally as a temporary bookmark.   Simply set a breakpoint and immediately disable it.    The problem with using a breakpoint as a bookmark is that you can’t label it and the bookmark is only loosely coupled to the code.  They also tend to clutter the Breakpoint Navigator when you are setting real breakpoints for debugging.

Using a Pragma which can show up in the Issue Navigator holds more promise and is the method I settled upon for Objective-C work.  Swift requires a different approach which I discuss later in this article.

Pragma bookmarks and temporary logging

Since Objective-C compilation calls a preprocessor, it is possible to make good use of macros.   A pragma can be used to issue a compiler message as documented in the GCC online spec in the section on Diagnostic Pragmas.  This message appears in the Issue Navigator.

I’ve created a set of macros that I use to set bookmarks which I can optionally combine with logging.

The following is the code for a temporary FIXME bookmark:

#define DO_PRAGMA(x) _Pragma (#x)

#define FIXME(x) DO_PRAGMA(message (STR(__LINE__) " FIXME: " #x))

// This macro can appear anywhere in your code, including outside
// of methods.  Quotes are not necessary when using the FIXME
// macro. Example:
//
FIXME(Need to fix this bug in the next iteration)

I can expand on this concept to provide a macro that will both log a temporary message to the console and appear in the Issue Navigator:


#define CLOG(x) NSLog(@#x); DO_PRAGMA(message (STR(__LINE__) " NSLog: " #x))

// The CLOG macro is used inside of methods as an alternative
// to NSLog for simple logging.  Quotes, the @ string prefix and
// the trailing semicolon are not needed.  Example:
//
CLOG(A simple constant string log message)

Taking it one step further, a macro can be created which can fully replace NSLog, including the ability to handle a variable number of arguments. This is known as a variadic macro. The NLOG macro below also adds the ability to include the function and line number in the console output.


// Note:
// The first arg to FLOG, LogType, can be used as an optional 
// prefix for console output. It is not currently used with NLOG
// (set to "")
//
// The second arg, MacroType, isn't used below.  It is present as a 
// placeholder and will be discussed later.
#define FLOG(LogType, MacroType, FormatLiteral, ...)  NSLog (@"%s(%u): " LogType " \n" FormatLiteral "\n\n", __FUNCTION__, __LINE__, ##__VA_ARGS__);  

#define NLOG(FormatLiteral, ...)  FLOG("","NSLog", FormatLiteral, ##__VA_ARGS__) DO_PRAGMA(message (STR(__LINE__) " NSLog : " FormatLiteral " " #__VA_ARGS__))

// NLOG is used like NSLog except that the @ string prefix and trailing semicolon are not needed.  Example:
//
NSString *msg = @"My message";
NLOG("msg = %@",msg)

The following screenshot shows all 3 macros in action. Note how both the NLOG and CLOG macros cause output to be sent to the console, the same as NSLog. All of the instances of the 3 macros (NLOG, CLOG, FIXME) appear in the Issue Navigator. One can click on an issue to quickly go to the section of code including the corresponding macro.

ObjC_logging_screenshot_1

Permanent Objective-C logging

Sometimes I want to add a log statement which retains the power of NLOG (including the addition of function and line number to the output) without causing it to show up in the Issue Navigator. I call these project specific logging macros as I name them according to their purpose or place in the code. As an example, I might use the following macro within code related to Core Data activity:


#define PLOG_CORE_DATA(FormatLiteral, ...)  FLOG("PLOG_CORE_DATA","PLOG_CORE_DATA", FormatLiteral, ##__VA_ARGS__)

This is used just like NLOG with a couple exceptions:

  • The macro does not appear in the Issue Navigator which I reserve for temporary logging.
  • I set the LogType arg of FLOG to a prefix that is easily recognizable in the console output (the generic NLOG macro does not use a prefix)

Objective-C logging wrap-up

One last loose end I didn’t cover was the second argument to FLOG – MacroType.

What I presented above was a slight simplification of my actual use of the macros.   When I use project specific logging macros, I sometime wish to toggle a setting to see them also.   Here is the actual implemention of FLOG and NLOG including the toggle ENABLE_PRAGMA_FOR_FLOG. Here you can see that when I enable all macros to be visible in the Issue Navigator (i.e. FLOG uses DO_PRAGMA instead of NLOG), MacroType is used as a string that appears in the Navigator to mark the type of macro being used.


// If ENABLE_PRAGMA_FOR_FLOG is set, FLOG and NLOG both use pragma.  If not set, only NLOG uses pragma.
#if ENABLE_PRAGMA_FOR_FLOG

#define FLOG(LogType, MacroType, FormatLiteral, ...)  NSLog (@"%s(%u): " LogType " \n" FormatLiteral "\n\n", __FUNCTION__, __LINE__, ##__VA_ARGS__);  DO_PRAGMA(message (STR(__LINE__) " " MacroType ": " FormatLiteral " " #__VA_ARGS__))

#define NLOG(FormatLiteral, ...)  FLOG("","NSLog", FormatLiteral, ##__VA_ARGS__)

#else

#define FLOG(LogType, MacroType, FormatLiteral, ...)  NSLog (@"%s(%u): " LogType " \n" FormatLiteral "\n\n", __FUNCTION__, __LINE__, ##__VA_ARGS__);  

#define NLOG(FormatLiteral, ...)  FLOG("","NSLog", FormatLiteral, ##__VA_ARGS__) DO_PRAGMA(message (STR(__LINE__) " NSLog : " FormatLiteral " " #__VA_ARGS__))
#endif


Swift

Swift does not use a preprocessor, so pragma flags and macros are not an option.

It is still possible though to provide similar functionality to the NLOG and FIXME macros used with Objective-C by making clever use of a benign warning.   What I settled on was the warning “Treating a forced downcast as optional will never produce ‘nil'”.

Here is the code that can be used for a FIXME bookmark:


var g_anyFixme: AnyObject = SNLog.Fixme()

var g_fixme: SNLog.Fixme?

class SNLog {
    class Fixme { }
}

// To provoke a warning in the Issue Navigator, include the following
// assignment inside of a function:
g_fixme = g_anyFixme as SNLog.Fixme

What is happening here?  The global variable g_anyFixme is of type AnyObject.  This is an alias  that can represent an instance of any class type. In our assignment to g_fixme we use a forced downcast. If we had instead used an optional downcast with “as?” then no warning would be generated since the “as?” form can produce nil. A forced downcast with “as” is only used when you are sure the downcast can succeed (and will not produce nil).  Since we cannot get nil, we trigger the warning.

How can we expand this to provide logging with a warning?   Let’s expand the SNLog class and add a new global variable g_log:


var g_log: SNLog?
class SNLog {

    class Fixme { }

    // The info() method calls display() with the prefix "INFO: "
    class func info (message: String, filePath: String = __FILE__, function: String = __FUNCTION__,  line: Int32 = __LINE__) -> AnyObject {
        return display("INFO: ", message: message, filePath: filePath, function: function, line: line)
    }

    private class func display (prefix: String, message: String, filePath: String, function: String,  line: Int32) -> AnyObject {
        let file: String = filePath.lastPathComponent

        println("\(prefix)\(file) - \(function)(\(line)):\n \(message)\n")

        return SNLog()
    }

}

// To provoke a warning in the Issue Navigator with logging,
// include the following assignment inside of a function:
g_log = SNLog.info("My log message") as SNLog

The principle here is the same.  The info class function returns AnyObject which we are force downcasting to SNLog triggering the same warning as with the downcast to SNLog.Fixme in the previous example.

We use default arguments in the info function that will provide us with the file, function and line number where SNLog.info() was called.     What if you want the extra functionality of this logging without a warning?  Just call SNLog.info without the assignment or downcast.

MIT license

I have released SNLog under an MIT license at scarter/SNLog on GitHub.

The SNLog class adds a couple extra methods than is shown above and the documentation includes several examples as well as an explanation of how to use SNLog within a closure.

Here is a screenshot showing all the functionality of SNLog:

SNLog_screenshot_1

Any feedback is welcome!

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s