Lately I have been working on introducing swift to an objective-c dynamic framework project.
As many project are structured, it contains public, private and project headers.

As I started implementing part of the feature I was writing in swift, I had to use a class that header was among the project's private headers. I quickly realised that I realised access the private/project headers classes as I do with the public ones.

That led to the question; How to access private and project headers in a Swift and Objective-c dynamic framework.

Background

In dynamic frameworks, your swift code will by default have access to all the headers that are imported in your umbrella header (that header that have the same name as your framework). In order for your framework to form a valid module (Modular framework), these headers have to be public headers. (more info about header accessibility)

If for example you are working on AwesomeKit (a dynamic framework project). which is structured like this:

  • AwesomePublicClass. A class that header's is public
  • ASwiftClass. A swift class with open accessibility
  • AwesomeKit.h. The umbrella header

And AwesomeKit.h contains the following:

#import "AwesomePublicClass.h"

Now since ASwiftClass is in the same target (AwesomeKit), And since the umbrella header imports AwesomePublicClass, then your swift code will be able to access and create an instance of AwesomePublicClass

ASwiftClass.swift

open class ASwiftClass {  
  let variable = AwesomePublicClass()
}

Now say we create InternalClass which has its header as private. We can try to import it in the umbrella header. Which will throw a compilation error:

Compilation Error due to adding private header in umbrella header

That happens since swift is only able to import modular frameworks. If the umbrella header contained a non-public header, it would make the framework a non modular framework.


Alright if that didn't work, then how can we access those slippery private and project headers in swift?

One thing that come to mind is the bridging header. This header is a place where we list all the Objective-c headers that we would like swift to use.
Before we dig deep into bridging header land and build our hopes up, let me stop you, sadly, we are not able to use then with framework targets. Trying to set a bridging header in the Xcode project will throw this error.

We cannot use bridging headers with framework targets

Module map for the rescue

Turns out that we can use module map to access project and private headers in our swift files.

We can use a project file by setting Import Paths (SWIFT_INCLUDE_PATHS) in build settings of the dynamic framework.

First of, in order to add some structure to the project, lets create a folder that contains a module map:

Our folder structure

- AwesomeKit. project
- AwesomeKit/
    - AwesomeKit.h
    - InternalClass.h
    - AwesomePublicClass.h
    - ASwiftClass.swift
    - other implementation file
    - ProjectModule/
        - module.modulemap 

It's important to name your modulemap file as module.modulemap as other names would be picked up when setting the path in the Import Paths build settings.

This module map file contains the following:

module AwesomeKitPrivate {  
  header "../InternalClass.h"
  export *
}

Above we defined a new module AwesomeKitPrivate that exports the InternalClass header.
The header statement accepts the relative path of the headers we want to expose. (in this case; go one level up for InternalClass.h)

Next, head to the target build settings and add the path to the modulemap directory in the Import Paths (SWIFT_INCLUDE_PATHS)

Setting the import path

Notice how we have to set the full path to the modulemap. We used $(SRCROOT)/ which will expand to the project path. The build setting should look like this:

SWIFT_INCLUDE_PATHS = $(SRCROOT)/AwesomeKit/ProjectModule  

After appending the import path to SWIFT_INCLUDE_PATHS, the swift compiler will parse the modulemap file and expose any module defined in the modulemap files it finds.
That will make our newly defined module AwesomeKitPrivate accessible to our swift code:

import AwesomeKitPrivate

open class ASwiftClass {  
  let variable = InternalClass()
}

In this post we started with a question; how to access objective-c private and project headers in our swift code in a dynamic framework project?
We experimented with multiple ways to achieve that. After a couple of failing attempts. modulemap files and SWIFT_INCLUDE_PATHS came to the rescue.

Dont forget to share the article and follow me on twitter @ifnottrue.