_.m is a port of Underscore.js to Objective-C. It strives to provide the fullest feature set possible in a way that is familiar to JavaScript developers (despite the differences between JavaScript and Objective-C).
To help achieve this vision, _.m uses SubjectiveScript.m to bring JavaScript-like syntax and features into Objective-C, and QUnit.m to port unit tests from JavaScript to Objective-C. You should check them out, too!
platform :ios pod '_.m', '~> 0.1.0' pod 'SubjectiveScript.m', '~> 0.1.0'
pod install {YourProject}.xcodeproj
{YourProject}.xcworkspace
file instead of {YourProject}.xcodeprojSubjective-Script helps get rid of [tooMany squareBrackets], compresses construction of objects and numbers, and implements a JavaScript-like syntax and feature set. Check out the GitHub Page for more details. An example....
_.compact([NSArray arrayWithObjects:[NSNumber numberWithInteger:0], [NSNumber numberWithInteger:1], [NSNumber numberWithInteger:2], nil]); // [1,2]
_.compact(AI(0, 1, 2)); // [1,2]
Note: Most of the following examples of Underscore.js are directly from the Underscore.js website to ease understanding.
_.each(list, iterator, [context])
docs
_.each([1, 2, 3], function(num){ alert(num); }); => alerts each number in turn... _.each({one : 1, two : 2, three : 3}, function(num, key){ alert(num); }); => alerts each number in turn...
Arg: _EachBlock => ^(id value, ...) ... => id key, id list
_.each(id list, _EachBlock iterator)
no context
_.each(AI(1, 2, 3), ^(N* num, ...){ SS.alert(num); }); => alerts each number in turn... _.each(OKV({@"one", N.I(1)}, {@"two", N.I(2)}, {@"three", N.I(3)}), ^(N* num, ...){ SS.alert(num); }); => alerts each number in turn...eachWithStopReturns: void
Arg: _EachWithStopBlock => ^B(id value, ...) ... => id key, id list
_.eachWithStop(id list, _EachWithStopBlock iterator)
specialized
_.eachWithStop(AI(1, 2, 3), ^B(N* num, ...){ SS.alert(num); return (num.I<3); }); => alerts each number under 3 in turn...
_.map(list, iterator, [context])
docs
_.map([1, 2, 3], function(num){ return num * 3; }); => [3, 6, 9] _.map({one : 1, two : 2, three : 3}, function(num, key){ return num * 3; }); => [3, 6, 9]
Arg: _MapBlock => ^id(id value, ...) ... => id key, id list
_.map(id list, _MapBlock iterator)
no context
_.map(AI(1, 2, 3), ^(N* num, ...){ return N.I(num.I * 3); }); => [3, 6, 9] _.map(OKV({@"one", N.I(1)}, {@"two", N.I(2)}, {@"three", N.I(3)}), ^id(N* num, ...){ return N.I(num.I * 3); }); => [3, 6, 9]
_.reduce(list, iterator, memo, [context])
docs
var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0); => 6
Arg: _ReduceBlock => ^id(id memo, id value, ...) ... => id key, id list
_.reduce(id list, _ReduceBlock iterator, id memo)
no context
N* sum = _.reduce(AI(1, 2, 3), ^(N* memo, N* num, ...){ return N.I(memo.I + num.I); }, N.I(0)); => 6
_.reduceRight(list, iterator, memo, [context])
docs
var list = [[0, 1], [2, 3], [4, 5]]; var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []); => [4, 5, 2, 3, 0, 1]
Arg: _ReduceBlock => ^id(id memo, id value, ...) ... => id key, id list
_.reduceRight(id list, _ReduceBlock iterator, id memo)
no context
A* list = AO(AI(0, 1), AI(2, 3), AI(4, 5)); A* flat = _.reduceRight(list, ^(A* a, A* b, ...) { return a.concat(b); }, A.new); => [4, 5, 2, 3, 0, 1]
_.find(list, iterator, [context])
docs
var even = _.find([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); => 2
_.find(id list, _FindBlock iterator)
no context
N* even = _.find(AI(1, 2, 3, 4, 5, 6), ^B(N* num){ return num.I % 2 == 0; }); => 2
_.filter(list, iterator, [context])
docs
var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); => [2, 4, 6]
Arg: _ItemTestBlock => ^B(id value, ...) ... => id key, id list
_.filter(id list, _ItemTestBlock iterator)
no context
A* evens = _.filter(AI(1, 2, 3, 4, 5, 6), ^B(N* num, ...){ return num.I % 2 == 0; }); => [2, 4, 6]
_.reject(list, iterator, [context])
docs
var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; }); => [1, 3, 5]
Arg: _ItemTestBlock => ^B(id value, ...) ... => id key, id list
_.reject(id list, _ItemTestBlock iterator)
no context
A* odds = _.reject(AI(1, 2, 3, 4, 5, 6), ^B(N* num, ...){ return num.I % 2 == 0; }); => [1, 3, 5]
_.all(list, iterator, [context])
docs
_.all([true, 1, null, 'yes'], _.identity); => false
Arg: _ItemTestBlock => ^B(id value, ...) ... => id key, id list
_.all(id list, _ItemTestBlock iterator)
no context
_.all(AO(N.B(true), N.I(1), nil, @"yes"), _.identityTruthy) => false
Arg: _ItemTestBlock => ^B(id value, ...) ... => id key, id list
_.any(id list, _ItemTestBlock iterator)
no context
_.any(AO(nil, N.I(0), @"yes", N.B(false)), nil); => true
_.include(id list, id target)
_.include(AI(1, 2, 3), N.I(3)); => true
_.invoke(list, methodName, [*arguments])
docs
_.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); => [[1, 5, 7], [1, 2, 3]]
_.invoke(id list, NSS* methodName, id arg1, ...)
nil terminated
_.invoke(AO(AI(5, 1, 7), AI(3, 2, 1)), @"sort", nil); => [[1, 5, 7], [1, 2, 3]]
_.pluck(list, propertyName)
docs
var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}]; _.pluck(stooges, 'name'); => ["moe", "larry", "curly"]
_.pluck(id list, NSString *propertyName)
A* stooges = AO(OKV({@"name", @"moe"}, {@"age", N.I(40)}), OKV({@"name", @"larry"}, {@"age", N.I(50)}), OKV({@"name", @"curly"}, {@"age", N.I(60)})); _.pluck(stooges, @"name"); => ["moe", "larry", "curly"]
_.max(list, [iterator], [context])
docs
var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}]; _.max(stooges, function(stooge){ return stooge.age; }); => {name : 'curly', age : 60};
_.max(id list, _MaxBlock iterator)
A* stooges = AO(OKV({@"name", @"moe"}, {@"age", N.I(40)}), OKV({@"name", @"larry"}, {@"age", N.I(50)}), OKV({@"name", @"curly"}, {@"age", N.I(60)})); // you can use stooge.age if you use SubjectiveScript's NamedProperties _.max(stooges, ^(O* stooge){ return stooge.get(@"age"); }); => {name : 'curly', age : 60};
_.min(list, [iterator], [context])
docs
var numbers = [10, 5, 100, 2, 1000]; _.min(numbers); => 2
_.min(id list, _MinBlock iterator)
A* numbers = AI(10, 5, 100, 2, 1000); _.min(numbers, nil); => 2
_.sortBy(list, iterator, [context])
docs
_.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); }); => [5, 4, 6, 3, 1, 2]
Arg: _SortByBlock => ^id(id value, ...) ... => id key, id list
_.sortBy(id list, id iteratorOrKey)
_SortByBlock or id
no context
_.sortBy(AI(1, 2, 3, 4, 5, 6), ^(N* num, ...){ return N.F(sin(num.I)); }); => [5, 4, 6, 3, 1, 2]
_.groupBy(list, iterator)
docs
_.groupBy([1.3, 2.1, 2.4], function(num){ return Math.floor(num); }); => {1: [1.3], 2: [2.1, 2.4]} _.groupBy(['one', 'two', 'three'], 'length'); => {3: ["one", "two"], 5: ["three"]}
Arg: _GroupByBlock => ^id(id value, ...) ... => id key, id list
_.groupBy(id list, id iteratorOrKey)
_GroupByBlock or id
no context
_.groupBy(AF(1.3, 2.1, 2.4), ^(N* num, ...){ return N.I(floor(num.F)); }); => {1: [1.3], 2: [2.1, 2.4]} _.groupBy(AO(@"one", @"two", @"three"), @"length"); => {3: ["one", "two"], 5: ["three"]}
_.sortedIndex(list, value, [iterator])
docs
_.sortedIndex([10, 20, 30, 40, 50], 35); => 3
Arg: _SortedIndexBlock => ^id(id value, ...) ... => id key, id list
_.sortedIndex(NSA* array, id list, _SortedIndexBlock iterator)
_.sortedIndex(AI(10, 20, 30, 40, 50), N.I(35), nil); => 3
_.shuffle(list)
docs
_.shuffle([1, 2, 3, 4, 5, 6]); => [4, 1, 6, 3, 5, 2]
_.shuffle(id list)
_.shuffle(AI(1, 2, 3, 4, 5, 6)); => [4, 1, 6, 3, 5, 2]
_.toArray(list)
docs
(function(){ return _.toArray(arguments).slice(1); })(1, 2, 3, 4); => [2, 3, 4]
_.toArray(id list)
(^(I arg1, ...){ ARGS_AI(arguments, arg1); return _.toArray(arguments).slice(1, 0); })(1, 2, 3, 4, AI_END); => [2, 3, 4]
_.size(list)
docs
_.size({one : 1, two : 2, three : 3}); => 3
_.size(id list)
_.size(OKV({@"one", N.I(1)}, {@"two", N.I(2)}, {@"three", N.I(3)})); => 3
_.first(NSA* array, I n)
n<0 for optional
_.first(AI(5, 4, 3, 2, 1), -1); => 5firstIteratorReturns: NSO*
_.firstIterator(NSA* array, ...)
specialized ... => id key, id list
_.map(AO(AI(2, 1), AI(3, 1)), _.firstIterator); => [2, 3]
_.initial(array, [n])
docs
_.initial([5, 4, 3, 2, 1]); => [5, 4, 3, 2]
_.initial(NSA* array, I n)
n<0 for optional
_.initial(AI(5, 4, 3, 2, 1), -1); => [5, 4, 3, 2]initialIteratorReturns: NSA*
_.initialIterator(NSA* array, ...)
specialized ... => id key, id list
_.map(AO(AI(2, 1, 3), AI(3, 1, 2)), _.initialIterator); => [[2, 1], [3, 1]]
_.last(array, [n])
docs
_.last([5, 4, 3, 2, 1]); => 1
_.last(NSA* array, I n)
n<0 for optional
_.last(AI(5, 4, 3, 2, 1), -1); => 1lastIteratorReturns: NSO*
_.lastIterator(NSA* array, ...)
specialized ... => id key, id list
_.map(AO(AI(2, 1), AI(3, 1)), _.lastIterator); => [1, 1]
_.rest(NSA* array, I index)
n<0 for optional
_.rest(AI(5, 4, 3, 2, 1), -1); => [4, 3, 2, 1]restIteratorReturns: NSA*
_.restIterator(NSA* array, ...)
specialized ... => id key, id list
_.map(AO(AI(2, 1, 3), AI(3, 1, 2)), _.restIterator); => [[1, 3], [1, 2]]
_.compact(array)
docs
_.compact([0, 1, false, 2, '', 3]); => [1, 2, 3]
_.compact(NSA* array)
_.compact(AO(N.I(0), N.I(1), N.B(false), N.I(2), @"", N.I(3))); => [1, 2, 3]
_.flatten(array, [shallow])
docs
_.flatten([1, [2], [3, [[4]]]]); => [1, 2, 3, 4]; _.flatten([1, [2], [3, [[4]]]], true); => [1, 2, 3, [[4]]];
_.flatten(NSA* array, B shallow)
_.flatten(AO(N.I(1), AI(2), AO(N.I(3), AO(AI(4)))), false); => [1, 2, 3, 4]; _.flatten(AO(N.I(1), AI(2), AO(N.I(3), AO(AI(4)))), true); => [1, 2, 3, [[4]]];
_.without(array, [*values])
docs
_.without([1, 2, 1, 0, 3, 1, 4], 0, 1); => [2, 3, 4]
_.without(NSA* array, id value1, ...)
nil terminated
_.without(AI(1, 2, 1, 0, 3, 1, 4), N.I(0), N.I(1), nil); => [2, 3, 4]
_.union(*arrays)
docs
_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); => [1, 2, 3, 101, 10]
_.union_(NSA* array, ...)
nil terminated reserved => union_
_.union_(AI(1, 2, 3), AI(101, 2, 1, 10), AI(2, 1), nil); => [1, 2, 3, 101, 10]
_.intersection(*arrays)
docs
_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); => [1, 2]
_.intersection(NSA* array, NSA* array1, ...)
nil terminated
_.intersection(AI(1, 2, 3), AI(101, 2, 1, 10), AI(2, 1), nil); => [1, 2]
_.difference(array, *others)
docs
_.difference([1, 2, 3, 4, 5], [5, 2, 10]); => [1, 3, 4]
_.difference(NSA* array, NSA* other1, ...)
nil terminated
_.difference(AI(1, 2, 3, 4, 5), AI(5, 2, 10), nil); => [1, 3, 4]
_.uniq(array, [isSorted], [iterator])
docs
_.uniq([1, 2, 1, 3, 1, 4]); => [1, 2, 3, 4]
_.uniq(NSA* array)
_.uniq(AI(1, 2, 1, 3, 1, 4)); => [1, 2, 3, 4]uniqAdvancedAlias: uniqueAdvancedReturns: A*
Arg: _MapBlock => ^id(id value, ...) ... => id key, id list
_.uniqAdvanced(NSA* array, B isSorted, _MapBlock iterator)
specialized
_.uniqAdvanced(AI(1, 2, 2, 3, 4, 4), true, ^(N* value, ...) { return N.I(value.I + 1); }); => [1, 2, 3, 4]
_.zip(*arrays)
docs
_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]); => [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
_.zip(NSA* array, ...)
nil terminated
_.zip(AO(@"moe", @"larry", @"curly"), AI(30, 40, 50), AB(true, false, false), nil); => [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
_.zipObject(keys, values)
_.zipObject(['moe', 'larry', 'curly], [30, 40, 50]); => {moe:30,larry:40,curly:50}
_.zipObject((NSA* keys, NSA* values)
_.zipObject(AO(@"moe", @"larry", @"curly"), AI(30, 40, 50)); => {moe:30,larry:40,curly:50}
_.indexOf(array, value, [isSorted])
docs
_.indexOf([1, 2, 3], 2); => 1
_.indexOf(NSA* array, id value)
_.indexOf(AI(1, 2, 3), N.I(2)); => 1indexOfSortedReturns: I
_.indexOfSorted(NSA* array, id value)
specialized
_.indexOfSorted(AI(10, 20, 30, 40, 50), N.I(30)); => 2
_.lastIndexOf(array, value)
docs
_.lastIndexOf([1, 2, 3, 1, 2, 3], 2); => 4
_.lastIndexOf(NSA* array, id value)
_.lastIndexOf(AI(1, 2, 3, 1, 2, 3), N.I(2)); => 4
_.range([start], stop, [step])
docs
_.range(10); => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] _.range(1, 11); => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] _.range(0, 30, 5); => [0, 5, 10, 15, 20, 25] _.range(0, -10, -1); => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] _.range(0); => []
_.range(I start, I stop, I step)
_.range(1, 11, 1); => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] _.range(0, 30, 5); => [0, 5, 10, 15, 20, 25] _.range(0, -10, -1); => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9]rangeSimpleReturns: A*
_.rangeSimple(I stop)
specialized
_.rangeSimple(10); => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] _.rangeSimple(0); => []
_.bind(function, object, [*arguments])
docs
var func = function(greeting){ return greeting + ': ' + this.name }; func = _.bind(func, {name : 'moe'}, 'hi'); func(); => 'hi: moe'
X
JavaScript only
_.bindAll(object, [*methodNames])
docs
var buttonView = { label : 'underscore', onClick : function(){ alert('clicked: ' + this.label); }, onHover : function(){ console.log('hovering: ' + this.label); } }; _.bindAll(buttonView); jQuery('#underscore_button').bind('click', buttonView.onClick); => When the button is clicked, this.label will have the correct value...
X
JavaScript only
_.memoize(function, [hashFunction])
docs
var fibonacci = _.memoize(function(n) { return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); });
Arg: _MemoizeBlock => ^id(id arg1, ...) nil terminated
Arg: _MemoizeHashBlock => ^id(id arg1, ...) nil terminated_.memoize(_MemoizeBlock func, _MemoizeHashBlock hasher)
__block _MemoizedBlock fibonacci = _.memoize(^N*(N* n, ...) { return n.I < 2 ? n : N.I(fibonacci(N.I(n.I - 1), nil).I + fibonacci(N.I(n.I - 2), nil).I); }, nil);
_.delay(function, wait, [*arguments])
docs
var log = _.bind(console.log, console); _.delay(log, 1000, 'logged later'); => 'logged later' // Appears after one second.
_.delay(_DelayBlock func, I waitNS)
no arguments
_.delay(^{ NSLog(@"%d", 1000); }, NSEC_PER_SEC*1); => 'logged later' // Appears after one second.delayBackgroundReturns: voidArg: _DelayBlock => ^()
_.delayBackground(_DelayBlock func, I waitNS)
added
_.delayBackground(^{ NSLog(@"%d", 1000); }, NSEC_PER_SEC*1); => 'logged later' // Appears after one second.
_.defer(function, [*arguments])
docs
_.defer(function(){ alert('deferred'); }); // Returns from the function before the alert runs.
_.defer(_DeferBlock func)
no arguments
_.defer(^{ SS.alert(@"deferred"); }); // Returns from the function before the alert runs.deferBackgroundReturns: voidArg: _DeferBlock => ^()
_.deferBackground(_DeferBlock func)
added
_.deferBackground(^{ SS.alert(@"deferred"); }); // Returns from the function before the alert runs.
_.throttle(function, wait)
docs
var throttled = _.throttle(updatePosition, 100); $(window).scroll(throttled);
Arg: _ThrottleBlock => ^id(id arg1, ...) nil terminated
_.throttle(_ThrottleBlock func, I waitNS, id arg1, ...)
nil terminated
_ThrottledBlock throttled = _.throttle(^id(id arg1, ...){ /* updatePosition */ return nil; }, 100, nil);
_.debounce(function, wait, [immediate])
docs
var lazyLayout = _.debounce(calculateLayout, 300); $(window).resize(lazyLayout);
Arg: _DebounceBlock => ^(id arg1, ...) nil terminated
_.debounce(_DebounceBlock func, I waitNS, B immediate, id arg1, ...)
nil terminated
_DebouncedBlock lazyLayout = _.debounce(^(id arg1, ...){ /* calculateLayout */ }, 300, true, nil);
_.once(function)
docs
var initialize = _.once(createApplication); initialize(); initialize(); // Application is only created once.
Arg: _OnceBlock => ^id(id arg1, ...) nil terminated
_.once(_OnceBlock func, id arg1, ...)
nil terminated
_OncedBlock initialize = _.once(^id(id arg1, ...){ /* createApplication */ return nil; }, nil); initialize(nil); initialize(nil); // Application is only created once.
_.after(count, function)
docs
var renderNotes = _.after(notes.length, render); _.each(notes, function(note) { note.asyncSave({success: renderNotes}); }); // renderNotes is run once, after all notes have saved.
Arg: _AfterBlock => ^id(id arg1, ...) nil terminated
_.after(I times, _AfterBlock func, id arg1, ...)
nil terminated
_AfteredBlock renderNotes = _.after(notes.length, ^id(id arg1, ...){ /* render */ return nil; }, nil); _.each(notes, ^(Note* note, ...) { note.asyncSave(OKV({@"success", renderNotes})); }); // renderNotes is run once, after all notes have saved.
_.wrap(function, wrapper)
docs
var hello = function(name) { return "hello: " + name; }; hello = _.wrap(hello, function(func) { return "before, " + func("moe") + ", after"; }); hello(); => 'before, hello: moe, after'
Arg: _WrapBlock => ^id(_WrappedBlock wrapped, id arg1, ...)
nil terminated
_.wrap(_WrappedBlock func, _WrapBlock wrapper)
_WrappedBlock hello = ^NSS*(NSS* name, ...) { return @"hello: ".add(name); }; hello = _.wrap(hello, ^NSS*(_WrappedBlock func, id arg1, ...) { return @"before, ".add(func(@"moe", nil)).add(@", after"); }); hello(nil, nil); => 'before, hello: moe, after'
_.compose(*functions)
docs
var greet = function(name){ return "hi: " + name; }; var exclaim = function(statement){ return statement + "!"; }; var welcome = _.compose(exclaim, greet); welcome('moe'); => 'hi: moe!'
Arg: _ComposeBlock => ^id(id arg1, ...) nil terminated
_.compose(_ComposeBlock func1, ...)
nil terminated
_ComposeBlock greet = ^(NSS* name, ...){ return @"hi: ".add(name); }; _ComposeBlock exclaim = ^(NSS* statement, ...){ return statement.add(@"!"); }; _ComposedBlock welcome = _.compose(exclaim, greet, nil); welcome(@"moe"); => 'hi: moe!'
_.keys(object)
docs
_.keys({one : 1, two : 2, three : 3}); => ["one", "two", "three"]
_.keys(NSD* obj)
_.keys(OKV({@"one", N.I(1)}, {@"two", N.I(2)}, {@"three", N.I(3)})); => ["one", "two", "three"]
_.values(object)
docs
_.values({one : 1, two : 2, three : 3}); => [1, 2, 3]
_.values(NSD* obj)
_.values(OKV({@"one", N.I(1)}, {@"two", N.I(2)}, {@"three", N.I(3)})); => [1, 2, 3]
_.functions(list, iterator, [context])
docs
_.functions(_); => ["all", "any", "bind", "bindAll", "clone", "compact", "compose" ...
_.functions(NSD* obj)
no iterator
_.functions(OKV({@"map", _.map}, {@"reduce", _.reduce})); => ["map", "reduce"]
Note: _.functions does not yet collect function names from classes. It only collects functions from an NSDictionary in block form.
_.extend(destination, *sources)
docs
_.extend({name : 'moe'}, {age : 50}); => {name : 'moe', age : 50}
_.extend(O* destination, NSD* source1, ...)
nil terminated
_.extend(OKV({@"name", @"moe"}), OKV({@"age", N.I(50)}), nil); => {name : 'moe', age : 50}
_.pick(object, *keys)
docs
_.pick({name : 'moe', age: 50, userid : 'moe1'}, 'name', 'age'); => {name : 'moe', age : 50}
_.pick(O* obj, id key1, ...)
nil terminated
_.pick(OKV({@"name", @"moe"}, {@"age", N.I(50)}, {@"userid", @"moe1"}), @"name", @"age", nil); => {name : 'moe', age : 50}
_.defaults(object, *defaults)
docs
var iceCream = {flavor : "chocolate"}; _.defaults(iceCream, {flavor : "vanilla", sprinkles : "lots"}); => {flavor : "chocolate", sprinkles : "lots"}
_.defaults(O* obj, NSD* default1, ...)
nil terminated
O* iceCream = OKV({@"flavor", @"chocolate"}); _.defaults(iceCream, OKV({@"flavor", @"vanilla"}, {@"sprinkles", @"lots"}), nil); => {flavor : "chocolate", sprinkles : "lots"}
_.clone(object)
docs
_.clone({name : 'moe'}); => {name : 'moe'};
_.clone(NSO* obj)
_.clone(OKV({@"name", @"moe"})); => {name : 'moe'};
_.tap(object, interceptor)
docs
_.chain([1,2,3,200]) .filter(function(num) { return num % 2 == 0; }) .tap(alert) .map(function(num) { return num * num }) .value(); => // [2, 200] (alerted) => [4, 40000]
_.tap(NSO* obj, _TapBlock interceptor)
_.chain(AI(1,2,3,200)) .filter(^B(N* num, ...) { return num.I % 2 == 0; }) .tap(SS.alert) .map(^(N* num, ...) { return N.I(num.I * num.I); }) .value(); => // [2, 200] (alerted) => [4, 40000]
OR:
_.chain(AI(1,2,3,200)) .filter(^B(N* num, ...) { return num.I % 2 == 0; }) .tap(SS.alert) .map(^(N* num, ...) { return N.I(num.I * num.I); }) .NSA; => // [2, 200] (alerted) => [4, 40000]
_.has(object, key)
docs
_.has({a: 1, b: 2, c: 3}, "b"); => true
_.has(NSO* obj, id key)
_.has(OKV({@"a", N.I(1)}, {@"b", N.I(2)}, {@"c", N.I(3)}), @"b"); => true
_.isEqual(object, other)
docs
var moe = {name : 'moe', luckyNumbers : [13, 27, 34]}; var clone = {name : 'moe', luckyNumbers : [13, 27, 34]}; moe == clone; => false _.isEqual(moe, clone); => true
_.isEqual(id a, id b)
O* moe = OKV({@"name", @"moe"}, {@"luckyNumbers", AI(13, 27, 34)}); O* clone = OKV({@"name", @"moe"}, {@"luckyNumbers", AI(13, 27, 34)}); moe == clone; => false _.isEqual(moe, clone); => true
_.isEmpty(object)
docs
_.isEmpty([1, 2, 3]); => false _.isEmpty({}); => true
_.isEmpty(NSO* obj)
_.isEmpty(AI(1, 2, 3)); => false _.isEmpty(O.new); => true
_.isElement(object)
docs
_.isElement(jQuery('body')[0]); => true
X
JavaScript only
_.isArray(object)
docs
(function(){ return _.isArray(arguments); })(); => false _.isArray([1,2,3]); => true
_.isArray(id obj)
difference: arguments are arrays
(^(id arg1, ...){ ARGS_AO(arguments, arg1); _.isArray(arguments); })(nil); => true // DIFFERENT - arguments are arrays! _.isArray(AI(1,2,3)); => true
_.isObject(value)
docs
_.isObject({}); => true _.isObject(1); => false
_.isObject(id obj)
_.isObject(O.new); => true _.isObject(N.I(1)); => false
_.isArguments(object)
docs
(function(){ return _.isArguments(arguments); })(1, 2, 3); => true _.isArguments([1,2,3]); => false
_.isArguments(id obj)
(^(I arg1, ...){ ARGS_AI(arguments, arg1); _.isArguments(arguments); })(1, 2, 3, AI_END); => true _.isArguments(AI(1,2,3)); => false
_.isFunction(object)
docs
_.isFunction(alert); => true
_.isFunction(id obj, id target)
difference: added argument
_.isFunction(_.isFunction, nil); => true
_.isString(object)
docs
_.isString("moe"); => true
_.isString(id obj)
_.isString(@"moe"); => true
_.isNumber(object)
docs
_.isNumber(8.4 * 5); => true
_.isNumber(id obj)
_.isNumber(N.F(8.4 * 5)); => true
_.isFinite(object)
docs
_.isFinite(-101); => true _.isFinite(-Infinity); => false
_.isFinite(id obj)
_.isFinite(N.I(-101)); => true _.isFinite(NF_NEG_INFINITY); => false
_.isBoolean(object)
docs
_.isBoolean(null); => false
_.isBoolean(id obj)
_.isBoolean(nil); => false
_.isDate(object)
docs
_.isDate(new Date()); => true
_.isDate(id obj)
_.isDate(Date.new); => true
_.isRegExp(object)
docs
_.isRegExp(/moe/); => true
X
JavaScript only
_.isNaN(object)
docs
_.isNaN(NaN); => true isNaN(undefined); => true _.isNaN(undefined); => false
_.isNaN(id obj)
_.isNaN(NF_NaN); => true
_.isNull(object)
docs
_.isNull(null); => true _.isNull(undefined); => false
_.isNull(id obj)
_.isNull(nil); => true _.isNull(NSNull.null); => true
_.isUndefined(variable)
docs
_.isUndefined(window.missingVariable); => true
X
JavaScript only
_.noConflict()
docs
var underscore = _.noConflict();
X
JavaScript only
_.identity(value)
docs
var moe = {name : 'moe'}; moe === _.identity(moe); => true
_.identity(value)
O* moe = OKV({@"name", @"moe"}); moe === _.identity(moe); => trueidentityTruthyReturns: _ItemTestBlock => ^B(id value, ...) specialized ... => id key, id list
_.identityTruthy(value)
O* moe = OKV({@"name", @"moe"}); _.identityTruthy(moe); => true
_.times(n, iterator)
docs
_(3).times(function(){ genie.grantWish(); });
_.times(UI n, _TimesBlock iterator)
__(N.I(3)).times(^(UI index){ genie.grantWish(); });
_.mixin(object)
docs
_.mixin({ capitalize : function(string) { return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase(); } }); _("fabio").capitalize(); => "Fabio"
X
JavaScript only
_.uniqueId([prefix])
docs
_.uniqueId('contact_'); => 'contact_104'
_.uniqueId(NSS* prefix)
prefix or nil
_.uniqueId(@"contact_"); => 'contact_104'
_.escape(string)
docs
_.escape('Curly, Larry & Moe'); => "Curly, Larry & Moe"
X
JavaScript only
_.result(object, property)
docs
var object = {cheese: 'crumpets', stuff: function(){ return 'nonsense'; }}; _.result(object, 'cheese'); => "crumpets" _.result(object, 'stuff'); => "nonsense"
_.result(NSO* object, id property)
O* object = OKV({@"cheese", @"crumpets"}, {@"stuff", ^(){ return @"nonsense"; }}); _.result(object, @"cheese"); => "crumpets" _.result(object, @"stuff"); => "nonsense"
_.template(templateString, [data], [settings])
docs
var compiled = _.template("hello: <%= name %>"); compiled({name : 'moe'}); => "hello: moe"
X
JavaScript only
_(value)
_.map([1, 2, 3], function(n){ return n * 2; }); _([1, 2, 3]).map(function(n){ return n * 2; });
__(id value)
change_.map(AI(1, 2, 3), ^(N* n, ...){ return N.I(n.I * 2); }); __(AI(1, 2, 3)).map(^(N* n, ...){ return N.I(n.I * 2); });
In addition, the prototype's methods are proxied through the chained Underscore object.
push
reverse
shift
sort
splice
unshift
concat
join
slice
pop
push
reverse
shift
sort
splice
unshift
concat
join
slice
_.chain(obj)
docs
var stooges = [{name : 'curly', age : 25}, {name : 'moe', age : 21}, {name : 'larry', age : 23}]; var youngest = _.chain(stooges) .sortBy(function(stooge){ return stooge.age; }) .map(function(stooge){ return stooge.name + ' is ' + stooge.age; }) .first() .value(); => "moe is 21"
_.chain(id obj)
A* stooges = AO(OKV({@"name", @"curly"}, {@"age", N.I(25)}), OKV({@"name", @"moe"}, {@"age", N.I(21)}), OKV({@"name", @"larry"}, {@"age", N.I(23)})); _.chain(stooges) .sortBy(^(O* stooge, ...){ return stooge.get(@"age"); }) .map(^(O* stooge, ...){ return stooge.get(@"name").add(@" is ").add((NSS*)stooge.get(@"age")); }) .first(-1) .value(); => "moe is 21"
OR
A* stooges = AO(OKV({@"name", @"curly"}, {@"age", N.I(25)}), OKV({@"name", @"moe"}, {@"age", N.I(21)}), OKV({@"name", @"larry"}, {@"age", N.I(23)})); youngest = _.chain(stooges) .sortBy(^(O* stooge, ...){ return stooge.get(@"age"); }) .map(^(O* stooge, ...){ return stooge.get(@"name").add(@" is ").add((NSS*)stooge.get(@"age")); }) .first(-1) .O; => "moe is 21"
_(obj).value()
docs
_([1, 2, 3]).value(); => [1, 2, 3]
__(obj).value()
change
__([1, 2, 3]).value(); => [1, 2, 3]
__(obj).I
__(obj).UI
__(obj).B
__(obj).F
__(obj).D
specialized
__(N.I(-1)).I; => -1 __(N.I(1)).UI; => 1 __(N.B(true)).B; => true __(N.F(1.2f)).F; => 1.2 __(N.D(3.14159)).D; => 3.14159
__(obj).NSS
__(obj).S
toMutable
__(obj).NSA
__(obj).A
toMutable
__(obj).NSD
__(obj).O
toMutable
__(obj).Date
specialized
__(@"bob").NSS; => @"bob" __(@"bob").S; => @"bob" __(AI(1, 2, 3)).NSA; => [1, 2, 3] __(AI(1, 2, 3)).A; => [1, 2, 3] __(OKV({@"key", N.I(1)})).NSD; => {key: 1} __(OKV({@"key", N.I(1)})).O; => {key: 1} __(Date.new).Date; => now
X
_.m only
isTruthy checks for things that are not nil, not empty, etc.
Returns: B_.isTruthy(id obj)
_.isTruthy(@"hello"); => true _.isTruthy(@""); => false
X
_.m only
isTruthy checks for things that are nil, empty, etc.
Returns: B_.isFalsy(id obj)
_.isFalsy(@""); => true _.isFalsy(@"hello"); => false
X
_.m only
isTruthy checks whether something is an NSDictionary or NSMutableDictionary.
Returns: B_.isDictionary(id obj)
_.isDictionary(NSD.new); => true _.isDictionary(O.new); => true _.isDictionary(N.B(true)); => false
X
_.m only
isTruthy checks whether something is a block.
Returns: B_.isBlock(id obj)
_.isBlock(^{}); => true _.isBlock(N.B(true)); => false
X
_.m only
Generates an _ItemTestBlock that can be used to test arrays by a non-string property value (for example: _.filter, _.reject, _.all, _.any).
Returns: _ItemTestBlock => ^B(id value, ...)_.valueTester(NSS* key, id match)
A* objects = AO(OKV({@"name", @"bob"}, {@"owner", owner1}), OKV({@"name", @"george"}, {@"owner", owner2}), OKV({@"name", @"fred"}, {@"owner", owner1})); _.pluck(_.filter(objects, _.valueTester(@"owner", owner1)), @"name"); => ["bob", "fred"]
X
_.m only
Generates an _ItemTestBlock that can be used to test arrays by a string property value (for example: _.filter, _.reject, _.all, _.any).
Returns: _ItemTestBlock => ^B(id value, ...)_.valueStringTester(NSS* key, NSS* match)
A* objects = AO(OKV({@"name", @"bob"}, {@"owner", @"this guy"}), OKV({@"name", @"george"}, {@"owner", @"that guy"}), OKV({@"name", @"fred"}, {@"owner", @"this guy"})); _.pluck(_.filter(objects, _.valueStringTester(@"owner", @"this guy")), @"name"); => ["bob", "fred"]
X
_.m only
Sets all the given properties to a given value.
Returns: void_.setProps(NSA* array, NSS* key, id value)
A* objects = AO(OKV({@"name", @"bob"}, {@"owner", owner1}), OKV({@"name", @"george"}, {@"owner", owner2}), OKV({@"name", @"fred"}, {@"owner", owner1})); _.setProps(objects, @"owner", nil); SS.stringify(_.pluck(objects, @"owner")); => [null,null,null]
X
_.m only
Returns an array with all the class names in array. Useful for debugging.
Returns: A*_.classNames(NSA* array)
A* objects = AO(ValueOwner.new, ValueOwner.new); _.classNames(objects).join(@",") => ValueOwner,ValueOwner
ARGS_KEY(value): id key = {2nd arg}; ARGS_KEY_LIST(value, LIST_NAME): id key = {2nd arg}; id LIST_NAME = {3rd arg}; ARGS_INDEX(value): I index = {2nd arg}; ARGS_INDEX_LIST(value, LIST_NAME): I index = {2nd arg}; id LIST_NAME = {3rd arg}; ARGS_LIST(value, LIST_NAME): id LIST_NAME = {3rd arg}; ARGS_LIST(value, LIST_NAME): id LIST_NAME = {3rd arg};
ARGS_AO(NAME, lastNamedArg): A* NAME = {all ids after lastNamedArgument (inclusive)}; // nil ARGS_AI(NAME, lastNamedArg): A* NAME = {all Is after lastNamedArgument (inclusive)}; // AI_END
ARG_B(NAME, lastNamedArg): B NAME = {one bool after lastNamedArgument}; ARG_I(NAME, lastNamedArg): I NAME = {one integer after lastNamedArgument}; ARG_UI(NAME, lastNamedArg): UI NAME = {one unsigned integer after lastNamedArgument}; ARG_F(NAME, lastNamedArg): F NAME = {one float after lastNamedArgument}; ARG_D(NAME, lastNamedArg): D NAME = {one double after lastNamedArgument}; ARG_N(NAME, lastNamedArg): N* NAME = {one NSNumber after lastNamedArgument}; ARG_NSO(NAME, lastNamedArg): NSO* NAME = {one NSObject or id after lastNamedArgument};
Variable Arguments: JavaScript allows arguments to be skipped, but Objective-C requires explicit declarations. To allow for the flexibility of Underscore.js, I used the C-style variable argument list: 'someFunction(id value ...)'.
For example, _.map optionally provides the key and collection.
A* doubled = _.map(AI(1, 2, 3), ^(N* num, ...){ return N.I(num.I * 2); });
__block A* keys = A.new; A* values = _.map(AO({@"int", N.I(1)}, {@"string", @"hello"}), ^(id value, ...){ ARGS_KEY(value); // helper macro to save the key in 'id key' variable; keys.push(key); return value; }); // keys = [@"int", string] // values = [1,@"hello"]
Or if you want to get extract variable arguments, you need to provide a nil terminator:
A* result = (A*) (^(id arg1, ...){ ARGS_AO(arguments, arg1); return _.flatten(arguments, false); })(N.I(1), AI(2), AO(N.I(3), AO(AO(AI(4)))), /* NIL_TERMINATION */ nil); // result = [1,2,3,4]
_.m is still young but it already has much of the Underscore.js goodness! Please feel free to submit bug fixes and feature requests on GitHub