Vincent Gable’s Blog

October 5, 2008

Restarting Your Mac OS X Cocoa Application

Filed under: Bug Bite,Cocoa,MacOSX,Objective-C,Programming,UNIX | , ,
― Vincent Gable on October 5, 2008

Restarting your own application is a tricky thing to do, because you can’t tell yourself to start up when you aren’t running. Here is one solution, use NSTask to run a very short script that you can embed right in your Objective-C code:
kill -9 YourPID
open PathToYourApp

Something to be aware of, kill -9 will immediately terminate your application, without going through the usual applicationWillTerminate: business that happens when an application quits more gracefully.

- (void) restartOurselves
{
   //$N = argv[N]
   NSString *killArg1AndOpenArg2Script = @"kill -9 $1 \n open \"$2\"";
   
   //NSTask needs its arguments to be strings
   NSString *ourPID = [NSString stringWithFormat:@"%d",
                  [[NSProcessInfo processInfo] processIdentifier]];
   
   //this will be the path to the .app bundle,
   //not the executable inside it; exactly what `open` wants
   NSString * pathToUs = [[NSBundle mainBundle] bundlePath];
   
   NSArray *shArgs = [NSArray arrayWithObjects:@"-c", // -c tells sh to execute the next argument, passing it the remaining arguments.
                killArg1AndOpenArg2Script,
                @"", //$0 path to script (ignored)
                ourPID, //$1 in restartScript
                pathToUs, //$2 in the restartScript
                nil];
   NSTask *restartTask = [NSTask launchedTaskWithLaunchPath:@"/bin/sh" arguments:shArgs];
   [restartTask waitUntilExit]; //wait for killArg1AndOpenArg2Script to finish
   NSLog(@"*** ERROR: %@ should have been terminated, but we are still running", pathToUs);
   assert(!"We should not be running!");
}

WARNING: don’t make the same mistake that I did and test restartOurselves without some kind of guard to keep your application from restarting forever. It is very difficult to kill such a beast, because whenever it starts up it takes keyboard focus away from what you are doing…. well I’m sure you get the idea.

- (BOOL) weHaveRunBefore {
   NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
   BOOL weHaveRunBefore = [prefs boolForKey:@"weHaveRunBefore"];
   [prefs setBool:YESs forKey:@"weHaveRunBefore"];
   [prefs synchronize];
   return weHaveRunBefore;
}

September 27, 2008

CFShow is NSLog for Core Foundation Types

Filed under: MacOSX,Programming,Tips | , , , ,
― Vincent Gable on September 27, 2008

CFShow(coreFoundationThingy) will print out a description of coreFoundationThingy to the console. Output looks something like:

{value = w:1186.000000 h:687.000000 type = kAXValueCGSizeType}

If NSLog() is printing something out as an NSCFType, try CFShow().

September 25, 2008

Simple Truths About Cross-Platform Apps

Filed under: Design,MacOSX,Programming,Quotes | , ,
― Vincent Gable on September 25, 2008

Scott Stevenson tells it like it is,

Even if Apple recommended cross-platform toolkits for Mac development, the basic premise of Mac software market would not change. Mac users bought the computer they did because they found the experience more appealing. Bringing an application across from Windows with minor tweaks simply won’t resonate with this sort of user.

And gives free advice,

Maybe the most important thing you will ever need to know about Mac development is this:

Mac users will generally favor an app with a better experience over the one with more features.

The full article.

September 24, 2008

XML Parsing: You’re Doing it Wrong

Filed under: Cocoa,MacOSX,Objective-C,Programming,Quotes,Sample Code,Tips | ,
― Vincent Gable on September 24, 2008

There are lots of examples of people using text searching and regular expressions to find data in webpages. These examples are doing it wrong.

NSXMLDocument and an XPath query are your friends. They really make finding elements within a webpage, RSS feed or XML documents very easy.

Matt Gallagher

