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:
Then setup ‘Import paths’ for Swift
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
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 ;)