Apple's shared frameworks are very useful for sharing executable code and its associated resources among multiple applications, but were historically not designed to be created by authors of consumer applications. I discourage developers from creating frameworks as a method of sharing code, because they encourage code bloat, increase launch times, complicate the developer's fix/compile/link/debug cycle, and require extra effort in setting up correct and useful developer and deployment builds.
The Origins of Shared Frameworks on NeXTstep
It will help us to understand the limitations of shared frameworks if we examine the prevailing conditions for the time when they were created on NeXTstep. The user-base of NeXT machines was largely businesses and schools; institutions which generally had a knowledgeable system administrator in charge of clusters of machines and which typically ran suites of custom applications; consumer software for the NeXT was not as common. The net was not as broadly adopted, so application patches were largely unheard of; bug fixes and new versions were relegated to major releases.
Application installation in those days was done by the Installer, which could install resources in different directories throughout the disk, and left a record ("receipt") of what it had done so the installations could be reversed. Many sites also had a server machine vending a shared disk of applications and resources, so application installation only had to be done once.
Disk space was much more expensive, as was RAM. Any way to minimize the use of both was a huge win.
Shared frameworks were created by NeXT (and many others at the time) to allow multiple applications that need to use common code (eg, window handling or graphics drawing code) to link against a single binary, and thus even when, say, ten applications are running that use graphics, only a single version of NeXT's graphics drawing code has be to physically in memory.
NeXT's take on shared frameworks was more clever than most, in that frameworks took the form of directories, containing the code and its associated resources: localization files, images, sounds, and interface descriptions (NIBs). This provided a very convenient encapsulation of shared system resources (although there weren't provisions for multiple applications sharing a single copy of these).
Initially NeXT kept shared framework creation to themselves, but in a later release they allowed third-party developers to create them as well. For developers of custom applications for businesses, this was a godsend, as typically custom applications come in suites, and have a ton of shared code. For instance, at AT&T Wireless (an early NeXT adopter), they might have an application where they can look up the last call a subscriber made, and another application where they can change a subscriber's rate plan. The basic code to look up and display subscribers could now be shared between the two applications, which saves on disk space and physical memory when the program is run, and provided an easy way to re-use code and resources in multiple applications for developers.
The distinction between the two functions is important to make, because almost all third-party developers now take advantage of only the second benefit when they create their own shared frameworks, and the crux of my argument is that shared frameworks are a relatively inefficient way to accomplish this one result.
In order to benefit from the savings in disk space and physical memory in an installation, an end-user need two critical things: she needs to have installed two applications from a single vendor, and she needs to have installed the frameworks associated with those applications in a shared folder (typically, /Library/Frameworks/), so that all the applications know where to look for them.
This was not a problem when applications were installed with the Installer, because the Installer could, as stated, put files in multiple directories throughout the system. However, the trend in consumer applications (started by Omni, and rewarded with one of our first Apple Design Awards) became to have simple drag-and-drop installation of the application to wherever the user chose, which precluded installing frameworks in a well-known shared folder.
But not only did application creators who had a suite of applications that all used the same frameworks not know where the user might install those applications, they also didn't know which applications might be installed, so all "shared" frameworks had to be bundled inside even the smallest of applications.
This is the case today with Omni's applications; if you look inside the application wrapper (control-click on an application, and select "Show package contents") you'll find a Contents/Frameworks folder with copies of all relevant Omni frameworks in every Omni application you own.
Thus, there is no space savings from third-party frameworks, either in RAM or disk space.
Difficulties for Developers
But, the situation is worse than this. Frameworks are made of position-independent code, so they can be relocated in memory when linked against any arbitrary client application. This code is larger and slower than non-position-independent code, AND requires a quick fix-up at run-time when the client application launches. This fix-up time can become quite lengthy if there are lots of frameworks with lots of symbols used by any given application; in the old days Omni applications would spend many seconds loading their frameworks on launch.
[I don't mean to keep picking on Omni, but since I had a big hand in creating their open source frameworks, which are one of the largest and most popular collections of open source Cocoa code out there, I know details of the problems we encountered, and that I solved by giving up on frameworks.]
This can be ameliorated by creating frameworks at a fixed location, but you have to (a) read a bunch to understand how to do this, (b) make sure no two of your frameworks overlap, and you don't overlap anyone else's third-party frameworks that you might use, and (c) set up your build system to do it. This was actually a huge hassle at Omni; I was on the team that tried to streamline the process and it's still not something that is easily grasped by newcomers to coding.
But the situation is worse than that, because even after an application loads a framework into the right place in memory, the application has to adjust all its own symbols to point to the correct places in the framework. Luckily, Cocoa handles all of this for you, but unluckily, it takes time.
Again, this can be ameliorated by prebinding against the frameworks to which you've already assigned a fixed address (from above). Again, this is kind of a complicated process, and you spend a bunch of time learning nm and other tools trying to figure out if your application is "correctly prebound" with the frameworks.
But, it gets worse. When developing, your application probably does NOT want to prebind against the frameworks, because prebinding takes extra time in the link cycle. So, you have more setup in Xcode to sometimes prebind and sometimes not.
These are just the problems and slowdowns with loading frameworks at run time. There are also myriad problems in having your source code split up into several projects in Xcode (eg, a client application project and a framework project).
For instance, you probably have some build settings you want to enforce on your project; you like to debug with optimization level -O1, but you like to deploy with -Os like Apple recommends this week. Well, you'll have to copy those settings to every Xcode project for every framework you have. And make sure they stay updated.
Does this sound like duplicated code? The thing I hate most in the world? Yes, yes it does.
Also, you need to make sure when you're doing a development build that you link against the development versions of the framework you built in your development directory (because it's a pain to copy in the enormous with-symbols versions of the frameworks into your application every time you do a debug build), but you also need to make sure your deployment rules correctly do copy the frameworks into the client application's wrapper, or the app won't run. So, you get to write a bunch more Xcode configuration code, and, hey, you get to copy it into every application you write. And, oh boy, if you update it one place, be sure to remember to update it everywhere. (I believe the very latest Xcode has shared settings to help this situation, but you still have to write the darn configuration code.)
And, you need to make darn sure you don't confuse which versions of the frameworks you are linked against at any one time; eg, if you make a special debug build of the frameworks with some odd options, then you debug one of your other applications which use the frameworks, you'll suddenly have some very odd behaviors. (This happens more than one might imagine.)
And, when you do a deployment build, you want to make sure you strip the headers from your frameworks, as well as any subversion files. Yes, you get to write more Xcode configuration code. And copy it around to all your applications. Oh, boy.
Difficulties for End-Users
Now, why are you creating shared frameworks again? Because you want to re-use code in multiple projects. This is a noble goal. However, frameworks make it too easy to just throw every dang class that might be useful into them, so the developer forgets to ever prune old files that are no longer used at all. Because, if you have multiple framework projects and multiple application projects which use them, and you wonder if any particular framework class is used, you have to search through all of these projects for references. At Omni, this was forty-something projects to open; just opening them all took minutes, let alone searching them.
So you end up with a lot of extra code in your application that none of you applications still use. How do I feel about extra code?
But also, even worse, you end up linking in a lot of extra code that some applications use, but not this application. Because, unlike a standard library, there's no such thing as partially including a shared framework; you either grab all of its functionality or none.
How bad of a problem is this? Well, consider OmniDictionary. It connects to the network, downloads a definition from a dictionary server, displays the HTML. Simple. It uses some of the classes from the Omni frameworks to do this, but not nearly all of them.
Still, it has to include ALL of the code from any framework it touches, and ALL of the associated resources (image files, NIBs, etc), and ALL of the frameworks which that framework uses, as well.
With OmniDictionary, the frameworks it bundles with take up 1.6 megabytes. The actual application takes up 448 kilobytes. The application takes up four times as much space because of the frameworks.
The Alternatives for Code Re-use
Download and install subversion, and store all your source code in it, religiously.
Create a folder in your source code repository for each of your applications, and a folder for shared code and resources. In each project, in Xcode, drag only the files you really need into your project, and tell Xcode not to copy them into the project directory, and instead refer to them relative to the source directory.
Removing dead code from your shared folder is easy; at the worst you can just delete the files and do a build of your applications, and if the build succeeds you're not using them. Or you can open only your applications' projects (because what were your 'frameworks' now don't have projects associated with them) and search for the code, because your source files will never have any header imports of the form "#import
You don't spend any time configuring the frameworks' build environment because there isn't one. You don't spend any time setting up your frameworks' header file (eg, OmniAppKit.h) because there isn't one. You don't worry about whether your framework is pre-bound or position-independed because it doesn't exist.
What are the drawbacks? Well, when you include a source file in your application from your shared directory, you'll have to pay attention to which NIBs and images the files requires from the shared directory, and drag those into your application yourself. I think this is actually kind of good, because it also makes it possible to sunset images and NIBs. And, if you forget to drag in an image or NIB initially, it's an error you'll find immediately, the fix is easy, and it only needs to be done once. Thus, this isn't a problem that needs a complex solution.
Of course, if you have multiple frameworks, you might create a separate shared source directory for each one just to conceptually organize them. Or, keep them in subfolders. Whatever you like.
Note that subversion makes it very easy to modify your shared code for one project and not worry about affecting other projects; you can branch your top-level directory for any given project in subversion, and it effectively creates a copy-on-write version of that project, so you don't burn your entire source disk with a thousand versions of all your frameworks. When you're happy with a project, you can merge it back to the head (with the shared code changes) and check at _that time_ that your other applications like your newer and hopefully improved shared classes.
This brings up another issue, which is that with only one project per application it's much easier to associate a single tag with any given build, and then if a user or beta-tester encounters bugs you can go back to that version of the your source code with a single command. The problem with frameworks is it is too easy to ship different versions of the frameworks with any given build, so you end up reporting all your framework versions to the user to mention in bug reports, which is just another thing for the developer to have to check, and another avenue for error.
To get around this, developers with frameworks will write scripts to update their frameworks in a clean place and do a brand new build, but then those scripts have to be maintained by someone, and developers have to be educated about a particular site's practices. For instance, at Omni, only a few people in know how to do a beta build of a particular piece of software (not including many of the developers, but including the support personnel), so the beta process can be entirely gated by the availability of the support team.
In my alternative system, creating a beta build is as easy as opening the Xcode project, updating (inside Xcode), changing the target to "deployment", cleaning, and building. These steps are well-known to any developer, as they are all the same (except one) as during development.
Who Should Create Frameworks
Apple should, for one; because their frameworks have static base addresses, and because we all link against them at compile time, we don't suffer the same overhead by default that we do for third-party frameworks. Further, because every application is going to use, say, AppKit, and AppKit is installed in a known location, Apple's frameworks are correctly shared and result in saved RAM and saved disk space.
Companies writing suites of custom software, where they can use an Installer package and write the frameworks in a standard location, are also encouraged to use frameworks for the same reasons that Apple is.
Creating shared frameworks is a lot of hassle for third-party developers of consumer software, introduces instability into the development process, and encourages slower and larger applications. Code sharing is better accomplished through creating new directories for shared code in subversion and judiciously including only the files and resources needed by any application in its Xcode project.
Irony and sarcasm do not translate well onto the web. The title of this article and the previous Unit testing is teh suck, Urr. (sic) are references to the teachings of the Mooninites Ignignokt (who speaks in a sarcastic singsong and flips off the entire earth as hard as he can; "The pain is excruciating, but it's worth it") and Err (who is the Beavis to Ignignokt's Butthead).
The Mooninites are know-it-alls who come from a land where everything is better than here on earth, and they are far superior to us in every way. They pity us and our pathetic three dimensions, because on the moon they have five...
Yes, five THOUSAND dimensions. Don't question it.