I haven’t used XPath before, but after seeing Matt’s example code, I am convinced he’s right, because I’ve seen the other side of things. (I’ll let you in on a dirty little secret — right now the worst bit of the code-base I’m working on parses XML.)

    NSError *error;
    NSXMLDocument *document =
        [[NSXMLDocument alloc] initWithData:responseData options:NSXMLDocumentTidyHTML error:&error];
    [document autorelease];
    
    // Deliberately ignore the error: with most HTML it will be filled with
    // numerous "tidy" warnings.
    
    NSXMLElement *rootNode = [document rootElement];
    
    NSString *xpathQueryString =
        @"//div[@id='newtothestore']/div[@class='modulecontent']/div[@class='list_content']/ul/li/a";
    NSArray *newItemsNodes = [rootNode nodesForXPath:xpathQueryString error:&error];
    if (error)
    {
        [[NSAlert alertWithError:error] runModal];
        return;
    }

(I added [document autorelease]; to the above code, because you should always immediately balance an alloc/init with autorelease, outside of your own init methods.)

September 20, 2008

Regex Matching and Filtering in Cocoa With NSPredicate

Filed under: Cocoa,MacOSX,Objective-C,Programming | , , ,
― Vincent Gable on September 20, 2008

Apples documentation of Regular Expressions With NSPredicate has the full scoop, but basically you do
[NSPredicate predicateWithFormat:@"SELF MATCHES regex-here"];. Unfortunately, you can only test if strings match a regex. You can not use an NSPredicate-regex to extract parts of a string. Depending on what you need to do, this may or may not be enough to save the day.

September 16, 2008

-description, Little-Known Hero of Debugging

Filed under: Cocoa,MacOSX,Objective-C,Programming,Sample Code,Tips | , , ,
― Vincent Gable on September 16, 2008

Or: How to Make NSLog() Useful With Your Objects

Say you have an archaically named NSArray that you want to inspect — it’s easy to do, since NSLog(@"The bestiary is %@", bestiary); prints out the array’s contents

2008-09-16 19:46:06.445 Tester[2678:10b] The bestiary is (
Cheetah,
Pumpa,
Jaguar,
Panther,
Tiger,
Leopard
)

But if you try to NSLog your own object, you get pretty useless output, like

myObject = <MyObject: 0x53f330>

Fortunately, it’s easy to fix! Just implement the method -(NSString*) description; and whatever it returns will be printed by NSLog and GDB (po object, will print object in GDB and the Xcode debugging console).

Here’s an (unfortunately complex) example,

- (NSString*) description;
{
  return [NSString stringWithFormat:@"<%@ %p> = {\n\tquestion=%@,\n\tanswer=%@,\n\tsource=%@\n}", [self className], self, self.question, self.answer, self.source];
}

output:

myObject = <MyObject 0x53eed0> = {
  question=What is the Best Thing Ever Of All Times, Ever?,
  answer=The Internet!,
  source=http://www.cabel.name/2008/01/2007-cabel-yay-awards.html
}

Useful Formatters and such

These macros have made my debugging-life easer.

%p tells NSLog to print the address of a pointer.

-className returns gives the name of a class as an NSString.

Don’t manually print out a Cocoa struct, ever, there are already NSStringTo* functions to do that for you, like NSStringFromPoint().

NSStringFromSelector() works as advertized (and paired with NSSelectorFromString() is very useful in general).

%lld tells NSLog to print a long long (64-bit integer). See also, printf reference.

%Lf tells NSLog to print a long double. See also, printf reference.

Best Practices

Whenever you make a new object, I strongly recommended immediately implementing a description method, and religiously keeping it up to date (it’s not hard, honest!). This won’t fix bugs, but it will make finding some of them much easier.

September 14, 2008

Fast Enumeration Really Is Faster

Filed under: Cocoa,MacOSX,Objective-C,Programming,Tips | , ,
― Vincent Gable on September 14, 2008

Your code will also run faster because the internal implementation reduces message send overhead and increases pipelining potential.

