Vincent Gable’s Blog

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

June 6, 2008

case:

Filed under: Bug Bite,C++,Cocoa,Design,Objective-C,Programming
― Vincent Gable on June 6, 2008

This bug was frustrating enough that half-way through squashing it, I promised myself I would document the solution. Unfortunately, it’s not a particularly interesting bug, but a promise is a promise!

I had some code very much like:

typedef enum {NoError, ReadError, WriteError, NetworkError, UnknownError} ErrorType;
...
void DescriptionOfError(ErrorType err, char *string)
{
   switch(err)
   {
      IOError:
         strcpy(string, "Could not read data.");
         break;
      
      WriteError:
         strcpy(string, "Could not write data!");
         break;

      NetworkError:
         strcpy(string, "Network Error. Make sure you have an internet connection and try again");
         break;
      
      NoError:
         strcpy(string, "No error.");
         break;

      default:      
      UnknownError:
         strcpy(string, "Unknown error.");
         break;
   }
}

int main (int argc, char** argv)
{
   char desc[1024];
   DescriptionOfError(ReadError,desc);
   printf("error: %s\n", desc);
   return 0;
}

And it just wouldn’t do the right thing, but for the life of me I couldn’t see what was wrong.

The “solution” was very simple, and somewhat embarrassing. I forgot the case keyword before the labels in the switch statement. Turns out that if you don’t have a case in-front of a label, it’s treated like a goto label, not a switch label. And this is something that I’ve known for years, but for 20 minutes I kept reading the code, and my brain would interpret what it should say, not literally analyze it.

This was extremely frustrating, not because it took me a long time to fix (I wish all my bugs could be squashed in just 20 minutes!), but because the results I was seeing totally violated my mental model of what should have happened. Violating someone’s mental model is more unsettling then you might imagine — avoid doing it at all costs.

My First Octal Value

Filed under: Bug Bite,C++,Cocoa,Objective-C,Programming | , , ,
― Vincent Gable on June 6, 2008

Octal is useless today. It is easy to convert between octal and binary, so octal was used in some early computers. But hexadecimal has totally replaced it in modern use. There’s no advantage that octal has over hexadecimal, which is why hexadecimal has replaced it.

The C programming language has support for values in octal. This wouldn’t be a problem, except for the horrible syntax used to define octal values.

In C, any integral value that starts with 0 is interpreted as octal! So 010 is eight, not ten. I’ve been bitten by this before. I don’t know why this syntax was chosen over an 0o prefix (which google calculator uses), that would match the 0x prefix for defining hexadecimal values. In retrospect it was almost certainly a mistake to go with the current syntax.

Anyway, the reason I’m writing this is because for the first time ever, I used an octal value in a C program. I had to create a directory structure that could be be accessed by different accounts on the same system. So I had to explicitly set it’s permissions when I created it with createDirectoryAtPath:attributes: . I wanted the NSFilePosixPermissions value that determines the folders permissions to have the same format that the chmod command takes. And it takes an octal value. So 0777 is the first, and only, octal constant that I’ve written in any program. Even when I’ve written in assembly I’ve used hexadecimal. There’s a good chance I will never write another octal value — I hope that’s the case.

May 31, 2008

Messages to Nowhere

Filed under: Bug Bite,Cocoa,iPhone,MacOSX,Objective-C,Programming | , , ,
― Vincent Gable on May 31, 2008

If you send a message to (call a method of) an object that is nil (NULL) in Objective-C, nothing happens, and the result of the message is nil (aka 0, aka 0.0, aka NO aka false). At least most of the time. There is an important exception if sizeof(return_type) > sizeof(void*); then the return-value is undefined under PowerPC/iPhone, and thus all Macs for the next several years. So watch out if you are using a return value that is a struct, double, long double, or long long.

The fully story:

In Objective-C, it is valid to send a message to a nil object. The Objective-C runtime assumes that the return value of a message sent to a nil object is nil, as long as the message returns an object or any integer scalar of size less than or equal to sizeof(void*).
On Intel-based Macintosh computers, messages to a nil object always return 0.0 for methods whose return type is float, double, long double, or long long. Methods whose return value is a struct, as defined by the Mac OS X ABI Function Call Guide to be returned in registers, will return 0.0 for every field in the data structure. Other struct data types will not be filled with zeros. This is also true under Rosetta. On PowerPC Macintosh computers, the behavior is undefined.

I was recently bitten by this exceptional behavior. I was using an NSRange struct describing a substring; but the string was nil, so the substring was garbage. But only on a PPC machine! Even running under Rosetta wouldn’t have reproduced the bug on my MacBook Pro.Undefined values can be a hard bug to detect, because they may be reasonable values when tests are run.

