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