Matt Gallagher

I’ve written before about the advantages of a better enumeration paradigm, clearer code that’s faster to write, shorter, and with fewer bugs.

If you program in Cocoa, you should adopt fast enumeration post-haste.

September 11, 2008

Detecting if Headphones are Plugged In

Filed under: MacOSX,Programming,Sample Code | , ,
― Vincent Gable on September 11, 2008

I found many posts on mailing lists asking the question of how to detect if something is plugged into the headphones jack, but no complete answer. So here’s how I do it.

Docs & Tools

Use the CoreAudio.framework, specifically CoreAudio/CoreAudio.h Unfortunately it’s a bit confusing sometimes, but the documentation is still very complete (if complex).

HALLab (/Developer/Examples/CoreAudio/HAL/HALLab/ if you have the developer tools installed) is the most complex bit of relevant CoreAudio sample code, and when built it’s a useful utility for poking-around with to boot.

How To Do It

Whenever something is plugged into the headphones jack, CoreAudio changes the data source for the output device, from internal speakers to headphones. Use AudioDeviceGetProperty(outputDeviceID, 0, 0, kAudioDevicePropertyDataSource, &size, &dataSource); to examine the 32-bit ID of the data source, which is best thought of as a 4 character code. If dataSource is 'ispk', sound is played through internal speakers, if it is 'hdpn', sound is playing through the headphones jack. I’m not sure what happens if you have speakers plugged in that use a different output source, like USB, FireWire, or optical out.

A word of caution, do not use the promising-looking AudioDevice Property kAudioDevicePropertyJackIsConnected it does not work on all systems (specifically it didn’t work in 10.4, and I have not tested it under Leopard).

Change Notifications

If you want to be notified when something is plugged/unplugged in the headphones jack, listen for a kAudioDevicePropertyDataSource change to the output audio device. Here is the code I use to test if a computer is using it’s internal speakers:

Sample Code:


//Returns YES if the default sound output device
//is using external speakers to play sound.
- (BOOL) usingInternalSpeakers
{
   AudioDeviceID deviceID;
   UInt32 size = sizeof(deviceID);
   OSStatus err = AudioHardwareGetProperty(kAudioHardwarePropertyDefaultSystemOutputDevice, &size, &deviceID);
   NSCAssert((err == noErr), @"AudioHardwareGetProperty failed to get the kAudioHardwarePropertyDefaultSystemOutputDevice property");
   //To be notified when something is plugged/unplugged into the headphones jack
   //listen for a kAudioDevicePropertyDataSource or kAudioDevicePropertyDataSources notification on deviceID
   
   //Check if headphones are plugged in right now:
   UInt32 dataSource;
   size = sizeof(dataSource);
   err = AudioDeviceGetProperty(deviceID, 0, 0, kAudioDevicePropertyDataSource, &size, &dataSource);
   NSCAssert((err == noErr), @"AudioDeviceGetProperty failed to get the kAudioDevicePropertyDataSource property");
   
   //'ispk' => internal speakers
   //'hdpn' => headphones
   return dataSource == 'ispk';
}

September 5, 2008

ASCII is Dangerous

Never use NSASCIIStringEncoding

“Foreign” characters, like the ï in “naïve”, will break your code, if you use NSASCIIStringEncoding. Such characters are more common then you might expect, even if you do not have an internationalized application. “Smart quotes”, and most well-rendered punctuation marks, are not 7-bit ASCII. For example, that last sentence can’t be encoded into ASCII, because my blog uses smart-quotes. (Seriously, [thatSentence cStringUsingEncoding:NSASCIIStringEncoding] will return nil!)

Here are some simple alternatives:

C-String Paths
Use - (const char *)fileSystemRepresentation; to get a C-string that you can pass to POSIX functions. The C-string will be freed when the NSString it came from is freed.