Code running in the iPhone simulator will return all-zero stucts when messaging nil, but the same code running on an actual device will return undefined structs. Be aware that testing in the simulator isn’t enough to catch these bugs.

A few simple guidelines can help you avoid my misfortune:

  • Be especially careful using of any objective-C method that returns a double, struct, or long long
  • Don’t write methods that return a double, struct, orlong long. Return an object instead of a struct; an NSNumber* or float instead of a double or long long. If you must return a dangerous data type, then see if you can avoid it. There really isn’t a good reason to return a struct, except for efficiency. And when micro-optimizations like that matter, it makes more sense to write that procedure in straight-C, which avoids the overhead of Objective-C message-passing, and solves the undefined-return-value problem.
  • But if you absolutely must return a dangerous data type, then return it in a parameter.
    Bad:
    - (double) crazyMath;
    Good:
    - (void) crazyMathResult:(double*)result;.

I love Objective-C’s “nil messaging” behavior, even though it is rough around the edges. It’s usefulness is beyond the scope of this article, but it can simplify your code if you don’t return a data-type that is larger then sizeof(void*). With time, when the intel-style return behavior can be universally relied on, things will be even better.

May 25, 2008

Objects that Won’t Hide

Filed under: Bug Bite,Cocoa,Interface Builder,MacOSX,Objective-C,Programming |
― Vincent Gable on May 25, 2008

NOTE: Although this specific Bug Bite is about NSTextView, and the “hidden” property, the same underlying issue applies to other interface-objects (NSTableView, etc.), and different properties, like size.

Problem

If you send a setHidden:YES message to an NSTextView, and it’s text disappears, but the view itself (white box) stays visible here’s the problem, and the solution.

It turns out that if you created the NSTextView by dragging it off the pallet in Interface Builder, then it’s not an NSTextView. It’s an NSTextView wrapped inside an NSClipView inside an NSScrollView. The NSScrollView is what puts up the scroll-bars if the NSTextView gets really big; the NSClipView helps make the scrolling work.

So if text is your IBOutlet to your NSTextView, then when you say [text setHidden:YES];, the NSTextView is hidden, but the the total package won’t disappear, unless you hide the NSScrollView as well.

Solutions

You can send the message to NSScrollView containing text, like so:
   [[text enclosingScrollView] setHidden:YES];.
This will hide everything inside the NSScrollView, including text.

Another solution is to create just an NSTextView in Interface Builder. To do this, put an NSView in your interface (it’s called a “Custom View”,in the Interface Builder objects pallet). Then select it, bring up the object inspector (cmd-shift-i), choose the “custom class” from the category menu at the top, and select NSTextView from the list of subclasses. This puts an NSTextView in your interface, without the surrounding clip and scroll views. Unfortunately, it also means you can’t configure it in Interface Builder, beyond resizing it. That’s why I’m not partial to this approach, although I have used it.

Thanks to ZachR for suggesting enclosingScrollView.

May 14, 2008

NSAlert + Sheets + Threads = Inexplicable Bugs

Filed under: Bug Bite,Cocoa,Interface Builder,MacOSX,Objective-C,Programming | , ,
― Vincent Gable on May 14, 2008

UPDATED 2008-12-26: in general, all AppKit code should be called on the main thread.

Problem:
When using an NSAlert to display a sheet in a multi-threaded application, unexpected badness can happen.

I was using
beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo:
To display an NSAlert as a sheet.

But when the sheet appeared, the window it was attached to disappeared and got into some weird broken state where it would appear iff the application was not frontmost.

Fortunately, I remembered having encountered weirdness with NSAlert sheets before. The symptoms were different (previously the alert didn’t have focus), but the same solution still worked.

Solution: make sure the message to display the sheet is sent by the main thread. To do this, put the call to beginSheetModalForWindow:modalDelegate:didEndSelector:contextInfo: inside another method, showMyAlert, then use performSelectorOnMainThread:withObject:waitUntilDone: to make sure showMyAlert is called on the main thread.

Work around use runModal to display the alert as a modal dialog instead of a sheet. runModal Does not appear to have any problems when called from other threads.

Just like last time:

The whole incident feels funny to me. I suspect there may be some deeper issue at work that I am not aware of. When I have time to investigate further I shall update this post. Unfortunately I don’t have time to look into ‘solved’ bugs today.

UPDATED 2008-12-26: in general, all AppKit code should be called on the main thread.

May 4, 2008

Getting Mac OS X Version Information

Filed under: MacOSX,Objective-C,Programming,Sample Code,UNIX | , ,
― Vincent Gable on May 4, 2008

Cocoa

For a human-readable string, use [[NSProcessInfo processInfo] operatingSystemVersionString]. It looks like “Version 10.5 (Build 9A581)”, but that format might change in the future. NSProcessInfo.h explicitly warns against using it for parsing. It will always be human-readable though.

