Thursday, October 5

Pimp My Code, Part 12: Frozen in Carbonite.

Carbon, briefly defined, is a compatibility library that ships with Mac OS X that enables older applications, written for Mac OS 9 and before, to run under Mac OS X with minimal changes (and a recompile). Carbon is descended from the original Mac Toolbox written in the early 1980s, and still shows signs of its Pascal and machine-language origins, even though it is now primarily accessed through and written in the C language.

Cocoa, briefly defined, is a new application environment that Apple got (and improved upon) when it bought NeXT in 1997. Cocoa uses a dynamic, object-oriented language (Objective-C) by design, and was the inspiration for modern languages like Java.

For a few years after the initial release of Mac OS X (10.0), there was speculation about whether Carbon or Cocoa would end up dominant. Many Mac programming groups inside and outside of Apple didn't know Cocoa and assumed it would be another flavor-of-the-month that would be quickly abandoned as unworkable, like Pink and Taligent and Copland.

Apple's official stance was initially that developers were encouraged to use either Carbon or Cocoa to write new applications; in the last year or so that message has quietly changed to just encouraging Cocoa, both because Cocoa is being enhanced faster than Carbon and because increasing numbers of Apple engineers are trying Cocoa and finding it an easier and faster to code environment within which to code.

But there are still people out there who use the Carbon APIs to program on the Mac, and there are still those who assume Carbon is "faster" and Cocoa is just "easier". While this has no basis in any timing tests I've seen, it's a myth that persists.

It should be noted that over the past eight years Apple has merged the Carbon and Cocoa runtime environments to the point where one can call Carbon routines from inside any Cocoa program, and vice-versa, so the idea that one environment is inherently "faster" is kind of crazy, although one can obviously fail to optimize one's code, and there is a partly-relevant question which goes: if you write the same number of lines of code in each language to do the same job, which would run faster?

The answer, in my experience, is Cocoa wins hands-down, because there's no such thing as a Carbon program that's the same number of lines as a Cocoa program that does the same thing. Carbon is wordy, in part because it is based on C, and in part because it still uses metaphors and programming conventions that were in vogue twenty to thirty years ago.

--

What's distressing to Cocoa programmers is that there are still critically important Apple APIs that are only available through the incredibly byzantine and ill-documented Carbon libraries, and some groups at Apple are still generating Carbon code, under the guise of "Core".

Now, let me be clear. I have nothing against "real" Core libraries, like CoreFoundation or CoreGraphics or CoreAnimation. These are all modern APIs that happen to be written in C, and they tend to have good to great support above them in Objective-C, either via separate APIs (like NSBezierPath in AppKit, which automatically emits the correct CoreGraphics calls) or via toll-free bridging (like NSStreams and CFStreams).

But some "Core" libraries aren't "Core" at all -- they are still using the conventions and structures of Carbon, and these libraries are nigh-impossible to use. QuickTime, Keychain / Security Services, Core Audio, Launch Services, Speech and Voice recognition -- these are the ones I've recently run up against. The problems with even well-written Carbon libraries are myriad:
  1. There is usually one, giant, all-encompassing "setAttributes" function and one "getAttributes" function for setting and getting every value associated with a Carbon "object", which requires to you laboriously build up and then laboriously unparse huge, special parameter structures for even the simplest call. For example, see one of the newer (v6.4) QuickTime calls, for setting parameters on a FireWire video camera: VDIIDCSetFeatures(VideoDigitizerComponent ci, QTAtomContainer *container). That one call is used to set a HUGE number of parameters, which is "simple," except, oh, it takes eighty lines of code to prepare for that one call, and the parameters you can set are not actually documented in the documentation. (This is not an exaggeration, I'll show Apple's own example code later.)

  2. Carbon calls with monolithic parameter blocks and anonymous parameters (like the one above) are not inherently self-documenting, and the documentation for them is often wrong and/or incomplete because the documenters' job is so much harder. For example, with the given documentation there's really no way you could intuit how to build up a legal parameter block to actually call VDIIDCSetFeatures() unless you also read the technote Apple wrote on it in 2005, which is very helpful, but was written a year too late for Delicious Library and several years after QuickTime 6.4 was released.

  3. Parameters passed into functions are passed by reference, whether they are going to be modified or not, which is just obscure. For example, see SecKeychainItemCopyAttributesAndData(), which takes the input "info" by reference, even though it's read-only!

  4. Carbon uses the same type, OSTypes (eg, 'sit!'), extensively for everything: return codes, building parameter blocks (specifying what you want to do, how you want to do it), loading QuickTime components, etc., which means reading a function description doesn't actually give you enough information to know what to actually pass it. (Eg: "Pass it some combination of four-character constants. Good luck!")

  5. Carbon memory management is a mish-mash of new and old metaphors, so you might easily find yourself using, in addition to CoreFoundation objects that can be retained and released, "Handles" or "Atoms", which are obtuse and easy to screw up.

  6. Carbon often uses FSRefs (although there are now often string-path equivalents) to refer to files, which are, again, not fun or easy to build or use.
