使用Go語言開發iOS應用(Swift版)
使用Go語言開發iOS應用(Swift版)
本文假設讀者對Go語言和Swift語言都有一定了解, 但是對二者混合使用不瞭解的同學.
本教程是基於一個真實上架的iOS應用做的簡單的總結。
我們先看看執行效果:
掃碼安裝:
背景
Go語言是Google公司於2010年開源的一個面向網路服務和多併發環境的程式語言,特點是簡單。 但是因為簡單,也就只能實現90%的效能,這是Go語言的最大優點,因為 少即是多 的道理不是每個人都能領悟的。
Swift是Apple公司於2014年釋出的用來替代ObjectiveC的語言,主要面向iOS和OS X上的介面程式開發。 當然用swift來開發伺服器也是大家關注的一個領域,作者比較看好在不遠的將來Swift將逐步替代C++和Rust語言。
Go語言和Swift語言本來是風馬牛不相及的兩個語言,為何非一定要整到一起呢? 原因很簡單,因為作者是一個Go粉,同時也算是半個Swift粉;想試水iOS開發,但是實在是受不了ObjectiveC的裹腳布語法。
補充下:本人雖然不喜歡ObjectiveC的語法,但是覺得ObjectiveC的runtime還是很強悍的。 理論上,基於ObjectiveC的runtime,可以用任何流行的程式語言來開發iOS應用,RubyMotion就是一個例子。
其實,現在流行的絕大部分語言都有一個交集,就是c語言相容的二進位制介面。 所以說,C++流行並不是C++多厲害,而是它選擇無縫相容了C語言的絕大部份規範。
但是,完全相容C語言的規範也有缺點,就是語言本身無法自由地發展,因為很多地方會受到C語言程式設計模型的限制。 C++和ObjectiveC是兩個比較有代表的例子。
所以說,Swift一出世就只相容C語言的二進位制介面規範,同時抱緊了ObjectiveC的runtime大腿,而去自己確實有很大優秀的特性。
但是,我們這裡暫時不關心Swift和ObjectiveC的混合程式設計,我們只關注作為ObjectiveC子集的C語言如何與Swift混合程式設計。
Swift呼叫C函式
Swift呼叫C函式的方法有多種:通過ObjectiveC橋接呼叫和直接呼叫。其實兩者的原理是一樣的,我個人跟喜歡選擇最直接也最暴力的直接呼叫C函式的方式。
比如有一個C函式:
#include <stdio.h>
void getInput(int *output) {
scanf("%i", output);
}
生成一個橋接的標頭檔案xxx-Bridging-Header.h
,裡面包含c函式規格說明:
void getInput(int *output);
swift就可以直接使用了:
import Foundation
var output: CInt = 0
getInput(&output)
println(output)
如果不用橋接檔案,可以在swift中宣告一個Swift函式,對應C函式:
@_silgen_name("getInput") func getInput_swift(query:UnsafePointer<CInt>)
為了明確區分C函式和swift函式,我們將getInput
重新宣告為getInput_swift
,使用方法和前面一樣:
import Foundation
var output: CInt = 0
getInput_swift(&output)
println(output)
Swift中如何管理c返回的記憶體
Swift語言本身是自帶ARC的,使用者很少直接關注記憶體問題。但是C函式如果返回記憶體到Swift空間, Swift的ARC是無效的,需要手工釋放C記憶體。
假設我們自己用C語言實現了一個字串克隆的函式:
char* MyStrDup(char* s) {
return strdup(s);
}
在swift中可以這樣使用:
@_silgen_name("MyStrDup")
func MyStrDup_swift(query:UnsafePointer<CChar>) -> UnsafeMutablePointer<CChar>
let p = MyStrDup_swift("hello swift-c!")
let s = String.fromCString(p)!
p.dealloc(1)
使用String.fromCString(p)!
從C字串構建一個swift字串,然後手工呼叫p.dealloc(1)
釋放c字串記憶體空間。
函式呼叫和記憶體管理是跨語言程式設計中最重要的兩個基礎問題,目前已久初步可以工作了。
Go語言匯出C靜態庫
Go語言提供了一個cgo的工具,用於Go語言和C語言互動。這是Go語言使用C語言的一個例子:
package main
//#include <stdio.h>
import "C"
func main() {
C.puts(C.CString("abc"))
}
既然要互動,自然會涉及到C語言回撥Go語言函式的情形。為此,cgo提供了一個export
註釋命令,
用於生成Go語言函式對應的C語言函式:
//export MyStrDup
func MyStrDup(s *C.char) *C.char {
return C.strdup(s)
}
MyStrDup
指定的名字必須和Go函式名字一致,函式的引數最後是C語言支援的型別。
現在,我們就得到了用Go語言實現的MyStrDup
函式,使用方法和前面的C語言實現的MyStrDup
是一樣的。
和引用C語言函式庫遇到的問題一樣,我們如何在工程中引用這些C程式碼或Go程式碼實現的函式呢?
答案還是來自C語言:將程式碼構建為C靜態庫或者C動態庫,然後將靜態庫或動態庫匯入Swift工程。
但是,對於iOS來說,構建C靜態庫或者C動態庫的過程要麻煩(使用xcode也只是隱藏了構建的具體步驟)。
因為,iOS涉及到多種CPU架構:模擬器的x86、4s的32位arm、5s以後的64位arm,64位arm中還有不同當版本...
這是C靜態庫或者C動態庫構建始終都要面對的問題。
交叉構建的引數
Go1.6之後增加了構建C靜態庫的支援,交叉編譯也非常簡單,只需要設定好GOARCH
和GOOS
就行。
因為,iOS的GOOS
只有Darwin
一種型別,我們只需要設定GOARCH
就可以了。
要構建C靜態庫,我們需要將上面的MyStrDup
實現放到一個main
包中:
package main
//#include <string.h>
import "C"
func main() {
//
}
//export MyStrDup
func MyStrDup(s *C.char) *C.char {
return C.strdup(s)
}
main
包中的main
函式不會被執行,但是init
函式依然有效。
使用下面的命令就可以構建當前系統的c靜態庫:
go build -buildmode=c-archive
要交叉編譯iOS可用的c靜態庫,我們需要先設定GOARCH
,同時開啟cgo特性(交叉編譯時,cgo預設是關閉的)。
下面是構建針對模擬器的x86/amd64型別的C靜態庫:
export CGO_ENABLED=1
export GOARCH=amd64
go build -buildmode=c-archive -o libmystrdup_amd64.a
我們使用-o
引數指定了輸出的靜態庫檔名。構建命令同時還會生成一個標頭檔案(可能叫libmystrdup_386.h
),
我們沒有用到這個標頭檔案,直接刪除掉就可以。
下面是構建針對模擬器的x86/386型別的C靜態庫:
export CGO_ENABLED=1
export GOARCH=386
go build -buildmode=c-archive -o libmystrdup_386.a
在構建x86/386型別的C靜態庫時可能會有一些link錯誤,我們暫時先用以下方法迴避。
建立一個patch_386.go
檔案:
// Copyright 2016 <chaishushan{AT}gmail.com>. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// 針對iOS模擬器link時缺少的函式
// 屬於臨時解決方案
package main
/*
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
size_t fwrite$UNIX2003(const void* a, size_t b, size_t c, FILE* d) {
return fwrite(a, b, c, d);
}
char* strerror$UNIX2003(int errnum) {
return strerror(errnum);
}
time_t mktime$UNIX2003(struct tm * a) {
return mktime(a);
}
double strtod$UNIX2003(const char * a, char ** b) {
return strtod(a, b);
}
int setenv$UNIX2003(const char* envname, const char* envval, int overwrite) {
return setenv(envname, envval, overwrite);
}
int unsetenv$UNIX2003(const char* name) {
return unsetenv(name);
}
*/
import "C"
當然,還是會有一些警告出現,暫時忽略它們。
然後,將C靜態庫加入到ios的xcode工程檔案就可以了。
構建多cpu型別的靜態庫
x86構建是比較簡單的,因為我們可以預設使用本地的構建命令。 但是,如果要構建arm的靜態庫,則需要先配置好構建環境。
我從Go程式碼中扣出了一個clangwrap.sh
指令碼(好像是在$GOROOT/misc/ios
目錄):
#!/bin/sh
# This uses the latest available iOS SDK, which is recommended.
# To select a specific SDK, run 'xcodebuild -showsdks'
# to see the available SDKs and replace iphoneos with one of them.
SDK=iphoneos
SDK_PATH=`xcrun --sdk $SDK --show-sdk-path`
export IPHONEOS_DEPLOYMENT_TARGET=7.0
# cmd/cgo doesn't support llvm-gcc-4.2, so we have to use clang.
CLANG=`xcrun --sdk $SDK --find clang`
if [ "$GOARCH" == "arm" ]; then
CLANGARCH="armv7"
elif [ "$GOARCH" == "arm64" ]; then
CLANGARCH="arm64"
else
echo "unknown GOARCH=$GOARCH" >&2
exit 1
fi
exec $CLANG -arch $CLANGARCH -isysroot $SDK_PATH "$@"
裡面比較重要的是IPHONEOS_DEPLOYMENT_TARGET
環境變數,這裡意思是目標最低支援ios7.0系統。
構建arm64環境的靜態庫:
export CGO_ENABLED=1
export GOARCH=arm64
export CC=$PWD/clangwrap.sh
export CXX=$PWD/clangwrap.sh
go build -buildmode=c-archive -o libmystrdup_arm64.a
構建armv7環境的靜態庫:
export CGO_ENABLED=1
export GOARCH=arm
export GOARM=7
export CC=$PWD/clangwrap.sh
export CXX=$PWD/clangwrap.sh
go build -buildmode=c-archive -o libmystrdup_armv7.a
然後我們用lipo
命令將以上這些不同的靜態庫打包到一個靜態庫中:
lipo libmystrdup_386.a libmystrdup_adm64.a libmystrdup_arm64.a libmystrdup_armv7.a -create -output libmystrdup.a
這樣的話,只要引入一個靜態庫就可以支援不同cpu型別的目標了。
總結
毛主席教導我們:要在戰爭中學習戰爭。
野雞醫院 這個app是作者第一個iOS應用,這篇教程也是在iOS開發過程逐步學習總結的結果。 裡面肯定有很多描述不準確的地方,作者歡迎任何不同意見或建議。
完整的例子:
- AppStore安裝: https://appsto.re/cn/QH8ocb.i
- Swift工程: https://github.com/chai2010/ptyy/tree/master/ios-app/yjyy-swift
- Go靜態庫工程: https://github.com/chai2010/ptyy/tree/master/cmd/yjyy
- 靜態庫構建指令碼: https://github.com/chai2010/ptyy/tree/master/ios-app/yjyy-swift/vendor/gopkg
所有的程式碼均可以免費獲取(BSD協議): https://github.com/chai2010/ptyy
相關文章
- ios應用開發+swift語言入門iOSSwift
- iOS 藍芽開發 - swift版iOS藍芽Swift
- 2021 年 iOS 應用程式開發七種最佳語言iOS
- Go語言開發者福利 - 國內版 The Go PlaygroundGo
- Go語言開發者福利 – 國內版 The Go PlaygroundGo
- GO語言整合開發工具:GoLand 2023 Mac 中文啟用版GoLandMac
- 使用go語言開發hive匯出工具GoHive
- 使用Go語言開發短地址服務Go
- Go語言反射(reflect)及應用Go反射
- go語言安卓開發Go安卓
- [譯]iOS開發者在Swift中應避免過度使用@objciOSSwiftOBJ
- GoLand 2022(GO語言整合開發工具環境)mac版GoLandMac
- go語言使用Go
- Go語言開發環境搭建Go開發環境
- go語言開發有哪些工具Go
- GO 語言快速開發入門Go
- GO語言整合開發GoLand 2022GoLand
- Go 語言 sync 包的應用詳解Go
- iOS 多國語言本地化與App內語言切換(Swift)iOSAPPSwift
- Go語言核心36講(Go語言實戰與應用二十)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十九)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十八)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十七)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十三)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十四)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十五)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十六)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用九)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十二)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用十一)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用七)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用八)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用五)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用一)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用二)--學習筆記Go筆記
- Go語言核心36講(Go語言實戰與應用四)--學習筆記Go筆記
- 使用go語言開發自動化API測試工具GoAPI
- [Go語言整合開發環境之GoLand安裝使用]開發環境GoLand
- GO語言併發Go