使用 Go Mobile 开发跨平台 Library

前情提要:

使用 Kotlin Native 开发跨平台 Library

为什么使用 Go Mobile

相对于 Kotlin Native 而言,Go 有更完善的生态支持,更小的二进制体积。

虽然 Go Mobile 维护者有跑路的嫌疑,但通过第三方的 Fork 我们已经可以支持 Apple Silicon 和 Catalyst.

下面还是用和使用 Kotlin Native 开发跨平台 Library里一样的 NASA API 来做一个 SDK,看看使用体验如何。

创建 Go Module

你可以到 gomobile-lib-demo 下载已经完成的项目

首先定义 GOPATH,我选了用户 zhoukaiwen 目录的 golang 文件夹。

export GOPATH=/Users/zhoukaiwen/golang

找个 GOPATH 之外的地方,创建 module 文件夹

mkdir go_lib_demo
cd go_lib_demo
go mod init Hello

编辑 go.mod 增加一个好用点的 HTTP 库 resty 依赖,以及解决了 Catalyst 和 Apple Silicon 的第三方 gomobile github.com/ydnar/gomobile

module Hello

go 1.16

require (
	github.com/go-resty/resty/v2 v2.6.0
	golang.org/x/mobile v0.0.0-20210614202936-7c8f154d1008 // indirect
)

replace golang.org/x/mobile v0.0.0-20210614202936-7c8f154d1008 => github.com/ydnar/gomobile v0.0.0-20210301201239-fb6ffafc9ef9

获取依赖

go get github.com/go-resty/resty/v2
go get golang.org/x/mobile/cmd/gomobile
go get golang.org/x/mobile/bind

初始化 gomobile

gomobile init

创建 API

使用 NASA 的 API 获得 Astronomy Picture of the Day 的 JSON 数据.

https://api.nasa.gov/planetary/apod?api_key={API_KEY}

src/hello/Nasa.go

package hello

import (
	"encoding/json"
	"fmt"

	"github.com/go-resty/resty/v2"
)

type APOD struct {
	Date           string `json:"date"`
	Explanation    string `json:"explanation"`
	HDurl          string `json:"hdurl"`
	MediaType      string `json:"media_type"`
	ServiceVersion string `json:"service_version"`
	Title          string `json:"title"`
	Url            string `json:"url"`
}

type NasaPath string

const (
	nasaBaseURL NasaPath = "https://api.nasa.gov"
)

var (
	apodPath NasaPath = "/planetary/apod"
)

func (m NasaPath) fullPath() string {
	return fmt.Sprint(nasaBaseURL + m)
}

type NasaClient struct {
	ApiKey string
}

func (nasaClient *NasaClient) GetAPOD() (*APOD, error) {
	url := apodPath.fullPath()

	client := resty.New()

	resp, err := client.R().
		SetQueryParams(map[string]string{
			"api_key": nasaClient.ApiKey,
		}).
		Get(url)

	if err != nil {
		return nil, err
	}

	var apod APOD
	if err := json.Unmarshal([]byte(resp.Body()), &apod); err != nil {
		return nil, err
	}

	return &apod, nil
}

编译出 xcframework

gomobile bind -target ios ./src/hello 

创建 Swift Package

方法和使用 Kotlin Native 开发跨平台 Library一样,你可以到 go_lib_swift_package_demo 查看已经完工的项目。

import PackageDescription

let package = Package(
    name: "go_lib_swift_package_demo",
    products: [
        .library(
            name: "go_lib_swift_package_demo",
            targets: ["go_lib_swift_package_demo", "HappyNasa"]),
    ],
    targets: [
        .target(
            name: "go_lib_swift_package_demo",
            dependencies: []),
        .binaryTarget(
                    name: "HappyNasa",
                    path: "Sources/hello.xcframework"),
        .testTarget(
            name: "go_lib_swift_package_demoTests",
            dependencies: ["go_lib_swift_package_demo"]),
    ]
)

在 iOS 中使用

你可以到 go_lib_ios_demo 查看已经完工的项目。

import UIKit
import Hello

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        let client = HelloNasaClient()
        client.apiKey = "{API_KEY}"

        do {
            let apod = try client.getAPOD()
            print(apod.title)
            print(apod.explanation)
        } catch let error {
            print(error.localizedDescription)
        }
    }
}

后记

相对于 Kotlin Native,Go 编译出的 SDK 要小很多,但比较遗憾的是 go func 在 Go Mobile 中并不能使用。如果你在函数里使用了 go func, 在编译后这个函数会被自动删除。