Let me note that I'm not trying to slam Apple's Carbon programmers here; if I were a programmer on some Carbon toolkit I wouldn't necessarily try to break every metaphor everyone else was using, either -- I'd add my code and clean things up a bit where I could. (Or, actually, I would, but I would suggest we just rewrite everything in Cocoa, which is what I'm getting to in this post.)

Carbon has matured a lot in the last several years (eg, more Cocoa-like). But it's not enough. What I want to do, with this post, is encourage Apple to finally move those necessary but neglected frameworks all the way to Cocoa.

--

Ok, so I've been a bit abstract. Let's look at two function calls from two different Carbon libraries that are under active development and that you MUST use if you want their functionality -- there is not equivalent in any other framework. We'll take them apart, see why they are so hard to use, and then look at an ideal Cocoa API that would accomplish the same thing but be a zillion times easier to call and understand.

First off, let's look at how you might set the hue, saturation, sharpness, brightness, gain, iris, shutter, white balance, gamma, temperature, zoom, focus, pan, tilt, optical filter, focus point, and more on IIDC cameras.

Ok, wait, wait a minute. What did I just say? "IIDC cameras"? What the hell is an IIDC camera? And why do I care? Doesn't QuickTime isolate me from the low-level nonsense and just provide me with an opaque Sequence Grabber object which handles all the hardware bullshit and provides me with a single unified interface?

Well, the answer is, "Of cou--no. No, not really. Psych."

If you have a certain class of FireWire cameras which have a common way of setting their parameters, Apple has provided low-level calls to set those parameters that only work with those cameras, and deprecated the other function calls. Note that, if you have an older (or newer) camera, or a USB camera, like the internal USB iSight that ships with EVERY iMac and notebook Apple sells, these IIDC functions don't work for you, and you have to use the older functions (where available), even though Apple says they are deprecated. Wheee!

Ok, so assume you have one of these cameras, and you want to set, say, the gain on it. How would you do it? Well, normally, you'd spend months guessing at how to build up the parameter block to please VDIIDCSetFeatures(), but as of 2005, as I said, Apple has a technote, which shows how the gain can be set, in only eighty lines of code.

Seriously. Eighty. Let's look:

Setting the Gain on a Camera in QuickTime
ComponentResult ConfigureGain(SGChannel inChannel)
{
QTAtomContainer atomContainer;
QTAtom featureAtom;
VDIIDCFeatureSettings settings;
VideoDigitizerComponent vd;
ComponentDescription desc;
ComponentResult result = paramErr;

if (NULL == inChannel) goto bail;

// get the digitizer and make sure it's legit
vd = SGGetVideoDigitizerComponent(inChannel);
if (NULL == vd) goto bail;

GetComponentInfo((Component)vd, &desc, NULL, NULL, NULL);
if (vdSubtypeIIDC != desc.componentSubType) goto bail;

// *** now do the real work ***

// return the gain feature in an atom container
result = VDIIDCGetFeaturesForSpecifier(vd, vdIIDCFeatureGain, &atomContainer);
if (noErr == result) {

// find the feature atom
featureAtom = QTFindChildByIndex(atomContainer, kParentAtomIsContainer,
vdIIDCAtomTypeFeature, 1, NULL);
if (0 == featureAtom) { result = cannotFindAtomErr; goto bail; }

// find the gain settings from the feature atom and copy the data
// into our settings
result = QTCopyAtomDataToPtr(atomContainer,
QTFindChildByID(atomContainer, featureAtom,
vdIIDCAtomTypeFeatureSettings,
vdIIDCAtomIDFeatureSettings, NULL),
true, sizeof(settings), &settings, NULL);
if (noErr == result) {
/* When indicating capabilities, the flag being set indicates that the
feature can be put into the given state.
When indicating/setting state, the flag represents the current/desired
state. Note that certain combinations of flags are valid for capabilities
(i.e. vdIIDCFeatureFlagOn | vdIIDCFeatureFlagOff) but are mutually
exclusive for state.
*/
// is the setting supported?
if (settings.capabilities.flags & (vdIIDCFeatureFlagOn |
vdIIDCFeatureFlagManual |
vdIIDCFeatureFlagRawControl)) {
// set state flags
settings.state.flags = (vdIIDCFeatureFlagOn |
vdIIDCFeatureFlagManual |
vdIIDCFeatureFlagRawControl);

// set value - will either be 500 or the max value supported by
// the camera represented in a float between 0 and 1.0
settings.state.value = (1.0 / settings.capabilities.rawMaximum) *
((settings.capabilities.rawMaximum > 500) ? 500 :
settings.capabilities.rawMaximum);

// store the result back in the container
result = QTSetAtomData(atomContainer,
QTFindChildByID(atomContainer, featureAtom,
vdIIDCAtomTypeFeatureSettings,
vdIIDCAtomIDFeatureSettings, NULL),
sizeof(settings), &settings);
if (noErr == result) {
// set it on the device
result = VDIIDCSetFeatures(vd, atomContainer);
}
} else {
// can't do it!
result = featureUnsupported;
}
}
}

bail:
return result;
}

Wow, easy!

Remember, this is only for a certain class of cameras (including external iSights, but excluding internal iSights) -- if you want your code to work in all cases, you also have to call SGSetSettings() (or some such, I honestly don't know, as the focus calls I was trying to do don't have equivalents) to set up other kinds of cameras, except this call isn't as flexible, and, of course, nowhere in the documentation for the call does it say which settings you can actually set. Because, since everything in Carbon just takes and returns OSTypes (see sin #4), it's not self-documenting what kinds of parameters you can pass in to any method -- there are tens of THOUSANDS of OSTypes defined in the system, good luck finding the list of the ones your particular function takes and returns! They certainly are never listed in the documentation of the functions that take them.

Now, if Apple had used an enumerated type instead of an OSType, as such:
Imaginary QuickTime C Typdef
typedef enum {
kSequenceGrabberFocus, kSequenceGrabberZoom, kSequenceGrabberGamma,
[...]
} SequenceGrabberParameters;

Well, then we could just command-double-click on the function prototype in XCode, and we'd have our own documentation (after a fashion), in the form of the header file where the parameters are defined.

(On a side note, sadly, the AppKit and Foundation team just announced they are moving AWAY from using enumerated types in Cocoa methods and switching everything to take plain ints, because they HATE self-documenting code and hate Cocoa programmers. No, it's because they are worried that enumerated types could have unspecified sizes. But, seriously, Ali, this is totally broken -- enumerated types make reading and writing code MUCH easier, and they enforce sanity in switch() statements and if() comparisons. Please don't break this! [Update: actually, it's not quite as bad as it could be: the proposed new types aren't totally anonymous, they just are typedef'ed ints, but at least they are defined right below real enums, so command-double-click still works. Debugging and switch() and if () still aren't handled as nicely, though.]

Now, the above enumerated type would be helpful IF QuickTime had to stay with the giant, all-encompassing set-get functions. But must they? Well, no. For some reason some Carbon programmers think it's easier for them to add and remove APIs if they never commit to any particular functionality, so they use those giant anonymous set-get functions and then just have you build up a list of parameters yourself, with the idea being that it's a lot more flexible if they change the parameters out from under you than if they change the functions.

Acrazypersonsezwhat? Ok, I don't know what life was like under Mac OS 9, but nowadays we've got a well-defined system for deprecating APIs, and it works. And the advantages to not building up parameter blocks are huge. To wit, imagine the above gain-setting code, rewritten in a (hypothetical) Cocoa interface to QuickTime:

Setting the Gain on a Camera in Imaginary Cocoa QuickTime
ComponentResult ConfigureGain(QTSequenceGrabblerChannel *sequenceGrabblerChannel)
{
return [sequenceGrabblerChannel setGain:1.0];
}

Sure, four lines is less than eighty, but the observant amongst you will say, "Wait a minute, in the original code he checks to see if this is an IIDC camera first!" Well, yes, but, honestly, I should be able to call my mythical -setGain: on any physical sequence grabber, and if the user's particular hardware doesn't implement gain, QT should just ignore me.

More observant folks may exclaim, "But, he sets the camera's gain feature to be: on, manual, and raw control!" And I must calmly say, if I call -setGain: on a sequence grabber, it's implicit that I am putting it under manual control! Otherwise, what does setting the gain mean? I should not have to write this code.

Even more observant folks may add, "Well, but, he also has some crazy code where he either sets the value to the camera's raw maximum or 500..." to which I respond: I should be abstracted away from such nonsense. Parameter values should be normalized from 0.0 to 1.0 for me; where 1.0 is the hardware maximum, whatever it is. I should never have to query the hardware.

Now, it could be I'm oversimplifying a bit. I can imagine there are complexities in this code that require, say, TWO lines of Cocoa inside my imaginary rewrite of ConfigureGain(), not just one. But you can see where I'm going with this.

The fact that Apple had to write EIGHTY lines of example code to show how to set A SINGLE PARAMETER on SOME TYPES of cameras to its maximum value shows that THESE APIS ARE SERIOUSLY BROKEN. If you ever find yourself saying, "Look, to use these APIs I've written, you just have to write four tons of this glue code which never changes..." just stop! Write the damn glue code yourself, and provide a higher-level API!

--

You might hope that QuickTime for Leopard, which has been publicly announced to be at least partly rewritten with Cocoa APIs, will solve these problems. I certainly hope so. But, historically, some Carbon teams, when faced with writing Cocoa code, take a simple and not-very-helpful route -- they provide only one or two methods in their Cocoa classes, when their Carbon APIs are incredibly rich and feature-ful. The first iteration of QuickTime in Cocoa was like this -- look at NSMovie, which has TWO whole methods, "-URL" and "-QTMovie".

Wow! I'm overwhelmed! I mean, it's pretty clear how I would add a track, or record from a camera to a movie, or save to disk, or... Oh, wait, no, I can't do any of those things from Cocoa. I've essentially been given a busy-box. "Here, you're a Cocoa programmer, you can't handle actual functionality... just put your little movie in your little view in your little NIB and be happy."

But, wait, my scorn is too quick. Look what we got in 10.4, to replace NSMovie: QTMovie, which seems pretty darn rich from where I'm standing. At least, it has more than two methods, so I think that's a big improvement.

Or let's take NSSpeechRecognizer, written by a new friend of mine from WWDC, whom I'm sorry to single out this way.

Now, NSSpeechRecognizer has a nice interface. It's clear how to use a NSSpeechRecognizer, and it's easy as heck to integrate into your applications. I put speech recognition in Delicious Library 1 in about a day using this class, and most of the work was in building up a sane list of words to recognize and keeping it up-to-date without too much overhead.

But if you only speak Cocoa, you'll think that all Apple's speech recognition can do is what NSSpeechRecognizer does: recognize a flat list of words and phrases. You'd assume you can't recognize a phrase from one list followed immediately by a phrase from another list, much less set up a tree of expected phrases. And certainly there's no way to turn someone's speech into a list of phonemes for further processing, or to record and analyze the actual tones the user uttered. Heck, there are only four attribute set/get methods in NSSpeechRecognizer, so that must be about it for Apple's code, huh?

Well, as you've guessed by now, no. I'm told, by the original programmers, that all the above functionality is in the Carbon APIs. However, I have no idea, because I simply don't have the patience to learn or program raw Carbon unless I'm forced to, and in this case I wasn't.

There are a ton of neat applications I could make if NSSpeechRecognizer (and its friend, NSSpeechSynthesizer) were fully Cocoa. It's too bad.

--

Ok, let's pick on another Carbon function call, and then invent another Cocoa API that should exist but doesn't.

This time, it's from Keychain. Now, Keychain is massively cool; all your passwords, from all your programs, can be stored securely in a central (encrypted) location, so you don't have to remember them -- all you have to do is remember the password to your keychain. You get to set the policy on which programs can access which passwords and how. Keychain is full-featured and awesome...

...aaaaaaaaaaaaand it's Carbon. So, it brings all that baggage with it, which is really sad.

Let's look at some code I wrote recently to build up a list of FTP sites that are currently stored in the user's keychain, so I can automatically suggest a place to upload a user's Delicious Library in 2.0:

Build List of FTP Sites Using Carbon Keychain
#define HANDLE_ERROR(returnCode)if (returnCode != noErr) NSLog(@"keychain error %d encountered on line %d of file %s", returnCode, __LINE__, __FILE__);

- (NSArray *)allFTPSiteDictionariesFromKeychain;
{
NSMutableArray *mutableSites = [NSMutableArray array];

[mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:kSecProtocolTypeFTPAccount]];
[mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:kSecProtocolTypeFTP]];
[mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:kSecProtocolTypeFTPS]];
[mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:kSecProtocolTypeFTPProxy]];

