Fork me on GitHub
Install
  • - From GitHub
  • - From CocoaPods
Collections
  • - each
  • - eachWithStop new
  • - map
  • - reduce
  • - reduceRight
  • - find
  • - filter
  • - reject
  • - all
  • - any
  • - include
  • - invoke
  • - pluck
  • - max
  • - min
  • - sortBy
  • - groupBy
  • - sortedIndex
  • - shuffle
  • - toArray
  • - size
Arrays
  • - first
  • - firstIterator new
  • - initial
  • - initialIterator new
  • - last
  • - lastIterator new
  • - rest
  • - restIterator new
  • - compact
  • - flatten
  • - without
  • - union
  • - intersection
  • - difference
  • - uniq
  • - uniqAdvanced new
  • - zip
  • - zipObject
  • - indexOf
  • - indexOfSorted new
  • - lastIndexOf
  • - range
  • - rangeSimple new
Functions
  • - bind
  • - bindAll
  • - memoize
  • - delay
  • - delayBackground new
  • - defer
  • - deferBackground new
  • - throttle
  • - debounce
  • - once
  • - after
  • - wrap
  • - compose
Objects
  • - keys
  • - values
  • - functions
  • - extend
  • - pick
  • - defaults
  • - clone
  • - tap
  • - has
  • - isEqual
  • - isEmpty
  • - isElement
  • - isArray
  • - isObject
  • - isArguments
  • - isFunction
  • - isString
  • - isNumber
  • - isFinite
  • - isBoolean
  • - isDate
  • - isRegExp
  • - isNaN
  • - isNull
  • - isUndefined
Utility
  • - noConflict
  • - identity
  • - identityTruthy new
  • - times
  • - mixin
  • - uniqueId
  • - escape
  • - result
  • - template
Chaining
  • - __() OO-Wrapper new
  • - chain
  • - value
Extensions
  • - isTruthy new
  • - isFalsy new
  • - isDictionary new
  • - isBlock new
  • - valueTester new
  • - valueStringTester new
  • - setProps new
  • - classNames new
Other
  • - Helper API
  • - Underscore.js Compatibility
  • - JavaScript differences

_.m

_.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!

Installation

Install From GitHub Source

  • Clone the _.m from GitHub
  • Add all the files from the Lib directory to your project
  • Add all of the files from the Pods/SubjectiveScript.m folder to your project or get them from SubjectiveScript.m on Github
  • Include Underscore.h in your project where you need it and away you go!

Install From CocoaPods

  • Install Ruby and CocoaPods
  • Configure a Podfile in the root directory of your XCode project. For example:
    platform :ios
    pod '_.m',                  '~> 0.1.0'
    pod 'SubjectiveScript.m',   '~> 0.1.0'
  • Install the pods from the Podfile pod install {YourProject}.xcodeproj
  • Use the generated {YourProject}.xcworkspace file instead of {YourProject}.xcodeproj

Note: _.m and Subjective-Script requires ARC to be enabled in your Objective-C project.

Making Objective-C more Scripty!

Subjective-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....

Objective-C:

_.compact([NSArray arrayWithObjects:[NSNumber numberWithInteger:0], [NSNumber numberWithInteger:1], [NSNumber numberWithInteger:2], nil]);
// [1,2]

Subjective-Script:

_.compact(AI(0, 1, 2));
// [1,2]

Comparing Underscore.js and _.m

Note: Most of the following examples of Underscore.js are directly from the Underscore.js website to ease understanding.

Collection Functions (Arrays or Objects)

Underscore.js

_.m

eachAlias: forEach

_.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...
eachAlias: forEachReturns: void

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...

mapAlias: collect

_.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]
mapAlias: collectReturns: A*

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]

reduceAliases: inject, foldl

_.reduce(list, iterator, memo, [context])docs

var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0);
=> 6
reduceAliases: inject, foldlReturns: id

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

reduceRightAlias: foldr

_.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]
reduceRightAlias: foldrReturns: id

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]

findAlias: detect

_.find(list, iterator, [context])docs