For machine-friendly numbers, use the Gestalt function, with one of the selectors:
gestaltSystemVersionMajor (in 10.4.17 this would be the decimal value 10)
gestaltSystemVersionMinor (in 10.4.17 this would be the decimal value 4)
gestaltSystemVersionBugFix (in 10.4.17 this would be the decimal value 17)
Do not use gestaltSystemVersion unless your code needs to run on OS X 10.2 or earlier. It’s a legacy function that can’t report minor/bugfix versions > 9; meaning it can’t distinguish between 10.4.9 and 10.4.11. The CocoaDev wiki has example code that works in 10.0 if you need to go that route.

Here’s a simpler example of using Gestalt:

- (BOOL) usingLeopard {
  long minorVersion, majorVersion;
  Gestalt(gestaltSystemVersionMajor, &majorVersion);
  Gestalt(gestaltSystemVersionMinor, &minorVersion);
  return majorVersion == 10 && minorVersion == 5;
}

Scripts

For scripts and command-line work, there is the sw_vers command.
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.5
BuildVersion: 9A581
$ sw_vers -productName
Mac OS X
$ sw_vers -productVersion
10.5
$ sw_vers -buildVersion
9A581

Java

Check the value of:

System.getProperty("os.name");
System.getProperty("os.version");

Fallback

Finally, if you must, you can parse /System/Library/CoreServices/SystemVersion.plist — but I don’t like the idea of doing that. There’s a chance that the file could have been corrupted or maliciously altered. It seems safer, and less complicated, to directly ask the operating system it’s version.

Minimum OS Requirements

To enforce a minimum OS requirement for your application, you can set the LSMinimumSystemVersion key in the Info.plist file. Big Nerd Ranch has a more robust, user-friendly, but complicated solution for older legacy systems.

April 23, 2008

Printing a FourCharCode

Filed under: Bug Bite,C++,Cocoa,MacOSX,Programming,Sample Code,Tips | , , , ,
― Vincent Gable on April 23, 2008

A lot of Macintosh APIs take or return a 32-bit value that is supposed to be interpreted as a four character long string. (Anything using one of the types FourCharCode, OSStatus, OSType, ResType, ScriptCode, UInt32, CodecType probably does this.) The idea is that the value 1952999795 tells you less, and is harder to remember, then 'this'.

It’s a good idea, unfortunately there isn’t a simple way to print integers as character strings. So you have to write your own code to print them out correctly, and since it has to print correctly on both big endian and little endian machines this is tricker then it should be. I’ve done it incorrectly before, but more importantly so has Apple’s own sample code!

Here are the best solutions I’ve found. Please let me know if you have a better way, or if you find any bugs. (The code is public-domain, so use it any way you please.)

FourCharCode to NSString:
use NSString *NSFileTypeForHFSTypeCode(OSType code);
Results are developer-readable and look like: @"'this'", but Apple says “The format of the string is a private implementation detail”, so don’t write code that depends on the format.

FourCharCode to a C-string (char*):
use the C-macro

#define FourCC2Str(code) (char[5]){(code >> 24) & 0xFF, (code >> 16) & 0xFF, (code >> 8) & 0xFF, code & 0xFF, 0}
(requires -std=c99; credit to Joachim Bengtsson). Note that the resulting C-string is statically allocated on the stack, not dynamically allocated on the heap; do not return it from a function.
If you want a value on the heap, try:


void fourByteCodeString (UInt32 code, char* str){
  sprintf(str, "'%c%c%c%c'",
    (code >> 24) & 0xFF, (code >> 16) & 0xFF,
    (code >> 8) & 0xFF, code & 0xFF);
}

In GDB, you can print a number as characters like so:

(gdb) print/T 1936746868
$4 = 'spit'

(via Borkware’s GDB tips)

If you do end up rolling your own FourCharCode printer (and I don’t recommend it!), then be sure to use arithmetic/shifting to extract character values from the integer. You can not rely on the layout of the integer in memory, because it will change on different machines. Even if you are certain that you’re code will only run on an x86 machine, it’s still a bad idea. It limits the robustness and reusability of your code. It sets you up for an unexpected bug if you ever do port your code. And things change unexpectedly in this business.

Printing a FourCharCode is harder then it should be. Experienced developers who should know better have been bitten by it (and so have I). The best solution is probably a %format for printf/NSLog that prints integers as character strings. Unfortunately, it doesn’t look like we’ll be seeing that anytime soon.

April 11, 2008

NSWindow setResizable:

Filed under: Cocoa,MacOSX,Objective-C,Programming,Sample Code | ,
― Vincent Gable on April 11, 2008

-[NSWindow setResizable:(BOOL)] does not exist. But, here is a way to get that effect as long as the window was resizable when created. (I don’t know of a way to make a window resizeable if it was first created unresizeable).

