标题要唬人…在做Golang跨平台的时候,使用gomobile bind -target=ios xxx 就可以生成对应平台的framework。
本文讨论的内容是Golang是如何实现了OC和Go的数据通讯与接口调用,以及如何做到让开发者无感知的从OC对象的引用计数切换到Golang的垃圾回收机制。
其实Go的语言层面的通讯实际上是由bind生成的中间层的代码来实现的,什么是中间层的代码?我们简单做实验…
写一个Go的例子,以下所有方法就是我们本文需要讨论的所有内容了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package g_mb_test type GMBInterface interface { InterfaceTest() } type GMBTest struct { } func (t *GMBTest) GMBTestOne(value int32) { } func (t *GMBTest) GMBTestSecond(data []byte, str string) { } func (t *GMBTest) GMBTest3() string { return “” } func (t *GMBTest) GMBTest4() []byte { return nil } func (t *GMBTest) GMBTest5() GMBInterface { return nil } func (t *GMBTest) GMBTest6() *GMBTest { return nil } func (t *GMBTest) GMBTest7(i GMBInterface) { }
Go 导出的对象在OC中是怎么获取到的?
OC是怎么调用Golang的对象方法的?
OC是怎么传递给Golang参数的?不同的类型是否一样(NSData -> []byte, NSString -> string, interface(OC) -> interface)?
OC传递给Golang 的参数Golang怎么释放(或者OC怎么释放)?
相反Golang是怎么返回值的?不同的类型是否一样([]byte -> NSData, string -> NSString, interface -> interface(OC))?
Golang 如果返回的是一个被导出的类型,又是什么样的?
首先Golang本身是由C来实现的,所以Golang可以”无压力”和C进行双向通讯(cgo)。其次,OC的本质其实也是C,OC也是可以和C无缝进行双向通讯。在这两个条件下我们可以假想,OC和Golang 的双向通讯是不是也可以用C?
实验开始,我们使用命令行gomobile bind -target=ios/amd64 -work Caio/g_mb_test来保留工作目录。
找到控制台输出的临时目录,生成了如下的结构。go-buildxxx下是生成的中间产物。而src下就是gomobile自动生成的中间代码。
先从对象的初始化开始… 1 2 3 4 5 6 7 - (instancetype)init { self = [super init]; if (self) { __ref = go_seq_from_refnum(new_g_mb_test_GMBTest()); } return self; }
new_g_mb_test_GMBTest 实际是由 go在静态库中实现的方法,可以从g_mb_test-amd64.h头文件中找到它的声明。对应了src中的go_g_mb_testmain.go文件中的new_g_mb_test_GMBTest()方法。这个go文件是gomobile在编译go之前自动生成的代码。
可以理解为在iOS中引入了一个”静态 framework”,这个静态库实现了new_g_mb_test_GMBTest()。只不过这个framework是由Go语言实现的。
可以看到 new_g_mb_test_GMBTest()返回了一个 int32值,而这个值最后传递给了go_seq_from_refnum(),这个方法是在seq_darwin.m中。
1 2 3 4 5 6 7 8 9 GoSeqRef *go_seq_from_refnum(int32_t refnum) { if (refnum == NULL_REFNUM) { return nil; } if (IS_FROM_GO(refnum)) { return [[GoSeqRef alloc] initWithRefnum:refnum obj:NULL]; } return [[GoSeqRef alloc] initWithRefnum:refnum obj:go_seq_objc_from_refnum(refnum)]; }
通过”字面”意思看,是返回了一个GoSeqRef,通过IS_FROM_GO判断传入的refnum(也就是上面提到的new_g_mb_test_GMBTest()返回的值是不是Go生成的),来决定GoSeqRef中的obj是否为NULL。如果不是Go生成的那么就返回go_seq_objc_from_refnum()转换refnum为obj,这个分支我们之后再讲。
那么我们看看这个所谓的”静态库”是怎么生成这个int32值的。注:以下运行的代码已经在Go中了
1 2 3 4 5 *go_g_mb_testmain.go* //export new_g_mb_test_GMBTest func new_g_mb_test_GMBTest() C.int32_t { return C.int32_t(_seq.ToRefNum(new(g_mb_test.GMBTest))) }
可以看到 new_g_mb_test_GMBTest()返回了一个 int32,而这个int32值还是由_seq.ToRefNum()方法返回的。这个方法是由golang.org/x/mobile/bind/seq/ref.go 下的bind提供的。方法参数就是一个go struct对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 *golang.org/x/mobile/bind/seq/ref.go* // ToRefNum increments the reference count for an object and // returns its refnum. func ToRefNum(obj interface{}) int32 { // We don’t track foreign objects, so if obj is a proxy // return its refnum. if r, ok := obj.(proxy); ok { refnum := r.Bind_proxy_refnum__() if refnum <= 0 { panic(fmt.Errorf("seq: proxy contained invalid Go refnum: %d", refnum)) } return refnum } refs.Lock() num := refs.refs[obj] if num != 0 { s := refs.objs[num] refs.objs[num] = countedObj{s.obj, s.cnt + 1} } else { num = refs.next refs.next-- if refs.next > 0 { panic(“refs.next underflow”) } refs.refs[obj] = num refs.objs[num] = countedObj{obj, 1} } refs.Unlock() return int32(num) }
关于proxy那块的代码,我们稍等之后再看。后面的代码也不急着读,先看后面提到的refs对象:
1 2 3 4 5 6 7 // refs stores Go objects that have been passed to another language. var refs struct { sync.Mutex next int32 // next reference number to use for Go object, always negative refs map[interface{}]int32 objs map[int32]countedObj }
注释写的很明确了,refs存储了从Golang传递给其他语言的对象。
结构体中有两个map成员变量,一个是refs,一个是objs。refs的key是对象,value就是上面提到的refnum,objs的key是refnum,value是这个Go对象的引用计数对象。还有一个变量是next,next是下一个Go对象的refnum,而且永远是负数。
那么上面的后半段代码也就很容易看懂了。从ref取出obj对应的num,计数加一。如果没有的话,next-1作为下一个refnum,把对象存进map。
所以:
在Go中有一个全局变量,维护了所有外部正在使用的Go对象。
从Go生成的对象,想要被外部使用,都是通过一个refnum的值在这个全局变量中索引的。
并且还维护了一个引用计数,每次取这个对象都会+1。
另外,从Go导出的refnum都是负数。
到这里,我们其实可以小总结 一下了: 从上面的代码可以知道 Go中的对象在 oc 中的体现是:
OC对象持有一个GoSeqRef,GoSeqRef 持有一个 refnum,refnum在Go中的refs的objs和refs中存储了真正的Go对象和引用计数值。
再看一个OC方法的基本调用 既然已经生成Golang的对象了,我们看一下 gmbTestOne 这个基本调用。
1 2 3 4 5 6 - (void)gmbTestOne:(int32_t)value { int32_t refnum = go_seq_go_to_refnum(self._ref); int32_t _value = (int32_t)value; proxyg_mb_test_GMBTest_GMBTestOne(refnum, _value); }
go_seq_go_to_refnum()是在Seq_darwin.m中实现的。
1 2 3 4 - (int32_t)incNum { IncGoRef(_refnum); return _refnum; }
可以看到最终是返回了_refnum。但是在此之前,还有一个IncGoRef(_refnum); 可以看ref.go的实现,方法实现是增加Go对象的引用计数。思考: 从OC ARC的对象回收的角度来看,似乎”言之有理”。”int32_t refnum “虽然是个int,但是得要持有这个对象,不然会有可能在调用过程中被释放。那么猜想一样,它会在方法域结束的时候释放?我们接着看…
然后和上面的逻辑一样,proxyg_mb_test_GMBTest_GMBTestOne() 是framework实现的方法,第一个参数是refnum,第二个参数是value的值。
猜想它的实现应该是,通过 refnum找到Go中的对象,然后调用对应的方法。我们来看Go的实现:注:以下运行的代码已经在Go中了
1 2 3 4 5 6 7 //export proxyg_mb_test_GMBTest_GMBTestOne func proxyg_mb_test_GMBTest_GMBTestOne(refnum C.int32_t, param_value C.int32_t) { ref := _seq.FromRefNum(int32(refnum)) v := ref.Get().(*g_mb_test.GMBTest) _param_value := int32(param_value) v.GMBTestOne(_param_value) }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // FromRefNum returns the Ref for a refnum. If the refnum specifies a // foreign object, a finalizer is set to track its lifetime. func FromRefNum(num int32) *Ref { if num == NullRefNum { return nil } ref := &Ref{num} if num > 0 { // This is a foreign object reference. // Track its lifetime with a finalizer. runtime.SetFinalizer(ref, FinalizeRef) } return ref }
和OC中一样,在Go中也有一个Ref对象,这个对象包裹的就是一个refnum。 num > 0是指代外部传入的对象,我们先不看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 // Get returns the underlying object. func (r *Ref) Get() interface{} { refnum := r.Bind_Num refs.Lock() o, ok := refs.objs[refnum] refs.Unlock() if !ok { panic(fmt.Sprintf(“unknown ref %d”, refnum)) } // This is a Go reference and its refnum was incremented // before crossing the language barrier. Delete(refnum) return o.obj }
前半段印证了我们的猜想,取出对象并且返回。Delete的实现是引用计数-1,如果到0了,那就从map中删除。
印证我们上面思考中的猜想! Golang对象并不是在结束的时候才释放,在Golang中 v := ref.Get().(*g_mb_test.GMBTest) 已经持有,其实不必再持有了,等Golang的方法结束的时候自然会通过Golang的垃圾回收释放。
最后代码就是一个Go的方法调用了,把int参数传入。
总结一下: OC调用Golang的方法,实际是,在OC中调用方法的时候,先用self.ref 获取 refnum。这个步骤会增加Go中对象的引用计数。然后调入Go的方法,把refnum传入,取出Golang对象,减小引用计数。然后调用具体实现的方法。
Golang返回对象 Golang的方法返回对象其实和上面的初始化方法大同小异,只是结合了基本方法调用。注:Go返回的对象类型必须是Go导出的类型,非Go导出的类型Go不识别或者OC不识别(除了NSData,NSString等基本类型)。
1 2 3 4 5 6 7 8 9 10 11 12 13 - (G_mb_testGMBTest*)gmbTest6 { int32_t refnum = go_seq_go_to_refnum(self._ref); int32_t r0 = proxyg_mb_test_GMBTest_GMBTest6(refnum); G_mb_testGMBTest* _ret0_ = nil; GoSeqRef* _ret0__ref = go_seq_from_refnum(r0); if (_ret0__ref != NULL) { _ret0_ = _ret0__ref.obj; if (_ret0_ == nil) { _ret0_ = [[G_mb_testGMBTest alloc] initWithRef:_ret0__ref]; } } return _ret0_; }
proxyg_mb_test_GMBTest_GMBTest6()方法返回了一个int值,这个值必然是对应的Golang的对象。
1 2 3 4 5 6 7 8 9 10 11 //export proxyg_mb_test_GMBTest_GMBTest6 func proxyg_mb_test_GMBTest_GMBTest6(refnum C.int32_t) C.int32_t { ref := _seq.FromRefNum(int32(refnum)) v := ref.Get().(*g_mb_test.GMBTest) res_0 := v.GMBTest6() var _res_0 C.int32_t = _seq.NullRefNum if res_0 != nil { _res_0 = C.int32_t(_seq.ToRefNum(res_0)) } return _res_0 }
方法调用的地方我们不再详述,返回的res_0如果是nil,则会返回_seq.NullRefNum = 41。即用一个特定的值来表示null。 返回后OC同样的会在go_seq_from_refnum()中判断41这个值。
总结一下: Go返回对象实际是返回一个refnum值,OC使用GoSeqRef来存储这个值。(返回的对象始终是Go已导出的对象类型)
Golang返回接口类型 实际上Go和其他语言通讯大部分都会使用接口类型
1 2 3 4 5 6 7 8 9 10 11 12 13 - (id<G_mb_testGMBInterface>)gmbTest5 { int32_t refnum = go_seq_go_to_refnum(self._ref); int32_t r0 = proxyg_mb_test_GMBTest_GMBTest5(refnum); G_mb_testGMBInterface* _ret0_ = nil; GoSeqRef* _ret0__ref = go_seq_from_refnum(r0); if (_ret0__ref != NULL) { _ret0_ = _ret0__ref.obj; if (_ret0_ == nil) { _ret0_ = [[G_mb_testGMBInterface alloc] initWithRef:_ret0__ref]; } } return _ret0_; }
proxyg_mb_test_GMBTest_GMBTest5()和上面的proxyg_mb_test_GMBTest_GMBTest6()返回对象没有什么差别,主要差别在于处理返回的值变成go中的refnum。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @implementation G_mb_testGMBInterface { } - (instancetype)initWithRef:(id)ref { self = [super init]; if (self) { __ref = ref; } return self; } - (void)interfaceTest { int32_t refnum = go_seq_go_to_refnum(self._ref); proxyg_mb_test_GMBInterface_InterfaceTest(refnum); } @end
在g_mb_test_darwin.m中自动生成了一个G_mb_testGMBInterface类,而这个类实现了G_mb_testGMBInterface,也就是我们定义的接口,并且存储了GO返回的refnum。而接口的实现内调用了proxyg_mb_test_GMBInterface_InterfaceTest()函数。这个函数实际就是通过返回的refnum来获取Go中的对象,调用Go中实现的接口。
总结: Go返回的接口类型,在OC上还是一个refnum。胶水代码自动生成了一个实现这个接口的类,并且保存了这个GoSeqRef。在调用接口的时候,实际上是调用的这个自动生成的类的方法。最终还是通过存储的GoSeqRef找到Go中的对象,来调用具体方法。
至此,Go方法返回数据都已经讲完了(NSData和NSString我们放在单独的环节)。接下来我们看一下从OC传对象至Go
OC传递对象至Go 这一节我们将补充讲完之前跳过的环节。
1 2 3 4 5 6 7 8 9 10 11 - (void)gmbTest7:(id<G_mb_testGMBInterface>)i { int32_t refnum = go_seq_go_to_refnum(self._ref); int32_t _i; if ([i conformsToProtocol:@protocol(goSeqRefInterface)]) { id<goSeqRefInterface> i_proxy = (id<goSeqRefInterface>)(i); _i = go_seq_go_to_refnum(i_proxy._ref); } else { _i = go_seq_to_refnum(i); } proxyg_mb_test_GMBTest_GMBTest7(refnum, _i); }
goSeqRefInterface接口表示的是Go传出的对象(因为有GoSeqRef)。 如果是Go的对象,则增加引用计数。 如果不是Go的对象,即OC new出来的对象,调用go_seq_to_refnum()
方法内部实际是调用的全局变量tracker。 看到这里其实已经明白了,在OC里实现了一个和Go中refs一样的结构。用来标识从OC传入Go的对象的引用计数。 Go在OC里的引用计数是,每次在调用Go对象之前持有一次,在Go由refnum找到对应对象之后,再释放一次。那么OC的对象呢?
继续看到下一个方法proxyg_mb_test_GMBTest_GMBTest7()的参数传递。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 //export proxyg_mb_test_GMBTest_GMBTest7 func proxyg_mb_test_GMBTest_GMBTest7(refnum C.int32_t, param_i C.int32_t) { ref := _seq.FromRefNum(int32(refnum)) v := ref.Get().(*g_mb_test.GMBTest) var _param_i g_mb_test.GMBInterface _param_i_ref := _seq.FromRefNum(int32(param_i)) if _param_i_ref != nil { if param_i < 0 { // go object _param_i = _param_i_ref.Get().(g_mb_test.GMBInterface) } else { // foreign object _param_i = (*proxyg_mb_test_GMBInterface)(_param_i_ref) } } v.GMBTest7(_param_i) }
之前提到过,_seq.FromRefNum只判断null(41),否则返回一个Ref指针。如果传入的参数是Go生成的,那么就直接使用。否则,如果是外部传入的,直接把_param_i_ref强转为了proxyg_mb_test_GMBInterface类型。
看到后面还有一个对proxyg_mb_test_GMBInterface 的定义
1 2 3 4 5 6 7 type proxyg_mb_test_GMBInterface _seq.Ref func (p *proxyg_mb_test_GMBInterface) Bind_proxy_refnum__() int32 { return (*_seq.Ref)(p).Bind_IncNum() } func (p *proxyg_mb_test_GMBInterface) InterfaceTest() { C.cproxyg_mb_test_GMBInterface_InterfaceTest(C.int32_t(p.Bind_proxy_refnum__())) }
两个方法: Bind_proxy_refnum__()调用了Ref的Bind_IncNum(),看名字和注释的意思是增加外部变量的引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func init() { _seq.FinalizeRef = func(ref *_seq.Ref) { refnum := ref.Bind_Num if refnum < 0 { panic(fmt.Sprintf(“not a foreign ref: %d”, refnum)) } C.go_seq_dec_ref(C.int32_t(refnum)) } _seq.IncForeignRef = func(refnum int32) { if refnum < 0 { panic(fmt.Sprintf("not a foreign ref: %d", refnum)) } C.go_seq_inc_ref(C.int32_t(refnum)) } }
在初始化的时候,会给seq设置两个全局函数,一个是释放,另一个就是刚刚Bind_IncNum()函数内调用的_seq.IncForeignRef(), C.cproxyg_mb_test_GMBInterface_InterfaceTest(),这个两个方法内实际调用了Cgo_seq_inc_ref()和C.go_seq_dec_ref(),这个是链接静态库的实现的符号。
1 2 3 4 5 6 7 8 9 10 11 12 void go_seq_dec_ref(int32_t refnum) { @autoreleasepool { [tracker dec:refnum]; } } void go_seq_inc_ref(int32_t refnum) { @autoreleasepool { [tracker inc:refnum]; } }
seq_darwin.m中可以看到,确实是增加和减少引用计数。
在上面讲到FromRefNum的时候,如果是外部传入的对象,则会给GoRef设置一个析构函数。 也就是说,在GoRef这个结构体的生命周期内,持有外部的对象。GoRef 映射的就是外部对象。
继续回到上面,InterfaceTest()是接口的另一个方法
1 2 3 4 5 6 void cproxyg_mb_test_GMBInterface_InterfaceTest(int32_t refnum) { @autoreleasepool { G_mb_testGMBInterface* o = go_seq_objc_from_refnum(refnum); [o interfaceTest]; } }
和_seq.FinalizeRef一样,调用到了oc。而OC的go_seq_objc_from_refnum()方法在拿到oc对象之后,调用了[tracker dec:refnum]; 这样,在OC层完成了这个对象的释放
总结: OC的接口对象传递至Go的时候(非Go导出对象),Go肯定也是以一个接口对象存在。OC在传入之前计数+1(计数为1),传入之后,Go对生成的GoRef增加析构函数,析构函数内对计数 -1。 在Go内调用GoRef重定义的接口函数时候,会先增加引用计数,调回OC之后,在OC取到对应的OC对象后,OC会主动对计数-1。这一点和Go传出对象很类似。
另外,刚刚我们跳过了go_seq_from_refnum()以及ToRefNum()的代码,都是考虑的特殊情况。我们刚刚讨论的都是OC创建的对象传递至Go,Go创建的对象传递至OC。但是还有OC传递至Go后,Go在把同一个对象返回给OC。相反也有Go给OC后,OC把同一个对象给了Go。(这个同一个对象,在OC内是指GoSeqRef,在Go内是指GoRef,两侧都是镜像的。)对于这种情况,两侧都是不需要转换的,OC内直接从GoSeqRef取出obj调用,Go内直接找到refnum对应的对象调用。
总结
Go导出的静态库实际是一个C实现的静态库,还会生成OC的代码(胶水代码)来包裹这个库,以达到导出OC类、方法和接口
Go的对象和OC对象在对方侧,都一个int32(refnum)作为对象参数,来对应每一个传递过去的对象。如果refnum>0表示OC对象,<0表示Go对象。
Go的对象和OC对象在对方侧,分别有一个存储了refnum的GoRef struct和GoSeqRef class来表示对方侧的对象。
Go和OC侧都有全局变量来维护已经传入至对方侧的对象,以及引用计数。
不论是OC还是Go,如果refnum表示的是本侧的对象,那么就直接从全局变量中取出对象使用。
OC调用Go对象方法,实际是调用Go的C函数,通过传递refnum告知Go具体的Go对象。
Go调用OC对象方法,实际是通过声明式,调用C函数,由.m实现。同理通过传递refnum来告知OC具体的OC对象。
OC调用Go的接口对象,实际是生成了实现接口的类的胶水代码,接口对象持有了GoSeqRef,接口方法实际也是通过传递refnum调用Go
Go调用OC的接口对象,实际是通过声明式,调用C函数,由.m实现。通过重定义GoRef,实现OC的接口方法。接口方法内通过传递refnum调用.m即OC
Go传递至OC的对象,Go在new或者函数返回之前会调用_seq.ToRefNum(),存储在全局变量中,并计数为1。OC存储的GoSeqRef在dealloc的时候通过DestroyRef()将计数-1。
OC传递至Go的对象,OC在传递给Go之前会调用assignRefnumAndIncRefcount:存储在全局变量中,并计数为1。Go在生成GoRef的时候会设置该对象的析构回调,在回调内,通过调用go_seq_dec_ref()将计数-1
Go对象方法被OC调用之前,OC会使用go_seq_go_to_refnum()将计数+1,Go在将refnum转换为go obj之后,又会将计数-1
OC对象方法被Go调用之前,Go会使用Bind_IncNum()将计数+1,OC在将refnum转换为oc obj 之后,又会将计数-1。
以上的总结已经完全可以解答文章开始时候的提问了。
//最后这里回头补充一个图解
后记 还有两个特殊的地方 NSString 和 NSData。这两个对象在Go <—> OC gomobile 是默认支持这两种数据类型的互转的。
1 2 3 4 5 6 7 8 9 10 typedef struct nstring { void *ptr; int len; } nstring; typedef struct nbyteslice { void *ptr; int len; } nbyteslice; typedef int nint;
这两个数据类型在传递的时候,都会被转换为定义好的C语言的struct类型。
String 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 // encodeString copies a Go string and returns it as a nstring. func encodeString(s string) C.nstring { n := C.int(len(s)) if n == 0 { return C.nstring{} } ptr := C.malloc(C.size_t(n)) if ptr == nil { panic("encodeString: malloc failed") } copy((*[1<<31 - 1]byte)(ptr)[:n], s) return C.nstring{ptr: ptr, len: n} } // decodeString converts a nstring to a Go string. The // data in str is freed after use. func decodeString(str C.nstring) string { if str.ptr == nil { return "" } s := C.GoStringN((*C.char)(str.ptr), str.len) C.free(str.ptr) return s }
Go 侧 string 通过encode和decode方法,在cstring和go string之间互转,每次转换都会copy 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 NSString *go_seq_to_objc_string(nstring str) { if (str.len == 0) { // empty string. return @“”; } NSString * res = [[NSString alloc] initWithBytesNoCopy:str.ptr length:str.len encoding:NSUTF8StringEncoding freeWhenDone:YES]; return res; } nstring go_seq_from_objc_string(NSString *s) { nstring res = {NULL, 0}; int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; if (len == 0) { if (s.length > 0) { LOG_INFO(@“unable to encode an NSString into UTF-8”); } return res; } char *buf = (char *)malloc(len); if (buf == NULL) { LOG_FATAL(@"malloc failed"); } NSUInteger used; [s getBytes:buf maxLength:len usedLength:&used encoding:NSUTF8StringEncoding options:0 range:NSMakeRange(0, [s length]) remainingRange:NULL]; res.ptr = buf; res.len = used; return res; }
OC 侧 NSString 通过go_seq_to_objc_string和go_seq_from_objc_string方法,在cstring和nsstring之间互转,每次转换都会copy 。
且对方侧生成的cstring由己方释放。
Bytes 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 // fromSlice converts a slice to a nbyteslice. // If cpy is set, a malloc’ed copy of the data is returned. func fromSlice(s []byte, cpy bool) C.nbyteslice { if s == nil || len(s) == 0 { return C.nbyteslice{} } ptr, n := unsafe.Pointer(&s[0]), C.int(len(s)) if cpy { nptr := C.malloc(C.size_t(n)) if nptr == nil { panic("fromSlice: malloc failed") } copy((*[1<<31 - 1]byte)(nptr)[:n], (*[1<<31 - 1]byte)(ptr)[:n]) ptr = nptr } return C.nbyteslice{ptr: ptr, len: n} } // toSlice takes a nbyteslice and returns a byte slice with the data. If cpy is // set, the slice contains a copy of the data. If not, the generated Go code // calls releaseByteSlice after use. func toSlice(s C.nbyteslice, cpy bool) []byte { if s.ptr == nil || s.len == 0 { return nil } var b []byte if cpy { b = C.GoBytes(s.ptr, C.int(s.len)) C.free(s.ptr) } else { b = (*[1<<31 - 1]byte)(unsafe.Pointer(s.ptr))[:s.len:s.len] } return b }
Go 侧 nbyteslice 通过fromSlice和toSlice方法,在nbyteslice和go slice之间互转。
OC -> Go 如果对方(OC)copy == true,Go也会拷贝,拷贝完就会释放nbyteslice,否则只是使用。 Go -> OC (copy 才会拷贝,go->OC copy=true)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 NSData *go_seq_to_objc_bytearray(nbyteslice s, int copy) { if (s.ptr == NULL) { return NULL; } BOOL freeWhenDone = copy ? YES : NO; return [NSData dataWithBytesNoCopy:s.ptr length:s.len freeWhenDone:freeWhenDone]; } nbyteslice go_seq_from_objc_bytearray(NSData *data, int copy) { struct nbyteslice res = {NULL, 0}; int sz = data.length; if (sz == 0) { return res; } void *ptr; // If the argument was not a NSMutableData, copy the data so that // the NSData is not changed from Go. The corresponding free is called // by releaseByteSlice. if (copy || ![data isKindOfClass:[NSMutableData class]]) { void *arr_copy = malloc(sz); if (arr_copy == NULL) { LOG_FATAL(@"malloc failed"); } memcpy(arr_copy, [data bytes], sz); ptr = arr_copy; } else { ptr = (void *)[data bytes]; } res.ptr = ptr; res.len = sz; return res; }
OC 侧 NSData 通过go_seq_to_objc_bytearray和go_seq_from_objc_bytearray方法,在nbyteslice和NSData之间互转。
OC -> Go (NSMutableData && !copy 才不会拷贝,OC->Go copy = false,即NSData拷贝,NSMutableData不拷贝),并且方法调用结束就会释放bytes 。(如果在Go内使用slice的时候切协程了,有大概率读取到的bytes是被释放了) Go -> OC OC不拷贝从Go返回的bytes,OC在释放NSData的时候会释放nbyteslice.
总之一个宗旨,一侧指定了copy,就由另一侧释放。没有指定copy,就由本侧释放,即谁创建谁释放的原则。(NSData是指定了false,但是执行拷贝了,还是由本侧释放)
2020-3-18 记用 Dart 来写 Objective-C 代码
这篇文章讲述的是在Dart中写OC,与GoMobile的原理相比有些许相似…