Vincent Gable’s Blog

December 16, 2008

isEmpty?

Checking if a Cocoa object is empty is a little harder then in other languages, say C++, (but easier in some ways). Because every object in Objective-C is actually a pointer to an object, there are two ways, obj, can be empty.

obj = {}

obj points to an object that is empty. Say an array with 0 items, or the string "", etc..

obj = nil

obj, the pointer obj, is NULL, nil, 0, or whatever you want to call it. You might argue that obj isn’t really an object, but it is empty, because there’s nothing in it.

Bug:

When I first started writing Objective-C, I made the mistake of writing code like: if([name isEqualToString:@""]){ ... }, to test for empty strings. And this code would work for a while until I used it in a situation where name was nil, and then, because sending any method called on nil “returns” NO, I would have mysterious errors. (Worse then a crash, because it’s harder to track down.)

Bug:

It’s tempting to avoid the previous bug, by explicitly testing for nil and {}. Say with code like:

if (email == nil || ![email isEqualTo:@""] )
   email = @"An email address is required";

But generally this is a bad idea. It means more code, which means more places for a bug. I know it’s only one trivial test, but I’m serious, when I say it’s asking for a bug — like the bug in the example above, which sets email to @"An email address is required", whenever it is not the empty string, rather then when it is empty. (Values have been changed tho protect the innocent but it’s a bug I’ve seen.)

Solutions:

Wil Shipley suggests using the global function:

static inline BOOL IsEmpty(id thing) {
    return thing == nil
        || ([thing respondsToSelector:@selector(length)]
        && [(NSData *)thing length] == 0)
        || ([thing respondsToSelector:@selector(count)]
        && [(NSArray *)thing count] == 0);
}

I’ve been using his IsEmpty() for about a year. I’ve had zero problems with it, while it’s made my code more readable and concise.

Another solution is to take advantage of what happens when you send a message to nil. (To over-simplify, you get back 0 or NO.) So you can just say “if ([obj count] == 0) then obj is empty.” This often means reversing your thinking, and testing “IsNotEmpty()” instead of “IsEmpty()”. I don’t think it’s as clear is IsEmpty() in general, but in cases where it is, there you have it.

December 12, 2008

Catching nil Member Variables

Filed under: MacOSX,Objective-C,Sample Code | , , ,
― Vincent Gable on December 12, 2008

When I wrote this method it seemed like it would be helpful,

//log a warning for any member variable that nil
- (void) warnOfNilIVars;
{
  unsigned int ivarCount = 0;
  Ivar * ivarList = class_copyIvarList([self class], &ivarCount);
  if(ivarList) {
    for(int i = 0; i < ivarCount; i++){
      const char *typeString = ivar_getTypeEncoding(ivarList[i]);
      if(typeString && typeString[0] == '@')
        if(!object_getIvar(self, ivarList[i]))
          NSLog(@"***WARNING: ivar %s of %@ is nil", ivar_getName(ivarList[i]), [self className]);
    }
    free(ivarList);
  }
}

But in practice I haven’t really used it (and when I have there were quite a few false-positives). Still, I think it’s pretty neat that you can do something like this in Objective-C.

If you find a use for it, please let me know!

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 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 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';
}

August 22, 2008

Is an Application Running?

Filed under: Cocoa,MacOSX,Objective-C,Programming,Sample Code,UNIX | ,
― Vincent Gable on August 22, 2008

UPDATED 2009-01-13 to correctly detect background applications.

Pass in the bundle-identfier of an application, and this will tell you if it’s currently running or not:
- (BOOL) ThereIsARunningApplicationWithBundleID:(NSString*)bundleID; {
   ProcessSerialNumber PSN = {kNoProcess};
   while(GetNextProcess(&PSN) == noErr) {
      CFDictionaryRef info = ProcessInformationCopyDictionary(&PSN,kProcessDictionaryIncludeAllInformationMask);
      if(info) {
         NSString *theirBundleID = (NSString*)CFDictionaryGetValue(info, kIOBundleIdentifierKey);
         BOOL bundleIDMatches = [bundleID isEqualTo:(NSString*)theirBundleID];
         CFRelease(info);
         if(bundleIDMatches)
            return YES;
      }
   }
   return NO;
}

