Mike Lee on Cocoa

Mike Lee of Delicious Monster has a post on things he'd like to see changed in Cocoa. I agree with some of what he says, but I think some parts of the frameworks he's critical of make more sense in context.

For the most part, I agree with the comments about things being returned by indirection and not relying on NSCell so much. In fact, I think Cocoa is already heading towards using views more liberally, in that NSCollectionView seems to be moving into the space previously occupied by NSMatrix.

Anyway, here's where things start to get a bit fuzzy:
Leopard unified the user interface, but the application programming interface remains as broken as ever.


What now? The air is too thin up north. But seriously. You're nuts. No, I really mean it. The coffee at Zoka must be too weak.

As broken as ever? Can you point me to a desktop API that makes Cocoa look broken by contrast? I'm not saying it's perfect by any stretch — and what is — but "broken" has a much different meaning than "imperfect."
NSFormatter is older than Lucas

Classic.
Apple: there's one engineering team that has an inexplicable hard-on for opaque proxy objects

I disliked opaque private API for a long time, then I realized something. If you introduce public API, you're expected to support it until the end of time. So unless you're ready to have it written in stone, it's better to keep it private. It's not a perfect solution, just a compromise.
Which brings me to Core Data. For the record, I adore Core Data, but it [...] does hijack a lot of NSObject�s methods, declaring them off limits, which succeeds in limiting its usefulness, mucking up the interface, and sending us into the documentation.


It succeeds in limiting what you think would be useful. It's possible that if Core Data was implemented without blocking you off from any of the normal patterns, you'd end up with something that was much easier to use but extremely slow and inflexible. As it stands, Core Data in Leopard is ridiculously fast if used properly.

I've actually written a persistence engine with SQLite before. It was my first real Cocoa project. Nothing on the scale of Core Data, but even in its relatively humble means, it was incredibly difficult to satisfy both ease-of-use and performance. My approach was very much like what you describe — use normal NSObjects, allow all of the normal patterns, and handle everything on the framework end.

If I was to start from scratch and now and do it all over again, this is the first part I would change. From what I've seen, trying to do a scalable persistence engine without "special" objects is just a mess. Apple's engineering team has, in one form or another, been at this particular task for something around fifteen years.
The list of things not to subclass or override is enormous. Dire warnings about intricate optimizations and object life-cycle management send a very clear message: trust us. Abandon all control over your objects and give yourself to Core Data.


Basically, yes. But that's the case for Cocoa in general. In fact, that's really the point of a framework. The idea is to express what you want done and be abstracted away from as many of the details as possible. This gets more complicated when you hit edges cases, but that's the just the nature of programming.
and maybe they really do need to take over the object life cycle, given the concept of faulting versus normal deallocation


Pretty much. The alternative is to have the application take days to load all of its data or run out of address space.
to give up control of your objects for all that Core Data gives you for free, but opacity breeds inflexibility


I completely disagree. If you didn't want an opaque implementation, you'd be using raw SQLite APIs. The reason Core Data is interesting is that you don't have to worry about all the details. Depending on specific implementation details breeds inflexibility. If one of those details changes, your application snaps in half. It's shades of grey, of course.
and when you run into the limits of what Core Data can do, things start to get very, very tricky


That's true for just about anything. If you run into the limits of what video hardware can do, it's not a walk in the park. You have to change strategies.

In fact, this is the whole point. Core Data needs to perform well on a wide range of hardware, so it takes a strategy of abstracting you from the engine. If it didn't, the complicated logic for faulting and caching would just move from the framework to your application.
The trouble is, writing out a bunch of files kind of negates the whole point of using Core Data, because writing and maintaining two different data stores takes time.


It makes it more complicated, I agree. It does not negate it, though. If it did, you'd just been storing all of your data in individual files. The on-disk files are basically a secondary cache, but having everything in a database means access is much, much faster.
Finally, why are collections mutually exclusive of one another?


They don't all behave the same. For example, NSSet doesn't allow duplicates, but the others do. So if you had twenty objects in an "array" NSCollection (of which ten are duplicates) and copied them to another NSCollection, you might wonder why you just lost half of your objects. Only later do you discover that the collection happened to be a NSSet. It might also be frustrating to learn your objects suddenly had lost their order because NSSet and NSDictionary have no inherent order to their contents.

I think merging all of these together would just lead to more confusion. In reality, there are only three main collection types. That's nothing.
Design Element
Mike Lee on Cocoa
Posted Nov 7, 2007 — 19 comments below




 

Oskar — Nov 07, 07 5022

Very valid arguments. Don't trust Cocoa? Go lower, but expect implementation headaches. Apple has been quite clear that they create their API's not as a "wait until everyone will be satisfied" kind of implementation; but more as along the lines of best-effort, best-tradeoffs, "get the basics solid first then build on it". Leopard is a prime example of this. (How much of what was private in Tiger went public in 10.5?)