return mutableSites;
}

- (NSArray *)_ftpSitesWithProtocol:(SecProtocolType)protocol;
{
NSMutableArray *mutableSites = [NSMutableArray array];

SecKeychainAttribute findInternetPasswordsAttributes[] = {
{kSecProtocolItemAttr, sizeof(protocol), &protocol},
};
SecKeychainAttributeList findInternetPasswordsAttributeList = {
sizeof(findInternetPasswordsAttributes) /
sizeof(*findInternetPasswordsAttributes), findInternetPasswordsAttributes
};

SecKeychainSearchRef searchRef = NULL;
OSStatus returnCode = SecKeychainSearchCreateFromAttributes(NULL,
kSecInternetPasswordItemClass, &findInternetPasswordsAttributeList,
&searchRef);
HANDLE_ERROR(returnCode);
if (!searchRef)
return nil;

do {
SecKeychainItemRef internetPasswordKeychainItemRef = NULL;
returnCode = SecKeychainSearchCopyNext(searchRef,
&internetPasswordKeychainItemRef);
if (internetPasswordKeychainItemRef == NULL || returnCode ==
errKCItemNotFound)
break;
HANDLE_ERROR(returnCode);

#define HACK_FOR_LABEL (7)
SecItemAttr itemAttributes[] = {kSecDescriptionItemAttr, kSecTypeItemAttr,
kSecCommentItemAttr, kSecProtocolItemAttr, kSecAccountItemAttr,
kSecServerItemAttr, kSecPathItemAttr, kSecSecurityDomainItemAttr,
kSecCreatorItemAttr, kSecTypeItemAttr, HACK_FOR_LABEL}; // RADAR
3425797 - You can't get the 'kSecLabelItemAttr' using this API
(it crashes), so either we have to use the number '7' or use
SecKeychainItemCopyContent(). I do the former. See
http://lists.apple.com/archives/Apple-cdsa/2006/May/msg00037.html
SecExternalFormat externalFormats[] = {kSecFormatUnknown, kSecFormatUnknown,
kSecFormatUnknown, kSecFormatUnknown, kSecFormatUnknown, kSecFormatUnknown,
kSecFormatUnknown, kSecFormatUnknown, kSecFormatUnknown, kSecFormatUnknown,
kSecFormatUnknown};
NSAssert(sizeof(itemAttributes) / sizeof(*itemAttributes) ==
sizeof(externalFormats) / sizeof(*externalFormats),
@"arrays must have identical counts");
SecKeychainAttributeInfo info = {sizeof(itemAttributes) /
sizeof(*itemAttributes), (void *)&itemAttributes,
(void *)&externalFormats};

SecKeychainAttributeList *internetPasswordAttributeList = NULL;
returnCode = SecKeychainItemCopyAttributesAndData(internetPasswordKeychainItemRef,
&info, NULL, &internetPasswordAttributeList, NULL, NULL);
HANDLE_ERROR(returnCode);
if (internetPasswordAttributeList) {
NSMutableDictionary *siteDictionary = [NSMutableDictionary dictionary];
unsigned int attributeIndex;
for (attributeIndex = 0; attributeIndex <
internetPasswordAttributeList->count; attributeIndex++) {
OSType tag = internetPasswordAttributeList->attr[attributeIndex].tag;
if (tag == HACK_FOR_LABEL)
tag = kSecLabelItemAttr;

NSString *tagString = [NSString stringForOSType:tag];
id value;
if (tag == kSecPortItemAttr)
value = [NSNumber numberWithUnsignedInt:*(unsigned int *)
&(internetPasswordAttributeList->attr[attributeIndex].data)];
else if (tag == kSecCreatorItemAttr || tag == kSecProtocolItemAttr)
value = [NSString stringForOSType:
(OSType)internetPasswordAttributeList->attr[attributeIndex].data];
else
value = [[[NSString alloc] initWithBytes:
internetPasswordAttributeList->attr[attributeIndex].data
length:internetPasswordAttributeList->attr[attributeIndex].length
encoding:NSUTF8StringEncoding] autorelease];
[siteDictionary setObject:value forKey:tagString];
}
if (IsEmpty([siteDictionary objectForKey:[NSString
stringForOSType:kSecLabelItemAttr]]))
[siteDictionary setObject:[siteDictionary objectForKey:[NSString
stringForOSType:kSecServerItemAttr]] forKey:[NSString
stringForOSType:kSecLabelItemAttr]];
[siteDictionary setObject:(id)internetPasswordKeychainItemRef
forKey:@"keychainItem"];

[mutableSites addObject:siteDictionary];

SecKeychainItemFreeAttributesAndData(internetPasswordAttributeList,
NULL);
}
CFRelease(internetPasswordKeychainItemRef);
} while (1);

CFRelease(searchRef);

return mutableSites;
}