(It’s a good idea to test if an application is running before sending it an AppleEvent, because that will launch it.)

Interestingly, none of the the Process Manager APIs can see into another user’s process-space; but ps can; at least on OS X 10.5 and 10.4.

August 5, 2008

Simplified Logging

Filed under: Cocoa,MacOSX,Objective-C,Programming,Sample Code,Tips,Usability | , ,
― Vincent Gable on August 5, 2008

I have noticed a pattern in my Cocoa code, which I have been able to simplify. I often print out the value of a variable for debugging. 99 times out of 100, the code looks like this: NSLog(@"actionURL = %@", actionURL);, where actionURL is some variable.

But using the macros, I can say LOG_ID(actionURL);. This is shorter, and non-repetitive.

The macros I use to simplify debugging (2008-09-16):

#define FourCharCode2NSString(err) NSFileTypeForHFSTypeCode(err)

#define LOG_4CC(x) NSLog(@"%s = %@", # x, FourCharCode2NSString(x))
#define LOG_FUNCTION() NSLog(@"%s", __FUNCTION__)
#define LOG_ID(o) NSLog(@"%s = %@", # o, o)
#define LOG_INT(i) NSLog(@"%s = %d", # i, i)
#define LOG_INT64(ll) NSLog(@"%s = %lld", # ll, ll)
#define LOG_FLOAT(f) NSLog(@"%s = %f", # f, f)
#define LOG_LONG_FLOAT(f) NSLog(@"%s = %Lf", # f, f)
#define LOG_OBJECT(o) LOG_ID(o)
#define LOG_POINT(p) NSLog(@"%s = %@", # p, NSStringFromPoint(p))
#define LOG_RECT(r) NSLog(@"%s = %@", # r, NSStringFromRect(r))
#define LOG_SIZE(s) NSLog(@"%s = %@", # s, NSStringFromSize(s))

Look in assert.h for insight on how to roll your own debugging macros.

August 3, 2008

How to Implement Automatic Updating

Filed under: Cocoa,Design,MacOSX,Objective-C,Programming,Sample Code,Usability
― Vincent Gable on August 3, 2008

If you are about to add automatic updating to your application (and you should), then check out the Sparkle Framework before you start. I wish I had done this before I spent a lot of time writing my own automatic updating code.

A quick-and-dirty hack.

Get your application’s version number by:
[[[NSBundle mainBundle] infoDictionary] valueForKey:@"CFBundleVersion"];
(This articles explains setting up Xcode to bake the current subversion-number into every build.)

Get the the latest version number out of a text-file on a website:
[NSString stringWithContentsOfURL:[NSURL URLWithString:@"www.your-url-here.com/whaterver/version.txt" encoding:NSASCIIStringEncoding error:nil]];
If there is any trouble, it will return nil. CAUTION: This will block the main thread for up to 30 seconds, while -stringWithContentsOfURL: tries to access the URL.

Making it Usable

If you don’t want your program to be unresponsive while it tries to get something off the internet, try this:

- (void) updatesReady
{
   //do something....
}

- (void) synchronousCheck
{
   NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

   NSURL* latestURL = [NSURL URLWithString:@"http://www.your-url-here.com/currentversion.txt"];
   NSString* latestVersion = [NSString stringWithContentsOfURL:latestURL encoding:NSASCIIStringEncoding error:nil];
   //latestVersion will be nil if there trouble was accessing latestURL.

   NSString* ourVersion = [[[NSBundle mainBundle] infoDictionary] valueForKey:@"CFBundleVersion"];
   if([ourVersion isLessThan: latestVersion])
      [self performSelectorOnMainThread:@selector(updatesReady) withObject:nil waitUntilDone:YES];

   [pool drain];
}

//will call updatesReady iff a new version is ready, otherwise does nothing.
- (void) checkForUpdates
{
   [NSThread detachNewThreadSelector:@selector(synchronousCheck) toTarget:self withObject:nil];
}