var even = _.find([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> 2
findAlias: detectReturns: idArg: _FindBlock => ^B(id value)

_.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

filterAlias: select

_.filter(list, iterator, [context])docs

var evens = _.filter([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [2, 4, 6]
filterAlias: selectReturns: A*

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

_.reject(list, iterator, [context])docs

var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 == 0; });
=> [1, 3, 5]
rejectReturns: A*

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]

allAlias: every

_.all(list, iterator, [context])docs

_.all([true, 1, null, 'yes'], _.identity);
=> false
allAlias: everyReturns: B

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

anyAlias: some

_.any(list, [iterator], [context])docs

_.any([null, 0, 'yes', false]);
=> true
anyAlias: someReturns: B

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

includeAlias: contains

_.include(list, value)docs

_.include([1, 2, 3], 3);
=> true
includeAlias: containsReturns: B

_.include(id list, id target)

_.include(AI(1, 2, 3), N.I(3));
=> true

invoke

_.invoke(list, methodName, [*arguments])docs

_.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
=> [[1, 5, 7], [1, 2, 3]]
invokeReturns: id

_.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

_.pluck(list, propertyName)docs

var stooges = [{name : 'moe', age : 40}, {name : 'larry', age : 50}, {name : 'curly', age : 60}];
_.pluck(stooges, 'name');
=> ["moe", "larry", "curly"]
pluckReturns: NSO*

_.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

_.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};
maxReturns: idArg: _MaxBlock => ^id(id value)

_.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

_.min(list, [iterator], [context])docs

var numbers = [10, 5, 100, 2, 1000];
_.min(numbers);
=> 2
minReturns: idArg: _MinBlock => ^id(id value)

_.min(id list, _MinBlock iterator)

A* numbers = AI(10, 5, 100, 2, 1000);
_.min(numbers, nil);
=> 2

sortBy

_.sortBy(list, iterator, [context])docs

_.sortBy([1, 2, 3, 4, 5, 6], function(num){ return Math.sin(num); });
=> [5, 4, 6, 3, 1, 2]
sortByReturns: id

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

_.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"]}
groupByReturns: O*

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

_.sortedIndex(list, value, [iterator])docs

_.sortedIndex([10, 20, 30, 40, 50], 35);
=> 3
sortedIndexReturns: UI

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

_.shuffle(list)docs

_.shuffle([1, 2, 3, 4, 5, 6]);
=> [4, 1, 6, 3, 5, 2]
shuffleReturns: A*

_.shuffle(id list)

_.shuffle(AI(1, 2, 3, 4, 5, 6));
=> [4, 1, 6, 3, 5, 2]

toArray

_.toArray(list)docs

(function(){ return _.toArray(arguments).slice(1); })(1, 2, 3, 4);
=> [2, 3, 4]
toArrayReturns: NSA*

_.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

_.size(list)docs

_.size({one : 1, two : 2, three : 3});
=> 3
sizeReturns: UI

_.size(id list)

_.size(OKV({@"one", N.I(1)}, {@"two", N.I(2)}, {@"three", N.I(3)}));
=> 3

Array Functions

Underscore.js

_.m

firstAlias: head, take

_.first(array, [n])docs

_.first([5, 4, 3, 2, 1]);
=> 5
firstAlias: head, takeReturns: NSO*

_.first(NSA* array, I n) n<0 for optional

_.first(AI(5, 4, 3, 2, 1), -1);
=> 5
firstIteratorReturns: NSO*

_.firstIterator(NSA* array, ...) specialized ... => id key, id list

_.map(AO(AI(2, 1), AI(3, 1)), _.firstIterator);
=> [2, 3]

initial

_.initial(array, [n])docs

_.initial([5, 4, 3, 2, 1]);
=> [5, 4, 3, 2]
initialReturns: NSA*

_.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

_.last(array, [n])docs

_.last([5, 4, 3, 2, 1]);
=> 1
lastReturns: NSO*

_.last(NSA* array, I n) n<0 for optional

_.last(AI(5, 4, 3, 2, 1), -1);
=> 1
lastIteratorReturns: NSO*

_.lastIterator(NSA* array, ...) specialized ... => id key, id list

