Port a Golang game to iOS
Tags: go, ios, game
Published: 2018-09-06 13:03:53

---
title: Port a Golang game to iOS
published: true
description: A way to build actual iOS game with Golang
tags: go,ios,game
---

These days, I'm porting our Android game [Shoot Stack](https://play.google.com/store/apps/details?id=io.korok.shootingblock) to iOS. It's a game written in Golang with our own [game engine](https://korok.io). This article will give some tips and our experiences in poring Golang game to iOS. If you have ever used the GoMobile you know that `gomobile build` can build `.app` and install directly to an iOS device. But if you want to integrate third-party SDK, like ads, analytics, it's impossible with `gomobile build`. We need a way to build library, then we can use it in xcode and `gomobile bind` just do this.

Here is our project structure:
```
✗ tree .
.
├── arena.go
├── audio.go
├── build.md
├── color.go
├── file.go
├── flash_message.go
├── gameover.go
├── input_glfw.go
├── input_mob.go
├── main.go
├── particle.go
├── ready.go
└── welcome.go
```
yeah, it's all in a main package. Using `gomobile bind` command, we can build `.framework` file for xcode. But we still need a way to invoke the `main` method of our lib. All xcode project has a `main.h` file:
```
int main(int argc, char * argv[]) {
@autoreleasepool {
	return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
```
So we can invoke our lib's main method here. As I said above, all my code stay in a `main` package, but GoMobile cann't bind file in `main`(it's just not supported). So I do have to change a little to my project:

```
✗ tree .
.
├── ios
│   └── ios.go
└── main
    ├── game
    │   ├── arena.go
    │   ├── audio.go
    │   ├── build.md
    │   ├── color.go
    │   ├── file.go
    │   ├── flash_message.go
    │   ├── gameover.go
    │   ├── input_glfw.go
    │   ├── input_mob.go
    │   ├── main.go
    │   ├── particle.go
    │   ├── ready.go
    │   └── welcome.go
    └── main.go
```
I put the source code in `main/game` package, then I can still use `gomobile build` to build Desktop and Android. Then I created `ios/ios.go` file, it's not a main file, so I can build it with `gomobile bind`, the content:
```
package ios

import (
   "korok.io/korok/gfx/dbg"
   "korok.io/korok"
   "korok.io/korok/math/f32"
   "main/game"
)

func Run() {
   dbg.DEBUG = dbg.None
   option := korok.Options{
      Width:360,
      Height:640,
      Clear:f32.Vec4{0,0,0,1},
   }
   korok.Run(&option, &game.StartScene{})
}
```
The method `Run` is same as the `main.main()`:
```
func main() {
	dbg.DEBUG = dbg.None
	option := korok.Options{
		Width:360,
		Height:640,
		Clear:f32.Vec4{0,0,0,1},
	}
	korok.Run(&option, &StartScene{})
}
```
It's just created to easy the bind process. Now, we can use `gomobile bind -target=ios ios` to build a `.framework`. If all is OK, next, we can integrate the `.framework` to our xcode project(just drag it to xcode). Modify the `main.h` file as following:

```
#import "Ios/Ios.h"

int main(int argc, char * argv[]) {
    IosRun();
}
```
Build & Run, now the project (with Go inside) works. 

## How to add ads SDK?

Now, we have successfully created a library and used it in xcode project. If we want to integrate Ad SDK, we still need add some lifecycle hook in the original `.m` file used by  GoMobile. In the package `x/mobile/app`, we can find a file `darwin_ios.m`, this is the file GoMobile used to build iOS application, there are two `@interface` defined here:
```
@interface GoAppAppController : GLKViewController<UIContentContainer, GLKViewDelegate>
@end

@interface GoAppAppDelegate : UIResponder<UIApplicationDelegate>
@end
``` 
I will hook the lifecycle method, so that I can use it in the xcode project:
```
@implementation GoAppAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    ...
    [self kkDidFinishLaunching];
    return YES;
}
- (void)viewDidLoad {
    ...
    [self kkViewDidLoad];
}
```
Then create a file (`ad.m`) in the xcode project, add the following code:
```
@implementation GoAppAppDelegate(AD)
-(void)kkDidFinishLaunching {
    NSLog(@"hi, implemenmt method");
}
@end

@interface GoAppAppController(AD)
@end

@implementation GoAppAppController(AD)

- (void)kkViewDidLoad {
    NSLog(@"view did load2..");
@end
```
Here we used the Category in Obj-C language. It'll implement the method we used in 'darwin_ios.h'.

Build & Run, you should see the log info printed in Console.

Here is a snapshot of our game (with ad, but I'll remove it when release):
 
![Golang game with admob](https://thepracticaldev.s3.amazonaws.com/i/vgvwlr5gxjwac1o8xwek.jpg)

## Other problem

Life is not easy, in fact, it takes days to build a actual game and run it on my iPhone6. There is a problem I just don't know how to solve it, the problem is when the Application suspended and reactive to the foreground the App will freeze. I have tested the basic example provide by GoMobile, it also have the problem, it's bug. In the basic example, it uses `switch e.Crosses(lifecycle. StageVisible)` to manage app lifecycle, if you change the `lifecycle. StageVisible` to `lifecycle. StageAlive`, it'll freeze.

I have digged into the problem for serval days. It seems that GoMobile use a custom loop to draw GL command, and call:
```
[EAGLContext setCurrentContext:ctx];
[ctx presentRenderbuffer:GL_RENDERBUFFER];
```
to swap buffer, but this code will fail if the app suspended and activated again. I tried serval ways to solve the problem, but it's not that easy as I thought, at last, I given up and created an issue in Github: [application freeze after resumed from suspend state on iOS ](https://github.com/golang/go/issues/27483). But I still need a way to work around the bug, after some research in the git commit, I found that the old implementation --- just use the system loop instead of Go loop. So I rollback to the old implementation:

```
self.glview.enableSetNeedsDisplay = NO;
CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(render:)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

- (void)render:(CADisplayLink*)displayLink {
    [self.glview display];
}

- (void)glkView:(GLKView *)view drawInRect:(CGRect)rect {
    drawloop();
}
//export drawloop
func drawloop() {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	for workAvailable := theApp.worker.WorkAvailable();;{
		select {
		case <-workAvailable:
			theApp.worker.DoWork()
		case <-theApp.publish:
			theApp.publishResult <- PublishResult{}
			return
		case <-time.After(50 * time.Millisecond): // incase the method blocked!!
			return
		}
	}
}
``` 
I also found the same(mostly) implementation in Cocos2DX and [Ebiten](https://github.com/hajimehoshi/ebiten). Finally, all the problem solved!! I'm uploading the Game to Apple Store Connect, you can download it in several days or weeks(you know AppStore review is not easy, too).