NodObjC

The NodeJS ⇆ Objective-C Bridge

blockbridgesupportclasscoreexceptionffi-extendglobalidimpimportindexivarmethodselstructtypes

Id

The 'id' function is essentially the "base class" for all Objective-C objects that get passed around JS-land.

wrap()

This is a private function used internally by NodObjC. You should not need to use this function in your code.

Wraps up a pointer that is expected to be a compatible Objective-C object that can recieve messages. This function returns a cached version of the wrapped function after the first time it is invoked on a given Pointer, using Objective-C's internal association map for objects.

function wrap (pointer) {
  debug('id#wrap(%d)', pointer.address)
  var rtn = null
    , p = core.objc_getAssociatedObject(pointer, KEY)
  if (p.isNull()) {
    rtn = createFunctionWrapper(pointer)
    // Store the wrapped instance internally
    var ref = new core.Pointer(core.TYPE_SIZE_MAP.Object)
    // don't call free() automatically when ref gets GC'd
    // TODO: we're gonna have to free this pointer someday!
    // XXX: use node-weak to get a callback when the wrapper is GC'd
    ref.free = false
    ref.putObject(rtn)
    core.objc_setAssociatedObject(pointer, KEY, ref, 0)
  } else {
    debug('returning cached associated instance')
    rtn = p.getObject()
  }
  //assert.equal(rtn.pointer.address, pointer.address)
  return rtn
}

Id#msgSend()

A very important function that does the message sending between Objective-C objects. When you do array('addObject', anObject), this msgSend function is the one that finally gets called to do the dirty work.

This function accepts a String selector as the first argument, and an Array of (wrapped) values that get passed to the the message. This function takes care of unwrapping the passed in arguments and wrapping up the result value, if necessary.

proto.msgSend = function msgSend (sel, args) {
  debug('sending message:', sel, args)
  var types = this._getTypes(sel, args)
    , argTypes = types[1]
    , msgSendFunc = core.get_objc_msgSend(types)
    , unwrappedArgs = core.unwrapValues([this, sel].concat(args), argTypes)
    , rtn

  debug('msgSend: before', sel)
  try {
    rtn = msgSendFunc.apply(null, unwrappedArgs)
  } catch (e) {
    if (!e.hasOwnProperty('stack')) {
      e = exception.wrap(e)
    }
    throw e
  }
  debug('msgSend: after', sel)
  // Process the return value into a wrapped value if needed
  return core.wrapValue(rtn, types[0])
}

Id#super()

Like regular message sending, but invokes the method implementation on the object's "superclass" instead. This is the equivalent of what happens when the Objective-C compiler encounters the super keyword:

self = [super init];

To do the equivalent using NodObjC you call super(), as shown here:

self = self.super('init')
proto.super = function super_ () {
  var args = []
    , sel = parseArgs(arguments, args)
  return this.msgSendSuper(sel, args)
}

Id#msgSendSuper()

Calls objc_msgSendSuper() on the underlying Objective-C object.

proto.msgSendSuper = function msgSendSuper (sel, args) {
  debug('sending `super` message:', sel, args)

  var os = new objc_super
  os.receiver = this.pointer
  os.class = this.getClass().getSuperclass().pointer

  var types = this._getTypes(sel, args)
    , argTypes = types[1]
    , msgSendSuperFunc = core.get_objc_msgSendSuper(types)
    , unwrappedArgs = core.unwrapValues([os, sel].concat(args), argTypes)
    , rtn

  debug('msgSendSuper: before', sel)
  try {
    rtn = msgSendSuperFunc.apply(null, unwrappedArgs)
  } catch (e) {
    if (!e.hasOwnProperty('stack')) {
      e = exception.wrap(e)
    }
    throw e
  }
  debug('msgSendSuper: after', sel)
  // Process the return value into a wrapped value if needed
  return core.wrapValue(rtn, types[0])
}

Id#_getTypes()

This is a private method used internally by NodObjC. You should not need to use this method in your code.

Accepts a SEL and queries the current object for the return type and argument types for the given selector. If current object does not implment that selector, then check the superclass, and repeat recursively until a subclass that responds to the selector is found, or until the base class is found.

