nodobjc-x
Version:
The Node.js ⇆ Objective-C bridge
235 lines (221 loc) • 9.13 kB
JavaScript
module.exports = exports = NodObjC;
/**
* **NodObjC** is the bridge between Node.js and the Objective-C runtime and
* frameworks, making it possible to write native Cocoa applications (both GUI
* and command-line) using 100% Node.js. Applications are written entirely in
* JavaScript and interpreted at runtime.
*
* ## Getting Started
*
* Every **NodObjC** application begins with requiring the `NodObjC` module.
* You can name the returned module variable anything you want, but the
* "canonical" name for it is `$`. This is mostly because you're going to be using
* the variable all over the place, and probably want to keep it short.
*
* var $ = require('NodObjC')
*
* The next step is to [`import()`](import.html) a desired "Framework" that is
* installed on the system. These frameworks are the APIs provided to Objective-C,
* which could be the default frameworks (provided by Apple) or 3rd party
* frameworks written by others (or you). The "Foundation" framework is the...
* well.. foundation of these APIs, providing the most basic and important
* classes like `NSString` and `NSArray`.
*
* $.import('Foundation')
*
* [`import()`](import.html) doesn't return anything, however it will throw an
* Error if anything goes wrong. What happens after the import call is that the
* `$` variable now has a whole bunch of new properties attached to it, the
* exports from the imported framework. At this point, you can fully interact
* with these Objective-C classes, creating instances, subclassing, swizzling,
* etc.
*
* A lot of core classes expect an `NSAutoreleasePool` instance on the stack, so
* the first Objective-C object instance you create is usually one of those.
*
* var pool = $.NSAutoreleasePool('alloc')('init')
*
* Pretty simple! You don't need to worry about the autorelease pool after this.
* Now, for an example, try creating an `NSArray` instance, well, an
* `NSMutableArray` technically, so we can also add an `NSString` to it.
*
* var array = $.NSMutableArray('alloc')('init')
*
* array('addObject', $('Hello World!'))
*
* console.log(array)
* // (
* // "Hello World!"
* // )
*
* So there's an `NSArray` instance with a `count` (Objective-C's version of
* `Array#length`) of `1`, containing an `NSString` with the text "Hello World!".
* From here on out, you will need to refer to your Cocoa documentation for the
* rest of the available methods `NSArray` offers.
*
* ## Message Sending Syntax
*
* To send an Objective-C message to an Objective-C object using **NodObjC**, you
* have to **invoke the object as a function**, where the **even number arguments
* make up the message name** and the **odd numbered arguments are the arguments**
* to send to the [object](id.html).
*
* object('messageNameWithArg', someArg, 'andArg', anotherArg)
*
* This sounds and probably looks strange at first, but this is the cleanest
* syntax while still being valid JS. It also maintains the "readabililty" of
* typical Objective-C method names.
*
* ## Dynamic Object Introspection
*
* Since **NodObjC** runs in an interpreted environment, it is actually *very
* easy* to dynamically inspect the defined methods, instance variables (ivars),
* implemented protocols, and more of any given Objective-C object (a.k.a.
* [`id`](id.html) instances).
*
* Using the same `array` instance as before, you can retreive a list of the
* type of class, and it's subclasses, by calling the `.ancestors()` function.
*
* array.ancestors()
* // [ '__NSArrayM',
* // 'NSMutableArray',
* // 'NSArray',
* // 'NSObject' ]
*
* Also commonly of interest are the given methods an object responds to. Use the
* `.methods()` function for that.
*
* array.methods()
* // [ 'addObject:',
* // 'copyWithZone:',
* // 'count',
* // 'dealloc',
* // 'finalize',
* // 'getObjects:range:',
* // 'indexOfObjectIdenticalTo:',
* // 'insertObject:atIndex:',
* // 'objectAtIndex:',
* // 'removeLastObject',
* // 'removeObjectAtIndex:',
* // 'replaceObjectAtIndex:withObject:' ]
*
* ## More Docs
*
* Check out the rest of the doc pages for some of the other important
* **NodObjC** pieces.
*
* * [Block](block.html) - How to use an Objective-C "block" function.
* * [Class](class.html) - Subclassing and adding methods at runtime.
* * [Exception](exception.html) - **NodObjC** exceptions *are* JavaScript `Error` objects.
* * [id](id.html) - The wrapper class for every Objective-C object.
* * [Import](import.html) - Importing "Frameworks" into the process.
* * [Ivars](ivar.html) - Instance variable definitions.
* * [Method](method.html) - Method definitions and swizzling.
* * [Structs](struct.html) - Using Structs and C functions in **NodObjC**.
*/
/**
* `NodObjC` makes it seamless to catch Objective-C exceptions, using the standard
* JavaScript `try`/`catch` syntax you are already familiar with.
*
* When an Objective-C method or function throws an `NSException`, you can catch
* the exception and inspect it further. The error object that gets passed can be
* invoked to send messages, just like any other Objective-C object in `NodObjC`.
* The error object also has it's `message` and `stack` properties set, so that
* you can easily retrieve the error message and get a dump of the stack trace.
*
* var array = $.NSMutableArray('alloc')('init')
*
* try {
*
* // This will throw an exception since you can't add a null pointer
* array('addObject', null)
*
* } catch (err) {
*
* err('name')
* // 'NSInvalidArgumentException'
*
* err('reason')
* // '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
*
* err('reason') == err.message
* // true
*
* err.stack
* // NSInvalidArgumentException: *** -[__NSArrayM insertObject:atIndex:]: object cannot be nil
* // at Function.msgSend (/Users/nrajlich/NodObjC/lib/id.js:139:21)
* // at id (/Users/nrajlich/NodObjC/lib/id.js:105:15)
* // at Object.<anonymous> (/Users/nrajlich/NodObjC/array-exception.js:8:3)
* // at Module._compile (module.js:411:26)
* // at Object..js (module.js:417:10)
* // at Module.load (module.js:343:31)
* // at Function._load (module.js:302:12)
* // at Array.0 (module.js:430:10)
* // at EventEmitter._tickCallback (node.js:126:26)
*
* }
*/
/**
* This function accepts native JS types (`String`, `Number`, `Date`. `RegExp`, `Buffer`, `function, [rtnType, [self, argType1, argType2, ...]`) and converts
* them to the proper Objective-C type (`NSString`, `NSNumber`, `NSDate`, `NSRegularExpression`, `NSData`, `Objective-C block ^`).
*
* Often times, you will use this function to cast a JS String into an NSString
* for methods that accept NSStrings (since NodObjC doesn't automatically cast to
* NSStrings in those instances).
*
* var jsString = 'a javascript String'
* var nsString = $(jsString)
*
* $.NSLog(nsString)
*
* @param {String|Number|Date} o the JS object to convert to a Cocoa equivalent type.
* @return {id} The equivalent Cocoa type as the input object. Could be an `NSString`, `NSNumber` or `NSDate`.
*/
function NodObjC (o, m) {
var t = typeof o;
if (t == 'string') {
return exports.NSString('stringWithUTF8String', String(o));
} else if (t == 'number') {
return exports.NSNumber('numberWithDouble', Number(o));
} else if ((o instanceof Date ) || (Object.prototype.toString.call(o) == '[object Date]')) {
return exports.NSDate('dateWithTimeIntervalSince1970', o / 1000);
} else if (Buffer.isBuffer(o)) {
return exports.NSData(
'dataWithBytesNoCopy', o,
'length', o.length);
} else if (Object.prototype.toString.call(o) === '[object RegExp]') {
var options = 0;
//if (o.global) options |= exports. ???
if (o.ignoreCase) options |= exports.NSRegularExpressionCaseInsensitive;
if (o.multiline) options |= exports.NSRegularExpressionAnchorsMatchLines;
// TODO: add NSError support here
var err = null;
return exports.NSRegularExpression(
'regularExpressionWithPattern', exports(o.source),
'options', options,
'error', err);
} else if (t == 'function') {
// create a block pointer
if (m) {
return Import.createBlock(o, m);
}
//TODO: create a function pointer, is this necessary?...
}
throw new Error('Unsupported object passed in to convert: ' + o);
}
var Import = require('./import');
var Types = require('./types');
exports.resolve = Import.resolve;
exports.import =
exports.framework =
exports.importFramework =
function framework (name, _recursive) {
var recursive = arguments.length <= 1 ? 99999999 : _recursive;
Import.import(name, false, exports, recursive);
};
exports.alloc = Import.allocReference;
exports.YES = '\x01';
exports.NO = '\x00';
exports.id = '@';
exports.selector = ':';
Types.registerTypes(exports);