TestsTested | ✓ |
LangLanguage | Obj-CObjective C |
License | MIT |
ReleasedLast Release | Mar 2015 |
Maintained by Adam Sharp.
EnumeratorKit is a collection enumeration library modelled after Ruby's
Enumerable
module and Enumerator
class.
It allows you to work with collections of objects in a very compact, expressive way:
#import <EnumeratorKit/EnumeratorKit.h>
NSArray *numbers = @[ @1, @2, @3 ];
// perform the block once for each element in the collection
[numbers each:^(id i){
NSLog(@"%@", i);
}];
// return a new array with each element converted to a string
[numbers map:^(id i){
return [i stringValue];
}];
Additionally, these operations can be chained together to form a higher-level operation:
NSDictionary *examples = @{ @"Hello": @"world", @"foo": @"BAR" };
[[[examples sortBy:^(id pair){
// sort entries by their keys, case insensitive
return [pair[0] lowercaseString];
}] map:^(id pair){
// combine each key-value pair into a new string
return [NSString stringWithFormat:@"%@ %@", pair[0], pair[1]];
}] select:^(id pair){
return [pair[1] hasSuffix:@"orld"];
}];
// => @[@"Hello world"];
EnumeratorKit implements this functionality for all of the core
collection classes (and their mutable counterparts): NSArray
,
NSDictionary
, NSSet
and NSOrderedSet
.
Any collection conforming to NSFastEnumeration
can also be conveniently
wrapped using the ek_enumerate
function:
[ek_enumerate(@[@1, @2, @3]) map:^(NSNumber *i) {
return @(i.integerValue * 2);
}];
// => @[@2, @4, @6]
EnumeratorKit also provides the EKEnumerator
class, which has a
number of advantages over NSEnumerator
, for example:
peek
at the next element without consuming the
enumerationEKEnumerable
API// you may have seen this in Ruby before
EKEnumerator *fib = [EKEnumerator new:^(id<EKYielder> yielder){
int a = 1, b = 1;
while (1) { // infinite loop (!)
[yielder yield:@(a)];
NSUInteger next = a + b;
a = b; b = next;
}
}];
[[fib take:10] asArray]; // => @[ @1, @1, @2, @3, @5, @8, @13, @21, @34, @55 ]
EnumeratorKit is available via CocoaPods. Add it to your Podfile
:
pod 'EnumeratorKit', '~> 0.1.1'
(And don't forget to pod install
.) Then import the header:
#import <EnumeratorKit/EnumeratorKit.h>
Now you're ready to go.
(Note: If you're interested in using EKFiber
without the rest of
EKEnumerable
, you can use this subspec: pod
'EnumeratorKit/EKFiber'
.)
The EKEnumerable
class defines an API of methods that are all based on one operation:
- (instancetype)each:(void (^)(id obj))block;
Different collection classes implement their own version of -each:
,
to define the order their elements should be traversed. For example, on
NSArray
, -each:
is defined to execute the block once for each item
in turn. For NSDictionary
, -each:
constructs a key-value pair as a
two-element array @[key, value]
and passes it to the block once for each
entry.
EnumeratorKit then uses the Objective-C runtime to "mix in" to another
class all the methods defined in EKEnumerable
(a la Ruby modules).
This is similar to using an Objective-C category to add methods to an
existing class, but is done entirely at runtime. (There is also the
added benefit that if you subclass an EKEnumerable
class, you can
override any of the methods provided by EKEnumerable
. Category
methods, however, would break polymorphism.)
By defining the API in terms of -each:
, any class can gain the
functionality just by implementing that method and mixing in
EKEnumerable
.
The Kiwi tests provide a lot of useful examples. (If you're interested in reading more, I'd also recommend having a look at Ruby's Enumerable docs.) Here's a whirlwhind tour of the supported operations:
-each
— perform the block once for each item in the collection-eachWithIndex
— like -each
, but with the current index passed
as a second argument to the block-asArray
— get an array representation of any enumeration-take
— get the specified number of elements from the beginning
of the enumeration-map
— apply the block to each item of the collection, returning a new
collection of transformed values-select
— create a new enumerable with all the elements for which the
block returns YES
-find
— return the first element for which the block returns
YES
, otherwise nil
if no matching element is found-any
— check if an element in the collection passes the block-all
— check if all elements in the collection pass the block-sort
— return a sorted array (items in the collection must
respond to compare:
)-sortWith
— like -sort
, but allows you to specify an
NSComparator
-sortBy
— use the result of applying the block to each element
as sort keys for sorting the receiver-reduce
— traverse the enumerable, evaluating the block against each
element, and accumulating a new value at each step (for example, "reducing"
an array of numbers into a single number that represents sum)You can very easily get the benefit of the entire EKEnumerable
API in
your own collection classes:
EKEnumerable
protocol in your class's public interface# import <EnumeratorKit.h>
@interface MyAwesomeCollection : NSObject <EKEnumerable>
...
@end
+load
and includeEKEnumerable
+ (void)load
{
[self includeEKEnumerable];
}
-each:
, and -initWithEnumerable:
@implementation MyAwesomeCollection
.
.
.
- (instancetype)initWithEnumerable:(id<EKEnumerable>)enumerable
{
// traverse the enumerable, adding each item to your collection
}
- (instancetype)each:(void (^)(id))block
{
// hypothetical enumeration code
for (int i; i < self.length; i++) {
// call the block, passing in each element
block(self[i]);
}
// make sure you return self, to enable enumerator chaining
return self;
}
.
.
.
@end
I'd recommend opening an issue first before spending a lot of time working on a new feature. However if your change is relatively self contained, it's often easier for me to evaluate in the form of a pull request.
Implementing the first version of this project taught me a whole lot
about Objective-C, and Ruby's Enumerable
and Enumerator
functionality. Special thanks to:
EKEnumerable
and EKEnumerator
.EKFiber
!