Vincent Gable’s Blog

December 22, 2008

How To Multi

Avoid distributed computing unless your code is going to be run by a single client with a lot of available hardware. Being able to snarf up CPU cycles from idle hardware sitting around in the user’s house sounds cool but just doesn’t pay off most of the time.

Avoid GPGPU on the Mac until Snow Leopard ships unless you have a really good application for it. OpenCL will make GPGPU a lot more practical and flexible, so trying to shoehorn your computationally expensive code into GLSL or CoreImage today just doesn’t seem worth it.

Using multiple processes is a good idea if the subprograms are already written. … If you’re writing your code from scratch, I don’t recommend it unless you have another good reason to write subprocesses, as it’s difficult and the reward just isn’t there.

For multithreading, concentrate on message passing and operations. Multithreading is never easy, but these help greatly to make it simpler and less error prone.

Good OO design will also help a lot here. It’s vastly easier to multithread an app which has already been decomposed into simple objects with well-defined interfaces and loose coupling between them.

Mike Ash (emphasis mine, line-breaks added). The article has more detail and is very much worth reading.

One point that this advice really drives home for me is that you need to focus on making good code first, and defer micro-optimizations. If taking the time to clean up some code makes it easier to parallelize, then you are optimizing your code by refactoring it, even if at a micro-level you might be making some of it slower by, say, not caching something that takes O(1) time to compute.

Apple does not sell a Mac that’s not multi-core, and even the iPhone has a CPU and a GPU. There’s no question that optimization means parallelization. And all signs point to computers getting more parallel in the future. Any optimization that hurts parallelization is probably a mistake.

December 20, 2008

Automatically Freeing Every @property

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

Here’s a category of NSObject that can simplify a dealloc method. It adds the method, setEveryObjCObjectPropertyToNil, that sets every property of the receiver to nil. (Properties that are readonly or not an Objective-C object are ignored.) This frees the underlying object, if the property is declared copy or retain; and it does no harm if it was declared assign.

If every ivar (member variable) in your object has a property declared for it, then your dealloc method can often be replaced by this macro, or it’s two-line expansion:

#define PROPERTY_ONLY_OBJECT_DEALLOC \
- (void) dealloc { \
   [self setEveryObjCObjectPropertyToNil]; \
   [super dealloc]; \
}

Limitations

Pointers

Any pointers (eg char*) will not be set to NULL; this includes pointers to an Objective-C object (eg NSError** outError). Of course NSObject* obj will be set to nil since it is considered an Objective-C object, even though it is written as if it were a pointer.

It is easy to build on setEveryObjCObjectPropertyToNil and have something that sets pointers to NULL as well. But I felt it was too risky. Sending a message to nil is valid, but dereferencing a NULL pointer is a “bus error” crash. [nil release]; does no harm, but free(NULL); is bad news. A settable @property that takes a raw pointer is a hybrid Objective-C, and “old”-C creature — I’ve never seen such a thing, so I’m wary of assuming that feeding it a NULL would be valid. Plus, opening the door to pointers means dealing with handles (pointers-to-pointers) and their ilk.

ivars

Any ivars (member variables) with no settable @property declared on them will not be freed. You can inspect your own ivars like you can @propertys (example), but it would not be safe to automatically release them. Some of them might be weak-links, meaning the object they point to was not sent a retain message. Weak-links are not terribly rare, for example objects always have a weak link to their delegate.

The Code

You will still need to download the source to get helper functions like SetterNameFromPropertyName() for this to actually run, but this should give you an idea of how it works:


@implementation NSObject(CleanUpProperties)
- (void) setEveryObjCObjectPropertyToNil;
{
   unsigned int i, propertyCount = 0;
   objc_property_t *propertyList = class_copyPropertyList([self class], &propertyCount);
   if(propertyList){
      for(i = 0; i < propertyCount; i++){
         const char *propertyAttrs = property_getAttributes(propertyList[i]);
         if(PropertyIsObjCObject(propertyAttrs) && PropertyIsSettable(propertyAttrs)) {
            NSString *setterName = SetterNameFromAttributes(propertyAttrs);
            if(!setterName)
               setterName = SetterNameFromPropertyName(property_getName(propertyList[i]));
            [self performSelector:NSSelectorFromString(setterName) withObject:nil];
         }
      }
      free(propertyList);
   }
}
@end


