Archive for the ‘Cocoa’ Category

Logging in Leopard

Sunday, January 6th, 2008

The release of Leopard has given third-party developers a lot to do: attempting to restore features lost from Tiger, for instance. (By the way, where is the second party, and why am I never invited?) My friend Rainer Brockerhoff has provided a way, or Quay, to display hierarchical popup menus in the Dock again. One of my most missed features in Leopard is using NSLog to spew output exclusively to Xcode’s console log. When you debug or run your app in Xcode on Tiger, you can put NSLog calls everywhere without worrying about polluting console.log. In my opinion, console.log is only for important messages and errors. I frequently ask users to consult it if they’re experiencing a problem with an app. Either that or the Oracle at Delphi.

Leopard dispenses completely with console.log, though there is a “Console Messages” database query in Console. Whereas on Tiger stdout and stderr standardly go to console.log, on Leopard they boldly go to system.log (as well as to the “Console Messages” query). On either version of Mac OS X, Xcode redirects stdout and stderr to its own console log, so they don’t appear in Console at all.

According to the documentation, NSLog sends a message to stderr. This is true for Tiger, and it’s also true for Leopard, but Leopard’s NSLog has the additional behavior of sending a message to system.log regardless of whether stderr is redirected. Thus, when you debug or run your app in Xcode (these may amount to the same thing in Xcode 3), messages from NSLog appear both in Xcode’s console log and in system.log! Curiously, there is no duplication of NSLog messages in system.log when stderr is not redirected.

