Archive for the ‘Cocoa’ Category

Cocoa memory management for smarties, Part 2: working with a nib

Monday, August 25th, 2008

Well, it’s back to work for the moment. I was offered the VP slot, but I had to turn it down because they refused to accept the NDA: my name would be on the ticket, but nobody could talk about me publicly. Also, they wouldn’t get rid of the brown M&Ms. I am waiting for an offer by email from another guy, but I don’t hold out much hope, since I’m also waiting for him to learn how to use email.

In part 1 of this series I talked about Cocoa memory management. For a summary of that post, load it in your browser, select all, and pass it to the Summarize system service. And now for something completely the same, in part 2 I’m going to talk about Cocoa memory management. As you all know, I love nibs. I find them to be a very human-friendly interface, much like the gom jabbar. A nib file contains archived Objective-C objects that are instantiated when the nib is instantiated, so their memory needs to be managed just as objects instantiated programmatically. Fortunately, if the nib File’s Owner is an NSWindowController or (new in Leopard) NSViewController, the File’s Owner takes care of the memory management automatically. Otherwise, it’s your job.

According to the documentation, Objects in a nib file are initially created with a retain count of 1. As it rebuilds the object hierarchy, however, Cocoa autoreleases any objects that have a parent or owning object, such as views nested inside view hierarchies. By the time the nib-loading code is complete, only the top-level objects in the nib file have a positive retain count and no owning object. This makes the top-level objects a potential memory leak if your code does not assume responsibility for them. If you look at your nib with the icon view in Interface Builder, the top-level objects consist of everything except the proxy objects such as File’s Owner, First Responder, and (new in Leopard) Application. Thus, top-level objects include not only windows but also contextual menus, array controllers, etc. For example, Vienna’s MainMenu.nib, pictured below, contains 14 top-level objects. (Kids, don’t try this at home.)

MainMenu.nib

By the way, the MainMenu.nib file, or whatever is specified by NSMainNibFile in Info.plist, is actually a special case, because the File’s Owner is NSApplication (or whatever is specified by NSPrincipalClass). You don’t need to worry about its memory management either, although the objects in the nib will usually remain in memory for the lifetime of the app. On termination, NSApplication may or may not choose to release those objects, as it pleases, so don’t depend on any code in dealloc. Cleaning up memory on app termination is like sweeping your floor right before a tornado hits.

In order to manage the memory of the nib’s top-level objects, you’ll need a reference to them. The method -[NSNib instantiateNibWithOwner: topLevelObjects:] is handy in this respect. The documentation of the (NSArray **) topLevelObjects argument may be a little confusing, though: On input, a variable capable of holding an NSArray object. On output, this variable contains an autoreleased NSArray object containing the top-level objects from the nib file. Although the NSArray is autoreleased, the top-level objects themselves are not. Each object in the NSArray will have a retain count of at least 2. Thus, after the NSArray is deallocated and releases its objects, they will each still have a retain count of at least 1. A typical way to handle this is as follows.


@interface MyObject : NSObject
{ NSArray * _topLevelObjects; }
@end

@implementation MyObject

-(id) init
{
	self = [super init];
	if (self)
	{
		NSNib * nib = [[[NSNib alloc] initWithNibNamed:@"MyNib" bundle:nil] autorelease];
		if (nib && [nib instantiateNibWithOwner:self topLevelObjects:&_topLevelObjects])
		{
			// The array is autoreleased, so we need to retain it.
			// Release the objects now so that they'll be deallocated along with the array.
			[[_topLevelObjects retain] makeObjectsPerformSelector:@selector(release)];
		}
	}
	return self;
}

-(void) dealloc
{
	[_topLevelObjects release];
	[super dealloc];
}

@end

If you use a different method to instantiate the nib, such as +[NSBundle loadNibNamed:owner:], or if you don’t want to keep an array of the top-level objects, you’ll need to create an IBOutlet for each top-level object in the nib and release the objects in dealloc along with any other ivars.