Download The Code


And please let me know how it works for you. I’ve started using setEveryObjCObjectPropertyToNil even if I only have one @property, because it means I’ll never forget to free one.

Warning (Update 2009-05-29)

Uli Kusterer gives a good reason not to use this code,

Don’t Use Accessors in Constructors or Destructors

This may sound a bit odd, but there is a reason to this madness. Constructors (i.e. -init methods in ObjC) and destructors (i.e. -dealloc or -finalize) are special methods: They are called before your object has fully been initialized, or may be called after it has already been partially torn down.

If someone subclasses your class, your object is still an object of that subclass. So, by the time your -dealloc method is called, the subclass has already been asked to do its -dealloc, and most of the instance variables are gone. If you now call an accessor, and that accessor does anything more than change the instance variable (e.g. send out notifications to interested parties), it might pass a pointer to its half-destructed zombie self to those other objects, or make decisions based on half-valid object state. The same applies to the constructor, but of course in reverse.

Now, some people that accessors should not be doing anything but change instance variables, but that is utter nonsense. If that was all they’re supposed to do, we wouldn’t need them. Accessors are supposed to maintain encapsulation. They’re supposed to insulate you from the internal details of how a particular object does its work, so you can easily revise the object to work in a completely different way, without anyone on the outside noticing. If an accessor could only change an instance variable, you would have very limited means to change this internal implementation.

Moreover, I don’t think Apple would have introduced Key-Value-Coding and Key-Value-Observing if they didn’t agree at least partially that it’s fine to do a bunch of things in response to an accessor being used to mutate an object.

Update 2009-11-29

I’m amused at the prevalent “Apple knows best” attitude. Bindings, garbage collection, NSOperationQueue, and so many other things, Apple has gotten wrong and burned me in the process. I always trust my own evaluation over their recommendations.

Mike Ash, Using Accessors in Init and Dealloc

“Most Windows Developers Couldn’t Care Less About Design”

Filed under: Design,Programming,Quotes | ,
― Vincent Gable on December 20, 2008

Judging by the applications I’ve used, most Windows developers couldn’t care less about design. That’s bad. What’s even worse is learning that same design carelessness has shipped in the box with every copy of Visual Studio since 2002.

See, even Jeff Atwood says it, and he’s no Mac fanboy.

December 19, 2008

Undocumented Automator.framework Goodness: Actions Library

In Leopard, Apple introduced AMWorkflowView and AMWorkflowController, which let you easily add automator-editing capabilities to your application (modulo bugs!)

Obviously, for editing a workflow to be useful, you need to be able to add actions to it. And to do that, you need to be able to browse and search actions. But the current Automator.framework has no official support for doing this.

But there are undocumented private APIs, which Automator.app (link launches it) uses…

How to Find Them

class-dump is an amazingly useful command-line tool that lets you generate headers from a compiled Objective-C binary. Download it, and point it at /System/Library/Frameworks/Automator.framework/Automator, to see everything Automator.framework really lets you do.

Instant Library Panel

The most useful definition I found was,
@interface NSApplication (AMLibraryPanel)
- (void)orderFrontAutomatorLibraryPanel:(id)sender;
@end

Calling [NSApp orderFrontAutomatorLibraryPanel:nil]; will show a panel with the same action-library view Automator has.

This is what I’m using right now in IMLocation to let people find actions for workflows. I don’t like the idea of using private APIs. But I like the idea of implementing my own AMLibraryView replacement even less. It would be a lot of work to make it as good as the real thing. Any differences give users a fractured experience — one way to do something in Automator, another in my program. And I would get dismal ROI, because I expect Apple to expose this functionality in the (near) future.

(I also wouldn’t be so bullish on using private APIs if I didn’t have workarounds in place right now. The only way orderFrontAutomatorLibraryPanel: gets called is if a user presses a “Show Actions” button on the toolbar. That same toolbar has an “Open in Automator” button that opens the worklfow in Automator, where they can edit it without bugs. So even if orderFrontAutomatorLibraryPanel: stopped working tomorrow, users could still do everything they could before — albeit less elegantly).

