Any time I write about design patterns, I expect a lot of disagreement, especially when we’re talking about abstractions that aren’t what we might call canonical, and even when they are. There are a lot of ways to bring pre-written code and functionality into our code. If we go back in time, there were two primary abstractions in how we go about that. Those are frameworks and libraries. Within those there are other abstractions that include concepts like Software Development Kits (SDKs), Applications (compiled and not compiled, or even JIT compiled), Extensions, and Kits. We’ll go through each in more detail, but if you talk to most developers, you’ll hear different responses based on their lived experiences leveraging pre-written code or distributing pre-written code.
Let’s look at the highly debatable overview of what each of these are, through a swift lens (because constraining the language helps when discussing design patterns):
Library - Pre-written code that can be imported into a project. Comes in two forms, dynamic libraries, which are less of a thing than they used to be (as can be seen with a checkbox in Xcode to disable dynamic libraries) and static libraries, which in most languages are imported into an application when called. This is less of a thing with system libraries (e.g. in python) but still a thing.
Framework - Opinionated pre-written code that isn’t just called but then imposes structure around how you write your code. The develoeprs design the final product in a way, and the framework can contain multiple libraries. You can define the headers and methods available (public) and which are only accessible to the framework private).
Extension - Components, methods, types, initializers, subscripts, protocols - executable files bundled in an app file, a keyword that allows developers to extend a type. These are discrete factored pieces of code that can be called by others.
Apps and App Extensions are completed products available for distribution (in whatever form that means given the state of an app). They are done, and you wouldn’t nest apps within apps or extensions in extensions, really (although extensions are nested in apps). System extensions are effectively app extensions for the kernel (although not Kernel Extensions, which are deprecated).
SDK: A collection of artifacts that can be used to design a specific type of application. Usually includes libraries and/or frameworks but can also include other artifacts, like documentation.
Kit - Apple’s manifestation of an SDK.
Package - A folder of folders and files with a specific format and a manifest. Can contain libraries of symbols. These are conceptually very clean, and effectively canonized.
Plug-in - Similar to an app extensions but those we build into apps for others to then consume.
Inversion of Control
Ultimately the difference between frameworks and libraries lies in a programming concept we call Inversion of Control, or IoC. IoC is a design pattern where the control of objects is transferred to a generic framework. In traditional procedural programming, custom code that expresses the purpose of code calls into reusable libraries help a programmer take care of generic tasks, like a CIDR calcuator, but in more complex environments where inversion of control is flipped, a framework that calls into the custom, or task-specific, code cedes object control to the developer or the original code being imported. Think of the Apache Software Foundation, where Stefano Mazzocchi popularized the term in 1999 - they could provide frameworks about how html would be rendered without ceding control of all the objects in an increasingly complicated web server.
There are many different ways to implement IoC. One common way is to use a dependency injection framework. A dependency injection framework provides a way to inject dependencies into classes at runtime. This allows classes to be decoupled from their dependencies, making them more flexible and easier to test. Another way to implement IoC is to use a service locator. A service locator is a class that provides access to a set of services. This allows classes to request services from the service locator without knowing where the services are located. This makes the classes more loosely coupled and easier to test.
Some of the benefits of using IoC:
Increased modularity: IoC can help to make your software more modular by decoupling classes from their dependencies. This makes your software easier to understand, maintain, and evolve.
Increased extensibility: IoC can help to make your software more extensible by making it easier to add new features and functionality. This is because IoC makes it easy to inject new dependencies into classes.
Improved testability: IoC can help to improve the testability of your software by making it easier to mock and stub dependencies. This allows you to test your code in isolation, which can help to improve the quality of your software.
Some of the drawbacks of using IoC:
Increased complexity: IoC can add some complexity to your code. This is because you need to learn how to use IoC frameworks and how to inject dependencies into your classes.
Increased coupling: IoC can increase coupling between classes. This is because classes that use IoC frameworks are more likely to depend on specific implementations of interfaces.
Decreased performance: IoC can decrease performance in some cases. This is because IoC frameworks may add some overhead to your code.
Frameworks vs Libraries
In software development, a framework and a library are both pre-written code that can be used to speed up development and add functionality to an application. However, there are key differences between the two.
A library is a collection of reusable code that provides a specific set of functionality. Libraries are typically used to perform common tasks, such as string manipulation, data manipulation, or graphics rendering. Libraries are typically written in a generic way so that they can be used by a variety of different applications.
Frameworks are usually more complete. Frameworks provide a set of classes, interfaces, and other tools that can be used to create an entire application. Frameworks typically provide a specific way of developing an application, and they often come with a set of design patterns and best practices. Frameworks are typically written in a more specific way than libraries, so they may not be as flexible.
The choice of whether to use (or create) a framework or a library depends on the specific needs of the application. If the application needs to perform a specific task, such as string manipulation, then a library may be the best option. If the application needs to be developed quickly or needs to follow a specific set of design patterns, then a framework may be the best option.
Within Libraries there are static and dynamic libraries. Static libraries are linked into the executable file at compile time, while dynamic libraries are linked at runtime. Static libraries are a collection of object files that have been linked together. When you compile your code, you can specify that you want to link to a static library by using the -l flag. For example, to link to the math library, you would use the following command:
swiftc -lmath my_file.swift
Once the code is compiled, the static library will be embedded in the executable file. This means that the code in the static library will be loaded into memory when the executable file is executed.
Dynamic libraries are similar to static libraries, but they are not linked into the executable file at compile time. Instead, they are linked at runtime. When you run your code, the dynamic linker will load the dynamic libraries that are needed by your code. Dynamic libraries have a number of advantages over static libraries. They allow you to update the code in the library without having to recompile your code. Second, they can be shared by multiple applications. This can save space on disk and in memory, but can be dangerous in that any change to the linked code could dynamically change what happens at run-time. Thus the option to disable dynamic libraries in swift. In general, when I talk about libraries I now pretty much just discuss static libraries, which require a recompile to update symbols in a compiled app, but are safer.
Frameworks and Extensions in Swift
Another way to add functionality in code is an extension. A framework is a collection of code that is compiled into a single binary file. Frameworks are typically used to provide a complete solution for a specific task, such as networking or graphics rendering. Frameworks can be imported into your code using the import keyword. An extension is a way to add new functionality to an existing type. Extensions are written in Swift and are compiled into the same binary file as your code. Extensions can be added to any type, including built-in types such as String and Array.
In general if Apple adds a complete solution for a specific task, they likely use a framework . If you need to add a small amount of functionality to an existing type, then it might be an extension. Extensions are then compiled into the same binary as the code. Frameworks provide more of a complete solution for a specific task, though. For example, the UIKit framework provides a complete solution for building user interfaces for iOS apps. Frameworks can be imported into your code using the import keyword.
When you import a framework, the framework's code is added to your code. This means that you can use the framework's classes, functions, and other features in your code. For example, if you import the UIKit framework, you can use the UIView class to create user interfaces.
The extensions symbol is a way to add new functionality to an existing type. Extensions can be added to any type, including built-in types such as String and Array. For example, to create an extension you use the extension keyword followed by the name of the type to extend. For example, the following code creates an extension for the String type:
extension String {
func reverse() -> String {
return self.characters.reversed().joined()
}
}
This extension adds a new method called reverse() to the String type. The reverse() method reverses the order of the characters in a string. Extensions are a great way to add new functionality to existing types but cannot add new properties to types. This is because properties are stored in a type's metadata, and extensions cannot modify a type's metadata.
Abstractions can be different in different languages though, especially when the determinism of type is concerned. For example, in Python, a protocol is a set of abstract methods that a class must implement in order to be considered a member of a particular type. Protocols are similar to interfaces in other programming languages, but they are more flexible. Protocols can be used to define the behavior of a class without specifying the implementation details.
Extensions and delving into protocols and various forms of methods, while important concepts, is more about object oriented programming concepts. The term “extension” can also be used to define an application extension as well, thus making that definition more pertinent to this article. In Swift, an app extension is a type of framework that can be used to add new functionality to an existing app. App extensions can be used to add new features, improve performance, or extend the capabilities of an app. There are two main types of app extensions:
Action extensions: Action extensions are used to add new actions to the Share sheet or the Quick Look panel. For example, you could create an action extension that allows users to share a photo from your app or preview a document.
Widget extensions: Widget extensions are used to create small, interactive widgets that can be added to the Home screen. For example, you could create a widget that displays the weather forecast, the latest news headlines, or the temperature and timer from a Traeger grill.
App extensions are usually created using the "App Extension" template in a new Xcode project. Once created, add the code for your extension. The code for your extension will be added to a new file called Extension.swift. App extensions can be used to improve the performance of an app by offloading some of the app's functionality to the extension. However, app extensions can be difficult to develop, test, and support. This is one of the reasons it’s mostly Apple that writes and distributes extensions. Having said that, if developers have multiple apps and want to share code between them, they can use extensions rather than dynamic libraries. Some extensions, like system extensions live in predefined hierarchies, like the Contents/Library/SystemExtensions folder of an app for those.
SDKs, *Kits, and Packages
A software development kit (SDK) is a set of tools, libraries, documentation, and code samples that developers use to create software applications for a particular platform. SDKs typically include everything developers need to get started, including compilers, debuggers, and code samples. SDKs are used by developers to create software applications for a variety of platforms, including operating systems, programming languages, and hardware platforms. For example, there are SDKs available for developing applications for Windows, macOS, Linux, iOS, Android, and many other platforms.
SDKs can help developers create more reliable and efficient applications by providing access to pre-tested and optimized code. SDKs help developers create more reliable and efficient applications by providing access to pre-tested and optimized code (theoretically at least). They probably worked great when created (giving the benefit of the doubt) but like all tech debt can become outdated or incompatible with newer versions of the platform or programming languages used.
Another abstraction we run into is a kit, which seems to be Apple’s Objective-C and Swift way of distributing SDKs. A kit in Swift is a collection of code, resources, and documentation that can be used to develop a specific type of application. There are different types of kits available for Swift, including kits for developing games, web applications, machine learning frameworks, and mobile applications. Kits typically include code that implements the core functionality of the application, as well as resources such as images, sounds, and fonts. Kits may also include documentation that explains how to use the kit and customize it for your own needs.
Kits typically provide all of the code and resources you need to get started, so you don't have to start from scratch, but may not be compatible with all versions of Swift, enforce certain entitlements requirements in apps, and restrict the build target (OS versions) available. Essentially “Kits” are a type of SDK.
A Swift package is a collection of Swift code, resources, and metadata that can be used to develop a specific type of application. Packages are typically created by third-party developers and often come with different licensing requirements. Packages can be used to speed up game, web, and network programming, or whatever else the developer creates or exposes. Packages can also include artifacts and resources such as images, sounds, fonts, and even documentation - so resemble Kits. But think of kits as Apple-provided and packages as imported assets from third parties. However, Apple has canonically defined what a package is in Swift Package Manager:
Packages are self-contained: A Swift package contains everything you need to use it, including the code, resources, and metadata (although let’s be honest, ymmv here). This makes it easy to install and use packages in your projects.
Packages are versioned: Swift packages are versioned, which means that you can specify which version of a package you want to use in your project. This helps to ensure that you are using the latest and most stable version of a package.
Packages are modular: Swift packages are modular, which means that you can use parts of a package without having to use the entire package.
Packages are discoverable: Swift packages are discoverable on the Swift Package Index, which makes it easy to find packages that you can use in your projects.
Plug-ins
One final way many developers abstract tools is a plug-in. Plug-ins can be used to add new features, improve performance, or extend the capabilities of an application. Some common uses for plug-ins include:
Add new features to an application: Plug-ins can be used to add new features to an application that would not be possible to add without modifying the application's code. For example, a plug-in could be used to add a new menu item to an application, or to add a new toolbar.
Improve performance: Plug-ins can be used to improve the performance of an application by offloading some of the application's functionality to the plug-in. For example, a plug-in could be used to handle image processing, or to handle network requests.
Extend the capabilities of an application: Plug-ins can be used to extend the capabilities of an application by adding new functionality that is not available in the base application. For example, a plug-in could be used to add support for a new file format, or to add support for a new protocol.
When thinking about creating a paradigm that allows for plug-ins, there are a few things to keep in mind. Not all applications support plugins, so check the documentation for the application to make sure it is supported (we won’t go into using private APIs to plug-into apps here). Also decide what kind of plug-in to allow for. What functionality and what limits on functionality will be provided and how. This might mean a command-line interface, allowing developers to deep link into a URI of the app, etc. Consider Logic and how plug-ins for that can be run in another tool like Hindenberg. Good plug-in toolkits are extensible and re-usable. Unit tests, while necessary, are incredibly difficult if/when you expose too much. Yet, creating a plugin can be a great way to add new functionality to an existing application.
Plug-ins can mean different things in diferent languages. An Objective-C plug-in is a software component that can be added to an Objective-C application to extend its functionality. Plug-ins can be used to add new features, improve performance, or extend the capabilities of an Objective-C-based application. They are typically compiled into a dynamic library. The dynamic library is then added to the application's Frameworks directory. When the application is launched, it will load the dynamic library and register the plug-in's classes and methods.
Plug-ins can be used to add new features to an application in a number of ways. For example, a plug-in could be used to add a new menu item to an application, or to add a new toolbar. Plug-ins can also be used to improve the performance of an application by offloading some of the application's functionality to the plugin. For example, a plug-in could be used to handle image processing, or to handle network requests. Plug-ins can also be used to extend the capabilities of an application by adding new functionality that is not available in the base application. For example, a plug-in could be used to add support for a new file format, or to add support for a new protocol.
Humans, Abstractions, and Design Patterns
Ultimately, many of the above concepts can be interchangeable in a way. Where one develoepor will use a Swift Package, another might create an extension so they don’t load too much code into memory when two different applications are used. Today, five developers gave me conflicting desicriptions and use cases for when they use the above design patterns to implement existing functionality or make options available. There were few best practices but many worst practices. For example, when to use a Swift Package vs create an extension. I would personally create an extension if I had re-usable bits of code that I wanted compiled in such a way that they only ran in memory once. This might be (and likely would be) derived from a package, but some things you just wanna’ run once. Some extensions are necessary, like Autofill, which is arguably an app that is being extended into webkit. If a developer has only worked in a scenario that calls for one of these they might not ever consider the other.
Computer science and formal declarations of some of these design patterns exist, but use comes down to familiarity unless, as with Swift Package Manager, a vendor has canonically defined use. Even here, where some tend to use packages one way, others might release a simple library as a Swift package. Similar in concept to creating a GitHub repository vs a gist, sometimes. Take the term design pattern, an abstraction of ideas. A design pattern is a reusable solution to a commonly occurring problem in software design. Design patterns are typically described in terms of their intent, structure, and participants.
Some though might think of a pattern that defines the skeleton of an algorithm, deferring some steps to subclasses as the meaning of that term when in fact that’s a Template Method, one of many formalized design patterns. Others include:
Singleton: A pattern that ensures that only one instance of a class exists in an application.
Factory: A pattern that creates objects without exposing the instantiation logic to the client.
Adapter: A pattern that converts the interface of one class into an interface that another class expects.
Strategy: A pattern that allows you to vary the algorithm used to perform a task without affecting the clients that use the algorithm.
Ultimately, what matters is that rather than arbitrarily use the terms, we define what they formally mean to us - and when various pluggable structures are used when possible. Much as spoken languages are a design pattern to communicate human thought with fidelity, the lexical references we make to describe programming techniques and design patterns communicate how we write code. The key word there is with fidelity. Little has changed about the fidelity of communication (no matter how consise we try to be) since Claude Shannon’s great work A Mathematical Theory of Communication, available at https://people.math.harvard.edu/~ctm/home/text/others/shannon/entropy/entropy.pdf. Generations later though, we have gotten to a point where we can describe vast bodies of knowledge about programming very consisely, like “let’s write a framework to do that.” Provided of course, we are all on the same page about what a framework is.
Comments