Hire Us
Creation of pure Swift module

Creation of pure Swift module

If you have already started playing with swift, you probably thought about how to include third party libraries into your project or how to distribute yours.

Apple provides a mechanism to distribute code via frameworks (eventually, for iOS too), so making a custom framework, which will include both ObjC and Swift code is very easy. But let’s dig deeper and create a pure Swift module, like apple does with Swift’ std lib and Cocoa/CocoaTouch bridge.

Note: this module will work in swift-only projects, in case if ObjC compiler generates Swift-to-ObjC bridging header and includes swift-module via @import directive, which doesn’t work with current Xcode/Apple Clang version.

Toy Swift Module

We’re going to create a simple module call Logger which will contain only one method: log.

You can see sample project here.

Each swift module consists of at least three files, so we should get all of them as an output:

Logger.swiftmodule – public interface/definitions

Logger.swiftdoc – docs (surprisingly)

libLogger.a – built library (there also might be a dylib, it depends on your task)

Note: you should switch xcrun to beta xcode’ version before running swift commands

sudo xcode-select -switch $xcode_dir/Contents/Developer

libLogger.a

Let’s create the simplest and useless Logger “library”.

class Logger
{
    var prefix: String
    
    init(_ prefix: String) 
    {
        self.prefix = prefix
    }
    
    func log<T>(object: T)
    {
        print(prefix)
        println(object)
    }
}

The class just takes some prefix and logs it before actual object

var logger = Logger("> ")
logger.log("Hello, World!")

Now it’s time to make a libLogger.a:

xcrun swift -emit-library -emit-object Logger.swift -sdk $(xcrun --show-sdk-path --sdk macosx) -module-name Logger
ar rcs libLogger.a Logger.o

-emit-library generates dynamically linked shared library, while -emit-object generates object file and includes main function, so you will have linker errors due to duplicated symbols.

Solution is pretty simple: include both flags -emit-object and -emit-library, as I did above.

Logger.swiftmodule

xcrun swift -emit-module Logger.swift -sdk $(xcrun --show-sdk-path --sdk macosx) -module-name Logger

This command will generate Logger.swiftdoc and Logger.swiftmodule.

Now we have complete module and can integrate it into real project. Just create simple swift-project and add the files:

swift_module_integration

Then setup ‘Import paths’ for Swift

swift_import_paths

We’re ready to check how it works:

import Foundation
import Logger

var logger = Logger("> ")
logger.log("Hello")

Just run and you’ll see an expected output:

> Hello

If you see linker errors, check your ‘Library search paths’ and ‘Other linker flags’ — they should contain path to libLogger.a and -lLogger respectively.

Logger.swiftdoc

Let’s figure out how to deal with documentation.

To add documentation to module you just need to comment it using ///, e.g.:

/// Simple Logger
///
/// Constructor takes prefix string which will be printed before actual object
///
class Logger
{
    var prefix: String
    
    init(_ prefix: String)
    {
        self.prefix = prefix
    }
    
    /// Prints `object` with specified `prefix`
    func log<T>(object: T)
    {
        print(prefix)
        println(object)
    }
}

After integrating module into a project you will see documentation on the right

Swift module documentation

But I didn’t manage to get it work without restarting Xcode after integrating.

Conclusion

This approach is not very nice for “everyday” usage for a regular iOS/OSX developer (because it requires creating and supporting Make/CMake file), but it might be useful if you want to create pure module which doesn’t use ObjC at all.

Also, Swift modules are similar to Java’s jar binaries.

The source code:

import Foundation

/// Simple Logger
///
/// Constructor takes prefix string which will be printed before actual object
///
class Logger
{
    var prefix: String
    
    init(_ prefix: String)
    {
        self.prefix = prefix
    }
    
    /// Prints `object` with prefix
    func log<T>(object: T)
    {
        print(prefix)
        println(object)
    }
}

transforms into module interface without details of implementation

/// Simple Logger
///
/// Constructor takes prefix string which will be printed before actual object
///
class Logger {
    var prefix: String
    init(_ prefix: String)

    /// Prints `object` with prefix
    func log<T>(object: T)
}

So you can even distribute proprietary libraries without any problems (except of crazy reverse engineers), but I hope you won’t do this in favor of Open Source Software ;)