The argument that Private API's are bad is the same one people used for the iPhone SDK. Private API's are private until they're ready to be committed on and supported (and taken crap for...) for years and years. I personally am quite stunned that Apple considers the iPhone SDK done after a meager 8 months from release.

Mike Lee — Nov 07, 07 5023

You, and a number of other people, have made some great points. It's awesome to have so many of my beliefs and ideas corrected, challenged, or just put into a new, wider perspective. But it's even more awesome (if painful for me ;) it can be done in public, where thousands of would-be Cocoa programmers can watch. You know more than a few people are thinking the same things I am, wondering what the hell good is NSCell and things like that. A few of the comments people have made about the collection classes have demonstrated, to me, and to those watching at home, of some of the awesomeness of Objective C.

That said, I might not have made myself entirely clear on a few points. When I say the interface is broken, I don't mean compared to other frameworks. I want to make clear that my entire post is entirely within the given that Cocoa is wonderful and closer to perfection than any other. That said, let's get brutal. In other words, we know Cocoa is a 9.x, so let's debate the X. If I say a feature rates a 2, I mean it rates a 9.2. That's good, but it's the shitty side of good, if you know what I mean.

Also, the coffee at Zoka is not too weak. It's just kind of burnt, like all the coffee in Seattle. Apparently in order to save coffee, Seattle had to destroy it. Yecch.

I don't think opacity is evil, but I do think it's potent and dangerous magic that we have to be very careful not to abuse. As I say in the post, Core Data probably has to have the level of opacity it has because of the level of abstraction it provides. The proxy objects in NSTreeView on the other hand? Yet to see any explanation that isn't strained carrots and bullshit.

You're right that operating around the edges is always tricky, but that doesn't mean I can't wish the edges were move out a little further. Especially with Leopard I see a lot of demo-design, where a certain class of obvious general use makes so much sense when you use it exactly like the demo, but as soon as you try to apply it to a real problem, you quickly realize its harsh limitations. Again, I have to point to the tree view on this. Bad, wicked, naughty tree view. Bad!

You're also right that shadow files don't literally negate the purpose of Core Data as an object model. Certainly the performance of a database is a big win. But, it does take big chunks of Core Data as a persistence model, Core Data as a time-saver for developers, and so forth. Yes, the purpose of frameworks is take us away from all this, but when frameworks collide, it's ugly for all of us. Garbage collection, meet Core Graphics. Crash. Suck. Bye.

Finally, I don't want to merge all the collections together. I just think that they do have a lot in common, and it would be nice to be able to decide at runtime (or trivially at design time) to change collection types without giving up type checking. A set is different from an array, but way more different than a string. I think the idea of a collection is a thing in and of itself.

And now, if you'll excuse me, I have to go work on some LOLhats.

Scott Stevenson — Nov 07, 07 5024 Scotty the Leopard

@Mike Lee: Especially with Leopard I see a lot of demo-design
Such as what, kind sir? And I mean Leopard, specifically.

Again, I have to point to the tree view on this
You mean tree controller, right?

Garbage collection, meet Core Graphics. Crash. Suck. Bye
I'm not convinced it's quite as dire as the release notes suggest, but yes, I understand the point.

A set is different from an array, but way more different than a string
I see where you're trying to get to, but I'm not sure this is good for Cocoa to adopt in general. The idea of adding and retrieving an object has a totally different meaning in NSDictionary than NSArray, for example. Given that those are the two most common functions in both, I'm not sure they can resonably decend from a common class.

Henry Maddocks — Nov 07, 07 5025

As broken as ever? Can you point me to a desktop API that makes Cocoa look broken by contrast?

Judging your own performance based on other peoples inadequacies is guaranteed to lead to mediocrity

Scott Stevenson — Nov 07, 07 5026 Scotty the Leopard

@Henry Maddocks: Judging your own performance based on other peoples inadequacies is guaranteed to lead to mediocrity

I think you missed my point. First of all, none of this is meant to be super-serious. But using the term "broken" in the way Mike does implies that there's a reference point of some sort for something that is not broken. I was curious if he had one in mind.

Mike Lee — Nov 08, 07 5029

implies that there's a reference point of some sort for something that is not broken

Yes, there is a reference point: the rest of Cocoa. By broken I mean inconsistencies within the interface, which are, by definition, parts of Cocoa that don't match the rest of Cocoa. Methods returning errors as other than NSError, Address Book returning its own collection class, the old DOM classes that tried to make Objective C look like C so it would match the DOM spec -- these are all things that would be perfectly fine by themselves, but which stick out like misaligned floorboards, ready to trip anyone new to Cocoa.

