Skip to main content

Extract module from swift app


Can the architecture of a mobile application be transformed into a more modular one? In the backend it always pays off but does it pay off in a mobile application? Is it worth to cut functionality into independent modules if we use them in one application anyway? Does it make sense to hide them behind another layer of abstraction? Is it finally possible to do it in a convenient way that helps us instead of standing in the way?
The traditional approach is to use CocoaPods. Each framework has its own repository from which we pull the sources and create a project with the pods. Everything is cool except one detail, I would like to be able to edit the sources of my modules in the place where I use them and compile them without any additional actions, i.e. from the point of view of using the code the fact that the components are in another repository is invisible. I wanted to use the Swift Package Manager and its packages. SPM is also very much like cocoapods when it comes to using components in a transparent way, because although you can have your local copy package, there is an original in the source repository. I'd keep an eye on that, but there are external libraries in the code I'm taking care of, which I also want to push into an independent module. As much as I try to pack them in SPM, I've always been bitten somewhere. Finally, I gave up and decided to use regular Frameworks. Separation of code into a framework doesn't seem to be difficult, but the library I mentioned caused me some trouble. 
The library comes as a .a file and header files. Directly in application on iOS I would import the headers in bridging-header and the problem would be solved. Unfortunately frameworks do not support bridging-headers. Modules come to the rescue here. 
Adding a module looks like this: we add a file (which I creatively named module.map) which is just a map of our module.



module MyModule {
    header "some_header.h"
    header "another_header.h"
    export *
}

This makes us have access to declarations from those headers after "import MyModule". But there's more to do. We need to update project's build settings in section "Swift compiler - custom flags" in the field "Other swift flags" something like this: -Xcc -fmodule-map-file='/path/to/file/module.map'.

It wasn't so easy to find it therefore I'm writing it here, I have wasted some time to find it, you don't have to :-). 

That will be enough to build the library.
I wanted to have project I could work on during normal work with app itself. I don't want to create situation in which I'll need to open one project (with framework), make my changes, build, attach to app project, find out I made a mistake, change framework again, build again, attach to app again... I want to eat a cookie and have a cookie. Make changes like all code was still one app and yet have separate package ready to be used in another project. Instead of building framework and attaching it's binary to app, I have put framework's project inside app's project which kind of solved my problem (you can drag framework's .xcodeproj file from finder into xcode with app's project open, that's what I'm talking about). But it did come with it's own problems. My app has multiple build configurations. Some of them passed the build, others didn't. Configuring all of them separately would be a torture, we have cocapods used in that project and I want my framework do some things automatically, like those cocoapods. Don't we have computers to do automatic things after all? Maybe I discarded cocoapods as solution for my framework too early. In the end I can import cocoapod with :path => or even make fixes in locally downloaded copy which I then only would need to update at the source repo. I can live with that.

If you are too moving your projects to cocoapods, I would suggest to ignore tutorial that starts with "pod lib create", it is a waste of time. Just create your .podspec file manually. Example podspec (with static library to include problems I have met) looks like this:


Pod::Spec.new do |s|
  s.name             = 'LibName'
  s.version          = '0.1.0'
  s.summary          = 'A short description of LibName ;) .'
s.description = <<-DESC Implementation of LibName longer description
DESC s.homepage = 'https://some.homepage.of.this.project' s.license = { :type => 'CUSTOM', :file => 'LICENSE' } s.author = { 'Me' => 'My@mail.com' } s.source = { :git => 'https://where.this.projets.lands.in.the.end.git' } s.ios.deployment_target = '11.0' s.source_files = 'Folder/with/sources/**/*' #system frameworks we use s.frameworks = 'SystemConfiguration','CoreLocation', 'UIKit' #system libs, WITHOUT _lib_ at the name beginning! s.libraries = 'resolv' #using another cocoapods? place them below. #Don't worry where to get them from, you'll do that in Podfile and not in Podspec s.dependency 'Other_Custom_cocoapod' #preserve path because we'll use it in a second s.preserve_path = 'my/path/to/module.map' s.pod_target_xcconfig = { 'HEADER_SEARCH_PATHS' => '$(PODS_ROOT)/my/path/to/' }
s.xcconfig = { 'SWIFT_INCLUDE_PATHS' => '$(PODS_ROOT)/LibName/my/path/to/',
'HEADER_SEARCH_PATHS' => '$(PODS_ROOT)/LibName/my/path/to/ }
#and here is how to import lib. s.ios.vendored_libraries = '/LibName/my/path/to/another_lib.a'
end
Finding some options have taken a lot of time. Much more than reading this config, so you you need to create your pods and find problems, you can use it as a cheat sheet (unless you find some extra problems). This way I managed to create my cocoapods and another which depends on the first one, import both of them in my main iOS app and have an app with better architecture. It was interesting task. One more thing. I have one repo for all components to be imported in the apps. To make two or more cocoapods in the same repo, we need to create separate podspec files for each project. Put those files in main folder of the repo and use s.source_files and similar settings containing path to select files. Very simple and working fine. That's it, enjoy!

Comments