TODO: Just merge this logic with msgSend()? It's not used anywhere else

proto._getTypes = function getTypes (sel, args) {
  var c = this.getClass()
    , t = c._getTypesClass(sel, this.isClass)
  if (!t) {
    // Unknown selector being send to object. This *may* still be valid, we
    // assume all args are type 'id' and return is 'id'.
    debug('unknown selector being sent:', sel)
    t = [ '@', [ '@', ':', ].concat(args.map(function () { return '@' })) ]
  }
  return t
}

Id#getClass()

Retrieves the wrapped Class instance for this object.

proto.getClass = function getClass () {
  return Class.wrap(core.object_getClass(this.pointer))
}

Id#getClassName()

Calls 'object_getClassName()' on this object.

proto.getClassName = function getClassName () {
  return core.object_getClassName(this.pointer)
}

Id#setClass()

Dynamically changes the object's Class.

proto.setClass = function setClass (newClass) {
  return Class.wrap(core.object_setClass(this.pointer, newClass.pointer))
}

Id#ancestors()

Walks up the inheritance chain and returns an Array of Strings of superclasses.

proto.ancestors = function ancestors () {
  var rtn = []
    , c = this.getClass()
  while (c) {
    rtn.push(c.getName())
    c = c.getSuperclass()
  }
  return rtn
}

Id#ivar()

Getter/setter function for instance variables (ivars) of the object, If just a name is passed in, then this function gets the ivar current value. If a name and a new value are passed in, then this function sets the ivar.

proto.ivar = function ivar (name, value) {
  // TODO: Add support for passing in a wrapped Ivar instance as the `name`
  if (arguments.length > 1) {
    // setter
    debug('setting ivar:', name, value)
    var ivar = this.isClass
             ? this.getClassVariable(name)
             : this.getClass().getInstanceVariable(name)
      , unwrapped = core.unwrapValue(value, ivar.getTypeEncoding())
    return core.object_setIvar(this.pointer, ivar.pointer, unwrapped)
  } else {
    // getter
    debug('getting ivar:', name)
    var ptr = new core.Pointer(core.TYPE_SIZE_MAP.pointer)
      , ivar = core.object_getInstanceVariable(this.pointer, name, ptr)
    return core.wrapValue(ptr.getPointer(), core.ivar_getTypeEncoding(ivar))
  }
}

Id#ivars()

Returns an Array of Strings of the names of the ivars that the current object contains. This function can iterate through the object's superclasses recursively, if you specify a maxDepth argument.

proto.ivars = function ivars (maxDepth, sort) {
  var rtn = []
    , c = this.getClass()
    , md = maxDepth || 1
    , depth = 0
  while (c && depth++ < md) {
    var is = c.getInstanceVariables()
      , i = is.length
    while (i--) {
      if (!~rtn.indexOf(is[i])) rtn.push(is[i])
    }
    c = c.getSuperclass()
  }
  return sort === false ? rtn : rtn.sort()
}

Id#methods()

Returns an Array of Strings of the names of methods that the current object will respond to. This function can iterate through the object's superclasses recursively, if you specify a maxDepth number argument.

proto.methods = function methods (maxDepth, sort) {
  var rtn = []
    , c = this.getClass()
    , md = maxDepth || 1
    , depth = 0
  while (c && depth++ < md) {
    var ms = c.getInstanceMethods()
      , i = ms.length
    while (i--) {
      if (!~rtn.indexOf(ms[i])) rtn.push(ms[i])
    }
    c = c.getSuperclass()
  }
  return sort === false ? rtn : rtn.sort()
}

Id#ref()

Returns a node-ffi pointer pointing to this object. This is a convenience function for methods that take pointers to objects (i.e. NSError**).

proto.ref = function ref () {
  debug('id#ref()')
  var ptr = this.pointer.ref()
  return ptr
}

Id#toString()

The overidden toString() function proxies up to the real Objective-C object's description method. In Objective-C, this is equivalent to:

[[id description] UTF8String]
proto.toString = function toString () {
  return this('description')('UTF8String')
}
Fork me on GitHub