An Alternate Encoding
NSUTF8StringEncoding is the closest safe alternative to NSASCIIStringEncoding. ASCII characters have the same representation in UTF-8 as in ASCII. UTF-8 strings will printf correctly, but will look wrong (‘fancy’ characters will be garbage) if you use NSLog(%s).

Native Foundation (NSLog) Encoding
Generally, Foundation uses UTF-16. It is my understanding that this is what NSStrings are by default under the hood. UTF-16 strings will look right if you print them with NSLog(%s), but will not print correctly using printf. In my experience printf truncates UTF-16 strings in an unpredictable way. Do not mix UTF-16 and printf.

Convenience C-Ctrings
[someNSString UTF8String] will give you a const char * to a NULL-terminated UTF8-string. ASCII characters have the same representation in UTF-8 as in ASCII.

Take a minute to search all your projects for NSASCIIStringEncoding, and replace it with a more robust option.

It never hurts to brush up on unicode.

August 31, 2008

Better List Termination

Filed under: MacOSX,Objective-C,Programming,Usability | , ,
― Vincent Gable on August 31, 2008

As I’ve written before, using nil to terminate a variable-length list (eg [NSArray arrayWithObject:foo,bar,nil]) is a bad choice because of how easy it is to get a nil value in Objective-C without knowing it. (Unlike, say C++, doing stuff with nil won’t cause an exception, leading to some subtle bugs). Well, here is a solution.

A Better Sentinel

#define END_OF_LIST ((id) -1)
is my current choice of a good sentinel-value. It is an invalid pointer, so unlike nil, you should never get it back from some library. Sending it any message will fail fast, and recognizably (you will see 0xFF...FF as an illegal address somewhere in the back-trace).

Example:


id aNilObject = nil;
NSArray *standard = [NSArray arrayWithObjects:@"1", @"2", aNilObject, @"4", nil];
LOG_ID(standard);
NSArray *better = [NSArray arrayWithSentinelTerminatedObjects:@"1", @"2", aNilObject, @"4", END_OF_LIST];
LOG_ID(better);


standard = (
   1,
   2
)
better = (
   1,
   2,
   <null>,
   4
)

The Code:


#define END_OF_LIST ((id) -1)

@implementation NSArray (SentinelTerminatedConstructors)

+ (NSArray*) arrayWithSentinelTerminatedObjects:(id) firstObject, ...
{
   NSMutableArray *array = [NSMutableArray array];
   
   if (firstObject != END_OF_LIST)
   {
      if(!firstObject)
         firstObject = [NSNull null];

      [array addObject:firstObject];
      
      id eachObject;
      va_list argumentList;
      va_start(argumentList, firstObject);
      while (END_OF_LIST != (eachObject = va_arg(argumentList, id)))
      {
         if(!eachObject)
            eachObject = [NSNull null];

         [array addObject:eachObject];
      }
      va_end(argumentList);
   }
   
   return array;
}

@end

Some Downsides

arrayWithSentinelTerminatedObjects” is too long of a name. If you’ve got a better one, please share!

You won’t get a compiler-warning if you leave out END_OF_LIST, but if you compile with -Wformat, you will get a warning if you leave out nil. The full story

There are six collection creation methods in Foundation which require nil termination to function properly. They are:

+[NSArray arrayWithObjects:]
-[NSArray initWithObject:]
+[NSDictionary dictionaryWithObjectsAndKeys:]
-[NSDictionary initWithObjectsAndKeys:]
+[NSSet setWithObjects:]
-[NSSet initWithObjects:]

These methods have been decorated with the NS_REQUIRES_NIL_TERMINATION macro, which adds an additional check to invocations of those methods to make sure the nil has been included at the end of the argument list. This warning is only available when compiling with -Wformat.

What do you think?

Frankly, I haven’t used END_OF_LIST-terminated collection creation enough yet to have a final verdict. So please let me know what you think! Was this code useful? Did it cause problems, or preempt them? As always, if you have a better solution, please share!

« Newer PostsOlder Posts »

Powered by WordPress