Blogs from the Ranch

< Back to Our Blog

By Your Command


Mark Dalrymple

Most Mac programmers have used the command line, even if only briefly. Some use it to drive their source code control, some use it for Unix utilities like awk and grep, and some use it to build and run. There’s a handy technique using the command line that lets you exert control over your GUI apps.

A very brief command-line refresher: you type the command at the shell prompt and follow it with some arguments:

$ ls -l /

The command goes and does its thing, based on the arguments you gave it:

$ <b>ls -l /</b>
total 30445
drwxrwxr-x+ 50 root   admin      1700 Feb 28 17:19 Applications
drwxrwxr-x  15 root   admin       510 Nov 21 14:27 Developer
drwxr-xr-x+ 63 root   wheel      2142 Jan 27 12:57 Library
drwxr-xr-x   4 markd  admin       136 Nov  5 13:19 Local
drwxr-xr-x@  2 root   wheel        68 Aug 16  2011 Network
drwxr-xr-x+  5 root   wheel       170 Feb 27 13:12 System
drwxr-xr-x   6 root   admin       204 Nov  5 15:42 Users

What many people don’t know is that you can run Mac applications from the command line. You dig into the Contents/MacOS directory in the application bundle to find the executable. You can run TextEdit like this:

$ /Applications/TextEdit.app/Contents/MacOS/TextEdit

Because you’re running the app just like any other command, you can pass command-line arguments to it. What kind of command-line arguments? You can pass some of the special toggles from TechNote 2124, OS X Debugging Magic. NSShowAllViews causes all of the NSViews to be outlined in different colors to show the containment hierarchy. You can run TextEdit in this mode:

$ /Applications/TextEdit.app/Contents/MacOS/TextEdit -NSShowAllViews YES

This causes TextEdit to look like an early 90’s X11 program:

TextEdit with views outlined

You’re not limited to passing arguments to Apple’s applications. You can pass them to your own, as well. Command-line arguments of the form

-argumentName argumentValue

are automatically added to the NSUserDefaults system. You can ask NSUserDefaults for the value for the key (object, string, integer, etc) minus the minus sign. In this case, you would ask NSUserDefaults for the value that’s located under the @"argumentName" key.

Say your app has a cutting-edge hardware-based graphics renderer, and a fallback software-based one. Hardware is faster, but might have artifacts. Software is slower, but is always correct. You could let the user override the hardware renderer on the command line:

$ GroovyApp.app/Contents/MacOS/GroovyApp -useSoftwareRenderer 1

And query it in code:

NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
BOOL softrend = [defs boolForKey: @"useSoftwareRenderer"];

If there was no command-line parameter this code would result in NO value (zero). In the case of the program invocation above, softrend would be YES value (one). A digit was used at the command line, because -useSoftwareRenderer YES causes NSUserDefaults to treat the YES as a string instead of a boolean.

So, how does this actually work? The NSUserDefaults database is constructed with keys and values from a number of different “domains”:

  • The registration domain, which holds values set by -registerDefaults:. These are transient and not stored on disk.

  • Language-specific settings

  • Global domain, with defaults for all applications

  • The bundle-ID domain. These are the values stored in ~/Library/Preferences/com.bignerdranch.groovyapp

  • The argument domain, with defaults parsed from the application’s arguments

Defaults are applied in that order, with items farther down in the list taking precedence over higher settings. That means you can override any defaults value from the command line.

When would you use command-line user default arguments? They’re great for adding hooks for debugging and testing. In the software rendering case above, you could have a method

- (BOOL) useSoftwareRendering;

The implementation of this method could use NSUserDefaults to decide:

- (BOOL) useSoftwareRendering {
    NSUserDefaults *defs = [NSUserDefaults standardUserDefaults];
    BOOL softrend = [defs boolForKey: @"useSoftwareRenderer"];

    return softrend;

} // useSoftwareRendering

Now you’re developing new features and you want to test with software rendering, either to verify functionality or track down a bug. You can run the app like you saw before:

$ GroovyApp.app/Contents/MacOS/GroovyApp -useSoftwareRenderer 1

You can also make this change “permanent” by setting the user default:

$ defaults write com.bignerdranch.groovyapp useSoftwareRenderer -bool YES

Now your app will always use the software renderer until you alter this preference via the NSUserDefaults API, with the defaults command, or via the command line. This can be a good work-around for a user that has an unusual hardware configuration that’s breaking your app in the field.

If you’re lucky enough to have a testing crew, you can build them “knobs” that they can turn to improve their test cases. Say your app fetches new data from the net on a daily basis. If you drive the update check interval by NSUserDefaults, your crew can exercise more test scenarios by decreasing the check time on the command line.


Mark Dalrymple

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project