Skip to content

Latest commit

 

History

History
113 lines (95 loc) · 6.31 KB

MixedModuleCompiling.md

File metadata and controls

113 lines (95 loc) · 6.31 KB

Mixed Language Compiling

Mixed language compiling is one of the daunting tasks in the area of iOS building. Xcode usually handles it nicely behind the scenes, but most of us don't know how to troubleshoot if errors occur.

Although technically C, C++ and Objective-C are mixed language, here we specifically mean Objective-C and Swift. Compiling those two language are particularly challenging because they use different compilers (clang and swiftc) and have different interface formats (.h and .swiftmodule).

Interface files

Besides invoking the compiler, the building process is mostly about dealing with interfaces - finding the interfaces of the dependencies and providing the interfaces for the downstream modules. Here are some common types of interface.

  • header file (.h): The traditional interface for .c, .cpp or .m files.
  • umbrella header (.h) A header file that only includes other headers files, grouping related header files together.
  • module map (module.modulemap): A file that includes an umbrella header and defines a module, which is an important concept in clang.
  • swift module (.swiftmodule): The interface of a Swift module. It's like a module map for Swift. Different than all above interfaces, this is compiler generated and in a binary format.

Inter-module compiling

For the sake of simplicity, we will discuss the case between two libraries. The process is almost identical between executable and libraries. For frameworks, since they are just libraries in a predefined directory structure, the underlying mechanism is the same.

Swift uses Objc

The Objc module needs to provide a module.modulemap file. This module map can be either manually created or generated by a build system.

module MyObjcModule {
    umbrella header "MyObjcModule.h"
    export *
    module * { export * }
}

In Swift code, we simply import the module like a Swift one.

// MySwiftModule.swift
@import MyObjcModule

To compile Swift code, we have to let the compiler know where to locate the module.modulemap. This is done by passing -I to swiftc.

swiftc ... -I/directory/of/module.modulemap MySwiftModule.swift ...

Objc uses Swift

For this scenario, we need to deal with Swift module first. In Swift code, we annotate the classes or methods with @objc. The Swift-only features, e.g. struct, tuple, generic, cannot be used in Objc.

// MySwiftModule.swift
@objc public class MySwiftModule : NSObject {}

Then we need to let Swift compiler generate a header file that can be imported by a Objc module. This is done by using -emit-objc-header. By convention, this header file has -Swift suffix.

swfitc ... -emit-objc-header -emit-objc-header-path MySwiftModule-Swift.h MySwiftModule.swift ....

Now in the Objc module, we import this header file like a regular header from other modules.

// MyObjcModule.m
#import <MySwiftModule/MySwiftModule-Swift.h>

To compile the Objc module, simplely let clang know where to find the -Swift.h file.

clang ... -I/directory/of/MySwiftModule-Swift.h MyObjcModule.m ...

Intra-module compiling

Intra-module compiling is more nuanced. Within the same module, dependencies still exist. Objc can call Swift code or the other way around. Moreover, they can call each other.

Objc uses Swift

First, we still need to annotate Swift classes or functions with @objc. If the class is only used within the module, the classes don't have to be public, but there is a caveat (see below).

// MySwiftClass.swift
@objc class MySwiftClass : NSObject {}

Then we still need to generate -Swift.h header file. The compiler flag is also -emit-objc-header. However, by default, the generated header contains interfaces for Swift declarations marked with the public or open modifier. Using -emit-objc-header with -import-objc-header (see below bridging header) make generated header also include interfaces marked with the internal modifier.

swiftc -c ... -emit-objc-header -emit-objc-header-path MixedModule-Swift.h ...

In .m file, import the generated Swift header file with double quotes, like other headers in the same module.

// MyObjcClass.m
#import "MixedModule-Swift.h"

If the Swift class is exposed in the Objc header file, use forward declaration.

// MyObjcClass.h
@class MySwiftClass;

Swift uses Objc

There are two ways for Swift to use Objc, underlying module and bridging header.

1. Use underlying module

Similar to inter-module compiling, the Objc code needs to have a module.modulemap. This is called underlying module and it should have the same name as the Swift module. Then pass -import-underlying-module and the search path to swift compiler.

swiftc ... -import-underlying-module -I /directory/of/module.modulemap ...

2. Use bridging header

We can also use a bridging header, which usually has -Bridging-Header.h suffix. A bridging header is basically an umbrella header. When compile Swift code, we use -import-objc-header flag.

swiftc ... -import-objc-header MixedModule-Bridging-Header.h ...

Please note that -import-underlying-module and -import-objc-header cannot be used at the same time.

error: using bridging headers with framework targets is unsupported

Swift and Objc uses each other (General Case)

In reality, it's pretty common that Swift and Objc code are entwined in the same module. Here is a general way to build it.

  1. Generate a module.modulemap file for Objc underlying module. It's easier to just include all .h in the module.
  2. Compile Swift code and generate objects (.o). Make sure to import the underlying module (-import-underlying-module). In the mean time, let the compiler generate .swiftmodule (-emit-module) and -Swift.h (-emit-objc-header).
  3. Compile Objc code and generate objects (.o). Make sure the generated -Swift.h is in one of the search paths.
  4. Make a library from all object files.
  5. Make a public module.modulemap. This public one should include the generated Swift header file (*-Swift.h).
  • For a Swift module that depends on this mixed module, provide the search path for .swiftmodule and the underlying module.modulemap.
  • For an Objc module that depends on this mixed module, provide the search path for the public module.modulemap.