Building Swift 4 frameworks and including them in your apps (Xcode 9)

Let’s talk about Swift 4 frameworks, one method for packaging, reusing, and sharing code. We’ll build our own framework and then include it in our own app. We could’ve talked about static libraries, but frameworks offer more advanced features — and will let us expand on code maintainability options in future discussions. (If you want to have that static versus dynamic library discussion, read this excellent article, but we won’t be debating the topic herein.) From Apple (my emphasis added):


A framework is a hierarchical directory that encapsulates shared resources, such as a dynamic shared library, nib files, image files, localized strings, header files, and reference documentation in a single package. Multiple applications can use all of these resources simultaneously. The system loads them into memory as needed and shares the one copy of the resource among all applications whenever possible.

A framework is also a bundle and its contents can be accessed using Core Foundation Bundle Services or the Cocoa NSBundle class. However, unlike most bundles, a framework bundle does not appear in the Finder as an opaque file. A framework bundle is a standard directory that the user can navigate. This makes it easier for developers to browse the framework contents and view any included documentation and header files.

Frameworks serve the same purpose as static and dynamic shared libraries, that is, they provide a library of routines that can be called by an application to perform a specific task. For example, the Application Kit and Foundation frameworks provide the programmatic interfaces for the Cocoa classes and methods. Frameworks offer the following advantages over static-linked libraries and other types of dynamic shared libraries:

  • Frameworks group related, but separate, resources together. This grouping makes it easier to install, uninstall, and locate those resources.
  • Frameworks can include a wider variety of resource types than libraries. For example, a framework can include any relevant header files and documentation.
  • Multiple versions of a framework can be included in the same bundle. This makes it possible to be backward compatible with older programs.
  • Only one copy of a framework’s read-only resources reside physically in-memory at any given time, regardless of how many processes are using those resources. This sharing of resources reduces the memory footprint of the system and helps improve performance. …

In addition to using the system frameworks, you can create your own frameworks and use them privately for your own applications or make them publicly available to other developers. Private frameworks are appropriate for code modules you want to use in your own applications but do not want other developers to use. Public frameworks are intended for use by other developers and usually include headers and documentation defining the framework’s public interface.

You already use frameworks

As iOS developers, we all heavily rely on Apple’s built-in frameworks. Can you imagine having to encode a UIButton or something much more complex like a UITableViewController every time you created an app? When you create a new iOS project using Xcode’s Single View App template, you’re automatically given access to frameworks like UIKit and Foundation. Without those frameworks, you wouldn’t have UIButton or UITableViewController — you wouldn’t even have basic data types like Int, Double, or Decimal. You wouldn’t be able to build an app.

I know I’m verging on the pedantic, but how many of us actually take the time to divide and conquer our own code? Some of you have used other people’s work, incorporating third-party code using dependency managers like Carthage or CocoaPods. What about building your own libraries, aggregating and compartmentalizing code with common and well-defined functionality, so you can conveniently reuse your own code?

We’ve previously talked about improving developers’ quality of life, lowering work-related stress, and increasing software quality. Do you remember how? We did so by practicing several techniques for managing code complexity, increasing code reusability, and increasing maintainability by using protocol-oriented programming, class inheritance, polymorphism, and generics. Building our own frameworks and including them in our apps is another technique we’ll add to our arsenal.

How to build frameworks

Let’s get started and build a Swift 4 framework.

Creating the base framework project
To create a Swift framework, use Xcode 9 to start a new project based on the Cocoa Touch Framework template, give the product a name (“SwiftFramework”), make sure the project’s language is Swift, select a location for the project, and save the new project. Watch here:


Video: Creating Swift 4 Framework Project in Xcode 9

Customizing framework project paths
I don’t like Xcode to spread my project’s assets all over my hard drive. I want everything to be stored inside the project directory. It’s easier for me to find things like my final build product (app or framework) or the “DerivedData” directory. (Some of you know that deleting the “DerivedData” directory and letting Xcode recreate it can solve a lot of problems.) To customize project paths, go to Xcode’s File menu and then select Project Settings... as shown in the next video:


Video: Customizing Xcode 9 Project Directories

Here’s my project structure after customizing directories:

Note that I built a debug version of the framework targeting an iPhone simulator. Please do the same.

Writing code — making your framework useful
At this point, all we have is a project that will build a framework that does nothing. Let’s add some code. Open the “SwiftFramework” project in Xcode, right-click on the “SwiftFramework” virtual group in the “Project Navigator,” select “New File…” from the context menu, choose the “Swift File” template, click “Next,” name the new file “FrameworkCode.swift,” make sure “SwiftFramework” is checked under “Targets,” specify a location, and click the “Create” button. Here’s a pictorial summary:

Huh? Access control?
I’m going to add a class that consumers of my framework can use — or can they? Look at this class carefully:

What would happen if I built the “SwiftFramework” project, included the framework bundle in an app, and then tried to create an instance of class “FrameworkClass?” Well… nothing! I wouldn’t even see “FrameworkClass.” Why?!?

From Apple (my emphasis added):

Access control restricts access to parts of your code from code in other source files and modules. This feature enables you to hide the implementation details of your code, and to specify a preferred interface through which that code can be accessed and used.

You can assign specific access levels to individual types (classes, structures, and enumerations), as well as to properties, methods, initializers, and subscripts belonging to those types. …

In addition to offering various levels of access control, Swift reduces the need to specify explicit access control levels by providing default access levels for typical scenarios. Indeed, if you are writing a single-target app, you may not need to specify explicit access control levels at all. …

Notice that I didn’t decorate my class nor any of its methods with explicit access control modifiers. So I got the default, which is not what I needed (my emphasis added):

All entities in your code … have a default access level of internal if you don’t specify an explicit access level yourself. As a result, in many cases you don’t need to specify an explicit access level in your code. …

You know what access level I need right? Think about it… Oh, here it is:

public access enable entities to be used within any source file from their defining module, and also in a source file from another module that imports the defining module. You typically use open or public access when specifying the public interface to a framework. …

Here’s my class “FrameworkClass” with appropriate access control:

Build the framework
Let’s build an instance of my “SwiftFramework.” Remember to build a debug version of the framework targeting an iPhone simulator. I generally always start development with the Active Scheme set for a Build Configuration of Debug, targeting a simulated device. Leave a comment if you don’t understand why I start development with such a configuration.


So the newly-built framework will reside in this directory:

How to consume (use) frameworks

Creating a framework client app
Let’s create an app that makes use of my custom Swift framework, “SwiftFramework.” Use Xcode 9 to start a new project based on the Single View App template, give the product a name (“FrameworkConsumerApp”), make sure the project’s language is Swift, select a location for the project, and save the new project.

Including the framework in the client app
You can count on frameworks like UIKit and Foundation being installed on iPhones/iPads. During development, you reference them from your app. When your app runs on a physical device, it calls into the relevant system frameworks already installed on devices. Your custom frameworks obviously don’t exist on Apple devices. (OK, yeah, yeah, yeah… you will find some .dylib files in an app bundle… all beyond this article’s scope.) You cannot assume that your custom framework exists on Apple devices.

The only straightforward method I’ve found for including custom frameworks in your own app is to:

  • Copy the custom framework bundle into your app project’s directory structure; and,
  • Add your custom framework to Xcode > [Your target] > General > Embedded Binaries.

I know this works — on simulators and devices. If I inspect my app bundle, I can see that my custom framework is included there in its entirety. There’s an alternative method: dragging the custom framework’s Xcode project file from Finder into the consumer app’s Project Navigator, thus creating a reference to the framework project. I find this practice to be too messy. I keep my Xcode projects separate and I’ve worked on some very complex OS X and iOS projects, and developed software for 30 years.

Let’s do it.

Create a subdirectory for the framework
Create a subdirectory inside the “FrameworkConsumerApp” app’s project structure like so:

The path to the folder we just created is:

Build the framework
Build a debug version of the framework targeting an iPhone simulator.

Copy the framework into the consumer app project structure
Copy the “SwiftFramework.framework” bundle into the “Frameworks” subdirectory of the consumer app’s project folder structure as shown here:


Copy Swift 4 Framework Bundle into Target App

RUNNING AN APP WITH AN EMBEDDED CUSTOM FRAMEWORK

We still need to perform a few more framework consumer configuration steps, but I want to show you the Swift framework code that will run in the consumer app, “FrameworkConsumerApp.” This code will be written in “ViewController.swift:”

Embed the framework into the consumer app
Add your custom framework to Xcode > [FrameworkConsumerApp] (target) > General > Embedded Binaries. Configure the the Active Scheme for a Build Configuration of Debug and target a simulated device. Finally, import the custom framework, instantiate a framework class, call a class method, click “Run,” and watch the console output, all as shown here:


Video: Embed Swift 4 Framework into App and Use Framework Code

Making changes to the custom framework

You’re probably thinking, “Every time I make any changes to the custom framework, I’m going to have to copy the newly-built “SwiftFramework.framework” bundle into the consumer app… And what if I want to deploy the app with the framework to a device?”