Looking Just Like Automator

I don’t recommend doing this, but I’ve been able to embed an AMLibraryView in an NSSplitView next to an AMWorkflowView, to get a more Automator.app-like appearance.

Here’s how I did it, given the IBOutlets workflowViewAndLibrarySplitView which is an NSSplit view with an AMWorkflowView in one side, and the other side’s view connected to the outlet workflowLibraryView,

[workflowViewAndLibrarySplitView replaceSubview:workflowLibraryView with:[[AMLibraryPanel sharedLibraryPanel] _libraryView]];

Will put the library view inside your split view.

Of course this screws up orderFrontAutomatorLibraryPanel:. And I would not expect it to work in two windows. And it’s using an underscore-private method of an already private and undocumented API. That’s just to too risky for me; so I stick with the panel. It gets the job done with one line of code, and that’s good enough for me.

December 18, 2008

Fast Enough or Not Enough Fast?

Filed under: Quotes,Usability | , ,
― Vincent Gable on December 18, 2008

…people are now willing to make trade-offs against performance. For the entire history of the PC industry, computers have been too slow, so trade-offs were made in favor of faster CPUs: higher prices and heavier laptops. But today, for many common tasks, the type of CPU you get when you build a $400 lightweight laptop is fast enough. That’s (a) breakthrough.

John Gruber

Cynically I also wonder if this is because “more cores” isn’t as compelling as faster. As Hank Williams says,

The problem of multi-core computing is really very simple. As most of us have experienced, every problem *can’t* be solved better or faster with more people. Some problems can be solved faster by adding a few people, but most problems cannot. In truth, most problems can best, or only be solved by one person at a time. And so it is with computing. The vast majority of problems can only be solved by one logic thread at a time. The reason is obvious. For most process-oriented work, step B is based on the results of step A. And step C is based on the results of step B, and so on.

Automator Bugs

I’ve started using Automator in IMLocation to let people build a set of actions that are executed when they arrive somewhere (for example, muting their laptop’s speakers in a classroom).

Unfortunately, my impression of Automator.framework so far is: buggy.

Here are some issues I’ve run into so far.

-[AMWorkflowController stop:] does not stop the workflow! I do not yet have a workaround.

AMWorkflowController sends its delegate a -document message whenever an action is removed from the workflowView, by clicking the (x) button. If the delegate does not implement it, the action will not be removed!

The (x) button is enabled , but does nothing, in workflows that are not editable.

-[AMWorkflow initWithContentsOfURL:error:] throws an exception when the URL is nil.

This project demonstrates the first two issues.

December 16, 2008

isEmpty?

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

obj = {}

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

obj = nil

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

Bug:

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

Bug:

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

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

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

Solutions:

Wil Shipley suggests using the global function:

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

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

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

December 12, 2008

Catching nil Member Variables

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

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

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

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

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

One assert() You Need?

Accidently disconnected outlets in shipping Cocoa apps are legend.

Jonathan “Wolf” Rentzsch

From what I can tell assert() is slowly going the way of the goto in the programming world. Exceptions, unit-tests, and other modern software engineering practices, seem to have a better answer for testing something at runtime, and ensuring that you never enter a bad state.

But there is one case where I think you should put an assert() (and not an NSAssert()) in your code: in awakeFromNib assert() that every IBOutlet is connected.

It’s surprisingly easy to accidentally disconnect something in Interface Builder, or rename something in Xcode. I’ve done it before. More then once. assert()-ing IBOutlets has saved me a lot of debugging time.

Hard to Find In Code

An IBOutlet that is not connected is nil. Because Objective-C quietly ignores messages sent to nil, it’s very easy not to notice the problem for a while; then spend a long time debugging a side effect of the issue.

Hard to Find By Eye

A widget in a nib/xib file that is not connected will never change it’s state. But it typically it has a reasonable initial state. This makes it difficult to detect disconnected IBOutlets by eye, because things will look right until they are supposed have changed, but the eye is drawn to change, and de-emphasizes unchanging things.

Why assert()?

As Wolf says, the worst-case-scenario is that right before release you make a trivial change in Interface Builder (“We can’t ship with that window saying “Claculator”), an IBOutlet gets disconnected, and nobody notices in time.

