In the most recent episode of Edge Cases, Wolf and Andrew discuss dependency management, specifically as it pertains to Objective-C applications that import libraries using the Cocoapods tool.
In one app I worked on a few years ago, two different libraries each tried to include (as part of the libraries themselves, not as dependencies) the Reachability classes from Apple’s sample code. The result was duplicate symbol definitions, because my executable was trying to link both (identical) definitions of the classes. Removing one of the source files from the build fixed it, but how could we avoid getting into that situation in the first place?
One way explored in the podcast is to namespace the classes. So Mister Framework could rename their Reachability
to MRFReachability
, Framework-O-Tron could rename theirs to FOTReachability
. Now we have exactly the same code included twice, under two different names. They don’t conflict, but they are identical so our binary is bigger than it needs to be.
It’d be great if they both encoded their dependency on a common class but didn’t try to include it themselves so we could just fetch that class and use it in both places. Cocoapods’s dependency resolution allows for that, and will work well when both frameworks want exactly the same Reachability
class. However, we hit a problem again when they want different libraries, with the same names in.
Imagine that the two frameworks were written using different versions of libosethegame
. The developers changed the interface when they went from v1.0 to v1.1, and Framework-O-Tron is still based on the older version of the interface. So just selecting the newer version won’t work. Of course, neither does just selecting the older version. Can we have both versions of libosethegame
, used by the two different frameworks, without ending up back with the symbol collision error?
At least in principle, yes we can. The dynamic loader, dyld
(also described in the podcast) supports two-level namespace for dynamic libraries. Rather than linking against the osethegame library with -losethegame
, you could deploy both libosethegame.1.0.0.dylib
and libosethegame.1.1.0.dylib
. One framework links with -losethegame.1.0
, the other links with -losethegame.1.1
. Both are deployed, and the fact that they were linked with different names means that the two-level namespace resolves the correct symbol from the correct version of the library, and all is well.
Of course, if you’ve got dynamic libraries, and the library vendor is willing to do a little work, they can just ship one version that supports all previous behaviour, looking at which version of the library the client was linked against to decide what behaviour to provide. Despite Mac OS X providing a versioned framework bundle layout, Apple has never (to my knowledge) shipped different versions of the system frameworks. Instead, the developers use the Mach-O load headers for an executable to find the linked version of their library, and supply behaviour equivalent to that version.
The above two paragraphs do rather depend on being able to use the dynamic linker. We can’t, on iOS, at the moment.