Swift can now ship binaries without requiring the corresponding Swift libraries due to its recently achieved ABI stability. Although a significant milestone, this doesn’t guarantee straightforward binary distribution of Swift frameworks. described, written by Peter Steinberger from PSPDFKit (and instabug), provides insights into why.
Achieving this partial success took nearly five years, mirroring C++’s enduring challenge with binary distribution. This contrasts with Objective-C’s lack of such issues, which is not coincidental.
Software ICs
Objective-C originated to realize the “Software-IC” concept, aiming to introduce the benefits of hardware ICs to software.
ICs revolutionized computer development by enabling entire subsystems to be combined onto one component. Standardized pin interfaces abstract away vast internal complexity. Software-ICs aimed for a similar approach: a binary, interface, and specification.
Two BYTE articles ([
and [
) elaborate on these concepts. Despite their now-quaint examples, their relevance endures, especially with decades of hindsight.
While the authors connect Software-ICs with objects and object-oriented programming, their vision differs from common practices today. They advocate for object/message programming, similar to Alan Kay’s idea that ‘The big idea is “messaging”’.
Messaging as the interconnect facilitates well-defined, dynamic interfaces, enabling binary Software-IC delivery and compatibility — a challenge for static languages like C++ and Swift.
ObjC excels at embedding a “COM”. Swift currently lacks this (and missed an opportunity by not utilizing the ObjC runtime for it)
— Helge Heß (@helje5) January 6, 2019
Essentially, Objective-C is middleware with language features.
Message Oriented Middleware
Static languages often require a separate, dynamic component mechanism. Examples include Windows’ COM, IBM’s SOM, Qt’s signals/slots and its meta-object system, and Be’s BMessages.
C++’s binary compatibility issues were a driving force behind COM:
Unlike C++, COM offers a stable application binary interface (ABI) across compiler releases.
COM’s success, powering much of the Windows and Office ecosystems, is undeniable. Even macOS has a COM implementation within CoreFoundation: [CFPlugin](https://developer.apple.com/documentation/corefoundation/cfplugin?language=objc).
CFPlugIn is a standard application extension architecture. It lets you design applications as host frameworks using executable code modules (plug-ins) for specific functionalities. Third-party developers can extend your application without source code access. You can also bundle plug-ins for various platforms, letting CFPlugIn load the appropriate one at runtime. Use CFPlugIn to add or utilize plug-in capabilities in your applications.
This COM implementation remains in use, for example, in Spotlight importers. However, there are, shall we say, issues:
Creating new Spotlight importers is challenging due to their CFPlugIn foundation. CFPlugIn is… diplomatically speaking… super ugly )-:. Using Xcode 9’s old template is one option. However, I wouldn’t recommend it because the old template… again, diplomatically… well, let’s just say it exposes CFPlugIn’s true nature! (-:
Having worked with both, I can attest that COM’s strength lies in its interoperable, stable binary interface, not its elegance.
However, discussing COM feels redundant considering [NSBundle](https://developer.apple.com/documentation/foundation/nsbundle):
Bundles are Apple’s way of representing apps, frameworks, plug-ins, and more.
NSBundle seemingly replicates CFPlugin’s functionality (and more) but is essentially a directory wrapper potentially containing a dynamic shared library. Objective-C inherently provides the interfacing, introspection, and binary compatibility. NeXT’s Windows product, d’OLE, even automatically transformed Objective-C libraries into COM-compatible OLE servers (.NET has similar features). This isn’t a coincidence: Objective-C’s Software-IC foundation emphasizes this interoperability.
Objective-C is middleware with language features.
Frameworks and Microservices
I see Software-ICs at the framework level, encompassing a binary, interface-defining headers, and ideally, documentation (potentially within the bundle’s Resources directory). As NSBundle instances, frameworks can be dynamically loaded or linked into applications.
I utilize this in Objective-Smalltalk, especially with the [stsh](http://objective.st/Scripting/). By loading frameworks, the shell transforms into an application-specific scripting language, exemplified by pdfsh — a shell for handling PDFs using EGOS.
``` #!/usr/local/bin/stsh #-pdfsh:file framework:EGOS_Cocoa load. pdf := MPWPDFDocument alloc initWithData: file value. shell runInteractiveLoop ```
This framework is also employed in PdfCompress, PostView, and BookLightning. It enabled me to build a functional drag-and-drop PDF application in 5 minutes (slowed only by my faulty memory of a PDF dictionary entry).
Framework-oriented programming is incredibly powerful, yet Apple discouraged it for a long time (even making it impossible on iOS until dynamic libraries were permitted). The current paradigm prioritizes self-contained apps, even if organized into frameworks.
However, apps aren’t Software-ICs; they’re not ideal packaging technology (AppleScript aside). This leads iOS and macOS developers toward sprawling codebases, sometimes called the Big Ball of Mud pattern.
[
](Integrated Circuits0/)
While deviations from the flexible, shared-framework model envisioned in Apple’s early “Mac OS X System Architecture” book are understandable (due to concerns like DLL hell), the need for modularity persists.
Swift’s compilation model, with its emphasis on whole-module optimization (even in “debug mode,” which isn’t truly optimized), exacerbates this issue. Its lack of a robust binary modularity story for years further complicates matters. This approach stems from Swift’s modularity being deeply intertwined with its source code, generics, and specialization (ironically requiring complex mechanisms for separate compilation that haven’t fully succeeded).
However, Swift’s slow compilation times are unintentionally reviving framework-oriented programming as teams seek to improve feedback cycles. By dividing projects into independently compiled and run frameworks, they achieve manageable build times. Ironically, Swift’s limitations encourage good practices.
The resurgence of Software-IC-like concepts in backend development (i.e., Microservices) is intriguing. Microservices, as deployable binary units with loose coupling and dynamic typing, resemble Software-ICs. Fred George, a proponent of the approach, even describes them as akin to Smalltalk objects.
Admittedly, there are issues. Reliable method calls are replaced by less reliable network calls, for instance. However, many touted Microservice benefits also apply to (true) Software-ICs. Objective-C’s conflation of Software-ICs with objects, while overlapping conceptually, muddied the waters. While it can be used to build and connect Software-ICs, it doesn’t do so automatically. Over time, it has become just another object-oriented language—adequate but not exceptional in that domain.
Interoperability
A compelling aspect of Microservices, like Software-ICs (and COM, SOM, etc.), is language agnosticism. A well-defined, simple interface is key.
Microservices excel at this, as do Unix filters. Almost any language or application on Windows interacts with COM. Despite selling only 50,000 computers, NeXT fostered bridges to numerous languages. Objective- languages, including Objective-Fortran, emerged. In contrast, despite Apple’s size and the vast number of iOS developers (over 2.8 million), Swift’s only known language integration is with Python, requiring significant effort, compiler changes, and Chris Lattner’s involvement.
This difference is deliberate. Swift prioritizes being a programming language, not middleware. Its modularity is a language feature, aiming to expose its rich programming model.
Objective-Swift
Middlewares like SOM take an outside-in approach:
SOM enables defining object classes in one language and using them in another. It allows updating class libraries without recompiling client code.
Interfaces are defined separately from implementations, perhaps inspiring Objective-C’s @interface. While writing declarations twice can be tedious, having a concrete, separate interface declaration is valuable (similar to how TDD encourages upfront interface consideration).
Swift’s classes prioritize implementation. Their interfaces are secondary, making component creation convenient but interconnection more complex.
This brings us back to Swift’s lack of stable binary compatibility. One consequence is relying on Objective-C for frameworks, particularly before ABI stability. Alternatively, Swift frameworks can expose an Objective-C wrapper as an interface, leveraging the seamless Objective-C runtime interoperability.
Did I mention Objective-C is middleware with language features?
Perhaps this “workaround” is the solution. Could Objective-C (or a refined “Objective-”) serve as our intended message-oriented middleware? Maybe with a tweak to shed C legacy and embrace pipes/filters, REST/Microservices, and other patterns?
Just a thought.
https://www.dropbox.com/s/f1wndhi38q8xlmw/Software-ICs.pdf?dl=0
https://www.dropbox.com/s/hub5us6g1m3p71o/Software-ICs-and-Interfaces.pdf?dl=0


