Swift: Revisiting printing with string formats

Support for NSLog is baked into Swift via the Foundation framework. The NSLog utility function offers string-formatting services that log error messages to standard error and to the system console. Unlike print, NSLog supports standard %-delimited format specifiers as well as Apple-supplied extensions. Apple’s String Programming Guide details its support for and extensions to the IEEE printf specification.

You pass NSLog a format string followed by a variadic list of Objective-C-compatible parameters. The parameters map to specifiers embedded within the format string.

NSLog("Dictionary: %@, Double: %0.2f", ["Hello":3], 2.7)

Swift supports format specifiers as one way the standard library offers to create and initialize strings.

/// Returns a `String` object initialized by using a given 
/// format string as a template into which the remaining argument 
/// values are substituted according to the user’s default locale. 
init(format: String, arguments: [CVarArgType])

Using this initializer, you can easily build a custom logger that mimics NSLog:

public func SWLog(format: String, _ args: CVarArgType...) {
    let dateFormatter = NSDateFormatter()
    dateFormatter.dateFormat = 
            "mm:ss:SSS", options: 0, 
            locale: NSLocale.currentLocale())
    let timeString = 
    print("\(timeString): " + 
        String(format: format, arguments: args))

This implementation minimizes printed time to just minutes, seconds, and fractions of a second compared to the exhaustive date output provided by NSLog. For example:

SWLog("Hello world") 
SWLog("Formatted double: %2.3f", 5.2)  
SWLog("Double plus string %2.3f, %@", 5.2, "Hello world")


55:40.706: Hello world 
55:40.707: Formatted double: 5.200
55:40.708: Double plus string 5.200, Hello world

You cannot use format arguments to express Swift constructs like enumerations, functions, and structs. They do not conform to the CVarArgType protocol. For example, this line results in a compile time error:

SWLog("Struct: %@, Int: %03zd", CGPoint(x: 50, y: 20), 5)

Instead, add Swift string interpolation to format string initialization. This approach combines the flexibility and nuance of format strings with pure Swift types:

SWLog("Struct: \(CGPoint(x: 50, y: 20)), Int: %03zd", 5)


  • Creating the date formatter each time is really expensive . You could optimize that.

    • Very good point. Here’s my actual code I’m using for this bit in the book:

      internal func BuildSimpleTimeFormatter() -> NSDateFormatter {
          let dateFormatter = NSDateFormatter()
          dateFormatter.dateFormat = NSDateFormatter.dateFormatFromTemplate("mm:ss:SSS", options: 0, locale: NSLocale.currentLocale())
          return dateFormatter
      internal let dateFormatter = BuildSimpleTimeFormatter()
      public func SWLog(format: String, _ args: CVarArgType...) {
          #if DEBUG
              let timeString = dateFormatter.stringFromDate(NSDate())
              print("\(timeString): " + String(format: format, arguments: args), &errStream)