About | ACP | Buy | Forum | Industry Watch | Learning Curve | Search | Twitter | Xnews
Home » Learning Curve » Developers Workshop

Messaging and Middlemen

Objective-C's a quantum leap - not a fetish.


Buy It

Try It

Objective-C is the only truly object oriented development environment out there today. The difference is that it uses messaging where other environments use function calls. There's a whale of a difference.

But making a 'call' - sending a message - actually literally involves putting your request in a 'wheelbarrow' and having it shipped over to a remote object. Think RPC - it's the same type of thing in a way (except it's different). What's important is that you have a slight overhead with message calls.

The iPhone activation hack released the other day demonstrated the same thing again. It's written in haste and without much thought for what's really going on. Take a preferences scenario as a simple example.

-(void)setPreferences:(id)sender {
    [[NSUserDefaults standardUserDefaults] setObject:@"foo1" forKey:@"Foo"];
    [[NSUserDefaults standardUserDefaults] setObject:@"bar1" forKey:@"Bar"];
}

This looks good enough. There's a single user defaults object floating around for each running application. You invoke the class method standardUserDefaults to get a pointer to it. Then you set the two objects for your two keys. And this type of code is really prevalent.

But it's also wrong. The nested [NSUserDefaults standardUserDefaults] is actually a method call - you have to get a pointer to the user defaults object to send the setObject:forKey: message to it.

There's an overhead both in code and time with that first call. Your code puts the standardUserDefaults message in the runtime wheelbarrow and goes out and looks for the NSUserDefaults object and asks it for a pointer.

Why are you doing it twice?

-(void)setPreferences:(id)sender {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

    [defaults setObject:@"foo1" forKey:@"Foo"];
    [defaults setObject:@"bar1" forKey:@"Bar"];
}

Now you're only sending three messages - in the first snippet you sent four. You just speeded up your snippet by ~25%.

You might go even further: what you come up with might not be faster but it might be more compact. Try it and see which one pans out better.

-(void)setPreferences:(id)sender {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString key[] = {@"Foo", @"Bar"}, object[] = {@"foo1", @"bar1"};
    int r;

    for (r = 0; r < sizeof(key) / sizeof(key[0]); r++)
        [defaults setObject:object[r] forKey:[r]];
}

In defence of the above: it might take more object code - in which case one might defer - but if it can scale it will take less source code and that's almost always good. And it makes things easier to understand and easier to gain an overview.

Middlemen are another great topic. Think in terms of middlemen with consumer products: they make prices goes up. It's the same thing with programming.

The other day someone was asking how to get random numbers in Objective-C. Did this person really think there was an operating system out there with no capability of generating random numbers? Odds are he didn't think or wouldn't know how to think.

Assume a 'higher level API' has a way to access file info - type, mode, and so forth.

Start with Cocoa's Foundation NSFileManager. The AppKit's NSWorkspace is yet another abstraction of NSFileManager. Calls to NSWorkspace often go through to NSFileManager. This in general is a Good Thing™. But do you pay?

NSFileManager *manager = [NSFileManager defaultManager];

Now you have your pointer. What do you want to do now? Get the file info.

NSDictionary *fileInfo = [manager fileAttributesAtPath:path traverseLink:0];

Now you have your file info in a dictionary and you can start looking. From Apple's own documentation.

This code example gets several attributes of a file and logs them.
if (fileAttributes != nil) {
    NSNumber *fileSize;
    NSString *fileOwner;
    NSDate *fileModDate;
    if (fileSize = [fileAttributes objectForKey:NSFileSize]) {
        NSLog(@"File size: %qi\n", [fileSize unsignedLongLongValue]);
    }
    if (fileOwner = [fileAttributes objectForKey:NSFileOwnerAccountName]) {
        NSLog(@"Owner: %@\n", fileOwner);
    }
    if (fileModDate = [fileAttributes objectForKey:NSFileModificationDate]) {
        NSLog(@"Modification date: %@\n", fileModDate);
    }
}
As a convenience, NSDictionary provides a set of methods (declared as a category in NSFileManager.h) for quickly and efficiently obtaining attribute information from the returned dictionary: fileGroupOwnerAccountName, fileModificationDate, fileOwnerAccountName, filePosixPermissions, fileSize, fileSystemFileNumber, fileSystemNumber, and fileType. For example, you could rewrite the last statement in the code example above as:
if (fileModDate = [fattrs fileModificationDate])
    NSLog(@"Modif Date: %@\n", [fileModDate description]);

[Yes there's a syntax error and yes it's lame code but don't worry about it. Ed.]

The point is: you've just wasted a lot of code - and have you thought what NSFileManager was doing? Where NSFileManager got its own data?

struct stat sb;

if (!lstat(path, &sb)) {
    /* * */
}

You've already got everything. And that's all NSFileManager did. Except NSFileManager was a middleman: it gave you nothing you couldn't get cheaper from the source. It cost you more.

And if you want further details about the info you've already received you have to go back to the wheelbarrow again: each followup call is a new message that has to go out - you need to send your request, have it sent over, and wait for your reply. In the latter better method you've already got it.

size = sb.st_size; owner = sb.st_uid; modified = sb.st_mtimespec;

[You also miss out on all that conditional code in this example. You needed one call only to check the return value; if it didn't go south you already know you have good data. Ed.]

There are times - lots of them - when messaging and middlemen can get in the way: when they're far from your best solution. Remember: the APIs you intend to call have nowhere to go except to the same operating system you go to yourself.

Not to speak of the chances you take: every time you introduce more 'OPC' - other people's code - you're increasing the risk something will go wrong with your application. If the system itself can't give you good data back you'd better change platforms.

And even though Objective-C is a quantum leap it's not a fetish: the operating system is still there, still has its API (which it is proud of) and is still the premiere source for the information you need.

About | ACP | Buy | Forum | Industry Watch | Learning Curve | Search | Twitter | Xnews
Copyright © Rixstep. All rights reserved.