If you prefer to keep your debug output out of system.log, the workaround for this new NSLog behavior is to abandon NSLog for debugging purposes on Leopard. :-( After much experimentation with asl, I realized that our old faithful printf would work. Since printf writes to stdout, its output is redirected by Xcode. Plus, when you’re debugging your app in Xcode you don’t really need NSLog to tell you the name of your app, the date, or your shoe size.

A limitation of printf is that it doesn’t handle the format specifier %@ for an Objective-C object. With Cocoa, therefore, we want an Objective-C wrapper around printf (like, um, NSLog). If you add the following code to your target’s .pch file, you’ll have an Objective-C debug logging function JJLog available throughout your target’s code. To enable logging in your app’s debug build, just add JJLOGGING to the GCC_PREPROCESSOR_DEFINITIONS setting (AKA “Preprocessor Macros”) in the debug build configuration.


#ifdef __OBJC__
	#import <Cocoa/Cocoa.h>
	#ifdef JJLOGGING
		#define JJLog(...) (void)printf("%s:%i %s: %s\n", __FILE__, __LINE__, __PRETTY_FUNCTION__, [[NSString stringWithFormat:__VA_ARGS__] UTF8String])
	#else
		#define JJLog(...)
	#endif
#endif

In your app’s release build, the debug function is a NOP that the compiler will almost certainly optimize out. This conditional code should not cause problems when using GCC_PRECOMPILE_PREFIX_HEADER, because Xcode already generates a separate precompiled prefix header for each build configuration. See the .pch.gch.hash-criteria files in /Library/Caches/com.apple.Xcode.###/SharedPrecompiledHeaders.

You can send gobs of gab to JJLog without repercussion or remorse. However, you’ll still want to use NSLog (sparingly, please) for runtime errors in your release build. Now to continue in the spirit of this post, I’ll redirect the epilogue to /dev/null.

How not to fix a build warning

Saturday, December 22nd, 2007

The Hollywood writers strike continues, and the desperation grows for alternative sources of entertainment. Fortunately, we programmers can find entertainment in our own sources. I’ve got some reality programming for you! The following snippet of code is taken from an actual CVS commit. (Yes, CVS. Don’t laugh. Do cry for me, Argentina.) This build warning ‘fix’ was made by some contractor for some project that I worked on at some point in time for some company. To protect the innocent and/or guilty, I won’t say who, what, when, or where. As for why, I wish I knew. Or maybe not.


NSEnumerator* fileEnum = [fileArray objectEnumerator];
NSDictionary* aDict = nil;
//Changed to Remove the Build Warnings
//while(aDict = [fileEnum nextObject])
while(aDict == [fileEnum nextObject])

Let this example serve as a lesson. Not for programmers — the one who wrote it is probably hopeless — but rather for managers. Please do not just hire the lowest bidder!

What ever happened to Cocoa Blogs?

Thursday, November 29th, 2007

It seems dead, though the clock is still ticking. (Countdown to extinction?) Anyway, I’ve just updated my own extensive list of cocoa blogs and other favorite feeds, which you can download from the Downloads section of my sidebar. FileMerge will reveal the changes in my all-important favor. Remember bloggers, X-Mas is coming soon, not to mention Y-Mas and Z-Mas. I’m keeping track of who’s naughty and nice — mostly by intercepting your wireless packets. If you’re nice, you’ll get a hot dog and a shake.

Working without a nib, Part 6: Working without a xib

Sunday, November 25th, 2007

I bear a heavy burden now, for with the ongoing Hollywood writers strike I am the lone remaining source of pablum in the world. Actually, I was on strike too, from the bagel shop. You know I can’t blog without bagels! However, the recent reconciliation of DLR and EVH inspired us to put aside our differences, and I thought the offer of SCO stock options and tickets to a Broadway show was more than fair. Thus, there will be a series finale to Working without a nib. Maybe they’ll even name a space shuttle after my cat.

Speaking of cats — as I am prone to do — there is a new cat on the Mac (though none on the mat). Apple finally released Mac OS X 10.5, code named “Leopard”, followed soon thereafter by Mac OS X 10.5.1, code named “Oops”. These updates to Mac OS X pose a problem for the Nibless project, because the crucial method +[NSObject poseAsClass:] has been deprecated in Leopard. In fact, this method does not work at all in the Leopard 64-bit runtime.

Fortunately, the new Leopard Objective-C runtime provides a nice solution to the problem. In earlier versions of Nibless we used poseAsClass: to override +[NSBundle loadNibNamed:owner:] while still allowing the original method to be called. We can accomplish the same thing in Leopard via a technique that computer scientists have termed the old switcheroo:


Class bundleClass = [NSBundle class];
Method originalMethod = class_getClassMethod(bundleClass, @selector(loadNibNamed:owner:));
Method categoryMethod = class_getClassMethod(bundleClass, @selector(JJ_loadNibNamed:owner:));
method_exchangeImplementations(originalMethod, categoryMethod);

These functions are documented in the Objective-C 2.0 Runtime Reference. It is straightforward to exchange the method implementations; the mind-bending trick is to call the original implementation afterward.


+(BOOL) JJ_loadNibNamed:(NSString *)aNibName owner:(id)owner
{
	BOOL didLoadNib = YES;
	if (aNibName != nil || owner != NSApp)
	{
		didLoadNib = [self JJ_loadNibNamed:aNibName owner:owner];
	}
	return didLoadNib;
}

Before the function method_exchangeImplementations() is called, the method JJ_loadNibNamed:owner: has the implementation defined above, but the method never gets called until that implementation has been given to the method loadNibNamed:owner:, at which point the method JJ_loadNibNamed:owner: has the original implementation of loadNibNamed:owner:, whatever that may be (only Apple and Uri Geller know for sure). We never get caught in an infinite loop, unlike poor Alice and Norman.

The Leopard version of Nibless you’ve long been waiting for — since the second paragraph — is ready for download. If you find it useful, then please send me a bagel. I’ll also accept a doughnut, but not a Danish. Spoon!

BOOLing for Dollars

Sunday, September 30th, 2007

While we’re all aiting-way or-fay eopard-Lay, I’d like to share a pointer that I picked up while mugging a C library. (I have no idea what that means. It seemed witty when I wrote it.) As you know, I’m always ahead of the curve, setting the trends, framing the public discourse. Thus, I should add my 1.2 cents — the dollar is weak, and I’m a little short this month — on a hot topic discussed on the Cocoa-dev mailing list recently (in geological time, anyway): the use of the Objective-C BOOL type.

If you look in the header file /usr/include/objc/objc.h, you can see how BOOL is defined:


	typedef signed char		BOOL;
	// BOOL is explicitly signed so @encode(BOOL) == "c" rather than "C"
	// even if -funsigned-char is used.

	#define YES             (BOOL)1
	#define NO              (BOOL)0

A char type — e.g., char, signed char, unsigned char — is always one byte, i.e., sizeof(signed char) == 1, whereas in most implementations an int type is more than one byte. A byte standardly consists of 8 bits, or 12 nibbles. What happens to the extra bits if you convert an int into a BOOL? According to the wacky rules of C type conversion, the result is implementation-dependent. Many implementations simply throw away the highest bits. (Other implementations recycle them into information superhighway speed bumps.) As a consequence, it’s possible that myIntVar != 0 && (BOOL)myIntVar == NO.

Usually we don’t have to worry about this, because ‘boolean’ operators in C, such as == and !, always return 1 or 0. When we use bitwise operators, on the other hand, the problem does come into play. Suppose, for example, that we’re testing whether the option key is down. The method -[NSEvent modifierFlags] returns a bit field indicating the modifier keys that are pressed, and bit masks can be used to test for specific keys. Consider the following code; there are situations where doSomethingAfterEvent: does something, yet doSomethingElseAfterEvent: does nothing.


	-(void) doSomethingAfterEvent:(NSEvent *)anEvent
	{
		if (anEvent)
		{
			if ([anEvent modifierFlags] & NSAlternateKeyMask)
			{
				[self doSomething];
			}
		}
	}

	-(void) doSomethingElseAfterEvent:(NSEvent *)anEvent
	{
		if (anEvent)
		{
			BOOL shouldDoSomethingElse = [anEvent modifierFlags] & NSAlternateKeyMask;
			if (shouldDoSomethingElse)
			{
				[self doSomethingElse];
			}
		}
	}

It has been suggested on the mailing list that the type conversion could be handled by


	BOOL shouldDoSomethingElse = !!([anEvent modifierFlags] & NSAlternateKeyMask);

or


	BOOL shouldDoSomethingElse = ([anEvent modifierFlags] & NSAlternateKeyMask) != 0;

However, these approaches would only work for single-bit masks. What if we wanted to test both the option key and the shift key?

The point I wish to make here actually has little to do with the BOOL type. (Say what?!?) Bitwise operators are not boolean operators. A boolean operator only returns 1 or 0. A bitwise operator, in contrast, can return any bit field. The proper way to handle a bitmask is to test whether the resulting bit field has the desired value:


	unsigned int myMask = NSAlternateKeyMask | NSShiftKeyMask;
	BOOL isMyKeyComboPressed = ([anEvent modifierFlags] & myMask) == myMask;

Yes, I know Robot Chicken already covered this subject a ha-while ago. I didn’t really care for it.

Do as we say, not as we do

Thursday, August 30th, 2007

From the Coding Guidelines for Cocoa:

Avoid ambiguity in API names, such as method names that could be interpreted in more than one way.

sendPort

Does it send the port or return it?

displayName

Does it display a name or return the receiver’s title in the user interface?

From the Cocoa API:

-[NSConnection sendPort]

-[NSPortMessage sendPort]

-[NSDocument displayName]

-[NSFont displayName]

Embedding frameworks in loadable bundles

Saturday, August 11th, 2007

While I worked for Marko Karrpinen & Co. I only made one commit to BaseTen, but as Sappho would say, that one was a doozie! BaseTen is an open source Cocoa framework for PostgreSQL. It has an API resembling Apple’s Core Data framework, which uses SQLite. You can check out the source and build BaseTen.framework as well as the optional BaseTenAppKit.framework and an Interface Builder palette, BaseTenPalette.palette. (By the way, I abhor Interface Builder. Or at least Interface Builder 2. I’ll reserve judgment on Interface Builder 3 until I learn more about it, and only then will I abhor it.) The frameworks are designed to be embedded within your application’s bundle, in the standard location for embedded frameworks: the directory Contents/Frameworks.

An app needs to know how to locate linked frameworks at runtime, so at compile time the app’s executable gets a record of each linked framework’s install name. An install name is, as you should expect by now, not a name. It’s a path, namely, the location of the dynamic library containing the framework’s code. To be exact, the install name is where the library should be at runtime, for a library wouldn’t even need an install name if it just indicated where the library actually is at compile time. Install names enable you to target Panther, for example, while still compiling with Tiger. You can use the command-line otool -D to see that the install name of

/Developer/SDKs/MacOSX10.3.9.sdk/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation

is

/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation

Using absolute paths for install names is fine when your app links against system frameworks, which reside in pre-determined locations, but absolute paths won’t suffice when your app links against embedded frameworks, because the app could be installed almost anywhere in the file hierarchy, e.g., ~/Desktop or /Volumes/MyDistributionDmg. That’s why an embedded framework needs a relative path install name. The BaseTen and BaseTenAppKit projects achieve this by setting the build setting INSTALL_PATH (what else would you do with a build setting but set it?) to @executable_path/../Frameworks. The relative @executable_path is the path to the Contents/MacOS directory in your application’s bundle. When BaseTen is built with that build setting (to answer my last question, you would build with it), the install name of the framework becomes

@executable_path/../Frameworks/BaseTen.framework/Versions/A/BaseTen

as you can verify with otool -D. Thus, when your app links against BaseTen.framework and records the install name, it can find the framework in its own bundle at runtime.

BaseTen’s IB palette needs to use the BaseTen frameworks too. The problem, however, is that if the frameworks are built to be embedded in an application, they aren’t configured correctly to be embedded in the palette. When Interface Builder launches it will fail to load the palette, logging an error:

Interface Builder[29996] *** -[NSBundle load]: Error loading code /Users/jeff/Library/Palettes/BaseTenPalette.palette/Contents/MacOS/BaseTenPalette for bundle /Users/jeff/Library/Palettes/BaseTenPalette.palette, error code 4 (link edit error code 4, error number 0 (Library not loaded: @executable_path/../Frameworks/BaseTen.framework/Versions/A/BaseTen
  Referenced from: /Users/jeff/Library/Palettes/BaseTenPalette.palette/Contents/MacOS/BaseTenPalette
  Reason: image not found))

The reason that the image is not found — the reason behind the reason — is that the executable in this case in not actually BaseTenPalette but rather Interface Builder itself, which is trying to load the palette. The @executable_path leads to

/Developer/Applications/Interface Builder.app/Contents/MacOS

but BaseTen is embedded in

~/Library/Palettes/BaseTenPalette.palette/Contents/Frameworks

so the install name doesn’t locate the framework at runtime.

In Tiger, the relative @loader_path was introduced to supplement @executable_path. The @loader_path is relative to the image loading the dynamic library, wherever that image may be. Thus, if we change the install name of BaseTen to

@loader_path/../Frameworks/BaseTen.framework/Versions/A/BaseTen

and the install name of BaseTenAppKit to

@loader_path/../Frameworks/BaseTenAppKit.framework/Versions/A/BaseTenAppKit

then BaseTenPalette should be able to locate the frameworks when Interface Builder launches. Problem solved, right?

If you’ve already skipped ahead to the end of this post, you’ll know that the problem is not solved. (Spoiler alert: Harry drops out of school to follow Trey Anastasio.) We’ve eliminated one error only to find another:

Interface Builder[2599] *** -[NSBundle load]: Error loading code /Users/jeff/Library/Palettes/BaseTenPalette.palette/Contents/MacOS/BaseTenPalette for bundle /Users/jeff/Library/Palettes/BaseTenPalette.palette, error code 4 (link edit error code 4, error number 0 (Library not loaded: @loader_path/../Frameworks/BaseTen.framework/Versions/A/BaseTen
  Referenced from: /Users/jeff/Library/Palettes/BaseTenPalette.palette/Contents/MacOS/../Frameworks/BaseTenAppKit.framework/Versions/A/BaseTenAppKit
  Reason: image not found))

Whereas BaseTenPalette can now find BaseTen at runtime, BaseTenAppKit cannot. They both have a record of BaseTen’s install name as

@loader_path/../Frameworks/BaseTen.framework/Versions/A/BaseTen

but they don’t have the same @loader_path.

At this point, you may throw up your hands and throw in the towel, exclaiming Alas, BaseTen cannot have two install names! — or some such exclamation perhaps not suitable for children. (Oh rat farts!). Yet your exclamation would be in vain, because the install name of the dynamic library doesn’t matter after linking. All that matters is the install name recorded in the linked executable, and that can be forged.

Apple provides the nefarious command-line install_name_tool to forge install names for dylibs and give them fake id’s. This is how frameworks get into bars, since there are very few that are twenty-one years old. You can examine the details of my fix in the BaseTen Trac, but basically what I did to allow BaseTenAppKit to find BaseTen was to run the following command in a build phase script for BaseTenPalette:


install_name_tool -change \
	"@executable_path/../Frameworks/BaseTen.framework/Versions/A/BaseTen" \
	"@loader_path/../../../../Frameworks/BaseTen.framework/Versions/A/BaseTen" \
	"$TARGET_BUILD_DIR/$FRAMEWORKS_FOLDER_PATH/BaseTenAppKit.framework/Versions/A/BaseTenAppKit"
	

I discovered the correct install name through a stroke a genius, or to put it another way, trial and error. The @loader_path for BaseTenAppKit turns out to be

~/Library/Palettes/BaseTenPalette.palette/Contents/Frameworks/BaseTenAppKit.framework/Versions/A

which makes sense in retrospect, but if you could guess ../../../.. on your first try, you’re a superfreak. Anyway, you can check the install names before and after with otool -l, or succinctly with otool -L.

Caveat developtor: for install_name_tool to work, you may need to build your frameworks with the option -header-pad_max_install_names. BaseTen already does this. See the man pages for more information, man.

Working without a nib, Part 5: Open Recent menu

Tuesday, July 10th, 2007

Judging from the search phrases in my referrer log and from posts to Apple’s mailing lists, quite a few people are interested in developing Cocoa applications without using nibs. I’ve heard the demand, and you’ll be pleased to learn that the wait is over. I have a sweet solution. Today I’m announcing a Cocoa nibless SDK. You can download the SDK immediately — from the web! Specifically, from my blog. Just load this web page in Safari and select Save As… from the File menu.

(Don’t worry about me. I hear that the Chairman of the Board has a sense of humor. I’m sure that these two large gentlemen at my door are here to convey his appreciation of my wit and to deliver an invitation to lunch.)

(No! No! Stop, please! Not the iPod too! Have mercy!)

At the end of Part 4 of this series, I suggested that we needed to call setValue:@"NSRecentDocumentsMenu" forKey:@"name" to set the Open Recent menu. This is why they call me “Good Ol’ Oftenwrong”. Luckily, if you have a document-based application, Cocoa will generously create an Open Recent menu for you. All you need to do is put a menu item with the action @selector(openDocument:) in pretty much any menu, and Open Recent will magically appear as the next item in the menu. Now that’s a sweet solution!

If you want an Open Recent menu for a non-document app, on the other hand, you need to use an ugly hack. Although it was clear that the NSMenu ivar _name is set to @"NSRecentDocumentsMenu" for the Open Recent menu in a standard Cocoa MainMenu.nib, I couldn’t get the menu to populate with recent items in my nibless app even after setting _name. By pure trial and error, I discovered that you have to call _setMenuName:@"NSRecentDocumentsMenu" rather than setValue:@"NSRecentDocumentsMenu" forKey:@"name". (It was a natural choice after trying setName: and _setName:, which are not implemented by NSMenu.) The method _setMenuName: does set the _name ivar, but apparently it does some other crucial stuff too. Perhaps it asks a favor of the iGodfather. Anyway, I’ve updated my Nibless Xcode project to demonstrate this behavior.

In summary, we have succeeded (by we I mean the royal we) in creating a Cocoa application with a full main menu but without any nib (and without any error messages). For this purpose we’ve had to call two private methods, -[NSApplication setAppleMenu:] and -[NSMenu _setMenuName:], as well as poseAsClass: to override +[NSBundle loadNibNamed:owner:]. Not bad. And it’s taken us less than two months to reproduce what Interface Builder can do in less than two seconds. Isn’t this fun? The hardest part is finished, though. From now on, it’s just smooth sailing, on the Good Ship of Pyaray.

Oh, one more thing. Let’s dance! Anyway you want it.

Working without a nib, Part 4: setAppleMenu

Sunday, June 17th, 2007

Wow, this series already has four parts. It’s entering Rocky territory, having already surpassed Rambo. If I continue much longer I’ll be typecast — (NSNib *)jeff — and no one will remember my work on Vienna or my poignant portrayal of Hamlet (in the community theater production of Guys and Dolls).

A comment by Jack Nutting inspired me to run some additional tests. As the-programmer-currently-known-as-j-o-a-r (formerly known as horse-spear) mentioned in another comment, you can use the method -[NSApplication setAppleMenu:] to set the application menu. The catch is that it’s been removed from the public API and is no longer declared in NSApplication.h. To call the method, you have to declare it yourself in a category:


@interface NSApplication (NiblessAdditions)
-(void) setAppleMenu:(NSMenu *)aMenu;
@end

You could also avoid the need for a category declaration by using [NSApp performSelector:NSSelectorFromString(@"setAppleMenu:") withObject:aMenu].

My tests revealed that AppKit does call -[NSApplication setAppleMenu:] when loading the main menu from a nib, so it appears that this method is no more (or less) fragile than the technique I discovered in Part 2. As far as I can tell, setAppleMenu: sets the title of the menu to @"Apple" and the name of the menu to @"NSAppleMenu", which is why my technique is functionally equivalent. (The method -[NSApplication setMainMenu:] sets the name of the menu to @"NSMainMenu", so my [mainMenu setValue:@"NSMainMenu" forKey:@"name"] isn’t strictly necessary.) It’s a mystery why Apple ‘disappeared’ the method. Maybe it will magically return in a later operating system, with a long beard and a dark tan.

I’m not aware of a method, hidden or otherwise, to set the recent documents menu. Thus, my technique of calling [openRecentMenu setValue:@"NSRecentDocumentsMenu" forKey:@"name"] still seems necessary. Chasing around a little chicken, though, just seems unnecessary and not very mature.

Working without a nib, Part 5: No, 3!

Sunday, June 10th, 2007

For all you desperate souls waiting in line at the Moscone Center, and you more desperate souls waiting in line at MacRumors, take heart, because there’s something even more desperate than you — NSApplicationMain(). It’s so desperate to load a nib that it’ll take the first one it can find. When your application launches, NSApplicationMain() instantiates the NSPrincipalClass from your app’s Info.plist and calls +[NSBundle loadNibNamed:owner:] with the instance as the owner. This method in turn calls +[NSBundle bundleForClass:] with your NSPrincipalClass and -[NSBundle pathForResource:ofType:] with type @"nib". If your Info.plist contains no NSMainNibFile key, then the nib name and path arguments for those methods are nil. Why in the world would your Info.plist be missing NSMainNibFile? See Part 1 of this series. If that doesn’t answer the question, see Part 2. If that doesn’t answer the question, see Part 3.

When I set the NSPrincipalClass key to my custom NSApplication subclass, the corresponding bundle for that class is my app’s main bundle, so if there’s no nib in the bundle, the app fails to launch with the error, No NSMainNibFile specified in info dictionary, exiting. However, when I leave NSPrincipalClass as NSApplication, the corresponding bundle turns out to be /System/Library/Frameworks/AppKit.framework. If you send the message -[NSBundle pathForResource:nil ofType:@"nib"] to that bundle, it returns /System/Library/Frameworks/AppKit.framework/Resources/English.lproj/NSAlertPanel.nib, which is the first nib file in the English.lproj folder. As a consequence, NSApplicationMain() attempts to load NSAlertPanel.nib and set the file’s owner to your app’s NSApplication instance. That particular nib file contains several buttons with the action buttonPressed: targeted at the file’s owner, but unlike NSAlert, which is specified as the class of the file’s owner in the nib, NSApplication doesn’t implement buttonPressed:, so you get the error, Could not connect the action buttonPressed: to target of class NSApplication. Mystery solved! And I would have gotten away with it too, if it wasn’t for those meddling kids!

There are a number of ways to handle this problem. My preferred workaround, which I’ve implemented in the revised version of the Nibless project, is to set NSPrincipalClass to JJApplication, call [[JJBundle class] poseAsClass:[NSBundle class]] in main.m, and override an NSBundle method in JJBundle.m:


+(BOOL) loadNibNamed:(NSString *)aNibNamed owner:(id)owner {
    if (!aNibNamed && owner == NSApp) {
        // We're lying here. Don't load anything.
        return YES;
    } else {
        return [super loadNibNamed:aNibNamed owner:owner];
    }
}

We now return to our regularly scheduled WWDC speculation. (I predict that everyone in the audience will get a car.) If you are attending The Keynote on Monday, remember to bring plenty of Scooby snacks. If you’re playing the home game: every time Steve says “cool”, drink!