_.map(AO(AI(2, 1), AI(3, 1)), _.lastIterator);
=> [1, 1]

restAlias: tail

_.rest(array, [index])docs

_.rest([5, 4, 3, 2, 1]);
=> [4, 3, 2, 1]
restAlias: tailReturns: NSA*

_.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

_.compact(array)docs

_.compact([0, 1, false, 2, '', 3]);
=> [1, 2, 3]
compactReturns: A*

_.compact(NSA* array)

_.compact(AO(N.I(0), N.I(1), N.B(false), N.I(2), @"", N.I(3)));
=> [1, 2, 3]

flatten

_.flatten(array, [shallow])docs

_.flatten([1, [2], [3, [[4]]]]);
=> [1, 2, 3, 4];

_.flatten([1, [2], [3, [[4]]]], true);
=> [1, 2, 3, [[4]]];
flatten
Returns: A*

_.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

_.without(array, [*values])docs

_.without([1, 2, 1, 0, 3, 1, 4], 0, 1);
=> [2, 3, 4]
withoutReturns: A*

_.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

_.union(*arrays)docs

_.union([1, 2, 3], [101, 2, 1, 10], [2, 1]);
=> [1, 2, 3, 101, 10]
unionReturns: A*

_.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

_.intersection(*arrays)docs

_.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]);
=> [1, 2]
intersectionReturns: A*

_.intersection(NSA* array, NSA* array1, ...) nil terminated

_.intersection(AI(1, 2, 3), AI(101, 2, 1, 10), AI(2, 1), nil);
=> [1, 2]

difference

_.difference(array, *others)docs

_.difference([1, 2, 3, 4, 5], [5, 2, 10]);
=> [1, 3, 4]
differenceReturns: A*

_.difference(NSA* array, NSA* other1, ...) nil terminated

_.difference(AI(1, 2, 3, 4, 5), AI(5, 2, 10), nil);
=> [1, 3, 4]

uniqAlias: unique

_.uniq(array, [isSorted], [iterator])docs

_.uniq([1, 2, 1, 3, 1, 4]);
=> [1, 2, 3, 4]
uniqAlias: uniqueReturns: A*

_.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

_.zip(*arrays)docs

_.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]);
=> [["moe", 30, true], ["larry", 40, false], ["curly", 50, false]]
zipReturns: A*

_.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

_.zipObject(keys, values)