The basic idea is to show/hide the resizing controls, and implement delegate functions that prevent the window from being resized when the resizing controls are hidden.

//show or hide hide the window's resizing controls:
[[window standardWindowButton:NSWindowZoomButton] setHidden:!resizable];
[window setShowsResizeIndicator:resizable];

Note that setShowsResizeIndicator: only shows/hides the resize-indicator in the lower-right of the window. It will not make the window non-resizeable.

To keep the window from being resized when showsResizeIndicator is false, implement the delegate methods:

- (NSSize)windowWillResize:(NSWindow *) window toSize:(NSSize)newSize
{
	if([window showsResizeIndicator])
		return newSize; //resize happens
	else
		return [window frame].size; //no change
}

- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame
{
	//let the zoom happen iff showsResizeIndicator is YES
	return [window showsResizeIndicator];
}

No Delegate Hack

Finally, there is a hack that avoids using delegate methods, although I don’t recommend it.

[[window standardWindowButton:NSWindowZoomButton] setHidden:!resizable];
[window setShowsResizeIndicator:resizable];
CGFloat resizeIncrements = resizable ? 1 : MAXFLOAT;
[window setResizeIncrements:NSMakeSize(resizeIncrements, resizeIncrements)];

setResizeIncrements “Restricts the user’s ability to resize the receiver so the width and height change by multiples of width and height increments.” So setting it to MAXFLOAT x MAXFLOAT means that the user can’t resize the window, because they won’t have a big enough screen. Note that this won’t disable the “zoom” function, although the user probably couldn’t use it with the zoom button hidden.

This approach uses slightly fewer lines of code, but it is more brittle and indirect, since it depends on the screen-size, not if the window is (visibly) resizeable. I wouldn’t use it over delegates. But here it is.

April 7, 2008

foreach For The Win

Filed under: C++,Cocoa,Design,Objective-C,Programming,Research
― Vincent Gable on April 7, 2008

I love foreach. What I really mean is that I really like dead-simple ways to iterate over the items in a container, one at a time. It goes beyond syntactic sugar; making errors harder to write, and easier to spot.

I’m using the term “foreach”, because the first time I realized the utility of simple iteration was seeing Michael Tsai’s foreach macro. I instantly fell in love with the ‘keyword’, because it greatly simplifies iterating over items in a Cocoa container. My reaction was so strong, because the (then current) Cocoa iteration idiom I was using was so bad. As Jonathan ‘Wolf’ Rentzsch says:

I have a number of issues with enumeration in Cocoa/ObjC. Indeed, I have a problem with every one of the three lines necessary in the standard idiom. It even goes beyond that — I have an problem with the very fact it’s three lines of code versus one.

Objective-C 2.0 (Leopard and later) introduced Fast Enumeration. It’s a better way of handling enumeration then Michael Tsai’s macro, but if you are stuck using Objective-C 1.0, then I highly recommend using his foreach macro.

I do not like the C++ stl for_each idiom. It’s not simple enough.

To explain what’s wrong with for_each I should explain what a “foreach” should look like. It should be a two-argument construct: “foreach (item, container)“, and no more. container should be evaluated only once. Syntax details aren’t terribly important, as long as they make sense.
foreach(child, naughtyList)
iterate(child, naughtyList)
ForEach(String child, naughtyList)
for child in naughyList:
for(NSString *child in naughtyList)
Are all fine constructs.

The most obvious benefits of a foreach is that it’s less code, easier to write, and less work to read. Implemented correctly, it also makes bugs harder to write — mostly because it minimizes side effects in the loop construct. For example, do you see the bug in the following code?

for( list<shared_ptr<const Media> >::iterator it = selectedMedias->selectedMediaList().begin(); it != selectedMedias->selectedMediaList().end(); ++it )

I didn’t; and it’s kind of a trick question. ->selectedMediaList() isn’t an accessor function — it constructs a new list every time it is called. So selectedMediaList() != selectedMediaList() because it returns a different list each time. The loop never terminates because it will never equal end(), since it is the end of a different list. But you have no way of knowing this without knowing details of what selectedMediaList() does.

Using BOOST_FOREACH avoids the problem:
BOOST_FOREACH(shared_ptr<const CSMediaLib::Media> media, selectedMedias->selectedMediaList())
works regardless of how selectedMediaList() is implemented, because it is only evaluated once. It’s also easier to write, and to read. I haven’t used BOOST_FOREACH much, but it’s been totally positive so far. (Yes, the name is ugly, but that’s not important).

Loops are a staple of programming. Simplifying and error-proofing the most common kind of loop is a huge productivity win. foreach regardless of it’s flavor, is worth a try.

« Newer PostsOlder Posts »

Powered by WordPress