No need to worry. That’s where Xcode comes to the rescue. All’s I need to do is go to my “SwiftFramework” project’s target, go to Build Phases, and select the “New Copy Files Phase” option. Remember the path to the folder in which “FrameworkConsumerApp” stores “SwiftFramework.framework?” Here’s a reminder:

Let’s make a change to the “SwiftFramework” code:

Now let’s click that “+” button and select “New Copy Files Phase” to copy the “SwiftFramework.framework” bundle to the “FrameworkConsumerApp” folder we called “Frameworks.” So every time we rebuild the framework product, the “FrameworkConsumerApp” gets updated. Here are the steps:


Video: Automatically Updating Swift Framework Consumer

Running an app with an embedded custom framework on a device

Does all this work on a physical device? Yes! Here are the steps I used, in Xcode, to get “FrameworkConsumerApp,” with “SwiftFramework.framework” embedded, running on an iPhone 6 with iOS 10.3.2 installed:

For the “SwiftFramework” project

  • Set Target > Build Settings > iOS Deployment Target = “iOS 10.3”
  • Set Active Scheme to target a Generic iOS Device
  • Rebuilt

Switching my target device did not interfere with my “Copy Files Phase.” Xcode automatically detected the new product output directory and copied the new “SwiftFramework.framework” to the “Frameworks” folder in the “FrameworkConsumerApp” project directory structure.

For the “FrameworkConsumerApp” project

  • Set Target > Build Settings > iOS Deployment Target = “iOS 10.3”
  • Attached my iPhone 6 to my Mac
  • Set Active Scheme to target [My iPhone 6's Name]
  • Clicked Run

The app appeared on my iPhone 6 and “LEFT:8 + RIGHT:8 = SUM:16” appeared in the Xcode console. Just to be fastidious, I subsequently wired up a simple user interface in the “FrameworkConsumerApp” project, installed the app/product on my iPhone 6, and can now start up the app, independent of any connection to a Mac, and run the app as many times as I want:

Epilogue

Swift frameworks are going to be especially useful on OS X because multiple applications will be able to load and use them simultaneously. Note that you’d need to distribute these frameworks with your OS X apps if you were distributing them publicly. The benefits from Swift frameworks for iOS will result from reusing your own prepackaged code and/or distributing functionality to other developers, outside the App Store, without giving away your coding secrets. Because iOS apps are so tightly sandboxed, you’ll bundle your custom frameworks with each of your apps.

For future reference: targets
Frameworks can be very powerful and portable. I could write a framework for some super-proprietary algorithm that could be used in both OS X and iOS — as long as I use targets to specify the Build Settings (Architectures, Base SDK, Supported Platforms, Valid Architectures) for each instance of the framework product appropriate to each consumer app. Targets and multi-platform support are beyond the scope of today’s discussion, but we will cover them in future posts. Don’t worry about this topic now.

Teaser: How many of you know what I’m doing with that #if DEBUG statement? Hint: It’s called a “conditional compilation block.”

Above all — enjoy!

Author: Andrew Jaffee

Avid and well-published author, software engineer, designer, and developer, now specializing in iOS mobile app development in Objective-C and Swift, but with a strong background in C#, C++, .NET, JavaScript, HTML, CSS, jQuery, SQL Server, MySQL, Oracle, Agile, Test Driven Development, Git, Continuous Integration, Responsive Web Design, blah, blah, blah ... Did I miss any fad-based catch phrases? My brain avatar was kindly provided by http://icons8.com under a Creative Commons Attribution-NoDerivs 3.0 Unported license.

4 thoughts on “Building Swift 4 frameworks and including them in your apps (Xcode 9)”

    1. How do I build a debug version of the framework targeting an iPhone simulator?

      If you look at my article, I showed you an example of building a debug version of the framework for the iPhone simulator:

      A framework is useless on its own. You want to use it. So you need a consumer app that links the framework:

      Add your custom framework using Xcode > [Your target] > General > Embedded Binaries

      To take full advantage of debugging, your consumer app should be a debug version. So when building the framework and consumer app, make sure you go to Set the active scheme, then Edit Scheme..., and set Build Configuration to Debug.

      Remember to link your consumer app, built for Debug, at the framework code location specific to the debug version for the simulator. Review my steps in the section entitled Copy the framework into the consumer app project structure.

  1. Excellent article.

    I’v been using frameworks forever in objc land (macOS)
    Now that i’m intensively moving to swift 4, i’m noticing some strange issues while debuging them.

    Xcode 9.4, Swift 4.1
    Sometimes after a few breaks and po’s i end up with

    error: :1:11: error: use of undeclared type ‘$__lldb_context’

    It appears as if the lldb gets corrupted, for lack of better word.
    Have you seen this ?

    Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *