Shipping a Library/Framework in iOS

Drone captures ethereal view at Uyuni salt flat in Bolivia - CGTN

Uyuni salt lake - Bolivia                 

In this article we will discuss all about Framework/Libraries in iOS, from choosing between framework/library, designing, architecture, security measures, testing and publishing..

Before we start, lets understand what is an module, how they are beneficial 

What is an module

> encapsulates code and data to implement a particular functionality. 
> has an interface that lets clients to access its functionality in an uniform manner.
> is easily pluggable with another module that expects its interface.
> is usually packaged in a single unit so that it can be easily deployed.

More on Modular architecture in iOS


Modules in iOS ecosystem

In iOS we can ship an module as an library or as an framework, lets see how they differ from each other 

Library

library is an pre compiled binary in swift IR format, it can be in any executable format like .a, unix executable etc.. library cannot have resources like images, xib's shipped as part of their executable...

Framework 

frameworks are containers, they can have compiled binary, resources like images, xib's, different versions of their own libraries, other libraries wrapped in Umbrella fashion

Static vs Dynamic 

static and dynamic are attributed to libraries/frameworks based on how they are linked into the application

Static libraries/framework are shipped as part of the client application binary, i.e lib/fw are embedded in the binary of the application which is using it when archived and the same is used when app is brought to memory.

Dynamic libraries/framework are not shipped in client application binary, its not shipped in application binary because all dynamic libraries/framework are part of the OS i.e iOS operating system itself, so these libraries/framework are linked to application environment during lunch time by dynamic loader dyld 🤔, and these are shared among multiple application 


Static library

In iOS we are entitled to ship only static libraries, since shipping dynamic libraries means something which can be part of iOS system so we can't do much about it.

Dynamic library

UIkit framework is an constituted of many libraries internally, which are shipped as part of OS, and these are updated as part of iOS update. As an developer we don't have an option to write code which can be part of iOS operating system. (unless you work for Apple 😎) so its out of our options.

Static framework

As said earlier frameworks are containers, and if any of the dependent modules of framework are shipped as part of our binary then then it becomes an static framework...

example: lets say our framework named "A-framework"has dependency on other modules like "x","y","z" which in-turn are shipped in our binary rather then referencing the OS as dependency, then it becomes static framework

Dynamic framework 

if modules which are framework dependents are linked dynamically at runtime, then its said to be a dynamic framework 


Embedding vs Linking 

Embedding - a.k.a include in binary - example : any custom framework
Linking - a.k.a provide reference - example :  CoreData, UIkit etc 
any framework which we include in our project to be part of our application binary needs to embedded, else if we want to just provide reference then it will be linked.

Metrics to choose between static/dynamic - library/framework

1. Speed 

static modules are fatter in getting the task done since they are explicit to app memory, while in case of dynamic modules since they are shared among multiple tasks across different apps where they are competing to get tasks done

2. Launch time

this part is tricky, if there less dependencies on internal dynamic libraries then dynamic modules are faster but takes more time if there are more dynamic libraries needs to be loaded this is because dynamic loader needs to resolve and load all dependencies (like a topological sort) which may take lot of time.

 In case of static launch time is faster since all dependencies are shipped as part of app binary itself, so dyld doesn't have to search and load, but static frameworks might take more time if they are heavy i.e larger binary size 

3. Binary size

static modules are heavier as all dependent libraries are packed as part of the application binaries itself, but dynamic modules are lightweight since all reference/links to dependent libraries are present and additional resources like images, xib's etc...

Design/Architecture 

Exposing functionality : public - open

exposing your functionality/service to client should be done meticulously and to very limited extent, exposing unnecessary part will always be troublesome, this also includes choosing open/public access specifier for your module classes and functions, which defines how client can interact to them 

Designing / Architecture :

choose a suitable architecture for your module, discuss with team, take time, fit everything properly. Most of the interactions will be. through. one common interface (follow fascade pattern) - followed by delegating the responsibilities to individual components (follow chain of responsibilities), also further if we decide to change our any internal services (say network layer), design such that there is minimalistic changes needed to adopt(follow clean architecture) 

Error and State management 

error propagation to client is very important to debug issues, create your own custom framework error type using enum(recursive ?? check whether its needed in your case), where we can propagate all kinds of error like network, system error, user defined error etc .. 

state management; while we serve requests from client, client will be waiting cluelessly until our module returns output, check whether you can propagate states as we serve requests, so that client can also update the UI accordingly to user. 

Security 

  1. code visibility : if you have a kind of service, whose code you wouldn't wish to ship, then just ship the .framework or .library, else you can ship entire code base like Alamofire does.
  2. networking: you should always have an secure connection in all your network communications made in module, you can do so by protecting your API calls with TrustKit library, further you can have an product-licence kind of an identifier which locally authenticates clients from using services
  3. hosting : if you wish to open source your library then you can host on GitHub and cocoa repository for public availability, else you can just ship them via any convenient protected means like mail, or behind any authorised service. 

Resolving module dependency at client end

1. GitHub - private/public

you can host your module at Github, make it public/private as per needs and. provide reference to it in pod file at the client side,

2. Local reference - private/public 

client can download modules from any of there known sources manually like email, GitHub and provide local directory reference to the module at pod file.

3. Cocoapods -  public

if vendor has published the module at cocoapods central repository,  then client can get them by specifying the registered name in pod file 

4. SPM - swift package manager

client can also use Swift Package Manager to resolve module dependency


Testing

Testing your module is very much important before you ship, few methods mentioned below can help

unit testing target 

you can create an unit testing target in your module, specify all dependencies in pod file, and test each functionality 

sample project

you can also create a sample project, integrate you module and test. this is the most preferred way as you can share this with your clients as an example project.

if you need to debug the functionalities on the fly, you can < xcode->debug->attach to process > to do stack trace of your module functionality


Versioning 

versioning of your modules is as important as versioning your IPA's, it helps you track bugs across different versions of your modules and fix them elegantly. 


Module stability 

modules shipped from swift 5.1 compiler are compatible for future versions of compliers as well, so no need to hustle to get latest complied modules from vendors.
 

XCFramework / FAT binaries 

Any module can be built specifically to a particular platform say iOS,tvOS.. each of these platform may have different architecture. iOS(arm64) tvOS(i386), simulator/macOS(x86_64), but if you need to ship a single module to serve all platforms then you should include binary mapping for all architecture.
This was earlier achieved by creating universal target which addresses all platforms and adding a shell script to pack all platform support into single binary folder termed fat binary.

Note: x86_64 platform code cannot be included in binary submitted for App Store, so we need to rip it off before archiving using some script

starting with Xcode 11.3.1 this can be done easily using XCframework, we need to just enable a flag in Xcode settings, which will minimise the work of creating universal target, and removing x86_64 platform code.



Comments