Vincent Gable’s Blog

July 5, 2008

-dealloc Warning

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

The only time you should call ever call -dealloc in Objective-C is on the last line of your own -dealloc method. This call should be [super dealloc];. The proper way to dispose of an object is to send it a -release message — -dealloc will then be called if appropriate.

Now when I was first learning Cocoa, I sometimes disposed of objects by calling -dealloc directly. This caused all sorts of problems. Truth be known, if I’ve been messing with several object’s -dealloc methods, I’ll sometimes dyslex out and type dealloc when I mean release, just because it’s more fresh in my head. This is very rare, but it has happened once, and will happen again. I’m fallible like that. Murphy’s law tells us that I won’t catch it every time.

So I humbly propose that GCC should warn you if you use -dealloc any way other then calling [super dealloc]; on the last line of your own -dealloc methods.

Missing Frameworks

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

I tried using an AMWorkflowView in a project, but when I ran it, I would crash with the following printed to the console:


*** Terminating app due to uncaught exception ‘NSInvalidUnarchiveOperationException’, reason: ‘*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (AMWorkflowView)’

It turns out I just forgot to include Automator.framework in my project!

It turns out that you also have to include Automator.framework to use any OSAScriptView objects.

I hope this helps someone who googles the error message :-).

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 28, 2008

The Minimum Screen Size You Must Support for Mac OS X Is 800×600

Filed under: Accessibility,Bug Bite,Design,MacOSX,Programming,Quotes,Usability |
― Vincent Gable on May 28, 2008

Mac OS X can run on systems with a screen size as small as 800 x 600 … Unless you know that your users will be using a specific display size, it is best to optimize your applications for display at 1024 x 768 pixels. … Design your user interface for a resolution of at least 800 x 600.

According to Apple’s Human Interface Guidelines (retrieved 2010-04-21).

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 13, 2008

Fixing Blurry Images

Filed under: Bug Bite,MacOSX,Programming
― Vincent Gable on May 13, 2008

I had an issue today where an image (A .png at 72 DPI with transparency, inside an NSImageView — if that makes any difference) in a window was blurry. The image was perfectly sharp in Interface Builder’s library panel, but when I placed it in a window, it turned blurry. Maddening!

A (mysterious) workaround: put the image where you want it. Then move it, then hit undo. When it snaps back to where you want it, it should be sharp.

I have absolutely no idea why this worked, or why the images were blurred by Interface Builder/XCode in the first place. (And yes, the images were blurry when the application was built and run. It wasn’t just a display bug in Interface Builder). It was purely coincidence that I stumbled across this “solution”.

« Newer PostsOlder Posts »

Powered by WordPress