You mean tree controller

Right, my bad: NSTreeController

Such as what, kind sir? And I mean Leopard, specifically.

I think we see a lot of this in Core Animation, but since I've not worked with it for a while, let me give you a more boring example: Calendar Store.

Calendar events work great in demo mode, but in reality have a lot of surprising limitations. For example, attendees are read-only. It's happy convenience that in the demo they don't bother trying to set attendees, but in real life you almost always would. Certainly you can do this from the iCal UI, from Sync Services, and from AppleScript.

Or the Scripting Bridge. They always demo this with iTunes, which is the most thoroughly scriptable (and scripted) program on the system. Even now on the mailing lists people are running into limitations on using the scripting bridge in real life, with real programs. And these edge cases are not that edgy!

I don't consider setting attendees on an event to be an edge case, but clearly Apple does. So, I have to ask myself: what is this the edge of? The demo? According my most reliable source on the matter: signs point to yes.

Blain — Nov 08, 07 5036

Agreed on NSTreeController, and while Core Data has its fair share of land mines, it's still very usable for a good deal. Of course, when it comes to multithreading, I fall back onto NSDicts in NSDicts to avoid headaches.

I haven't played with Calendar Store or scripting bridge much, but I think scripting bridge is less of an issue than why it's not really usable: It's still not easy to make an app Applescriptable.

To make a bog-standard cocoa app, a lot of the linking and setup is drag and drop and control-drag in IB. To set the info.plist, you can use the get info command in xCode. The old adage of making a usable app with almost no coding applies, even moreso with core data.

To make it applescriptable, the first step is to make some files that aren't in the "New File" template list, and then hand-type in xml, making sure it's right and with almost no error checking. When even Hillegass says, "Before you begin, you should recognize that the AppleScript system is rather finicky. Your plists must be carefully created, or nothing works." (Second edition, p390) you know that there's tools missing and something's wrong. And it's not like Applescript is a brand new API.

I've got a feeling that the scripting bridge would be magically fixed were it easier to make apps scriptable.

* I should disclaimer that I don't have the 10.5 API in front of me right now, but the online docs make no hint of anything that doesn't sound like magic incantations.

Mike Lee — Nov 08, 07 5038

I think we see a lot of this in Core Animation

You know what? That sentence was extremely ill-conceived and I regret saying it. I was tossing off an impression from before CA was released, and that's just not fair. Core Animation is awesome.

For that matter, I just want to make triple clear that Core Data is also awesome, as is just about all of Cocoa. I feel like that point is being lost. I am not attacking Cocoa. I love Cocoa and just want to think about how it can be even better.

Scott Stevenson — Nov 08, 07 5039 Scotty the Leopard

@Mike: I feel like that point is being lost. I am not attacking Cocoa. I love Cocoa and just want to think about how it can be even better.

I knew what you meant, but it's good to say it for everyone else who hasn't met you in person and especially may not know your sense of humor or the context of everything you do in terms of Mac programming.

Coming up with better ways to do things is what makes this job interesting. I get that. There's no question in my mind that Core Animation is better because you guys hammered on it so hard during the betas.

Nicko — Nov 08, 07 5041

A set is different from an array, but way more different than a string

I see where you're trying to get to, but I'm not sure this is good for Cocoa to adopt in general.


I think that much of what Mike is after could be achieved by simply making NSArray, NSSet and NSDictionary sub-classes of an abstract 'NSCollection' and moving the API commonalities such as enumeration and tests of membership into the superclass. That way code which can be generalised to not care what sort of collection is needed could just accept an NSCollection and still have some degree of type checking, as opposed to having to accept an NSObject and not notice until runtime that an NSWigglyWidget is inappropriate.

Blain — Nov 09, 07 5048

This is Sparta, er, Cocoa. We need not fiddly subclasses of subclasses, when this calls more for protocols.

Make an <NSCollection> protocol, like <NSCopying>, and apply as necessary. That way, someone making a new group container class can join without having to subclass.

David Halliday — Nov 09, 07 5049

I most certainly agree with Blain: Subclassing should be used when you need/want to share implementation; protocols should be used when what you need/want to share is only interface.

John C. Randolph — Nov 09, 07 5050

I concur with Mike regarding Cell and View. The reason we have cells is because NSView is rather more complicated than it should be.

My biggest pet peeve in Cocoa is the number of controls that will hog the event loop when tracking the mouse. This really wasn't necessary on a 68030, and it's definitely not necessary today.

Sometime around 10.6 or 10.7, I hope we can get an "AppKit 2" that draws on the lessons learned in fifteen years of Appkit development.

I'm looking forward to the UIKit. I'm expecting it to show what people with a great deal of AppKit experience come up with when they get a chance to start over.

