avatarAnurag Ajwani

Summary

This context provides a comprehensive guide on how to create and use Swift static libraries and resource bundles for iOS app development, focusing on code reusability and resource sharing.

Abstract

The context discusses the concept of Swift static libraries and resource bundles in iOS app development. It explains how to create a static library and a resource bundle, including the creation of a Login screen using Interface Builder and controlling it through code in the static library. The context also covers the advantages of using static libraries over dynamic frameworks and provides a step-by-step guide on how to consume static libraries and resource bundles in an app. The article concludes with a summary of the key learnings and final notes on the complexity and maintenance of static libraries and resource bundles.

Bullet points

  • Introduction to Swift static libraries and resource bundles in iOS app development
  • Explanation of static libraries and their advantages over dynamic frameworks
  • Step-by-step guide on how to create a static library and a resource bundle
  • Creation of a Login screen using Interface Builder and XIB files hosted in the resource bundle
  • Control of the Login screen through code in the static library
  • Consumption of static libraries and resource bundles in an app
  • Summary of key learnings and final notes on the complexity and maintenance of static libraries and resource bundles.

Modular iOS

Reusing code and resources with Swift static libraries and resource bundles

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.

Static library compilation representation
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.

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.

Loading framework packaged with app
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.

Linking static library to your app

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

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.

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.

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.

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.

Select the Bundle template from macOS

Name the bundle LoginLibraryResourceBundle and then click Finish.

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).

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.

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.

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.

Create XIB file

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

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).

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:

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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.

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
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.

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.

Target settings tabs. Build phases selected

Under Target Dependencies click the plus icon (+).

Target dependencies section in the build phases tab of the ConsumerApp target

Select both LoginStaticLibrary and LoginLibraryResourceBundle. Click on Add.

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 (+).

Copy Bundle Resources in build phases

Search and select LoginLibraryResourceBundle.bundle. Click Add.

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.

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.

iOS
Static Library
Resource Bundle
Swift
Xcode
Recommended from ReadMedium