_.zipObject(['moe', 'larry', 'curly], [30, 40, 50]);
=> {moe:30,larry:40,curly:50}
zipObjectReturns: O*

_.zipObject((NSA* keys, NSA* values)

_.zipObject(AO(@"moe", @"larry", @"curly"), AI(30, 40, 50));
=> {moe:30,larry:40,curly:50}

indexOf

_.indexOf(array, value, [isSorted])docs

_.indexOf([1, 2, 3], 2);
=> 1
indexOfReturns: I

_.indexOf(NSA* array, id value)

_.indexOf(AI(1, 2, 3), N.I(2));
=> 1
indexOfSortedReturns: I

_.indexOfSorted(NSA* array, id value) specialized

_.indexOfSorted(AI(10, 20, 30, 40, 50), N.I(30));
=> 2

lastIndexOf

_.lastIndexOf(array, value)docs

_.lastIndexOf([1, 2, 3, 1, 2, 3], 2);
=> 4
lastIndexOfReturns: I

_.lastIndexOf(NSA* array, id value)

_.lastIndexOf(AI(1, 2, 3, 1, 2, 3), N.I(2));
=> 4

range

_.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);
=> []
rangeReturns: A*

_.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);
=> []

Function (uh, ahem) Functions

Underscore.js

_.m

bind

_.bind(function, object, [*arguments])docs

var func = function(greeting){ return greeting + ': ' + this.name };
func = _.bind(func, {name : 'moe'}, 'hi');
func();
=> 'hi: moe'
bind

XJavaScript only

bindAll

_.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...
bindAll

XJavaScript only

memoize

_.memoize(function, [hashFunction])docs

var fibonacci = _.memoize(function(n) {
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
});
memoizeReturns: _MemoizedBlock => ^NSO*(id arg1, ...) nil terminated

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

_.delay(function, wait, [*arguments])docs

var log = _.bind(console.log, console);
_.delay(log, 1000, 'logged later');
=> 'logged later' // Appears after one second.
delayReturns: voidArg: _DelayBlock => ^()

_.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

_.defer(function, [*arguments])docs

_.defer(function(){ alert('deferred'); });
// Returns from the function before the alert runs.
deferReturns: voidArg: _DeferBlock => ^()

_.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

_.throttle(function, wait)docs

var throttled = _.throttle(updatePosition, 100);
$(window).scroll(throttled);
throttleReturns: _ThrottledBlock =>^id()

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

_.debounce(function, wait, [immediate])docs

var lazyLayout = _.debounce(calculateLayout, 300);
$(window).resize(lazyLayout);
debounceReturns: _DebouncedBlock => ^()

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

_.once(function)docs

var initialize = _.once(createApplication);
initialize();
initialize();
// Application is only created once.
onceReturns: _OncedBlock => ^id()

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

_.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.
afterReturns: _AfteredBlock => ^id(id arg1, ...) nil terminated

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

_.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'
wrapReturns: _WrappedBlock => ^id(id arg1, ...) nil terminated

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

_.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!'
composeReturns: _ComposedBlock => ^id(id arg1, ...) nil terminated

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!'

Object Functions

Underscore.js

_.m

keys

_.keys(object)docs

_.keys({one : 1, two : 2, three : 3});
=> ["one", "two", "three"]
keysReturns: A*

_.keys(NSD* obj)

_.keys(OKV({@"one", N.I(1)}, {@"two", N.I(2)}, {@"three", N.I(3)}));
=> ["one", "two", "three"]

values

_.values(object)docs

_.values({one : 1, two : 2, three : 3});
=> [1, 2, 3]
valuesReturns: A*

_.values(NSD* obj)

_.values(OKV({@"one", N.I(1)}, {@"two", N.I(2)}, {@"three", N.I(3)}));
=> [1, 2, 3]

functionsAlias: methods

_.functions(list, iterator, [context])docs

_.functions(_);
=> ["all", "any", "bind", "bindAll", "clone", "compact", "compose" ...
functionsAlias: methodsReturns: A*

_.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

_.extend(destination, *sources)docs

_.extend({name : 'moe'}, {age : 50});
=> {name : 'moe', age : 50}
extendReturns: O*

_.extend(O* destination, NSD* source1, ...) nil terminated

_.extend(OKV({@"name", @"moe"}), OKV({@"age", N.I(50)}), nil);
=> {name : 'moe', age : 50}

pick

_.pick(object, *keys)docs

_.pick({name : 'moe', age: 50, userid : 'moe1'}, 'name', 'age');
=> {name : 'moe', age : 50}
pickReturns: O*

_.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

_.defaults(object, *defaults)docs

var iceCream = {flavor : "chocolate"};
_.defaults(iceCream, {flavor : "vanilla", sprinkles : "lots"});
=> {flavor : "chocolate", sprinkles : "lots"}
defaultsReturns: O*

_.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

_.clone(object)docs

_.clone({name : 'moe'});
=> {name : 'moe'};
cloneReturns: id mutable (if possible)

_.clone(NSO* obj)

_.clone(OKV({@"name", @"moe"}));
=> {name : 'moe'};

tap

_.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]
tapReturns: NSO*Arg: _TapBlock => ^(id obj)

_.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

_.has(object, key)docs

_.has({a: 1, b: 2, c: 3}, "b");
=> true
hasReturns: B

_.has(NSO* obj, id key)

_.has(OKV({@"a", N.I(1)}, {@"b", N.I(2)}, {@"c", N.I(3)}), @"b");
=> true

isEqual

_.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
isEqualReturns: B

_.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

_.isEmpty(object)docs

_.isEmpty([1, 2, 3]);
=> false
_.isEmpty({});
=> true
isEmptyReturns: B

_.isEmpty(NSO* obj)

_.isEmpty(AI(1, 2, 3));
=> false
_.isEmpty(O.new);
=> true

isElement

_.isElement(object)docs

_.isElement(jQuery('body')[0]);
=> true
isElement

XJavaScript only

isArray

_.isArray(object)docs

(function(){ return _.isArray(arguments); })();
=> false
_.isArray([1,2,3]);
=> true
isArrayReturns: B

_.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

_.isObject(value)docs

_.isObject({});
=> true
_.isObject(1);
=> false
isObjectReturns: B

_.isObject(id obj)

_.isObject(O.new);
=> true
_.isObject(N.I(1));
=> false

isArguments

_.isArguments(object)docs

(function(){ return _.isArguments(arguments); })(1, 2, 3);
=> true
_.isArguments([1,2,3]);
=> false
isArgumentsReturns: B

_.isArguments(id obj)

(^(I arg1, ...){ ARGS_AI(arguments, arg1); _.isArguments(arguments); })(1, 2, 3, AI_END);
=> true
_.isArguments(AI(1,2,3));
=> false

isFunction

_.isFunction(object)docs

_.isFunction(alert);
=> true
isFunctionReturns: B

_.isFunction(id obj, id target) difference: added argument

_.isFunction(_.isFunction, nil);
=> true

isString

_.isString(object)docs

_.isString("moe");
=> true
isStringReturns: B

_.isString(id obj)

_.isString(@"moe");
=> true

isNumber

_.isNumber(object)docs

_.isNumber(8.4 * 5);
=> true
isNumberReturns: B

_.isNumber(id obj)

_.isNumber(N.F(8.4 * 5));
=> true

isFinite

_.isFinite(object)docs

_.isFinite(-101);
=> true

_.isFinite(-Infinity);
=> false
isFiniteReturns: B

_.isFinite(id obj)

_.isFinite(N.I(-101));
=> true

_.isFinite(NF_NEG_INFINITY);
=> false

isBoolean

_.isBoolean(object)docs

_.isBoolean(null);
=> false
isBooleanReturns: B

_.isBoolean(id obj)

_.isBoolean(nil);
=> false

isDate

_.isDate(object)docs

_.isDate(new Date());
=> true
isDateReturns: B

_.isDate(id obj)

_.isDate(Date.new);
=> true

isRegExp

_.isRegExp(object)docs

_.isRegExp(/moe/);
=> true
isRegExp

XJavaScript only

isNaN

_.isNaN(object)docs

_.isNaN(NaN);
=> true
isNaN(undefined);
=> true
_.isNaN(undefined);
=> false
isNaNReturns: B

_.isNaN(id obj)

_.isNaN(NF_NaN);
=> true

isNull

_.isNull(object)docs

_.isNull(null);
=> true
_.isNull(undefined);
=> false
isNullReturns: B

_.isNull(id obj)

_.isNull(nil);
=> true
_.isNull(NSNull.null);
=> true

isUndefined

_.isUndefined(variable)docs

_.isUndefined(window.missingVariable);
=> true
isUndefined

XJavaScript only

Utility Functions

Underscore.js

_.m

noConflict

_.noConflict()docs

var underscore = _.noConflict();
noConflict

XJavaScript only

identity

_.identity(value)docs

var moe = {name : 'moe'};
moe === _.identity(moe);
=> true
identityReturns: _IdentityBlock => ^id(id value)

_.identity(value)

O* moe = OKV({@"name", @"moe"});
moe === _.identity(moe);
=> true
identityTruthyReturns: _ItemTestBlock => ^B(id value, ...) specialized ... => id key, id list

_.identityTruthy(value)

O* moe = OKV({@"name", @"moe"});
_.identityTruthy(moe);
=> true

times

_.times(n, iterator)docs

_(3).times(function(){ genie.grantWish(); });
timesReturns: voidArg: _TimesBlock => ^(I index)

_.times(UI n, _TimesBlock iterator)

__(N.I(3)).times(^(UI index){ genie.grantWish(); });

mixin

_.mixin(object)docs

_.mixin({
  capitalize : function(string) {
    return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
  }
});
_("fabio").capitalize();
=> "Fabio"
mixin

XJavaScript only

uniqueId

_.uniqueId([prefix])docs

_.uniqueId('contact_');
=> 'contact_104'
uniqueIdReturns: S*

_.uniqueId(NSS* prefix) prefix or nil

_.uniqueId(@"contact_");
=> 'contact_104'

escape

_.escape(string)docs

_.escape('Curly, Larry & Moe');
=> "Curly, Larry &amp; Moe"
escape

XJavaScript only

result

_.result(object, property)docs

var object = {cheese: 'crumpets', stuff: function(){ return 'nonsense'; }};
_.result(object, 'cheese');
=> "crumpets"
_.result(object, 'stuff');
=> "nonsense"
resultReturns: NSO*

_.result(NSO* object, id property)

O* object = OKV({@"cheese", @"crumpets"}, {@"stuff", ^(){ return @"nonsense"; }});
_.result(object, @"cheese");
=> "crumpets"
_.result(object, @"stuff");
=> "nonsense"

template

_.template(templateString, [data], [settings])docs

var compiled = _.template("hello: <%= name %>");
compiled({name : 'moe'});
=> "hello: moe"
template

XJavaScript only

Chaining

can use Underscore in either an object-oriented or a functional style, depending on your preference. The following two lines of code are identical ways to double a list of numbers.

Underscore.js

_(value)
_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });

_.m

__(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.

Underscore.js

pushreverseshiftsort
spliceunshiftconcatjoinslice

_.m

poppushreverseshiftsort
spliceunshiftconcatjoinslice

Underscore.js

_.m

chain

_.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"
chainReturns: _Wrapper => wrapped _

_.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"

value

_(obj).value()docs

_([1, 2, 3]).value();
=> [1, 2, 3]
valueReturns: NSO*

__(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

Extensions

Underscore.js

_.m

isTruthy

X_.m only

isTruthy

isTruthy checks for things that are not nil, not empty, etc.

Returns: B

_.isTruthy(id obj)

_.isTruthy(@"hello");
=> true
_.isTruthy(@"");
=> false

isFalsy

X_.m only

isFalsy

isTruthy checks for things that are nil, empty, etc.

Returns: B

_.isFalsy(id obj)

_.isFalsy(@"");
=> true
_.isFalsy(@"hello");
=> false

isDictionary

X_.m only

isDictionary

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

isBlock

X_.m only

isBlock

isTruthy checks whether something is a block.

Returns: B

_.isBlock(id obj)

_.isBlock(^{});
=> true
_.isBlock(N.B(true));
=> false

valueTester

X_.m only

valueTester

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"]

valueStringTester

X_.m only

valueStringTester

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"]

setProps

X_.m only

setProps

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]

classNames

X_.m only

classNames

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

Helper API

Value, Key, Collection Arguments (_.each, _.map, etc) - No Terminators

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};

Variable Arguments (_.without, _.intersection, _.difference, etc) - Terminators Required

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

One Variable Argument - No Terminators Required

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};

Differences with Underscore.js

  • 'this' context: doesn't exist in Objective-C
  • undefined (_.isUndefined): doesn't exist in Objective-C
  • prototype modifications: not simple in Objective-C
  • native functions (like indexOf): doesn't exist in Objective-C
  • arguments not an array: they are an array in Objective-C (using the Argument macros)
  • DOM-related (_.template, _.isElement): no browser in Objective-C
  • JavaScript-related (_.noConflict, _.bind, _.bindAll, _.mixin): JavaScript-only workarounds or features
  • HTML escaping: could be added, but adds size to the library
  • regex (_.isRegExp): could be supported. Just submit a pull request!
  • equality of self-referencing objects: could be supported. Just submit a pull request!
  • _.isFunction(name, object): added an optional second parameter to find the function

Differences Between Objective-C and JavaScript

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.

You Can Ignore Them

A* doubled = _.map(AI(1, 2, 3), ^(N* num, ...){ return N.I(num.I * 2); });

You Can Use Them

__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]

Please Contribute to _.m!

_.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