A major caveat for do-it-yourself nib loading is that even if you take care to release your top-level objects, you can still get a memory leak if you use Cocoa bindings in your nib. This occurs when an object in the nib binds to the File’s Owner. The File’s Owner never gets deallocated in this case, and thus neither do the top-level objects. Again, NSWindowController and NSViewController already take care of this problem automatically, but you’ll need to deal with it if the File’s Owner is not one of those classes or their subclasses. Basically, there are three possible solutions:

  1. Unbind the bindings programmatically. You’ll need to do this elsewhere than the File Owner’s dealloc, which won’t get called unless you unbind.
  2. Bind to a different object in the nib rather than to the File’s Owner.
  3. Don’t use Cocoa bindings and nibs, because they’re teh suck.

Cocoa memory management for smarties, Part 1: release me not

Monday, July 28th, 2008

If I were to offer one bit of advice to young programmers, I would say always … uh … never … forget to check your references. Not to mention your optics and your closet. Supposedly, the rules of Cocoa memory management are simple. Just set GCC_ENABLE_OBJC_GC = required and wait for the money to roll in, right? For those who don’t enjoy the smell of Objective-C garbage collection, we have the tried-and-true manual rules. If you obtain a reference to an object by calling +alloc, +allocWithZone:, +new, or -copy, then the reference should remain valid indefinitely. A reference obtained in any other way is guaranteed to be valid only during the current event loop, so you’ll need to call -retain if the reference should remain valid for longer (e.g., if you want to store it in an instance variable). Each of the aforementioned method calls must be balanced at some point by a call to -release, if it’s safe for the object to deallocate immediately, or -autorelease, if you need the object to stick around until the end of the event loop.

In theory, these rules are foolproof. In practice, however, they’re not even expert-proof, as we see when the experts’ apps unexpectedly quit. Thus, I believe that writing a series of posts exploring the subtleties of Cocoa memory management is a worthy endeavor. Moreover, I need to kill some time before football season kicks off.

Below is a simplified version of a crasher I once discovered and fixed. I’m only giving the bare essentials here. There was a lot of code involved, which made it more difficult to diagnose.


@implementation MYChild

-(void)doSomething
{
	...
	if (...)
	{
		[self setMyProperty:theValue];
	}
	if ([_myArray count] > 0)
	...
}

@end

@implementation MYParent

-(void)start
{
	...
	_myChild = [[MYChild alloc] init];
	[_myChild addObserver:self forKeyPath:@"myProperty" options:0 context:NULL];
	...
}

-(void)stop
{
	...
	[_myChild removeObserver:self forKeyPath:@"myProperty"];
	[_myChild release];
	_myChild = nil;
	...
}

-(void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary*)change context:(void*)context
{
	...
	[self stop];
	...
}

@end

Can you guess where the crash occurred? The particularly tricky part is that observeValueForKeyPath: is called by the Cocoa runtime rather than by the app’s own code, which means that if you’re not already aware of the key-value observer, the cause of the crash is rather mystifying.

My solution, by the way, was to replace [_myChild release] with [_myChild autorelease]. Ideally, you would probably access all ivars via getters and setters, but that’s a topic for a later post in this series.

In light of the above example, let’s now attempt to add to the rules of memory management:

  • Rules are for suckers, simpletons, and lawyers.
  • No method is safe, only call paths are safe. You can’t look at a method in isolation and determine that it handles memory management properly. On the other hand, if you’re doing object-oriented encapsulation correctly, you shouldn’t have to worry about the implementation of other objects. The proper unit of analysis, I believe, is the entire call path within the object’s implementation.
  • Don’t release any method arguments. It’s almost always possible for the caller of aMethod: to have code such as { [anObject aMethod:anArgument]; [anArgument anotherMethod]; }, so don’t invalidate anArgument during the current event loop. This is the reason why -autorelease was invented. (Also, it would be pretty silly to have NSAutoreleasePool without anything to go in the pool.

As for the jello, I can explain. I was hungry and preparing for gravity to reverse.

Compiler indirectives and metaphorical keypaths

Wednesday, July 2nd, 2008

I am, like, literally ROTFRTFMIMHOLOLYMMVIIRCFUBAROTOH!

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(likeNoWay:) name:@"ROTFRTFMIMHOLOLYMMVIIRCFUBAROTOH" object:nil];

