As I make iOS and Mac framework as a living, I get to deal with them on a daily basis. In this article I will attempt to jot down some of the not so popular information about them.

What is a modular framework

A modular framework is a framework that contains a module map. This module map can either be generated from building the framework, or , not so-commonly, specified as a build settings in the framework project.

In order for Xcode to generate the module map file, a framework has to have an umbrella header with the same name of the framework and to have the Define Module (DEFINES_MODULE) build settings set to yes. When building this framework, Xcode will generate a framework bundle that contains a Modules director.

This Modules directory contains the generated modules map (module.modulemap) with the following content:

framework module AFramework {
  umbrella header "AFramework.h"

  export *
  module * { export * }
}

Before deciphering the default module map file, lets roll our sleeves and write our own map file.

Custom module map

A framework can specify a custom map file by setting Module Map File (MODULEMAP_FILE) Xcode build setting to the map file path. Lets start by creating the simplest module map possible.

framework module AFramework {
  umbrella header "AFramework.h"
}

In this module map we only specify the umbrella header file. This header is the header that imports all other headers. In our example, the umbrella header looks like:

#import "File1.h"
#import "File2.h" 

If however you don't have am umbrella header, you can specify all the headers you want your module to expose:

framework module AFramework {
  header "File1.h"
  header "File2.h"
}

Modules and submodules

A single module map file can define many frameworks. Lets study the following module maps:

framework module AFramework {
  header "File1.h"
}

framework module OtherFramework {
  header "File2.h"
}

In the above, we defined two modules AFramework and OtherFramework. AFramework, which is the main framework, has the name of the actual framework. The name matching is a requirement.

OtherFramework while still is a separate module, it still need AFramework to be imported to be available. Lets take a couple of examples:

import AFramework

let f1 = File1()
let f2 = File2()

File2 is not available since we didn’t import OtherFramework

Trying to import OtherFramework without first importing AFramework will also not compile. The correct way to using OtherFramework is as following:

import AFramework
import OtherFramework

let f1 = File1()
let f2 = File2()

Modules can define submodules, lets define a couple of submodules:

framework module AFramework {
  module Sub1 {
    header "File1.h"
  }

  module Sub2 {
    header "File2.h"
  }
}

By using submodules you can have more granular control on what headers to import to your project. Importing AFramework.Sub1 will make File1.h available, while AFramework.Sub2 will import File2.h into the project. If however you want both of the files, you would import AFramework.

Modules map also have a concept of explicit submodules. These are modules that has to be explicitly imported in order for their header files to be useable in the project.

Lets rewrite the above submodules:

framework module AFramework {
  explicit module Sub1 {
    header "File1.h"
  }

  explicit module Sub2 {
    header "File2.h"
  }
}

If we now import the parent moduleAFramework File1 and File2 cannot be used. To use them we need to explicitly import one or both the submodules.

import AFramework.Sub1
import AFramework.Sub2

Now let say you want to have some dependencies between submodules, in a way that importing a submodule imports a set of other submodules. As an example, importing AFramework.Sub1 also imports AFramework.Sub2. This is possible by using the export syntax.

framework module AFramework {

  explicit module Sub1 {
    header "File1.h"
    export Sub2
  }

  explicit module Sub2 {
    header "File2.h"
  }
}

In Sub1 module definition we state that we want to also export Sub2. This results in importing AFramework.Sub2 when we import AFramework.Sub1.

Export also works for the parent module:

framework module AFramework {
  explicit module Sub1 { ... }
  explicit module Sub2 { ... }

  export Sub1
  export Sub2
}

In the previous example, whenever we import AFramework we also bring along all the headers defined in AFramework.Sub1 and AFramework.Sub2.


Now that we have more info about how module maps work, lets review the default file generated from Xcode when defining a module.

framework module AFramework {
  umbrella header "AFramework.h"

  module * { export * }
  export *
}

First of all, module * specifies that all the header files imported in the umbrella AFramework.h are themselves submodules. The export * inside the module * declares that any header file imported from those header files - which are imported in umbrella- are also submodules. To understand it lets consider an example.

AFramework.h contains the following

#import "File1.h"
#import "File2.h" 

And File1.h:

#import "File3.h"  

The module * { export * } makes AFramework.File1 and AFramework.File2 available as submodules, and the internal export * makes AFramework.File3 also a module (even if it was not in the umbrella header). The above module * is a short hand for the following:

framework module AFramework {
  module File1 {
    header "File1.h"
    export *
  }

  module File2 {
    header "File2.h"
    export *
  }
}

Now you can import all these:

import AFramework.File1
import AFramework.File2
import AFramework.File3

If we used module * { } - without the internal export * - then import AFramework.File3 would not be valid.

Modular headers

If you are like me, then you banged your head trying to solve “Include of non-modular header inside framework module ..”. But what is a modular header.

A modular header is a header that is included in the module map. This header can be either imported in the umbrella header in case you are using umbrella header "header.h", or it can be explicitly imported in the module file using header "header.

Any header that is not included in the module map is not a modular header and hence cannot be imported in any of the modular headers (modular headers can only include modular headers).


Thats pretty much wraps it up, for a way more descriptive and awesome list of module map file customisation read on from here