Image for post
Image for post

Modular iOS

Reusing code and resources with Swift static libraries and resource bundles

Anurag Ajwani
Jul 30, 2019 · 14 min read

Have you ever copied and pasted the same code into more than one of your app projects? What happens when you find a bug in that code? You will have to fix that same code in every single project where you have used it. Is there a way to manage duplicated code amongst various of your iOS app projects?

Yes there is! Apple provides us a mechanism to share code through Swift static libraries. But what about resources such xibs, nibs and images? Apple provides us with another mechanism of sharing resources called resource bundles.

In this post I will show you how you can build your own Swift static library and resource bundle.

In an earlier post I showed how to reuse code and resources using Swift dynamic framework. Static libraries and resource bundles is an alternative mechanism to dynamic frameworks. We will further explore the differences between them.

In this post we will learn:

  • what static libraries are
  • how static libraries compare to dynamic frameworks
  • why use static libraries
  • how to reuse code and resources using static libraries and resource bundles
  • consuming static libraries and resource bundles in an app

Feel free to skip any sections if you are already familiar with the concepts.

What is a static library?

A static library is a collection of compiled source code files. Let’s say we have FileA.swift, FileB.swift and FileC.swift. With a static library we can compile these files and wrap them inside a single file containing all of these. The file has has an extension of .a; short for archive. Sort of like getting some pages and making a book out of it.

Image for post
Image for post
Static library compilation representation
Image for post
Image for post
Discovering the contents of MyStaticLib

Static library vs Dynamic frameworks

iOS allows us two methods of grouping code together for distribution:

  1. dynamic frameworks
  2. static libraries

The difference between the two that we will cover in this post is:

  • dynamic frameworks can hold resources (i.e images), static libraries can’t
  • dynamic frameworks are loosely linked to the app, static libraries are hard linked to the app

To explain linking further let’s look at examples for each type of code grouping mechanisms.

Dynamic framework linking

When our app consumes code dynamically the code is not loaded with the app when the app is launched. The system loads our app which in turns tells the system that it requires to consume some code to run. The system then loads the necessary chunks of code that our app relies on. All of this happens at runtime.

An example of this is UIKit. iOS apps make use of UIKit. However UIKit is not included with each app. Rather the app has reference to it and the functionality it requires to function but not the actual code. UIKit is included in the operating system. The system loads UIKit if it was not already loaded.

Image for post
Image for post
System loading system framework

What about dynamic frameworks that are not included within the operating system? What about frameworks that we build and distribute with our apps?

ConsumerApp can’t work without MyDynamicFramework or it will crash. Thus the system must load it. It must be thus included with the app package.

Xcode creates a directory within the app package called Frameworks. This directory is where to host dynamic frameworks required by the app.

Image for post
Image for post
Loading framework packaged with app
Image for post
Image for post
Frameworks folder in ConsumerApp app package

What if the framework is not included within our app’s package Framework directory or within the system? App crash at runtime💥.

Static library linking

When our app consumes code statically the code that it consumes gets copied into the app executable binary.

Image for post
Image for post
Linking static library to your app

When the system loads the app, the static library functionality is loaded with it as single executable.

Image for post
Image for post
iOS loading app containing static library

Why use static libraries?

Some reasons to use static libraries similar as for dynamic frameworks:

  • code reusability
  • hiding code that is not related to the app (using private and internal access)

For both static libraries and dynamic frameworks if we distribute only the compiled form then we can also:

  • avoid sharing our secret code and only distribute the functionality
  • reduce app compilation time

But why use static libraries over dynamic frameworks? Apps with static libraries load faster than those with dynamic frameworks. Let’s dive a little deeper to understand why.

Dynamic frameworks have the advantage of being loaded into memory lazily at runtime and the possibility of being shared with multiple processes. That works great if the dynamic framework is used by multiple apps in iOS.

iOS system dynamic frameworks are used by multiple apps and these are likely to be loaded before your app launches thus saving app launch time. However in case of embedded dynamic frameworks in apps (these are the ones included inside your app and not by operating system) the launch times can be much slower. How much slower?

To test it out I wrapped three Swift files in two different targets:

  • Dynamic framework
  • Static library

I created an empty app. I configured the app to print loading times to console. Then I linked the app to a dynamic framework. I ran the app on a simulator from cold(was not cached in memory). The results:

Total pre-main time: 1.0 seconds (100.0%)
dylib loading time: 193.54 milliseconds (18.3%)
rebase/binding time: 760.93 milliseconds (71.9%)
ObjC setup time: 60.19 milliseconds (5.6%)
initializer time: 42.81 milliseconds (4.0%)
slowest intializers :
libSystem.B.dylib : 18.22 milliseconds (1.7%)

I repeated the process with one difference. I linked the app to a static framework. The static framework contains the same code as the dynamic framework as in the previous run. The results:

Total pre-main time: 290.34 milliseconds (100.0%)
dylib loading time: 44.15 milliseconds (15.2%)
rebase/binding time: 54.60 milliseconds (18.8%).
ObjC setup time: 60.76 milliseconds (20.9%)
initializer time: 130.68 milliseconds (45.0%)
slowest intializers :
libSystem.B.dylib : 3.08 milliseconds (1.0%)
libMainThreadChecker.dylib : 118.62 milliseconds (40.8%)

The app launch time was nearly 4 times faster when linked statically!

How to build Swift static libraries and resource bundles

In this section we will create a static library and a resource bundle. The resource bundle will contain a login screen in a xib file. The static library will contain the controller code for login screen.

In the next steps we will:

  1. Create a new project in Xcode using the Cocoa Touch Static Library template
  2. Add resource bundle to project
  3. Create Login screen using Interface Builder
  4. Create a controller for the Login screen

1. Create project

Let’s begin by creating a new Xcode project using the Cocoa Touch Static Library template. Open Xcode and from menu select File > New > Project… Next select the Cocoa Touch Static Library template.

Image for post
Image for post
Create a new project using Cocoa Touch Static Library template

Click Next. Once the Xcode “Choose option for new project:” pop-up appears, set the Product Name LoginStaticLibrary. Make sure the language is set to Swift. Lastly click Next and then Create.

Image for post
Image for post
Choose options for new project prompt

2. Add resource bundle to project

So far we have created a project with a static library. The static library can hold Swift code. However in this tutorial we will create a screen through the Interface Builder. We will create an XML Interface Builder(XIB) file that will hold the Login screen specification with all its views and layout rules. We will later load the Login screen based on the XIB file and control the interaction with it through a view controller. The view controller will be inside the static library.

Image for post
Image for post
App architecture with static library and resource bundle

Let’s add the resource bundle to our project. From menu select File > New > Target… Next select from the Bundle template from the macOS tab.

Image for post
Image for post
Select the Bundle template from macOS

Name the bundle LoginLibraryResourceBundle and then click Finish.

Image for post
Image for post
Create new Bundle target and name is LoginLibraryResourceBundle

There is one more thing to do before we can make a resource bundle work with iOS apps. The bundle template was created for macOS app target. There is no bundle template for iOS. However it is very simple to make these work for iOS.

Once created on the project navigator select the Xcode project file (the one with the blue icon).

Image for post
Image for post
Select LoginStaticLibrary project file

Next from the targets list within the project select LoginLibraryResourceBundle. Then select Build Settings tab. Search for Base SDK and change the value from macOS to iOS.

Image for post
Image for post
Select iOS as the Base SDK of the LoginLibraryResourceBundle

3. Create Login screen using the interface builder

As mentioned in the previous section, a static library can’t hold XML Interface Builder files (XIB) so we created a resource bundle to deploy the XIB file which would hold the Login screen. Next let’s add the Login screen to the resource bundle.

Select LoginLibraryResourceBundle from the project navigator. Next from menu select File > New > File… Select the View template. Click Next.

Image for post
Image for post
Add new file using the View template from iOS

Name the file as “LoginScreen”. Make sure that LoginStaticLibrary is unchecked and LoginLibraryResourceBundle is checked. Lastly click on Create.

Image for post
Image for post
Create XIB file

Once created drop a label and a text field for username and another label and text field for password.

Image for post
Image for post
Insert a label and a text field for each input; username and password

Change the text on the labels, select one of the labels and then open the attributes inspector (from menu select View > Inspectors > Show Attributes Inspector).

Image for post
Image for post
Name the labels appropriately

Next add a button to allow the user to login. Name it Login. The Login screen should look like the following:

Image for post
Image for post
Login screen design

Don’t worry about making a perfect layout. Good layout and layout constraints are beyond the scope of the post.

