NodObjC

The NodeJS ⇆ Objective-C Bridge

blockbridgesupportclasscoreexceptionffi-extendglobalidimpimportindexivarmethodselstructtypes

Core

This 'core' module is the libffi wrapper. All required native functionality is instantiated and then exported in this module.

References:

exports.getClassList()

Convienience function to return an Array of Strings of the names of every class currently in the runtime. This gets used at the during the import process get a name of the new classes that have been loaded. TODO: Could be replaced with a native binding someday for speed. Not overly important as this function is only called during import()

exports.getClassList = function getClassList () {
  // First get just the count
  var num = objc.objc_getClassList(null, 0)
    , rtn = []
  if (num > 0) {
    var s = ffi.Bindings.TYPE_SIZE_MAP.pointer
      , c = null
      , classes = new ffi.Pointer(s * num)
      , cursor = classes
    objc.objc_getClassList(classes, num)
    for (var i=0; i<num; i++) {
      c = cursor.getPointer()
      rtn.push(objc.class_getName(c))
      cursor = cursor.seek(s)
    }
    // free() not needed since ffi allocated the buffer, and will free() with V8's GC
  }
  return rtn
}

exports.copyProtocolList()

Gets a list of the currently loaded Protocols in the runtime.

exports.copyProtocolList = function copyProtocolList () {
  var num = new ffi.Pointer(exports.TYPE_SIZE_MAP.uint32)
    , rtn = []
    , protos = objc.objc_copyProtocolList(num)
    , p = protos
    , count = num.getUInt32()
  for (var i=0; i<count; i++) {
    var cur = p.getPointer()
      , name = objc.protocol_getName(cur)
    rtn.push(name)
    p = p.seek(exports.TYPE_SIZE_MAP.pointer)
  }
  ffi.free(protos)
  return rtn
}

exports.copyIvarList()

Copies and returns an Array of the instance variables defined by a given Class pointer. To get class variables, call this function on a metaclass.

exports.copyIvarList = function copyIvarList (classPtr) {
  var numIvars = new ffi.Pointer(exports.TYPE_SIZE_MAP.uint32)
    , rtn = []
    , ivars = exports.class_copyIvarList(classPtr, numIvars)
    , p = ivars
    , count = numIvars.getUInt32()
  for (var i=0; i<count; i++) {
    var cur = p.getPointer()
      , name = exports.ivar_getName(cur)
    rtn.push(name)
    p = p.seek(exports.TYPE_SIZE_MAP.pointer)
  }
  ffi.free(ivars)
  return rtn
}

exports.copyMethodList()

Copies and returns an Array of the instance methods the given Class pointer implements. To get class methods, call this function with a metaclass.

exports.copyMethodList = function copyMethodList (classPtr) {
  var numMethods = new ffi.Pointer(exports.TYPE_SIZE_MAP.uint32)
    , rtn = []
    , methods = exports.class_copyMethodList(classPtr, numMethods)
    , p = methods
    , count = numMethods.getUInt32()
  for (var i=0; i<count; i++) {
    var cur = p.getPointer()
      , name = SEL.toString(exports.method_getName(cur))
    rtn.push(name)
    p = p.seek(exports.TYPE_SIZE_MAP.pointer)
  }
  ffi.free(methods)
  return rtn
}

exports.copyMethodDescriptionList()

Iterates over the Methods defined by a Protocol.

exports.copyMethodDescriptionList = function copyMethodDescriptionList (protocolPtr, required, instance) {
  var numMethods = new ffi.Pointer(exports.TYPE_SIZE_MAP.uint32)
    , methods = exports.protocol_copyMethodDescriptionList(protocolPtr, required, instance, numMethods)
    , rtn = []
    , p = methods
    , count = numMethods.getUInt32()
  for (var i=0; i<count; i++) {
    var cur = new exports.objc_method_description(p)
    rtn.push(SEL.toString(cur.name))
    p = p.seek(ffi.sizeOf(exports.objc_method_description))
  }
  ffi.free(methods)
  return rtn
}

exports.getMethodReturnType()

Convienience function to get the String return type of a Method pointer. Takes care of free()ing the returned pointer, as is required.

exports.getMethodReturnType = function getMethodReturnType (method) {
  return getStringAndFree(objc.method_copyReturnType(method))
}

exports.getMethodArgTypes = function getMethodArgTypes (method) {
  var num = objc.method_getNumberOfArguments(method)
    , rtn = []
  for (var i=2; i<num; i++) {
    rtn.push(getStringAndFree(objc.method_copyArgumentType(method, i)))
  }
  return rtn
}

exports.getStringAndFree = function getStringAndFree (ptr) {
  var str = ptr.getCString()
  ffi.free(ptr)
  return str
}

exports.get_objc_msgSend()

