Creating Objective-C modules

objective-c   iOS   alchemic   framework  

For some time we have been able to import system frameworks as modules into Objective-C code. For example, to use Foundation, the Objective-C runtime, XCTest frameworks and my DI framework for testing, I would have some imports that look like this:

@import Foundation;
@import ObjectiveC;
@import XCTest;
#import <Alchemic/Alchemic.h>

@interface MyTests : XCTestCase
// ...
@end

Having to mix import styles like this always seemed a bit annoying and I felt that I should be able to import my APIs the same way I import Apple's. Yet the documentation I have read said I could not - unless I'm using Swift .. which I'm not. Today though, I think I've figured it out ... And modules rock!

So lets go through this and I'll show you how I updated Alchemic so that the above imports ow look like this:

@import Foundation;
@import ObjectiveC;
@import XCTest;
@import Alchemic;

@interface MyTests : XCTestCase
// ...
@end

Creating a module

For Alchemic to be recognised as a module, I needed module map file. So I created a file in the root of my project called module.modulemap. This name is the convention that most people seem to follow.

The module map file tells the compiler about my module, what it exposes and what it needs. The syntax is extensive and well explained on the Clang website.

As I was already using an umbrella header in Alchemic, my module map file looks like this:

module Alchemic {  
    umbrella header "Headers/Alchemic.h"
}

My map file is pretty basic. I just tell it what my module name will be and where to find the umbrella header. Notice I use the path as it exists in the compiled framework.

Update project settings

To use your module map file you need to update your project settings. Here's a screen shot of mine:

And thats it, rebuild the project and now you can import your framework using a module import like this:

@import Alchemic;

But wait, what if you want to have private headers in your project for those classes which are not part of what you consider to be your 'public' interface?

Private module headers

Private module header can solve this. I created a second module map in my project using the default naming convention of module.private.modulemap. Then I declared a module in it the same way I declared the public module. Like this:

explicit module Alchemic.Private {  
    header "PrivateHeaders/ALCConfigClassProcessor.h"
    header "PrivateHeaders/ALCTypeDefs.h"
}

Here there is a couple of new things happening. Firstly there is the explicit declaration. This tells the compiler that the module can only be used if explicitly imported. And notice that I've have called the module Alchemic.Private. You can call it anything you like, but I like this convention.

Next I imported the headers I had marked as private, using individual header references because there is no umbrella header for private headers in my project. Again I'm using the path as they appear in the compiled framework package.

finally as per the main header setting, I updated the settings of the project to specify the private headers module map.

And it's all done! Now my tests can access the private headers like this:

@import Foundation;
@import ObjectiveC;
@import XCTest;
@import Alchemic;
@import Alchemic.Private;

@interface MyTests : XCTestCase
// ...
@end

Cool!


Comments powered by Disqus