github.com/F4RD1N/gomobile@v1.0.1/bind/objc/seq_darwin.m.support (about) 1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 #include <stdio.h> 6 #include <stdint.h> 7 #include <string.h> 8 #include <Foundation/Foundation.h> 9 #include "seq.h" 10 #include "_cgo_export.h" 11 12 // * Objective-C implementation of a Go interface type 13 // 14 // For an interface testpkg.I, gobind defines a protocol GoSeqTestpkgI. 15 // Reference tracker (tracker) maintains two maps: 16 // 1) _refs: objective-C object pointer -> a refnum (starting from 42). 17 // 2) _objs: refnum -> RefCounter. 18 // 19 // Whenever a user's object conforming the protocol is sent to Go (through 20 // a function or method that takes I), _refs is consulted to find the refnum 21 // of the object. If not found, the refnum is assigned and stored. 22 // 23 // _objs is also updated so that the RefCounter is incremented and the 24 // user's object is pinned. 25 // 26 // When a Go side needs to call a method of the interface, the Go side 27 // notifies the Objective-C side of the object's refnum. Upon receiving the 28 // request, Objective-C side looks up the object from _objs map, and sends 29 // the method to the object. 30 // 31 // The RefCount counts the references on objective-C objects from Go side, 32 // and pins the objective-C objects until there is no more references from 33 // Go side. 34 // 35 // * Objective-C proxy of a Go object (struct or interface type) 36 // 37 // For Go type object, a objective-C proxy instance is created whenever 38 // the object reference is passed into objective-C. 39 // 40 // While crossing the language barrier there is a brief window where the foreign 41 // proxy object might be finalized but the refnum is not yet translated to its object. 42 // If the proxy object was the last reference to the foreign object, the refnum 43 // will be invalid by the time it is looked up in the foreign reference tracker. 44 // 45 // To make sure the foreign object is kept live while its refnum is in transit, 46 // increment its refererence count before crossing. The other side will decrement 47 // it again immediately after the refnum is converted to its object. 48 49 // Note that this file is copied into and compiled with the generated 50 // bindings. 51 52 // A simple thread-safe mutable dictionary. 53 @interface goSeqDictionary : NSObject { 54 } 55 @property NSMutableDictionary *dict; 56 @end 57 58 @implementation goSeqDictionary 59 60 - (id)init { 61 if (self = [super init]) { 62 _dict = [[NSMutableDictionary alloc] init]; 63 } 64 return self; 65 } 66 67 - (id)get:(id)key { 68 @synchronized(self) { 69 return [_dict objectForKey:key]; 70 } 71 } 72 73 - (void)put:(id)obj withKey:(id)key { 74 @synchronized(self) { 75 [_dict setObject:obj forKey:key]; 76 } 77 } 78 @end 79 80 // NULL_REFNUM is also known to bind/seq/ref.go and bind/java/Seq.java 81 #define NULL_REFNUM 41 82 83 // RefTracker encapsulates a map of objective-C objects passed to Go and 84 // the reference number counter which is incremented whenever an objective-C 85 // object that implements a Go interface is created. 86 @interface RefTracker : NSObject { 87 int32_t _next; 88 NSMutableDictionary *_refs; // map: object ptr -> refnum 89 NSMutableDictionary *_objs; // map: refnum -> RefCounter* 90 } 91 92 - (id)init; 93 94 // decrements the counter of the objective-C object with the reference number. 95 // This is called whenever a Go proxy to this object is finalized. 96 // When the counter reaches 0, the object is removed from the map. 97 - (void)dec:(int32_t)refnum; 98 99 // increments the counter of the objective-C object with the reference number. 100 // This is called whenever a Go proxy is converted to its refnum and send 101 // across the language barrier. 102 - (void)inc:(int32_t)refnum; 103 104 // returns the object of the reference number. 105 - (id)get:(int32_t)refnum; 106 107 // returns the reference number of the object and increments the ref count. 108 // This is called whenever an Objective-C object is sent to Go side. 109 - (int32_t)assignRefnumAndIncRefcount:(id)obj; 110 @end 111 112 static RefTracker *tracker = NULL; 113 114 #define IS_FROM_GO(refnum) ((refnum) < 0) 115 116 // init_seq is called when the Go side is initialized. 117 void init_seq() { tracker = [[RefTracker alloc] init]; } 118 119 void go_seq_dec_ref(int32_t refnum) { 120 @autoreleasepool { 121 [tracker dec:refnum]; 122 } 123 } 124 125 void go_seq_inc_ref(int32_t refnum) { 126 @autoreleasepool { 127 [tracker inc:refnum]; 128 } 129 } 130 131 NSData *go_seq_to_objc_bytearray(nbyteslice s, int copy) { 132 if (s.ptr == NULL) { 133 return NULL; 134 } 135 BOOL freeWhenDone = copy ? YES : NO; 136 return [NSData dataWithBytesNoCopy:s.ptr length:s.len freeWhenDone:freeWhenDone]; 137 } 138 139 NSString *go_seq_to_objc_string(nstring str) { 140 if (str.len == 0) { // empty string. 141 return @""; 142 } 143 NSString * res = [[NSString alloc] initWithBytesNoCopy:str.ptr 144 length:str.len 145 encoding:NSUTF8StringEncoding 146 freeWhenDone:YES]; 147 return res; 148 } 149 150 id go_seq_objc_from_refnum(int32_t refnum) { 151 id obj = [tracker get:refnum]; 152 // Go called IncForeignRef just before converting its proxy to its refnum. Decrement it here. 153 // It's very important to decrement *after* fetching the reference from the tracker, in case 154 // there are no other proxy references to the object. 155 [tracker dec:refnum]; 156 return obj; 157 } 158 159 GoSeqRef *go_seq_from_refnum(int32_t refnum) { 160 if (refnum == NULL_REFNUM) { 161 return nil; 162 } 163 if (IS_FROM_GO(refnum)) { 164 return [[GoSeqRef alloc] initWithRefnum:refnum obj:NULL]; 165 } 166 return [[GoSeqRef alloc] initWithRefnum:refnum obj:go_seq_objc_from_refnum(refnum)]; 167 } 168 169 int32_t go_seq_to_refnum(id obj) { 170 if (obj == nil) { 171 return NULL_REFNUM; 172 } 173 return [tracker assignRefnumAndIncRefcount:obj]; 174 } 175 176 int32_t go_seq_go_to_refnum(GoSeqRef *ref) { 177 int32_t refnum = [ref incNum]; 178 if (!IS_FROM_GO(refnum)) { 179 LOG_FATAL(@"go_seq_go_to_refnum on objective-c objects is not permitted"); 180 } 181 return refnum; 182 } 183 184 nbyteslice go_seq_from_objc_bytearray(NSData *data, int copy) { 185 struct nbyteslice res = {NULL, 0}; 186 int sz = data.length; 187 if (sz == 0) { 188 return res; 189 } 190 void *ptr; 191 // If the argument was not a NSMutableData, copy the data so that 192 // the NSData is not changed from Go. The corresponding free is called 193 // by releaseByteSlice. 194 if (copy || ![data isKindOfClass:[NSMutableData class]]) { 195 void *arr_copy = malloc(sz); 196 if (arr_copy == NULL) { 197 LOG_FATAL(@"malloc failed"); 198 } 199 memcpy(arr_copy, [data bytes], sz); 200 ptr = arr_copy; 201 } else { 202 ptr = (void *)[data bytes]; 203 } 204 res.ptr = ptr; 205 res.len = sz; 206 return res; 207 } 208 209 nstring go_seq_from_objc_string(NSString *s) { 210 nstring res = {NULL, 0}; 211 int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; 212 213 if (len == 0) { 214 if (s.length > 0) { 215 LOG_INFO(@"unable to encode an NSString into UTF-8"); 216 } 217 return res; 218 } 219 220 char *buf = (char *)malloc(len); 221 if (buf == NULL) { 222 LOG_FATAL(@"malloc failed"); 223 } 224 NSUInteger used; 225 [s getBytes:buf 226 maxLength:len 227 usedLength:&used 228 encoding:NSUTF8StringEncoding 229 options:0 230 range:NSMakeRange(0, [s length]) 231 remainingRange:NULL]; 232 res.ptr = buf; 233 res.len = used; 234 return res; 235 } 236 237 @implementation GoSeqRef { 238 } 239 240 - (id)init { 241 LOG_FATAL(@"GoSeqRef init is disallowed"); 242 } 243 244 - (int32_t)incNum { 245 IncGoRef(_refnum); 246 return _refnum; 247 } 248 249 // called when an object from Go is passed in. 250 - (instancetype)initWithRefnum:(int32_t)refnum obj:(id)obj { 251 self = [super init]; 252 if (self) { 253 _refnum = refnum; 254 _obj = obj; 255 } 256 return self; 257 } 258 259 - (void)dealloc { 260 if (IS_FROM_GO(_refnum)) { 261 DestroyRef(_refnum); 262 } 263 } 264 @end 265 266 // RefCounter is a pair of (GoSeqProxy, count). GoSeqProxy has a strong 267 // reference to an Objective-C object. The count corresponds to 268 // the number of Go proxy objects. 269 // 270 // RefTracker maintains a map of refnum to RefCounter, for every 271 // Objective-C objects passed to Go. This map allows the transact 272 // call to relay the method call to the right Objective-C object, and 273 // prevents the Objective-C objects from being deallocated 274 // while they are still referenced from Go side. 275 @interface RefCounter : NSObject { 276 } 277 @property(strong, readonly) id obj; 278 @property int cnt; 279 280 - (id)initWithObject:(id)obj; 281 @end 282 283 @implementation RefCounter { 284 } 285 - (id)initWithObject:(id)obj { 286 self = [super init]; 287 if (self) { 288 _obj = obj; 289 _cnt = 0; 290 } 291 return self; 292 } 293 294 @end 295 296 @implementation RefTracker { 297 } 298 299 - (id)init { 300 self = [super init]; 301 if (self) { 302 _next = 42; 303 _refs = [[NSMutableDictionary alloc] init]; 304 _objs = [[NSMutableDictionary alloc] init]; 305 } 306 return self; 307 } 308 309 - (void)dec:(int32_t)refnum { // called whenever a go proxy object is finalized. 310 if (IS_FROM_GO(refnum)) { 311 LOG_FATAL(@"dec:invalid refnum for Objective-C objects"); 312 } 313 @synchronized(self) { 314 id key = @(refnum); 315 RefCounter *counter = [_objs objectForKey:key]; 316 if (counter == NULL) { 317 LOG_FATAL(@"unknown refnum"); 318 } 319 int n = counter.cnt; 320 if (n <= 0) { 321 LOG_FATAL(@"refcount underflow"); 322 } else if (n == 1) { 323 LOG_DEBUG(@"remove the reference %d", refnum); 324 NSValue *ptr = [NSValue valueWithPointer:(const void *)(counter.obj)]; 325 [_refs removeObjectForKey:ptr]; 326 [_objs removeObjectForKey:key]; 327 } else { 328 counter.cnt = n - 1; 329 } 330 } 331 } 332 333 // inc is called whenever a ObjC refnum crosses from Go to ObjC 334 - (void)inc:(int32_t)refnum { 335 if (IS_FROM_GO(refnum)) { 336 LOG_FATAL(@"dec:invalid refnum for Objective-C objects"); 337 } 338 @synchronized(self) { 339 id key = @(refnum); 340 RefCounter *counter = [_objs objectForKey:key]; 341 if (counter == NULL) { 342 LOG_FATAL(@"unknown refnum"); 343 } 344 counter.cnt++; 345 } 346 } 347 348 - (id)get:(int32_t)refnum { 349 if (IS_FROM_GO(refnum)) { 350 LOG_FATAL(@"get:invalid refnum for Objective-C objects"); 351 } 352 @synchronized(self) { 353 RefCounter *counter = _objs[@(refnum)]; 354 if (counter == NULL) { 355 LOG_FATAL(@"unidentified object refnum: %d", refnum); 356 } 357 return counter.obj; 358 } 359 } 360 361 - (int32_t)assignRefnumAndIncRefcount:(id)obj { 362 @synchronized(self) { 363 NSValue *ptr = [NSValue valueWithPointer:(const void *)(obj)]; 364 NSNumber *refnum = [_refs objectForKey:ptr]; 365 if (refnum == NULL) { 366 refnum = @(_next++); 367 _refs[ptr] = refnum; 368 } 369 RefCounter *counter = [_objs objectForKey:refnum]; 370 if (counter == NULL) { 371 counter = [[RefCounter alloc] initWithObject:obj]; 372 counter.cnt = 1; 373 _objs[refnum] = counter; 374 } else { 375 counter.cnt++; 376 } 377 return (int32_t)([refnum intValue]); 378 } 379 } 380 381 @end