Now, as I said, Keychain doesn't have the worst interface in the world. It's really pretty good for Carbon -- you build the equivalent of an NSEnumerator with SecKeychainSearchCreateFromAttributes() (albeit without the nice generality of the former class), enumerate through it with SecKeychainSearchCopyNext(), and then get the data you want from the item in SecKeychainItemCopyAttributesAndData(). But at this point in this marathon post (aren't you sorry you picked on me for not posting in so long) you're an old Carbon hand, so you can spot the sins Carbon brings with it.

But, just in case, let's go over them:
  • We have to build up a bunch of structures by hand so we can build up a "search specifier" in a single parameter in SecKeychainSearchCreateFromAttributes(), which hurts our readability a ton -- we really want for each statement we write to read like a sentence, almost, and if we have to write four lines of "struct this" and "allocate that" before we get to the verb, we've got pretty awkward sentences.

  • We loop over the items we find using "SecKeychainSearchCopyNext()" (nice), but we have to manage memory ourselves in Carbon; there's no autorelease (or garbage collection, heaven forfend!) so we have to free every returned value explicitly.

  • The returned object is an array of structures of structures of void pointers to things with counters and stuff and things -- ok, I've totally forgotten, honestly. But, my point is, it's not just an NSDictionary full of nice objects (or a CFDictionaryRef), so I have to parse out the values by hand, INCLUDING byte-swapping on Intel machines. Whee! I love it!

  • SecKeychainItemCopyAttributesAndData() has a crasher if you ask to retrieve the most common attribute you'd ever ask for from a Keychain item: the label. I should point out that this function is documented to deprecate the older method of getting data from an item, which was more obtuse but didn't crash.

    Now, ok, I write crashers too. I don't blame the Apple engineers for that, and it was really nice of them to step up and provide a workaround. Because of their help, this only delayed me by a couple hours instead of a couple days. My point here is, if this whole framework were a lot more easier to use (-cough-Cocoa-cough-) then it'd get used a whole lot more, and this bug would have been found LONG ago. The problem here isn't that a bug was written, it's that an incredibly valuable framework is needlessly crippled by being in Carbon and thus not used enough to be as robust as it should be.

  • SecKeychainItemCopyAttributesAndData() isn't actually documented correctly, so the only way to write code for it is to get on the web and look at the source code and read Apple's mailing lists. In particular, the docs say for "attrList": "On input, the list of attributes in this item to get..." which is, frankly, just false. A look at the source code (search for "ItemImpl::getAttributesAndData(") clearly shows "attrList" is ignored on input, which only makes sense, because it's an output structure and you've already passed in the list of attributes to get in "info", so why would you pass it in twice? Note also the docs say that "itemClass" is "A pointer to the item’s class" but don't say if you are supposed to pass it in, or if it gets returned to you? (It turns out to be the latter). And "outData" is described opaquely as "A pointer to a buffer containing the data in this item." Oh, the data, you say? Well, that's certainly specific! I'm always getting and setting data on my items.

    It turns out that data in this case means the actual encrypted password. So, in summary, what the documentation for this function doesn't say, but should, is, "You build up a list of parameters you want to fetch from itemRef in info, and this method returns their values in attrList if that's not passed in NULL. The item's class (here there'd be a link to all the classes) is returned by reference in itemClass if it's not passed in NULL, and the password is decrypted from the keychain into outData if that is not passed in NULL."
Ok, we've rewritten the documentation, now let's rewrite my original method to use a hypothetical Cocoa Keychain framework. Note that this rewrite is longer than the last because the base Keychain APIs are more recent than the QuickTime ones I pimped above, so they already use more modern metaphors and are thus already somewhat efficient. But can we do better? (Hint: yes.)
Build List of FTP Sites Using Hypothetical Cocoa Keychain
- (NSArray *)allFTPSiteDictionariesFromKeychain;
{
NSMutableArray *mutableSites = [NSMutableArray array];

[mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:NSKeychainSecurityProtocolTypeFTPAccount]];
[mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:NSKeychainSecurityProtocolTypeFTP]];
[mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:NSKeychainSecurityProtocolTypeFTPS]];
[mutableSites addObjectsFromArray:[self _ftpSitesWithProtocol:NSKeychainSecurityProtocolTypeFTPProxy]];


return mutableSites;
}

- (NSArray *)_ftpSitesWithProtocol:(NSString *)protocol;
{
NSKeychainFetchRequest *keychainFetchRequest = [[[NSKeychainFetchRequest alloc] init] autorelease];
[keychainFetchRequest setPredicate:[NSComparisonPredicate
predicateWithLeftExpression:[NSExpression expressionForKeyPath:NSKeychainSecurityProtocol]
rightExpression:[NSExpression expressionForConstantValue:protocol]
modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0]];
NSError *error = nil;
NSArray *matchingKeychainItems = [[NSKeychain defaultKeychain]
executeFetchRequest:keychainFetchRequest error:&error];

if (error) {
NSLog(@"%@: Error doing keychainFetchRequest '%@'", keychainFetchRequest);
return nil;
}

return matchingKeychainItems;
}

_ftpSitesWithProtocol got so tiny because, assuming I didn't have to access the returned keychain results as raw data -- that is, if there were real, full-fledged NSKeychainItem objects that could be queried for their -protocol and -account and -password and -label, then I wouldn't need to do all the complicated object-building I did in the above example, I'd just return the actual keychain items, and even bind to them in my interface directly! This would be pretty rocking... you can imagine feeding an NSArrayController from this list and then binding columns in a tableView to "label" or what-have-you, and you've got a poor man's Keychain Access app in like five lines of code.

Now, again, this might be an oversimplification of Keychain's APIs. There may be subtleties that I'm missing that would require a slightly more complicated set of Cocoa classes. But in general, I think porting Keychain to Cocoa would decrease the amount of effort to program to it (and increase its audience) by an order of magnitude.

--

Let me wrap up, finally, by apologizing profusely to everyone at Apple and elsewhere whom I've insulted tonight. I've been a programmer for 25 years now, and every year I look back at the code I wrote the year before and think, "What the hell? What idiot-monkey wrote this crap?"

And yet, here I sit, on my medium-size pony, and insult code that some of you wrote ten years ago, viewing it through the lens of modern coding practices that you guys taught me. Let me stress this: the good programming practices I've learned, such that I've learned them, are from studying the very best of what Apple's done. There are tools and techniques that I love and am in awe of in Apple's code: bindings, enumerators, object-oriented database access, model-view-controller, all those buzzwords. I didn't invent 'em, you guys did.

I know that Apple people exist in the real world, where going back and rewriting blecherous code is not something any engineer wants to do, nor any manager to fund. I know if you meet with Steve in six months and say, "Hey, sure, we didn't add anything cool in Leopard for you to demo, but check it: Keychain is Cocoa now! Huh? Huh?" he'd probably put his foot so far... well, you know where I'm going with this.

And I know that it's a small miracle that things like QuickTime and Keychain and Speech Synthesis and Core Audio exist in the first place. These have truly amazing functionality, using research and math far beyond my ken: if they weren't, I honestly wouldn't give a crap that all their power is frozen in Carbonite right now. (You don't see me complaining about the, uh, Scrap Manager, do you?)

So, I write this to help you, putative Carbon-coding-Apple-engineer-reader, not to harm you. I want you to take this to your manager, and say, "Look, see! We've got to get to Cocoa! This has got to be a priority! We can't keep slapping tar on a boat made out of milk cartons!"

Don't do it for me, do it for your team. Do it because you'll love it, and because more programmers will use your stuff, and they'll show it off in ways you never imagined, and suddenly you'll be in all the Stevenotes, and you'll be wealthy and happy and famous.

Labels:

160 Comments:

ssp said...

Don't you think it's a bit misleading to link the text 'a myth that persists' to a text from 2003? But that's probably just meant to be a snide remark towards the unsanity guys (who unlike many others are quite good at handling the Carbon stuff in their haxies).

Back then, this observation was true. Cocoa applications did feel a bit more sluggish than their Carbon counterparts. Time has moved on, Cocoa and the knowledge about it have improved, machines have become faster over these years and by now the speed difference vanished. Great for everyone!

Not that I'd disagree with any about the real points on Carbon and Cocoa you make. Trying the Mac Toolbox back in the 1990s was painful enough for me to give up programming. Just Cocoa made it possible for me as a casual programmer to come back and get the computer to do the things I want with a reasonable effort.