-jcr

mark — Nov 11, 07 5052

I just wish more people were familiar with the way active record (in ruby on rails) handles the same kinds of issues than core data. It's so simple and yet effective in providing the solution for the most common cases. It's easy to use, it has a clutter free clean API, it uses only subclasses, no parallel-universe entities. It has easy to grasp migrations. The list goes on.

Instead of weeks and months I have spent on reading the documentation for core data, I was ready to jump right in within minutes. And using rubycocoa, active record is available at my fingertips.

Scott Stevenson — Nov 11, 07 5053 Scotty the Leopard

@mark: I just wish more people were familiar with the way active record (in ruby on rails) handles the same kinds of issues than core data

ActiveRecord is well-designed, but it's not setting out to address the same things as Core Data. For example, a Mac app may need to account for a case where a user launches an application and wants to scroll 8,000 items in a table, each containing multiple megabytes of data.

It's easy to use, it has a clutter free clean API, it uses only subclasses, no parallel-universe entities

Part of the reason entities exist is that a desktop application doesn't always want to use SQL. In the case of using XML or a binary (or even in-memory) store, you can't depend on table and column definitions. In Leopard, you can implement your own store types using NSAtomicStore.

It's also particularly important for multi-document applications or a single-document application which loads data from multiple stores and projects them as a single store.

libre — Jan 21, 08 5381

I'm a newcomer to the whole Mac environment, especially huge frameworks like Cocoa. I found this website a few weeks ago and have read as much as I can to catch up on some historical aspects of Mac programming, especially from a supposed guru.

That said, it's really interesting to see that your site isn't a bunch of dead paper, but rather a live context (much like the internet should be), especially in very interactive entries like this one! Being the first blog I've ever read, this site is going to remain on my bookmarks bar for quite a while!

Also, I want to mention that it is extremely useful for many people who "lurk" (so to speak) to get valuable information on proper methods and ways to write programs, when conflicts such as this arise. Not only is it more peaceful thank sitting in #macdev with mikeash, but it's obvious there has been a huge effort put into being clear and concise about certain topics, otherwise this whole blog wouldn't exist (and the follow-ups would not have continued for so long). This is highly appreciated, at least to me, but probably also to a lot of people who don't respond in here.

Scott Stevenson — Jan 21, 08 5382 Scotty the Leopard

@libre: This is highly appreciated, at least to me, but probably also to a lot of people who don't respond in here

Thanks. I really appreciate that. I'm glad you find it helpful.

mark — May 23, 08 5892

Part of the reason entities exist is that a desktop application doesn't always want to use SQL. In the case of using XML or a binary (or even in-memory) store, you can't depend on table and column definitions. In Leopard, you can implement your own store types using NSAtomicStore.


Could you elaborate on this, I don't quite understand how this has anything to do with the store type, be it sql or plain text. We have had initWithCoder: etc. a long time to serialize objects. Why NSManagedObject couldn't just use something like that and leave storage details for the persistent store?

That being said, I don't see any benefit in keeping NSManagedObject separate from it's corresponding NSEntityDescription - other than having not to force creating custom subclasses of NSManagedObject. Which, it seems to me, is not a benefit at all, on the contrary.

Maybe core data could be modified to be more activerecord like by always generating custom subclasses of NSManagedObject which would automatically be associated with corresponding NSEntityDescription?

Any thoughts appreciated.

Scott Stevenson — May 24, 08 5909 Scotty the Leopard

@mark: We have had initWithCoder: etc. a long time to serialize objects. Why NSManagedObject couldn't just use something like that and leave storage details for the persistent store?

I'm not quite sure what you mean. NSManagedObject doesn't do any sort of persistence itself, and it does leave all of that to the persistent store. If NSManagedObject serialized all of its data prior to handing it off to SQLite, I think it would negate the benefits of SQL or multiple store types in general. Maybe I misunderstood your point?

I don't see any benefit in keeping NSManagedObject separate from it's corresponding NSEntityDescription

NSManagedObject is a runtime class that holds data, and NSEntityDescription is a class that tells you about the schema/model. They have different responsibilities, so I'm not sure it would help to combine them.

Maybe core data could be modified to be more activerecord like by always generating custom subclasses of NSManagedObject which would automatically be associated with corresponding NSEntityDescription?

That's effectively how it works, though without actually making a public subclass. In Leopard, NSManagedObject instances fill in methods for all of their modeled properties. So if you have a property called "fullName", Core Data will automatically add the "fullName" getter and "setFullName" setter at runtime.

I hope that helps. If I didn't cover something, let me know.




 

Comments Temporarily Disabled

I had to temporarily disable comments due to spam. I'll re-enable them soon.





Copyright © Scott Stevenson 2004-2015