A few thoughts on automatic updating:

When the user explicitly runs an update-check, they should get immediate feedback that something happened. You should put up a window with a spinner. Even though checking for updates may only take 100msec in your lab, if the user is on a flakey wireless connection while traveling, it could take several seconds for them.

It feels like it takes several years for an HTTP fetch to time out, if you can’t do anything while you wait.

Anything having to do with threads is harder then it looks.

“Check for updates {daily, weekly, monthly}” is a useless preference. Just because Apple’s Software Update has it does not mean you should offer those choices! They do not give the user any real control over when and how they are annoyed by requests to update. Users don’t care when the update checks are performed — they only care when new software is announced to them. Ether go with the a simple check-box that toggles automatic updating, or go with something like:
“Let me know about new software: at 12 PM”.

Display the date of the last successful update check, never display “Last Update Monday 9AM — update failed”. (This should be obvious, but I see it all the time). Version checking should be silent and unobtrusive. Don’t tell the user about it — only tell them about results.

Automatically downloading updates in the background is a great idea if they are small, and the computer has a reasonably fast internet connection.

You might also want to check out: NiftyFeatures 0.4 By Uli Kusterer .

An example project containing two classes, UKUpdateChecker and UKFeedbackProvider that can be plugged into your applications to add “Check for Update…” and “Send Feedback…” menu items to your applications.

July 5, 2008

FourCharCode2NSString

Filed under: MacOSX,Objective-C,Programming,Sample Code,Usability | , , ,
― Vincent Gable on July 5, 2008

As I have written before, the best way to convert a FourCharCode to an NSString* for NSLog()ing is to use the NSFileTypeForHFSTypeCode() function. But for the life of me I can’t remember that name, even though I use it about once a month. It’s too long, and it has too little to do with what I’m using it for.

So I have added the line:
#define FourCharCode2NSString(err) NSFileTypeForHFSTypeCode(err)
To my prefix-files, because I can remember FourCharCode2NSString().

UPDATE: (2008-08-06) There is an even easier way.

July 3, 2008

NSApplicationName Inconsistencies

Filed under: Bug Bite,Cocoa,MacOSX,Objective-C,Programming,Sample Code | , , ,
― Vincent Gable on July 3, 2008

The value stored under the NSApplicationName key of the result of [[NSWorkspace sharedWorkspace] activeApplication] is not the always the name the user knows the application by. Worse, it’s not always the same as the name for the application that other APIs expect or return. Even fullPathForApplication: in NSWorkspace sometimes won’t recognize it!

The problem stems from the fact that there are at least five application names floating around, at least in concept: (1) the file name the Finder sees, which in the case of an application package is the package (bundle) name; (2) the name of the executable inside the package, (3) the long name used in many places for display purposes only; (4) the short name used as the application menu title and in a few other places where a long name won’t fit for display purposes; and (5) the process name of a running application. They aren’t always the same, especially in Microsoft and Adobe products.

–From an informative message by Bill Cheeseman.

So instead of relying on NSApplicationName I now use -[[NSFileManager defaultManager] displayNameAtPath:] then strip off the filename extension. This should give exactly the filename the user sees. Every time.


NSDictionary *appInfo = [[NSWorkspace sharedWorkspace] activeApplication];
NSString *appPath = [appInfo objectForKey:@"NSApplicationPath"];
NSString *name = [[[NSFileManager defaultManager] displayNameAtPath:appPath] stringByDeletingPathExtension];

And of course, you really should be using bundle identifiers, instead of names, to identify an application. Unfortunately, a very few applications are not bundles. (For example, Microsoft stuff prior to Office 2008), so it might be necessary to fall back on using a name to locate them in a path-independent way.

Creating a custom CFBundleName in an application’s info.plist file seems to confuse NSApplicationName. For this reason I don’t think setting it is a good idea.

UPDATE 2010-01-20: See also, Technical Q&A QA1544: Obtaining the localized application name in Cocoa

« Newer PostsOlder Posts »

Powered by WordPress