I definitely wish to see all of Carbon's features available through Cocoa, as many of them - just like your example - mean, that with the time and effort I can put into a fun project, I just have to do without them.

Hmmm, Cocoa Keychain...

October 05, 2006 4:16 AM

 
Wil Shipley said...

I don't write anything with the sole purpose of being snide towards someone. They wrote something I disagree with, I don't think it's snide to link to it.

I couldn't find the other link I was looking for, which was the much more recent announcement of some toolkit for Mac OS that billed itself as "having the speed of Carbon and the ease-of-use of Cocoa".

October 05, 2006 4:49 AM

 
Cameron Hayne said...

It strikes me that what you have said about some of the Carbon APIs is analogous to the situation in user-interface design.

A bad UI designer might put both "operationA" and "operationB" on the menu even though task analysis has shown that users will always do these two operations in sequence. A good designer would create a new higher-level operation that subsumes the two.

In more general terms, a bad API, like a bad UI, often results from a failure to take the time to understand what the users need to do, and/or a failure to make decisions about what should be included. ("We're not sure what they'll need so let's just put it all out there - make it flexible.")

Of course, a bad API, like a bad UI, can also result from an attempt to meet the needs of two (or more) very different groups of users in one product. (serving two masters)

October 05, 2006 5:32 AM

 
[maven] said...

What scares me most is that the Keychain-bug is still unfixed and in the mean-time there have been 1125164 other Radar-entries...

October 05, 2006 5:34 AM

 
Chris Ridd said...

So you think the security framework's horrible...

I would strongly advise against looking at the Directory Services framework, which is not only worse, but worse and inconsistent. :-(

October 05, 2006 6:58 AM

 
Mark Lilback said...

I'm largely in agreement with almost everything you say. However, I think you're nuts complaining about FSRefs.

Paths are evil, way more than int constants versus enums. Fortunately, NSDocument internally uses FSRefss or aliases, so if I move an open document it saves to the new location, not where it used to be. Which is what you get when you use paths.

And FSRefs aren't hard to use. There are two functions to interchange between them and paths. Not even 10 lines of code to add a couple of methods to NSFileManager via a category. Same for aliases.

October 05, 2006 8:34 AM

 
Daniel Jalkut said...

FWIW there is already a pretty extensive ObjC wrapper on the Keychain, authored by one of Apple's (former?) engineers, Wade Tregaskis:

https://sourceforge.net/projects/keychain/

I haven't used it, but I noticed it while searching google for help in debugging my own, much less developed wrappers.

October 05, 2006 9:00 AM

 
Anonymous said...

So what about all those Cocoa methods that take an NSDictionary full of parameters? It's really not that different.

And paths are definitely evil. About all you can say in their favor is they're cross-platform, so you don't behave any worse than elsewhere.

October 05, 2006 9:23 AM

 
Anonymous said...

You’ve finally discovered that the QuickTime team at Apple is totally insane. We all have plenty of complaints about the APIs that live under the Carbon umbrella, but most have dropped Handles by now. Oh, but not QuickTime. They even have their own documentation site, carefully designed to place the information you need in three or four completely unrelated and separately-styled sections. Phew.

October 05, 2006 10:00 AM

 
Tristan O'Tierney said...

Great article Wil. I think ideally there should be some sort of free license utility kit of carbon-wrapping foo made/distributed by fellow mac devs like ourselves. Of course we don't live in a fantasy world where work is given out for free, but if someone started an initiative and publicised it well enough it might get *somewhere*. I know every time I deal with carbon I get the sensation that I require several showers, so I'd certainly contribute where appropriate if I ran into carbon snags.

October 05, 2006 12:25 PM

 
fet said...

Be careful; "psyche" is a different word from the slang exclamation "psych!"

October 05, 2006 12:52 PM

 
Anonymous said...

With respect to transitioning QuickTime to Cocoa, what about the Windows community? There seem to be a fair number of Microsoft developers hanging around and it would be interesting to see how Apple would accomodate them if they transitioned QuickTime from Carbon to Cocoa. Would they divide their energies equally to provide support for OS X AND Windows users? Yikes!

October 05, 2006 12:59 PM

 
Andre said...

So this is what you've been doing all this time!
I've been waiting for another "Will Shipley" blog entry for a long while.

Well said.

October 05, 2006 1:06 PM

 
Aaron Tait said...

I think we (Cocoa Developers) should create our own open source collection of Carbon->Cocoa APIs. I'm sure that there are tons of developers, including me, that are writting this code. I don't know how exactly someone could implement this (Subversion maybe?), but there has got to be someone that is up to the challenge (i'm jsut a mere starving student). Who knows, maybe Apple would pick it up?

October 05, 2006 1:33 PM

 
Wil Shipley said...

Objective-C runs perfectly fine on Windows right now. There's no reason for the QuickTime team to avoid it.

In the past the QuickTime team has ported the entirety of the Mac OS Toolbox to Windows (and that port is now called "Carbon"); I don't think porting the support parts of Cocoa they'd need would be insurmountable.

October 05, 2006 2:15 PM

 
Anonymous said...

It's funny you mention Core Audio. I don't know what it's like on their team, but on my team within Apple, we also have to support 3rd party developers who write plugins. And let me tell you that they do not want to have to learn or write that confusingly hard Objective-C stuff! (Aaaahhhh!! Brackets!)

In their defense, many of them need their code to be cross-platform and work in other applications in addition to ours, so I can understand wanting to do as little work as possible to deal with cross-platform issues rather than writing cool new plugins. But there is strong resistance to anything Objective-C in some of those areas. So I understand why certain technologies haven't added a Cocoa interface, especially when the development community for them is extremely small and opposed to using Objective-C.

October 05, 2006 2:29 PM

 
Ian said...

Wow - it's like you can see inside my head.
I pity myself for not having the time/patience/wherewithall to have got this down myself.
Feel free to ask my rant-weary colleagues if you don't believe me.
Thank you Wil,
Much appreciation.

October 05, 2006 3:27 PM

 
Anonymous said...

Don't you think the whole Carbon vs. Cocoa thing is a bit disingenuous? If you're going to go on the record and say that you have nothing against "modern APIs that happen to be written in C" then the problem with Carbon is that it's a bad API, not that its not Cocoa.

October 05, 2006 4:32 PM

 
Anonymous said...

I'd like to make some points:

- Cocoa comes from chocalate. The brown delicious stuff that melts in your mouth. Apple did not, I repeat, did not invent this!!

- If i had to choose between Carbon or Cocoa, I'd choose Cocoa.

- About this objective-c character. I'd like to tell him one thing. You cannot be still on a moving train. You cannot be objective in a subjective world.

- Hi Fish-a-meal. You might want to switch it up. Maybe Fish&vegetables-a-meal. A little wine, some bread. Oh yea. Get drunk and watch batman.

- Chris, get ridd of that name. Tristan, change that name. Aaron, you need to stop wearing make-up. Fet, you missing an 'e' in the middle.

- Over and out Macdaddy's. Spend some time trying to get laid once in a while.

October 05, 2006 4:37 PM

 
TIm Buchheim said...

I agree with almost everything here. The big exception is on FSRef vs pathnames. Pathnames suck, at least in the Mac world where users want to be able to move and rename files all the time. Not that FSRef is all that great .. we need a Foundation class with the same functionality.

And to be really smart, it could implement NSString's methods and automatically resolve pathnames when needed, so you could call old path-based API with it. (There could be problems with that .. I haven't thought it through completely, as it just popped into my head, but it could be cool.)

Toll-free bridging with FSRef would be icing on the cake.


At least we're reaching parity on the GUI stuff (although a few widgets are still only in one or the other) although NSViews and HIViews still don't play together the way we'd all like them to do. (Every release has moved us closer to that, however small those steps might be.)

I think QTKit and Core Image are both good indicators that Apple is trying to move towards making it possible to access everything via Cocoa but I imagine it takes a lot of careful consideration to wrap functionality in ObjC without ending up with half-assed solutions like NSSpeechRecognizer.

October 05, 2006 5:04 PM

 
The Nog said...

If you're going to go on the record and say that you have nothing against "modern APIs that happen to be written in C" then the problem with Carbon is that it's a bad API, not that its not Cocoa.

Cocoa is useful for comparison because of how high-level its APIs are. It's a handy example of what a "good" Carbon should strive to be rather than requiring so much low-level glue code. Avoiding that stuff is part of the reason I embraced Cocoa in the first place.

October 05, 2006 5:57 PM

 
Anonymous said...

means reading a function description doesn't actually give you enough information to know what to actually pass it

Dude, welcome to C. I can't imagine what would happen if you had to program for Win32. Your head would surely implode.

October 05, 2006 6:50 PM

 
Peter Hosey said...

In your remodeled _ftpSitesWithProtocol: method, did you forget to use the “protocol” argument? (And I presume that it should be re-typed as NSKeychainSecurityProtocolType and the calls in the method above should be changed to pass NSKeychainSecurityProtocolTypeFoo.)

October 05, 2006 6:52 PM

 
John Siracusa said...

I can't wait until Wil graduates to a managed, dynamically typed language in 15 years and writes a big article about the barbarism of Obj-C and the Cocoa APIs. Come on in, Wil! The water's fine! ;)

