Gomobile lets you turn a Go package into a Framework that can be imported into a Swift application which allows you to call Go code from Swift, and vice versa.
Most online tutorials focus on calling Go from Swift, and not the other way round but if we are to really use Go to build out the bulk of our iOS applications, this is a vital ingredient. It took me several hours to get it right thanks to the temperamental nature of Xcode mixed with my lack of understanding of Gomobile, so this tutorial presents a solution that is repeatable (by me) and hopefully useful to you too.
This tutorial will cover:
- Common errors or unexpected blockers (search for GOTCHA throughout this post to highlight these), and how to overcome them
- Calling a Go constructor in Swift to create an instance of a Go struct
- Define an interface in Go that we want our Swift code to implement
- Pass the Swift implementation to the Go constructor, and initiate two-way communication
You will need:
- A mac — I’m running macOS Sierra 10.12.6 (16G29)
- A text editor — I use Visual Studio Code
- Xcode — I have version 8.2 (8C38)
- Go installed
- Gomobile installed and setup — see this tutorial for how to get setup
Structure
Let’s start with a simple folder structure so we can discuss the constituents of our project.
Somewhere inside your $GOPATH
, create a folder called gomobiletest
and add the following subfolders:
- The
frameworks
folder is where we’ll put the framework that Gomobile generates for us - The
go
folder will contain our Go code, and thegreeter
package is where our business logic will live - The
ios
folder is where we’ll create our Swift project later
In this tutorial, we are going to build an iOS Framework from our greeter
package.
Our Go package
Let’s start by building a simple Go package that lets us greet somebody.
We will write:
- A constructor to create and return a struct type
- An interface for printing values, which we’ll implement in Swift
- A Go method on the struct that prepares the greeting and calls the method on the interface to print it out
To gomobiletest/go/greeter
, add the following greeter.go
file:
package greeter// Printer types can print things.
type Printer interface {
PrintSomething(s string)
}// Greeter greets people.
type Greeter struct {
printer Printer
}// NewGreeter makes a new Greeter.
func NewGreeter(printer Printer) *Greeter {
return &Greeter{
printer: printer,
}
}// Greet greets someone.
func (g *Greeter) Greet(name string) {
g.printer.PrintSomething(“Hello “ + name)
}
GOTCHA: Originally I named my Printer method Print, but once this gets translated into Swift it clashes with the builtin print function, and I ended up recursing forever. SOLUTION: Be careful with names; they might have to be slightly more verbose (PrintSomething vs Print) than we would like in traditional Go code so it doesn’t clash in Swift.
The above code is fairly simple; we create a Greeter
with the NewGreeter
function, taking in a Printer
which we use later when sending the greeting. In Go we might omit the NewGreeter
constructor, but we can’t in Gomobile because Swift code cannot instantiate Go objects.
In Gomobile, all memory allocation of the types must occur in the world where that type is defined.
Building the framework
Now our package is ready to go, it’s time to build the iOS framework that can be imported into our Swift project.
We are going to use the gomobile
command to do this, specifically its bind
feature. In a terminal, navigate to the gomobiletest/go/greeter
folder and use the gomobile
command to create the framework:
gomobile bind -target ios -o ../../frameworks/Greeter.framework
After a few moments, you will notice that a new item has appeared in our frameworks
folder called Greeter.framework
.
You’ll notice that an iOS framework is actually just a folder full of stuff, including the Greeter
binary. Take a moment to check out what was generated:
Peek at the Objective-C headers
Let’s have a quick look at the Objective-C headers for our framework (remembering that we’re not going to be using these). Open the Greeter.objc.h
file:
This generated code describes the Objective-C objects that represent our Go greeter
package. Notice specifically:
@protocol GreeterPrinter
— this is ourPrinter
interface represented as a protocol
Did you know that protocols in Objective-C are actually the precursor to interfaces in Go? — Blaine Garst, who I have the privilege of calling a friend, cooked-up protocols (with Bertrand Serlet and Steve Naroff) when they worked at NeXT in 1990. You can read more about that in A Short History of Objective-C by Hansen Hsu.
- (void)printSomething:(NSString*)s
— This line inside the protocol describes ourPrintSomething
method; the cases are mutated to match the iOS patternsFOUNDATION_EXPORT GreeterGreeter* GreeterNewGreeter(id<GreeterPrinter> printer)
— This describes ourNewGreeter
constructor function
You will notice that all types are in the global namespace (rather than being encapsulated into a package) and are instead just prefixed with the package/framework name. This is the same in Swift so you should bear this in mind when naming things, and be sure to avoid clashes.
GOTCHA: Your package name must match the name of your framework. So
package greeter
means our framework must be calledGreeter.framework
. Originally my package had a different name and it was failing without telling me why.
Create an iOS application
Now open Xcode and choose Create a new Xcode project. In the iOS tab, select Single View Application and click Next.
Be sure to select Swift as the language, and fill out the rest of the fields as you see fit. Mine looks like this:
Give your project a name (let’s stick with GoMobileTester
for now) before finally clicking Next and then Create once you’ve navigated to the ios
folder.
Importing a Gomobile framework
Now we are going to import our Greeter.framework
so that we can interact with it in Swift. Open Finder, and navigate to the frameworks
folder. Then drag the Greeter.framework file into Xcode at the bottom of the Project navigator. A dialog will open to confirm the action, be sure to check Copy items if needed.
GOTCHA: Some tutorials say that copying the framework is optional, but I couldn’t get it to work without doing so — let me know if you learn differently.
Click Finish and the framework should be added:
You will also notice that the framework has been added to the Link Binary With Libraries section of the Build Phases tab:
This is really what tells Xcode to bundle the framework with the iOS application.
Implementing a Go interface in Swift
Now that the framework is imported, let’s write the Swift implementation of the Printer
interface we defined in Go.
Right-click the GoMobileTester
folder (not the Xcode project at the top, the one below that), and choose New File... Select Cocoa Touch Class, and click Next. Call the class SwiftPrinter
and make sure it’s a subclass of NSObject
and click Next and then Create.
The skeleton class that Xcode creates for us looks like this:
import UIKitclass SwiftPrinter: NSObject {}
The first thing we are going to do is inform Swift that this object is going to implement the protocol (our Go Printer
interface).
Update the code to import the Greeter
framework and add the protocol to the list of things the SwiftPrinter
inherits:
import UIKit
import Greeterclass SwiftPrinter: NSObject, GreeterPrinterProtocol {}
GOTCHA: The Objective-C objects actually has different names to the Swift objects, so if you’re using Swift, it’s better to completely ignore those Header files we looked at earlier, because they’ll only confuse things.
The actual protocol name is GreeterPrinterProtocol
. The import Greeter
line at the top ensures that this code file can access the objects inside the Greeter framework. Without it, we’d see an error complaining that it doesn’t know what GreeterPrinterProtocol
is.
Inspect the generated Swift code
While holding Cmd+Shift, click on the GreeterPrinterProtocol
protocol in the code and notice that we are taken to a new file that contains the Swift code. This is the Swift equivalent of the Objective-C code we looked at earlier, although the names are different:
Hit Cmd+B to build the project, and notice that we encounter an error:
It tells us that the SwiftPrinter
does not conform to our protocol; which is fair enough.
Update the code adding the printSomething
function:
import UIKit
import Greeterclass SwiftPrinter: NSObject, GreeterPrinterProtocol { public func printSomething(_ s: String!) {
print("This just in:", s)
}}
Build again (press Cmd+B) and notice that there are no errors.
Calling Go code from Swift
Now it’s time to call our Go constructor from Swift. The simplest place to do this is in the ViewController.swift file, just after the default view is loaded.
First, add the same import to the top of the file:
import UIKit
import Greeter
Next, inside the viewDidLoad
function, let’s first prepare the Printer
argument that our NewGreeter
function expects:
override func viewDidLoad() {
super.viewDidLoad() let printer = SwiftPrinter()}
So the printer
variable will be an instance of the SwiftPrinter
we just made.
Now let’s create a Greeter
(actually in Swift it’s a GreeterGreeter
):
override func viewDidLoad() {
super.viewDidLoad() let printer = SwiftPrinter()
let greeter = GreeterNewGreeter(printer)}
So our greeter
variable will be an instance of the Greeter
type (that we wrote in Go) and the Printer
interface will be satisfied by our printer
variable, which is an object that implements the appropriate protocol.
Finally, let’s just call the greet
method:
override func viewDidLoad() {
super.viewDidLoad() let printer = SwiftPrinter()
let greeter = GreeterNewGreeter(printer)
greeter!.greet("Mat")}
Step-by-step, here’s what’s going on:
- Swift: iOS application calls
viewDidLoad
- Swift: We create a
SwiftPrinter
, and useGreeterNewGreeter
to create a newGreeter
, passing in theprinter
- Go:
NewGreeter
creates a newGreeter
object (saving theprinter
as a field so we can access it later), and returns it - Swift: We save the returned
GreeterGreeter
in thegreeter
variable (strange name; remember it’s the package name and the struct name combined) - Swift: We call the
greet
method on thegreeter
, passing in a name - Go: Accessing the
printer
, we call thePrintSomething
method, by concatenatingHello
and the passed-in name - Swift: The
SwiftPrinter
type’sprintSomething
method is called, where we print outThis just in:
with the greeting string - Finally, the string should be printed to the console via the built-in
print
method
No Bitcode
Press Cmd+B again and notice that we get an error.
Thanks Xcode, that’s very easy to deal with!
Seriously, this is a great example of why Go and its toolchain win over Swift and its toolchain in my opinion — although you do get used to Xcode the more you work with it.
If you read carefully and haven’t already taken your own life, you will notice that it says this:
...framework does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE), obtain an updated library from the vendor, or disable bitcode for this target.
Gomobile doesn’t support Bitcode right now, so we will take this advice and go and disable it.
GOTCHA: You must disable Bitcode for any application that imports a Gomobile framework.
In the Project navigator, select the topmost GoMobileTester project item, and select the Build Settings tab. Type bitcode
in the search bar and change the Enable Bitcode setting to No:
Build again by pressing Cmd+B and notice this time that it builds successfully.
Running the solution
Select an iPhone simulator from the toolbar (top left) and press the play (run) button.
After a moment or two, the simulator will startup and the code will execute.
Click the middle button from the group of window controls in the top right of Xcode to open the console:
We did it
Eventually you’ll see this message in the console in Xcode:
We have just successfully called Go code from Swift, and had a Swift method called by Go. 👍
Conclusion
We have successfully solved two-way communication between Swift and Go. Which means, in theory, you can build any kind of application you like.
But be careful, Gomobile is still very young and there are some limitations that you need to be aware of as you design your solution.
The remainder of this article will discuss limitations, as well as other ideas that might increase the chances of success.
Not all types are supported
Currently some types (like slices) are not supported by Gomobile, so you’ll have to wrap things in structs and methods. This is a pain, because your Go code ends up looking very non-Go, and if you have other customers for your code, you might end up with a pretty ugly and hard to use package.
It is assumed that slices will end up being supported by Gomobile, so this might be a temporary thing.
- For more information about this read about Supported Go types for Android with gomobile bind by Alex Pliutau
Bridging?
You might consider writing a bridge package that sits between your idiomatic Go code, and your Swift code. This bridge package would become the framework that gets imported.
In the bridge package, you could present the bare minimum capabilities that you want including in your package, and don’t have to worry about unsupported features from the rest of the code.
And you could build some method based slice abstraction without ruining your original Go code.
Keep up Xcode
Xcode tries to help keep things quick by caching a lot. This means, that if you change your framework API (say you rename the Greet
method to SendGreeting
) you might struggle to get Xcode to recognise the changes. I managed to get around this by creating a new Xcode project each time, but obviously as you get further down a project, that’s not a great solution.
You might want to try updating the framework in place.
Update in place
Instead of having a frameworks folder at all, you could ask the gomobile
tool to put the output directly into the Xcode project. You can do this just by figuring out the appropriate path, like this:
gomobile bind -target ios -o ../../ios/GoMobileTester/Greeter.framework
This way, whenever you rebuild your framework, it’ll update the files directly.
What next?
- Build something awesome
- Browse the Gomobile documentation
- If you like the style of this tutorial and want to build more things in Go, then please consider buying my book Go Programming Blueprints: Second Edition.