4. Create a controller for the Login screen

In this section we will create a controller which will load the Login screen specification and bind the views of the controller.

In the project navigator select the LoginStaticLibrary folder.

Image for post
Image for post
Select LoginStaticLibrary folder

Next create a new Swift class file which will subclass UIViewController. From menu select File > New > File… Search and select for Cocoa Touch Class from the iOS templates. Click Next.

Image for post
Image for post
Create new file in StaticLibrary using the Cocoa Touch Class template

Name the Class LoginViewController. Set the Subclass to UIViewController. Uncheck Also create XIB file. Set the language to Swift. Click on Next.

Image for post
Image for post
Options for the Login screen controller file

Before creating the file make sure the group is set to LoginStaticLibrary and the target list has LoginStaticLibrary checked and LoginLibraryResourceBundle unchecked. Finally click on Create.

Image for post
Image for post
Options for creating the LoginViewController file

Next we will hook up the login button from the XIB file to the controller. For that open LoginScreen.xib. Then on the document outline select File's Owner.

Image for post
Image for post
Select File’s Owner from the document outline

Once File's Owner is selected open the attributes inspector (View > Inspectors > Show Attributes Inspector). The attributes inspector will open on the right hand pane of Xcode. For Class fill in LoginViewController. For Module make sure StaticLibrary is the value. Lastly, make sure Inherit Module From Target is unchecked.

Image for post
Image for post
Set LoginViewController as the File Owner, the module as StaticLibrary and uncheck the checkbox.

Once the File’s Owner is selected we have to set the UIView within our LoginScreen.xib as the root UIView of the LoginViewController.

Select File's Owner from the document outline. Hold control and drag and drop File's Owner to the View.

Image for post
Image for post
Hold control and drag and drop File’s Owner to the container View

Once you let it go you will see a pop-up appear. Select view from the pop-up.

Image for post
Image for post

Next let’s hook up the tap to the Login button taps from the user to a function in our LoginViewController.

Open the assistant editor (View > Assistant Editor > Show Assistant Editor). LoginViewController should have opened up automatically on a new pane on the right hand side from the XIB editor.

Next hold the control button and drag and drop the login button inside the LoginViewController class. Once a pop-up for the linking option appears, set the Connection type as Action. Name the action as onLoginTapped. Finally click on Connect.

Image for post
Image for post

Now let’s add a property that will hold a closure that will be called when the user taps the login button. Add the following line to the LoginViewController class:

var onLogin: (() -> ())?

Now call the onLogin closure when the user taps the login button from the onLoginTapped function:

@IBAction func onLoginTapped(_ sender: Any) {
self.onLogin?()
}

One final thing to do so an app can consume our static library and resource bundle is to create functionality that the consumer can access. Our LoginViewController is internal by default. That means that only code within the static library can access our login screen. Next we will add a public convenience function so our integrators can login their users. Add the following function in the LoginViewController.swift file outside the LoginViewController class:

public func getLoginScreen(onLogin: @escaping ()->()) -> UIViewController {
let bundlePath = Bundle.main.path(forResource: "LoginLibraryResourceBundle", ofType: "bundle")!
let bundle = Bundle(path: bundlePath)!
let loginViewController = LoginViewController(nibName: "LoginScreen", bundle: bundle)
loginViewController.onLogin = onLogin
return loginViewController
}

Above we have added a function that will load and return the LoginViewController using the LoginScreen xib from the resource bundle. The function takes a closure as parameter which is passed on to the LoginViewController’s onLogin property.

Note: the public keyword before the function declaration exposes this function to the consumer app. The function returns UIViewController instead of LoginViewController as LoginViewController is inaccessible (internal) to the integrator whereas UIViewController is accessible (public interface in UIKit).

That’s all for creating our own Static Library and Resource Bundle! 🎉

Consuming the static library and resource bundle

In this section we will consume the static library and resource bundle created in the previous section through an app.

We will:

  1. Create a new app target using the Single View App template
  2. Add sign in button to the app
  3. Link the LoginStaticLibrary and LoginLibraryResourceBundle to the app
  4. Present login screen from the static library on sign in button tap

1. Create a new app target using the Single View App template

From menu select File > New > Target… Next select the iOS template Single View App. Click Next.

Image for post
Image for post
Create a new project using the Single View App template