October 05, 2006 7:07 PM

 
Wil Shipley said...

I can't imagine what would happen if you had to program for Win32.

Nobody has to program for Win32. That's kind of the point of my life.

October 05, 2006 7:15 PM

 
Wil Shipley said...

John: I can't wait, either. When I see a set of APIs that's better than Cocoa I'll switch to them. I'm not going to switch languages without the APIs, though.

October 05, 2006 7:16 PM

 
Wil Shipley said...

Peter: thanks for the pointer. Should be better.

October 05, 2006 8:22 PM

 
Wil Shipley said...

So what about all those Cocoa methods that take an NSDictionary full of parameters? It's really not that different.

It's not good, but it's certainly better. The nice thing about the Cocoa methods is that NSDictionary is a standard class, so it's easy to build up -- you can do it in one line, and in fact you can do it in-line if you don't have a big dictionary to build.

Since they are autoreleased, you don't have to declare them, set them up, pass them in, and tear them down. You just say:

[myThing setParameters:[NSDictionary dictionaryWithObjectsAndKeys:@"orange", NSThingColor, @"blue", NSThingBackgroundColor, nil]];

And you're done.

However, you still don't get type-checking and it's not self-documenting what -setParameters: takes, so this is broken like Carbon in those ways, but at least the Cocoa documentation always has a reference to where you can find the parameters for their monolithic set/get methods.

It's sometimes a good idea to do monolithic set/getters (eg, when setting up styles in an NSAttributedString, it makes sense, because you often want to re-use the same groups of styles over and over, so you'll set up the dictionaries once and hold on to them), but in general I think it's a bad practice under Cocoa and discourage it.

But it's certainly not the same as Carbon.

October 05, 2006 8:27 PM

 
Tim said...

For Chris Ridd: For what it's worth, I did an Obj-C translation/extension of Apple's C++ DSWrappers sample code for Directory Services a couple years ago. Disclaimer: There are known problems in the code, but it works well enough for what I needed to do that I've never got around to dealing with them.

October 05, 2006 10:55 PM

 
Anonymous said...

Only 80 lines of code? You Mac programmers have it good. You should try calling COM stuff from .NET once in a while :-)

October 06, 2006 12:14 AM

 
Anonymous said...

I enjoy your hilarious implication that Carbon dates from 1980 and Cocoa from 1997. The facts are that Carbon was rebuilt from the ground up in the late 90s, and all it shares with the original Mac toolbox is some APIs and basic ideas, whereas Cocoa is the direct continuation of OpenStep which dates from 1993. Yes, Cocoa is significantly older than Carbon.

Another thing I enjoyed about your post was your very strong implication that silent failure is a good way to deal with errors. Your code must be incredibly fun to debug.

Anyway, keep up the amusing posts.

October 06, 2006 12:16 AM

 
Rosyna said...

There are a few major issues with your "argument".

1. Complaining about FSRefs pretty much completely invalidates the entire thing. Paths suck, plain and simple. Paths put the control of the user's computer and files on the running application. FSRefs put the control in the user's hands.

2. This entire article is prefaced with the idea that it's about carbon. Yet you don't mention a single carbon API other than an opaque type, FSRef. QuickTime isn't carbon, the Security framework isn't carbon, Core Audio isn't carbon. The "closest" thing to carbon you mention are the Speech APIs. The rest are just procedural C APIs. The QuickTime team is a completely separate entity all together. You can easily determine this as they made simple bugs in HIMovieView that no Carbon engineer would make.

Hell, none of the specific functions you complained about even existed in Mac OS 9. Again, the closest thing was FSRefs, which were added to the Mac OS in Mac OS 9, but the finder and other things didn't use them so they didn't offer much benefit.

3. Virtually nothing in carbon returns a FourCharCode. Well, excepting GetEventClass() of course. Many APIs take FourCharCodes as it allows a method to create a unique int value that means something and is mostly human readable. After all, FourCCs are a standard (http://en.wikipedia.org/wiki/FourCC).

4. You say Carbon is a legacy API, but if you took a program written for the Mac toolbox in 1990, and tried to compile it for Mac OS X, it would not compile. Massive amounts of changes would be needed to make it modern. On the other hand, take an OpenStep program and try to compile it, most things would work except those that used frameworks that have better replacements today, such as MediaKit. Cocoa is the much, much older and legacy API in this case.

5. Your problem with the 80 line sample seems to be with error checking. Are you saying it's a bad idea to check for errors, recover from those errors, and present the user with meaningful information on how they can resolve the errors? All of your code either has the calls failing silently or throwing an exception, which leaves the program in an undeterminable state which can (and usually does) lead to a crash. Are crashes better than proper and correct error checking?

6. I stand by the slow speed assertion. Most Cocoa programs are slower than the carbon counterparts. Look at Backup.app, iPhoto, Delicious Library, Mail.app, and others. They all slow down immensely when given huge sets of data. Huge being relative, of course. Most of the speed issues aren't really caused by issues with cocoa itself, but with the mentality cocoa creates. The belief that cocoa will do everything for you. This causes the programmer to believe there is no reason to optimize. So you've got some extremely expensive calls such as creating an NSDateFormatter, iterating a directory, et cetera being done every single time a row in a table row is drawn. When the same formatter and the same iteration could be cached and used for every single row. KVO and things like bindings just make this worse and it makes so the programmer has to write even less code, which leads to slower code.

7. Also, it seems to be that everything you complain about seem to be personal difficulties with the APIs, not a failure in the API, just your personal approach to using them. A simple example is the fact most C APIs on OS X that are documented are documented mostly in the actual header files. Whereas the cocoa documentation is all in HTML files and the ilk. FourCCs are probably one of the easiest things to look up. Imagine trying to search all the headers for what 'rgnh' means in any given context compared to what the int value 7 means.

October 06, 2006 12:51 AM

 
Anonymous said...

As if someone who accidentally leaked a full serial number generator with his application, and a very ugly one to boot, would have so much authority on what makes for good or bad code!

October 06, 2006 1:13 AM

 
Michael Stroeck said...

Rosyna, you don't seriously think that Mail.app, iPhoto and Delicious Library are too slow for more than 0,5% of the users? Nobody single piece of software will ever be fast enough for the power users, so that argument is not very convincing.

Besides, that is not the point! Applications such as iPhoto and Delicious Library and the vast majority of modern independent Mac software very definitely would NOT EXIST in their current form and feature-richness if there were no high-level APIs to work in.

October 06, 2006 1:46 AM

 
Wil Shipley said...

Wow, the Carbon trolls are out in full force. I hardly know where to start.

First off, I'm not going to argue what is and isn't Carbon. If you want to define Carbon to be twelve men reading the Torah, that's fine. Whatever you decide we should call the set of procedural C interfaces that encompass QuickTime and Keychain and Core Audio and Speech, well, that's what I'm talking about. Don't care about the name.

And if you guys seriously don't know the difference between handling errors and writing tons of crappy code for no reason, there's not much I can do for you. I posit that I should be able to call -setGain: on any camera, whether it has gain or not. If you disagree, that doesn't negate my argument that Cocoa-style APIs would be better -- you could just add a single call like "-(BOOL)gainControlIsAllowed;". Now we're up to TWO lines of code. GASP!

My point isn't that there shouldn't be error-checking, it's that Apple should do it for us, and just return one status. Hell, if they want to raise an exception that says, "Sorry, no camera" or "Sorry, no gain on this camera" or whatever, that's fine. But I shouldn't have to write the exact same 80 lines of code every other developer does.

Also, seriously, Apple's sample code error-checking sucks. They never do anything constructive with the errors. Most of the sample code just says, "Here's where you'd handle the error!" Sometimes they log it to the console. Oh, wow! Good thing you trapped that! But what, pray tell, should I *do* if the sequence grabber shuts down with return code -2124, which isn't documented anywhere? Stand up? Sit down? Fight? Fight? Fight?

I'd like to mention I didn't attack you or you guys' damn apps in my post, and I think it's pretty classless when you to attack me and mine. It's the lamest form of logical fallacy to attack the speaker instead of his argument. ("Gee, you write some pretty ugly code for someone who is saying that George Bush tortures people!")

But, hey, since you brought it up, let's talk about Delicious Library, ok, Rosyna? Yes, it's slow if you load in hundreds of items. Version one was not designed for that many. Version 2 is designed for tens of thousands, so customers with larger collections will be happy there.

Yes, I made design decisions so I could ship my app. Surprise. I decided to concentrate on making a beautiful and functional and stable app, at the expense of it being slow for large collections. I wish I could have done EVERYTHING in version 1, but I simply couldn't. I made a trade-off. Big fricking deal.

Are there lots of optimizations I could have made to Delicious Library 1? Yup, you bet. But, for most of the project, it was just me coding. Seven months, start to finish, brand-new app. We were living on our savings, I had no severance pay from Omni (just a used laptop), and I had to ship. So we prioritized what we thought was important, and we did it.

But, I'm going to state this: I've never seen any application by a startup company sell as well in its first year as Delicious Library. Period. I've never seen any third-party Mac app sell this well in its first year. Period.

Sure, they may be out there -- I don't have everyone's sales data. Maybe Unsanity is selling a zillion copies of your apps every day, I don't know. But I'm pretty damn happy with the success of Delicious Library, and it was pretty much written by me.

So I think I did OK. I think my priorities are NOT wack. I think speed is great, but it's obviously not the _most important_ factor to most users. Cocoa isn't inherently slow. It does allow you to write code very quickly, and some of it might be slow. The nice thing is, you can choose which things need optimizing, and which are fast enough.

Look, it comes down to this: if you think Delicious Monster is a success and you'd like to see how I did it, I write about that here, in this little blog. Go ahead and read. If you think my code sucks and I'm full of myself, well, hey, guess what... DON'T FUCKING READ MY BLOG!

Seriously. Guys. I don't force you to come here and read my opinions. I take time off work to write up things I feel strongly about so I can help people out. If you disagree, fine, but don't attack my shit. It's just not cool.

As for the publishing my key generation code -- let's talk about that. Yes, I had to figure out some ugly Carbon APIs to generate keys, and yes, it was ugly. A lot of the reason it was ugly was because I was trying to be clever and do all the checking in inline functions, so that a hacker couldn't just NOP a single function or method in my code and do an end-run around security -- the security code would actually be injected into the main app in several places.

However, I used a framework (legacy!) for part of the application, and in linking to this framework I forgot to check off the "don't export headers" button, so all the headers (including the one containing all the ugly licensing code) got published in version one of Delicious Library.

Yup, that's right. Gasp. If you're a pirate, you can generate keys to Delicious Library. You can steal my software, much like, oh, every other piece of software in the known world. Guess what my attitude is towards that? If you guessed "yawn", you're only partly right.

It actually turned out to be a huge win to publish my licensing code, because this way the hacking groups on the net went to the trouble to generate their own illiegal license keys instead of stealing a legitimate customer's keys.

So, with each point release of the software we do, we can just look on the net for the latest illegal keys and shut them down -- if they were keys owned by legitimate customers, we wouldn't want to shut them down, because we're not Microsoft (-cough-WindowsGenuineAssholes-cough-). Now, some of those people are going to just go and get another illegal key. All right, fine, some people really aren't going to pay for software. When I was 12 I stole software too, I don't think the economy died because of it.

But SOME people are going to say, "Damn it, this is cool software, I don't want to keep screwing with using pirated keys that will get shut down all the time," and they're going to go ahead and purchase Delicious Library.

What makes me so sure? Last time we did a minor release, which ONLY fixed one minor bug (focussing control for external iSights on Mac Pros), we did an EXTRA $12,000 of business that day and the next. Cash money business. Not $12,000 total, an EXTRA $12,000 above our normal business.

So, yes, if you'd like to keep flaming me with an oversight I made two years ago regarding header files and key generation, go ahead. But I can't help but feel I got the last laugh here.

October 06, 2006 1:58 AM

 
Wil Shipley said...

The facts are that Carbon was rebuilt from the ground up in the late 90s, and all it shares with the original Mac toolbox is some APIs and basic ideas

I feel like what I'm talking about here are archaic APIs, not how old the code is. What I said was its heritage is in the 80s, which is a statement you didn't refute in the slightest.

But I'm glad I amuse you. Keep laughing!

October 06, 2006 2:02 AM

 
Anonymous said...

Seven months, start to finish, brand-new app.

So despite the articles to the contrary, the developer mentioned in your about box, and the upgrade pricing, Chronopath Library didn't help jumpstart DL's development at all?

October 06, 2006 2:32 AM

 
Wil Shipley said...

Bait bait bait. I don't talk about Chronopath out of respect for the deal we struck.

If you've got a problem with me, why don't you come out and tell me what it is? It's obvious you want to get me upset. Did I do something to you? Hurt you in some way? Maybe I can make it right.

October 06, 2006 2:44 AM

 
Anonymous said...

My problem is simple: you constantly state that you singlehandedly built DL from scratch in seven months, when the reality is that you upgraded an existing product, which is a much simpler job. Whether you say this to make yourself look better or for some other unknown reason is not something I can say, but it is very annoying to see someone with so many fans acting that way.

October 06, 2006 2:48 AM

 
Wil Shipley said...

Actually, I didn't say that -- I said I worked mostly alone.

You don't know the percentage of code from Chronopath Library that remained in Delicious Library the day we shipped it, do you? No, you don't. Guess who does? Me.

As well, you don't know what percentage of code that was written by the owner of Chronopath Library that made it into Delicious Library 1.0, do you? No, again, you don't. Again, take a guess who ran those numbers? Who did a long subversion check, figured out the percentages.

Any guesses? Any at all? No? Ok, it was me, again. I know, you don't.

I don't respond when people say that we based our app on a teenager's work because it really wouldn't be cool to talk about that whole deal. He's a good kid, he's got a good future.

You can believe what you wish about me, but if you don't LIKE being angry, I'd ask you to consider that you may not have all the facts, or even some of the facts, and that there may be a reason I'm not giving those facts which is not purely selfish.

October 06, 2006 3:03 AM

 
Anonymous said...

You just proved me right. The percentage of code that remained is irrelevant. I've worked on more than enough code to know that starting from a stable base makes life much easier, even when none of the base remains at the end of the day.

So say what you like, and yes I don't have all the facts, but by your own word I know that DL was not "Seven months, start to finish, brand-new app." So why do you insist on saying that over and over again?

October 06, 2006 3:32 AM

 
Anonymous said...

Very interesting article indeed. To my opinion, it stresses the importance of SW architecture as you are mainly comparing high-level calls (cocoa) with low-level ones (carbon).
OK, this sounds like an overall simplification of the issue, but it still strikes me to see that despite the thousands of documentation pages on developer.apple.com, there is no real overview of the MacOS X SW architecture.
I wish we could have a nice and layered block diagram, going further than Application Services on top of Core Services (as presented in the Getting Started section of Cocoa).
Wil's QT example is very typical in that respect: 80 lines of code due to mixing low-level calls for high-level purpose (setGain:)...

Regards, Arnaud

October 06, 2006 3:57 AM

 
Anonymous said...

The problem you' re presenting doesn't seem to be with "Carbon" vs Cocoa at all. It's with C APIs vs ObjC APIs. I'll posit that you won't ever meet a commercial, successful C API that you will like, at least judging from this post. Because to obtain the features you seem to like, the API designers had to move on to an OO language.

The Quicktime example is just an example of how Byzantine the Quicktime API is. Everyone agrees. And lots of "Carbon" engineers find it just as difficult to read. We all agree that Quicktime isn't as easy to use as it could be -- even Apple. If you look at some of the newer stuff they are doing exactly what your asking. Like the audio extraction APIs. Setup some parameters for the extraction, ask for audio, do what you like with it. It takes care of the details. Although it doesn't use dedicated property getter/setters and it doesn't use dictionaries everywhere, it's still a great API for getting sound from formats that Quicktime understands.

And speaking of that, there is a reason that Quicktime generally doesn't have dedicated property getters/setters. Because you can have some new object (file type, hardware, decoder, etc) that knows how to do something that Quicktime itself has no clue about! The programmer can know about the new property without Quicktime having to have documented it anywhere, rather than waiting for an API to expose that property. For some applications (such as the types of file that Quicktime can access) the full list can never be fully documented, because it is ever changing and specific to each user's machine. If I wanted to present to a user the file formats that they could save to, I could either hardcode a list (which is bad) or enumerate the list of available ones (which is good) but the latter is always going to be more complicated, no matter how simple a veneer is put on it.

And lets talk about what is Carbon and what is Cocoa for a moment. You know what Apple generally considers Cocoa? AppKit, Foundation and CoreData. That's it. Every other ObjC API in the system is just that -- and ObjC API. That doesn't mean they might not use meteaphors from Cocoa, but as others have pointed out most modern code on the system uses those metaphors too (to the extent possible at least).

So what's Carbon then? This one's harder. The lines aren't nearly as clear because a lot of Carbon has been extended to be the basis of extensive system functionality. The major reasoning being that Carbon is a C API, thus there has never been any issue calling it from Cocoa (C++ had issues early on, but we're past those now). One is tempted to define Carbon as the entire C API on the system, and you would be completely wrong. Calling IOKit a Carbon API will get you smacked by some people, as just one example. And there are a lot of C APIs on the system that Apple didn't write. That pesky open source thing. There is also a lot of code written specifically for Mac OS X. By your own definition of Carbon they couldn't possibly be part of Carbon because they never existed on Mac OS 9. Your most amusing example is CoreAudio -- CoreAudio is complex by definition, it's an API for exposing hardware in a uniform way. But unless you care about the hardware itself you shouldn't be using it. Instead use the AudioUnit or AudioToolbox frameworks, they do more work for you.

