本文加上读者对Go语言和Swift语言都有必定了解, 可是对两者混合使用不了解的同窗.ios
本教程是基于一个真实上架的iOS应用作的简单的总结。git
咱们先看看运行效果:github
Go语言是Google公司于2010年开源的一个面向网络服务和多并发环境的编程语言,特色是简单。 可是由于简单,也就只能实现90%的性能,这是Go语言的最大优势,由于 少便是多 的道理不是每一个人都能领悟的。编程
Swift是Apple公司于2014年发布的用来替代ObjectiveC的语言,主要面向iOS和OS X上的界面程序开发。 固然用swift来开发服务器也是你们关注的一个领域,做者看好在不远的未来Swift将逐步替代C++和Rust语言。swift
Go语言和Swift语言原本是风马牛不相及的两个语言,为什么非必定要整到一块儿呢? 缘由很简单,由于做者是一个Go粉,同时也算是半个Swift粉;想试水iOS开发,可是实在是受不了ObjectiveC的裹脚布语法。xcode
补充下:本人虽然不喜欢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函数的方法有多种:经过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语言自己是自带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语言提供了一个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架构:模拟器的x8六、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工程文件就能够了。
x86构建是比较简单的,由于咱们能够默认使用本地的构建命令。 可是,若是要构建arm的静态库,则须要先配置好构建环境。
我从Go代码中扣出了一个clangwrap.sh
脚本(好像是在$GOROOT/misci/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开发过程逐步学习总结的结果。
完整的例子:
全部的代码都可以避免费获取(BSD协议): https://github.com/chai2010/ptyy