Creates and/or returns an appropriately wrapped up 'objc_msgSend' function based on the given Method description info. TODO: Move this into a node-ffi helper class thingy.

exports.get_objc_msgSend = function get_objc_msgSend (objcTypes) {
  var type = []
    , rtn = [ types.map(objcTypes[0]), type ]
    , args = objcTypes[1]
    , i = 0
    , l = args.length
  for (; i<l; i++) {
    type.push(types.map(args[i]))
  }
  // Stringify the types
  var key = rtn.toString()
  //console.warn('INFO: types key: %s', key)

  // first check the cache
  if (msgSendCache[key]) return msgSendCache[key]
  debug('key not found in msgSendCache, generating new copy:', key)

  // If we got here, then create a new objc_msgSend ffi wrapper
  // TODO: Don't use the Library helper, use ffi low-level API
  var lib = new ffi.Library(null, {
    objc_msgSend: rtn
  })
  // return and cache at the same time
  return msgSendCache[key] = lib.objc_msgSend
}

exports.get_objc_msgSendSuper()

Creates and/or returns an appropriately wrapped up 'objc_msgSendSuper' function based on the given Method description info. TODO: Move this into a node-ffi helper class thingy.

exports.get_objc_msgSendSuper = function get_objc_msgSendSuper (objcTypes) {
  var type = []
    , rtn = [ types.map(objcTypes[0]), type ]
    , args = objcTypes[1]
    , i = 0
    , l = args.length
  for (; i<l; i++) {
    type.push(types.map(args[i]))
  }
  // Stringify the types
  var key = rtn.toString()
  //console.warn('INFO: types key: %s', key)

  // first check the cache
  if (msgSendCacheSuper[key]) return msgSendCacheSuper[key]
  debug('key not found in msgSendCacheSuper, generating new copy:', key)

  // If we got here, then create a new objc_msgSend ffi wrapper
  // TODO: Don't use the Library helper, use ffi low-level API
  var lib = new ffi.Library(null, {
    objc_msgSendSuper: rtn
  })
  // return and cache at the same time
  return msgSendCache[key] = lib.objc_msgSendSuper
}


// Struct type returned by `protocol_getMethodDescription`.
exports.objc_method_description = ffi.Struct([
    ['pointer', 'name']
  , ['string', 'types']
])

exports.wrapValue()

Wraps up a node-ffi pointer if needed (not needed for Numbers, etc.)

exports.wrapValue = function wrapValue (val, type) {
  debug('wrapValue():', type)
  if (val === null || (val.isNull && val.isNull())) return null
  var rtn = val
  if (type.function_pointer) {
    if (type.type == '@?') {
      return block.createBlock(val, type)
    } else {
      return IMP.createUnwrapperFunction(val, type)
    }
  }
  // get the raw type from Type objects
  if (type.type) type = type.type
  if (type == '@') {
    rtn = id.wrap(val)
  } else if (type == '#') {
    rtn = Class.wrap(val)
  } else if (type == ':') {
    rtn = SEL.toString(val)
  } else if (type == 'B') {
    rtn = val ? true : false
  }
  if (rtn)
    rtn._type = type
  return rtn
}

exports.wrapValues()

Accepts an Array of raw objc pointers and other values, and an array of ObjC types, and returns an array of wrapped values where appropriate.

exports.wrapValues = function wrapValues (values, types) {
  var len = values.length
    , rtn = []
  for (var i=0; i<len; i++) {
    rtn.push(exports.wrapValue(values[i], types[i]))
  }
  return rtn
}

exports.unwrapValue()

Unwraps a previously wrapped NodObjC object.

exports.unwrapValue = function unwrapValue (val, type) {
  debug('unwrapValue():', type)
  var rtn = val
  if (type.function_pointer) {
    if (type.type == '@?') {
      return block.getPointer(val, type)
    } else {
      return IMP.createWrapperPointer(val, type)
    }
  }
  // get the raw type from Type objects
  if (type.type) type = type.type
  if (type == '@' || type == '#') {
    if (!val) return null
    rtn = val.pointer
  } else if (type == ':') {
    rtn = SEL.toSEL(val)
  }
  if (rtn)
    rtn._type = type
  return rtn
}

exports.unwrapValues()

Accepts an Array of wrapped NodObjC objects and other values, and an array of their cooresponding ObjC types, and returns an array of unwrapped values.

exports.unwrapValues = function unwrapValues (values, types) {
  debug('unwrapValues():', types)
  var len = values.length
    , rtn = []
  for (var i=0; i<len; i++) {
    rtn.push(exports.unwrapValue(values[i], types[i]))
  }
  return rtn
}

var types = require('./types')
  , SEL = require('./sel')
  , id = require('./id')
  , Class = require('./class')
  , IMP = require('./imp')
  , block = require('./block')
Fork me on GitHub