But we still haven't defined Carbon. I bet if you ask this question on Carbon-Dev you'll get a different answer from every developer. Carbon is an overused Marketing Term (just like Cocoa is) at this point. The only thing that is even remotely exclusive is HIToolbox vs AppKit for creating UIs. That's it. And you can even intermingle them in the same application. And who knows, someday you may be able to mix them at an even finer level. And the line gets even fuzzier.

So what do you define as Carbon then? If you define it as a set of APIs, then I'd define it, at maximum, as HIToolbox, CarbonCore, and maybe CoreFoundation. That's it. If you define it as the clients of those frameworks as well, then almost everything is Carbon. Yea, no one is gonna by that one. But then, many Cocoa folk seem to believe that everything that is a client of the ObjC Runtime is Cocoa. Which makes every application a Cocoa application. Isn't this so very confusing?

So in the end, what does it matter? File some bugs requesting simpler alternatives if you don't like some APIs. But framing it in terms of a Carbon vs Cocoa your implying that there is a systemic issue here when there isn't. Some APIs are good, some are bad, some are new, some are old (some are DIRT old), and some use old metaphors, some new ones. Your going to get a culture shock whenever you move from an API that uses a metaphor that your used to to one that doesn't. It doesn't make the API bad, it just makes it different.

October 06, 2006 4:28 AM

 
Anonymous said...

Well, it seems that Apple recently updated its view on MacOS X architecture as it now provides a good block diagram with the different elements: see Mac OS X System Architecture

From this page, you'll notice that Quicktime is one level bellow both Carbon and Cocoa... Maybe the idea that everything in C must be Carbon while objective C is Cocoa needs to be refined!

Regards, Arnaud

October 06, 2006 5:11 AM

 
JS said...

There's a response to this post on Daring Fireball, but it's not very good. No offense to Gruber, but he sort of completely misses the point of this post so that he can point out that Carbon and Cocoa both date back to the 80s, even though you didn't actually call one "old" and one "new".

October 06, 2006 7:25 AM

 
Jonathan Wight said...

So Wil,

These 80 lines of IIDC code? Should Apple be spending engineering resources providing a nice OO replacement for Carbon/QT code that already works and is used by very few applications? Or should be using their resources to add things like Spotlight, CoreData, CoreAnimation, TimeMachinet, etc to Mac OS X?

I know where I'd rather Apple use their Engineers.

October 06, 2006 8:55 AM

 
Anonymous said...

Amen, Wil.

Let me add that during my time at Apple, I saw one group after another realize that if they made their technology easier to use, then more people would use it (and they'd be in a better position to compete for staffing, funding, etc.)  The whole company is moving that way, but some parts will of course do it faster than others.  

One big part of the problem with the persistence of Carbon however, is that the squeaky wheel gets the grease, and the most numerous and loudest complaints that came into DTS and WWDR in general were from the luddites.  That's why HIView exists, for example.  Yes, it was necessary to let OS 9 code run outside of the Classic environment, but Carbon should have been in maintentance mode from then on.  As heroic an effort as it was, there was no need for HIView, period.

Now, DTS's chain of command does not consist of ex-NeXT developers, and they frankly do not realize the difference in productivity between the two environments, so they went a bit overboard in coddling the foot-draggers. 

The way I think of this is that the organization acted as an enabler rather than as a therapist:  the patients suffer more if you allow them to drag out their addiction than if we'd just told them flat out: NO, you can't get all the new tech into your out-of-date application.  Sorry, but you'll really be better off if you chop your 300K lines of Mac Toolbox code down to the 30K lines or so it would take to re-implement using the modern approach. 

Hell, you should have seen how tough it was to get some people to even give up using Quickdraw, for god's sake.

-jcr

October 06, 2006 9:43 AM

 
Anonymous said...

For TIm Buchheim:

There is an NSString subclass for FSRefs, sort of. Check out NSPathStore2. It's private, though.

October 06, 2006 10:19 AM

 
Anonymous said...