As we all scramble to learn this fantastic new language Apple gifted to us at WWDC 2014, we're coming across new ways of doing things, either because the new way is better, or because the old way is no longer possible.
One of the main features that Swift has taken away, is the C preprocessor. That's what enabled #define's to work. A common #define used is for debug logging, to include useful info with every line.
#define DLog(...) NSLog(@"%s(%p) %@", __PRETTY_FUNCTION__, self, [NSString stringWithFormat:__VA_ARGS__])
This lets us go from this:
2014-06-08 05:38:54.649 TestApp[35062:60b] Simple Message
2014-06-08 05:38:54.649 TestApp[35062:60b] -[TSTAppDelegate application:didFinishLaunchingWithOptions:](0x10961f2e0) Simple Message
We now get a lot more info in our log messages and can see where in our code the log message came from without having to type it in for every line.
In Swift, we lost this functionality. The reason this was traditionally done with a #define, and not a function or class, is so that we can use the __PRETTY_FUNCTION__, __FUNCTION__, __FILE__, and __LINE__ macros. The preprocessor replaces those macros with their actual values. For example, __FILE__ will always contain the filename where the macro is placed for example. If you were to use them in a logging function, the macros would always contain the information of the logging function, not the calling function, rendering them useless.
This looked like it was going to be a major inconvenience in Swift so I filed a radar about it: http://openradar.appspot.com/17170702. After playing with Swift for a while, I've discovered a solution. I've built a library you can use in your projects.
Introducing XCGLogger. My first open sourced third party library, that I think will be essential to add to your project.
The source can be found on GitHub here: https://github.com/DaveWoodCom/XCGLogger, with basic instructions on how to use it.
At a glance, it will change your logs from this:
2014-06-09 06:44:43.600 [Debug] [AppDelegate.swift:40] application(_:didFinishLaunchingWithOptions:): Simple message
By writing code like this:
instead of this:
A few things to note:
- Swift is brand new and in a state of flux, so it — and any libraries using it — will be subject to frequent changes.
- This is my first released library, so please let me know what you think and please share any ideas for improvement.
- There is a bug in Xcode 6 when using __FUNCTION__. XCGLogger uses a workaround for now, but will remove that once the bug is fixed. See: http://openradar.appspot.com/17219684
- This library is intended for use in Swift projects. There's no Objective-C compatibility included in it at this time. If it's a requested feature, it can be added.
Since Swift is brand new, there are a lot of different ways to accomplish the same thing. Over time, some best practices and patterns will emerge. I've used what I think are good practices and patterns in this library and hopefully they'll be helpful for developers as we work to establish what's best. For example, how to store static tokens for dispatch_once calls, shared instances, and global variables etc.
How does this work when I said earlier that using __FUNCTION__ and its friends in a function only gives you the information in the function instead of where it's called? Well, that's the secret sauce I discovered last week. If you set the default value of a parameter in a function to one of the macros, the compiler fills in that value at the calling location giving us the effect we need. Giving us access to the correct macro values inside the function that's called.
If you find this library helpful, you'll definitely find these other tools helpful:
Watchdog - monitors Xcode® and automatically cleans up stale cache files
Slender - cleans up Xcode projects, removing duplicate and/or unused assets
Briefs - powerful app prototyping, lets you and your clients try before you build
Follow me on Twitter @DaveWoodX