But an assert() failing will be noticed with casual testing, and triggered if the nib is loaded at all, even if the series of interactions needed to cause the widget to change state are not performed.

An assert() is very light-weight and easy to do. It’s exactly one line of code, and you don’t have to add a unit testing framework to your project to do it. And that makes it perfect for test projects. I’m a believer in getting a feature working in a test project first, then copying it into your real product. (Honestly I don’t do it as much as I should, and I almost always kick myself for it.) It lets you test and learn without hacking-up your product. Plus, new things are often easier to try without the weight of a big code base.

Speaking of unit tests, I don’t have complete faith in them here. That’s because a disconnected IBOutlet in a shipping program is fundamentally an issue with the way the release-build is configured. So anything that’s not testing the the actual release build is not exhaustively testing for this problem. an OCUnit-test can catch a disconnected IBOutlet if it is loaded into the application. But in my experience, such tests are cumbersome. Running them involves launching your full application, and programatically manipulating it. This can take a while to run since all the UI will be displayed and animated. And you have to be careful about state since each test will be changing the state of your program.

Ultimately, assert() just seems to be optimal, in dependability and simplicity, for catching a disconnected IBOutlet.

But I suspect there’s a better way I don’t know.

How do you catch IBOutlets?

December 11, 2008

There are Worse Things Than Crashing

Filed under: Design,Programming | , ,
― Vincent Gable on December 11, 2008

Crashing is not the worst thing a program can do. Data-loss and corruption are worse bugs than crashing. I think this is generally accepted in theory, but I often hear developers say things like,

If there’s one behavior of your application that you should focus on eliminating, it’s the behavior of crashing. Above all other “nuisances,” this is the one that is absolutely unacceptable.1


And I certainly agree that crashing is unacceptable, and a high-priority bug. But it’s not the highest priority kind of bug.

For example, say your bank’s website crashes all the time. That sure sucks for everyone! People will be upset, some of them enough to take their business elsewhere. But if the website even occasionally moved the decimal point around when you sent money, the bank would be out of business faster than you could say suedfornegligence.

Even for software that isn’t used by banks, hospitals, nuclear reactors, etc., the basic principle is the same, it’s better to crash than vandalize people’s data.

As with any general rule, there are exceptions. It’s probably worse if an email program crashes than if it “corrupts” data by adding an extra space to the end of of a message. In fact, Mail could be doing this right now, and I wouldn’t know or care. (Outside of movie plot scenarios where evil CS 101 students are holding the city hostage, and I have 10 seconds to defuse the bomb by sending an email with a body containing the de-activation code and not a single extra character. Although to be fair, there’s probably a clause in Mail’s EULA saying it’s a violation of the terms of sale to use it to defuse thermonuclear weapons.)

But the threshold of “acceptable” corruption is vanishingly small. If about 1% of the time Mail made the subject-line of an email all lowercase, that is plausibly worse than a 1% chance of crashing. It’s not OK for the subject of a job-application email to be changed to “vincent gable, professional and detail-oreinted programmer“[sic] (and you know what they say about first impressions).

The Unthinkable

I think a lot of why crashes are thought the worst thing a program can do is, fortunately, crashes are the worst thing most programs will ever do. So they end up taking the #1 spot on the bug severity triage sheet.

Data-vandalization is orders of magnitude less common then crashing in shipping programs. And if you count crashes during development, that proportion shrinks dramatically. Every non-trival program has crashed at some point during the development process.

And a leading cause of data loss is programs crashing without saving something. Do you classify that as a data-loss bug or a crash? Well, the crash is the thing that needs to be fixed to solve the problem, so probably a crash.

Small wonder then that crashes are considered by some to be the worst kind of bug, even though erasing or corrupting someone’s data is worse. Most recently, I have been going by Wil Shipley’s bug triage priorities, which give highest priority to “Data-loss bugs”.


1 I don’t mean to call out Daniel Jalkut; his was just the first quote that jumped to mind. And for all I know he agrees with me, and classifies mauling a user’s data as something worse than a “nuisance”.

« Newer PostsOlder Posts »

Powered by WordPress