Name the app ConsumerApp. Set Swift as the language of the app. Make sure all checkboxes are unchecked. Click Finish.

Image for post
Image for post
ConsumerApp target create options

2. Add sign in button to the app

Next open Main.storyboard. On the blank canvas drop in a button. Select the button. Using the attributes inspector(View > Inspectors > Show Attributes Inspector) change the button text to Sign in.

Image for post
Image for post
Change the button text to Sign in

Next let’s hook up the tap on the sign in button to a function within the already created ViewController for this screen. Open the assistant editor (View > Assistant Editor > Show Assistant Editor). Hold control and drag and drop the sign in button inside the ViewController class.

Once the pop-up appears, change the Connection type to Action. Name the function as onSignInTapped. Finally click on Connect.

Image for post
Image for post
Link sign in button tap to function on ViewController

You should now see an empty onSignInTapped function within ViewController class. We will load and present login screen from the static library from within this function:

@IBAction func onSignInTapped(_ sender: Any) {
// call Static Lib login from here!
}

3. Link the Static Library and Resource Bundle to the app

Before we can load the login screen from the consumer app we must link the static library and resource bundle to our consumer app.

Let’s link the static library to the app.

  1. Select the StaticLibrary project (with blue icon)
  2. Select the ConsumerApp target from the target list
  3. Select General tab
Image for post
Image for post
General settings for the ConsumerApp target

Under Linked Frameworks and libraries select the plus (+) button. Once the Choose frameworks and libraries to add option opens, select libStaticLibrary.a. Click Add.

Image for post
Image for post
Link static library

Note this tells Xcode to include the compiled code within the static library in the executable of the app.

Next select the Build Phases tab.

Image for post
Image for post
Target settings tabs. Build phases selected

Under Target Dependencies click the plus icon (+).

Image for post
Image for post
Target dependencies section in the build phases tab of the ConsumerApp target

Select both LoginStaticLibrary and LoginLibraryResourceBundle. Click on Add.

Image for post
Image for post
Add LoginStaticLibrary and LoginLibraryResourceBundle to target dependencies

Note: this will tell Xcode that the app relies on the static library and resource bundle target to be built before the app. The app depends on these targets to compile.

Finally under Copy Bundle Resources click on the plus icon (+).

Image for post
Image for post
Copy Bundle Resources in build phases

Search and select LoginLibraryResourceBundle.bundle. Click Add.

Image for post
Image for post
Add LoginLibraryResourceBundle to the Copy Bundle Resources

Note: this step packages the resource bundle with the app package.

4. Present login screen from the static library on sign in button tap

In this section we will open the login screen in the static library and bundle resource when the user taps the Sign in.

Open ViewController.swift file in ConsumerApp. Under import UIKit let’s import our static library.

import LoginStaticLibrary

Whilst the compiled files are included with the app once linked the files are still separated under a module. So we still need a import statement. Modularisation helps in secluding files that are unrelated to the rest of the app i.e. login logic.

Next within the onSignInTapped function, add the following lines of code:

let loginViewController = getLoginScreen(onLogin: {
self.dismiss(animated: true, completion: nil)
})
self.present(loginViewController, animated: true, completion: nil)

And that’s all! 🎉 Run the ConsumerApp and see it in action. You’ll see a screen with a single button Sign in. The Sign in button is from the app. Tap Sign in, the login screen from the static library and resource bundle will appear. Tap Login from the login screen and the screen will disappear.

Image for post
Image for post
ConsumerApp running

You can find the full source code for this post here. Each git commit on the repo is a step on this post to follow along.

Summary

In this post we have learnt:

  • what static libraries are
  • what resource bundles are
  • why use static libraries
  • why use static libraries over dynamic frameworks
  • how to build a static library and resource bundle
  • create screen using Interface Builder and XIB’s hosted in resource bundle and controlled from code in static library
  • how to consume static library and resource bundle

Final Notes

In this post we have seen the advantages in using static libraries along with resource bundles over dynamic framework. However we have also seen that this can be complex to build, maybe even to maintain over the more convenient dynamic framework. To check how to build dynamic framework checkout my earlier post on Reusing code with Swift frameworks.

Stay tuned for more on iOS development! Follow me on Twitter or Medium.

Onfido Tech

Stories from Design, Engineering, Product and Research at…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store