A string literal is a sequence of characters enclosed in quote-unquote ‘\”quotation marks\”‘ (wiggles index and middle fingers). In the C programming language — so-named because its inventors lacked imagination — a string literal represents an array of char terminated by a null (or by a comma when the feeling’s not that strong). In the Objective-C programming language — otherwise known as The O.C. — a string literal in a compiler directive (e.g., @"NSBirdJustFlewIntoMyWindowException") defines a constant NSString object.

Chris Hanson and Sanjay Samani have offered some excellent advice about avoiding the use of these NSString literals in your method calls. The problem is that the compiler will accept pretty much any directive: with Objective-C 1.0 the compiler only warns about non-ASCII characters, and with Objective-C 2.0 it doesn’t even do that (for better or worse, but that’s a subject for a different post). Thus, if you happen to misspell a notification name, you’ll never know until your app misbehaves at runtime. I’m sorry, Dave, I’m afraid I can’t do that.

I recommend replacing NSString literals with macros or constant variables (huh?) wherever spelling matters. (Spelling matters everywhere. I’ve seen some pretty bad method names.) Here’s a little trick for handling arbitrary keypaths:

#define KEY1 @"key1"
#define KEY2 @"key2"
#define KEY3 @"key3"
#define DOT @"."

[self valueForKeyPath:KEY1 DOT KEY2 DOT KEY3];

Unfortunately, we’re still at the mercy of misspellings in nib bindings. Yet another reason to do without nibs. But that’s also a subject for a different post and horse of a different color (dried poop).

I’m a Rogue

Saturday, June 7th, 2008

I’m delighted to announce that I’ve been hired as Rogue Amoeba Employee 008. (Employee 007 is Longwell, Justin Longwell.) I’ll be working on Rogue Amoeba’s apps as a software engineer, as well as providing comic relief. Thanks to Alex, Paul, and Quentin for this once-in-a-lifetime opportunity. My coworkers at Rogue Amoeba Software, LLC include fellow bloggers Mike Ash and Guy English. Together we form perhaps the most talented group of Mac programmers outside of Apple. (Yes, I’m talking to you, Adobe and Microsoft.) Indeed, I haven’t seen such an array of stars in one place since the Ghostbusters music video.

I’ve joined Rogue Amoeba just in time to miss WWDC ‘08. In an earlier post, I mentioned that I left Marko Karppinen & Co. LLC just in time to miss WWDC ‘07. (I’ll make it there someday! :-( ) The question is, what have I been doing in the meantime? Backpacking through Europe? Rotting in Gitmo? Running for President? No, I’ve been working on super-secret projects for Francis Technical Services, LLC. Although FTS is a little-known company, its software is well-known, at least inside Apple. For example, FTS is responsible for Radar, the Apple-internal Cocoa app that opens when employees click on the funny-looking rdar://problem/ URLs you often see. The number at the end of the URL corresponds to the problem ID of the bugs you file on the web with Apple Bug Reporter (also known as RadarWeb). It’s useful to have one of these URLs available when communicating with Apple engineers, because their managers forbid them from lifting a finger without a Radar problem number.

While at FTS, I did some bug analysis for Radar: that is, analysis of bugs in Radar’s source code, not analysis of bugs in Radar’s database, to which as a contractor I had extremely limited access. However, I mostly worked on other projects, such as Radar’s sibling app Sonar. Apple’s DTS uses Sonar to communicate with ADC members (such as me!). If you’ve ever seen sonr://request/ URLs — the Mighty Quinn provides one in an Apple Mailing List post — those open in Sonar. (By the way, I’ve read all of your emails to DTS. No, you haven’t been accepted to the iPhone Developer Program. Stop whining.) Another project that I worked on was Merlin, an app for Apple’s Human Resources department. I think that Steve Jobs is doing an outstanding job at Apple, so before I left I used Merlin to double his annual salary.

I’d like to thank Dave Francis, the owner and founder of Francis Tech, for the opportunity to work there. Despite appearances, I’m not really the type to hop from job to job. I’ve just been looking for Mr. Right, LLC, a company to fall in love with, marry, and have baby apps with. (As for any other support claims, sorry Billie Jean.) I can see myself growing old with Rogue Amoeba. (Easy to see when you’re already old?) Besides, someone has to stick around and keep an eye on mikeash, stop him from taking over the world.

Now without further ado, please take out your credit card and buy some of our fine software. As a special bonus for our Leopard customers, your purchase will be (code-)signed by your favorite Rogue Amoeba star.

Cocoa Blogs free, as in beer

Sunday, March 30th, 2008

Wait, beer is not free! Or so my bartender yelled last night as I ran out the door. (I also had one bourbon and one scotch.) The front page of Cocoa Blogs was already free, but the syndicated feed has only been free to contributors — kind of like Congress. Now Scott has decided (he is the decider) to make the feed available for free too. Indeed, new items are appearing in the feed. Remember, folks, you heard it here first. This is your Number 1 source for news, rumors, and outright lies.

My own long list of Cocoa blogs, including the Cocoa Blogs feed, can be downloaded for free from the “Favorite Feeds” link in the sidebar of my blog. The list is in convenient .opml format for import into your favorite free feed reader. There seem to be many choices nowadays. (Too bad that they all suck. Mine sucks the least, though.) The contents of my web site have been and always shall be free, as in loader. If I asked for contributions, that would just embolden the terrorists. Last call!

The beach is open

Saturday, March 1st, 2008

I know that March may be too early to think about going to the beach, but Code Beach does not require a tan, six-pack abs (so-called because beer drinkers usually have nice abs), or even swim trunks. No, it’s not for nudists, it’s for Mac developers! Nudist Mac developers, use your own discretion. The goal of Code Beach is to create a central repository where Mac developers can share pieces of useful code (like custom controls, formatters, categories) which is easier to search/use than forums or mailing lists. Brought to you by Briksoftware, Code Beach could use more traffic and more code. Don’t worry, there’s only one Shark, mostly harmless.

NSScrollView in a key view loop (or Fembot in a wet T-shirt)

Wednesday, February 20th, 2008

Now that the Pats have won the Super Bowl, Rudy has won the GOP nomination, and the Jedi have won the Clone Wars, all is right with the world (and the galaxy), so we can focus again on our lives, on our families, and most important, on Cocoa. If you’re a Cocoa developer, you are morally required to open System Preferences, Keyboard & Mouse, Keyboard Shortcuts and select All controls in Full keyboard access. This is not optional. If you do not comply, you will be driven out of the Continuum and forced to spend the rest of your pitiful mortal existence writing kernel extensions.

With the mandatory preference setting, you will observe the full Cocoa key view loop. Actually, it doesn’t have to be a loop: it could be a key view cul-de-sac. Anyway, Apple clearly wants developers to “leave the driving to us”, i.e.,

-[NSWindow setAutorecalculatesKeyViewLoop:YES]

because configuring and updating the key view loop can be a pita — like training a cat. The docs make it sound oh so easy, but that’s only because the docs don’t cover the complex or problematic cases. Suppose, for example, that you have a scroll view enclosing a view that contains multiple controls. You want the key view loop to follow a particular route both outside and inside the scroll view. How should you hook up the views in Interface Builder? (Or in code, when you’re working without a nib.)

I believe that the proper way to handle an NSScrollView in a key view loop is to ignore it. Walk by quickly without making eye contact. Whatever you do, don’t engage in conversation! Otherwise you’ll get invited to coffee or lunch. The reason you can ignore NSScrollView is that it never actually becomes the firstResponder of an NSWindow. The scroll view returns YES from acceptsFirstResponder if its documentView does, but if you then call -[NSWindow makeFirstResponder:] with the scroll view as argument, the window’s first responder will end up being not the scroll view itself but rather its document view.

This behavior is adequate if the document view is a single control such as an NSTableView or an NSTextView (though maddeningly, an NSTextView tends to interpret the tab key as, well, a tab). In the window nib, you can simply include the scroll view in the window’s key view loop, ignoring the document view, and when the window loads, the scroll view will automatically rearrange the key view loop to make its NSClipView the nextKeyView, followed by the document view and then the scroll view’s original nextKeyView. The scroll view and the clip view are superfluous in the key view loop, however, because only the document view becomes the first responder when tabbing through the window.

This behind-the-curtain key-view wizardry may seem impressive, but the scroll view is really deaf, dumb, and blind. It has no idea about any loop you’ve defined within the document view. There is no way to inform the scroll view of the beginning and end points of the ’sub-loop’. If you attempt to insert the scroll view containing your sub-loop into the window’s key view loop, it will turn into either a key view dead end within the scroll view or a key view overpass, depending on the configuration.

To avoid these problems, simply connect your views together in the key view loop as if the scroll view did not even exist. You know, like Mac OS X Tiger. (Of course the keyboard firmware update requires Quick Look and Time Machine!) Only the views that become first responder — as opposed to becomeFirstResponder — need to be hooked up. When a cat is lying on the trackpad, you will be thankful for setting the key view loops in your app, and so will your users. Cat-friendliness is in fact the most crucial consideration for designing both software and hardware.

I am saying that I will neither aspire to nor accept — I repeat, I will neither aspire to nor accept — the positions of President of the State Council and Commander in Chief. I will, on the other hand, host the Tonight Show if asked. Hasta la vista!

FUD from Rixstep: NSDocumentController in Leopard

Saturday, February 2nd, 2008

At the risk of provoking their ire and being branded a moron, I wish to dispute a claim that has been made several times by Rixstep about a supposed security vulnerability in Leopard’s NSDocumentController: Cocoa’s document controller overrides file system permissions without authentication. As far as I can tell, this claim is false.

I should note at the outset that I have nothing personal against Rixstep. Their syndicated feed has long been among my (numerous) favorites that I subscribe to in Vienna, as you can see by downloading the exported opml from my blog. I welcome legitimate criticism of Apple, and I have found Rixstep’s articles entertaining in the past, though of late they have become overly juvenile. As far as the whole ‘Cross-Platform Bait & Switch’ incident is concerned, I can’t comment on the legality of reproducing the quotations, because I’m not a lawyer, but I don’t think that Rixstep can be accused of misquoting or quoting out of context in this case.

Anyway, I’ve included the full text below of the email I sent to Rixstep a month ago. Retraction was perhaps too much to hope for, but I thought they would at least stop making the false claim after reading my email. They haven’t, which is why I’m now ‘going public’. (This should knock Britney off the front page.)

Hi. I read your article at <http://rixstep.com/2/1/20071227,00.shtml>, and it got me a little worried, so I did some testing. My results are that NSDocumentController in Leopard does not allow you to override Unix permissions. In fact, NSDocumentController in Leopard is more strict than the Unix permissions: it won’t allow you to save a writable document when you don’t have write permissions for the enclosing directory.

You are correct that saving a document always deletes the existing file and creates a new one with a different inode, and you are also correct that some of the user-visible NSDocumentController warning messages are misleading. However, if you don’t have write permissions for a directory, then NSDocumentController won’t let you delete or add a file, and even if you do have write permissions for the directory, NSDocumentController won’t let you delete someone else’s file or add a file with the same name if the sticky bit is set.

Thus, I believe that the ramifications for system administrators are negligible. With certain Unix permissions, it has always been possible to delete a file in a directory and replace it with a new file of the same name. Leopard has not changed that at all, so system administrators should take the same precautions against this scenario that they have always taken.

-Jeff

You don’t have to take my conclusions for granted, though. In a matter of minutes, you should be able to throw together a bare-bones document-based application suitable for testing the behavior yourself.

I have no desire to discourage criticism of Apple or Leopard. There are major problems in Leopard, and I don’t yet find it acceptable for use as my primary operating system. I just think that this spurious security issue obscures the real issue of whether the new Leopard NSDocumentController behavior is desirable.

P.S. If you really want to talk about a waste of precious disk space, /Applications/Mail.app/Contents/Resources is an astounding 277 MB on Leopard.

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!