Bringing a Linq-style fluent query API to Objective-C.
This project contains a collection of NSArray
methods that allow you to execute query using a fluent syntax, inspired by Linq. In order to use Linq to Objective-C simply copy the NSArray+LinqExtensions.h
and NSArray+LinqExtensions.m
files into your project and import the header within any file where you wish to use the API.
As an example of the types of query this API makes possible, let's say you have an array of Person
instances, each with a surname
property. The following query will create a sorted, comma-separated list of the unique surnames from the array:
Selector surnameSelector = ^id(id person){
return [person name];
};
Accumulator csvAccumulator = ^id(id item, id aggregate) {
return [NSString stringWithFormat:@"%@, %@", aggregate, item];
};
NSArray* surnamesList = [[[[people select:surnameSelector]
sort]
distinct]
aggregate:csvAccumulator];
For a detailed discussion of the history of Linq and why I implemented this API, see the related blog post.
NSArray
methods:
- where
- select
- sort
- ofType
- selectMany
- distinct
- aggregate
- firstOrNil
- lastOrNil
- skip
- take
- any
- all
- groupBy
- toDictionary
NSDictionary
methods:
This section provides a few brief examples of each of the API methods. A number of these examples use an array of Person instances:
interface Person : NSObject
@property (retain, nonatomic) NSString* name;
@property (retain, nonatomic) NSNumber* age;
@end
- (NSArray*) where:(Predicate)predicate;
Filters a sequence of values based on a predicate.
The following example uses the where method to find people who are 25:
NSArray* peopleWhoAre25 = [input where:^BOOL(id person) {
return [[person age] isEqualToNumber:@25];
}];
- (NSArray*) select:(Selector)transform;
Projects each element of a sequence into a new form. Each element in the array is transformed by a 'selector' into a new form, which is then used to populate the output array.
The following example uses a selector that returns the name of each Person
instance. The output will be an array of NSString
instances.
NSArray* names = [input select:^id(id person) {
return [person name];
}];
- (NSArray*) sort;
- (NSArray*) sort:(Selector)keySelector;
Sorts the elements of an array, either via their 'natural' sort order, or via a keySelector
.
As an example of natural sort, the following sorts a collection of NSNumber
instances:
NSArray* input = @[@21, @34, @25];
NSArray* sortedInput = [input sort];
In order to sort an array of Person instances, you can use the key selector:
NSArray* sortedByName = [input sort:^id(id person) {
return [person name];
}];
- (NSArray*) ofType:(Class)type;
Filters the elements of an an array based on a specified type.
In the following example a mixed array of NSString
and NSNumber
instances is filtered to return just the NSString
instances:
NSArray* mixed = @[@"foo", @25, @"bar", @33];
NSArray* strings = [mixed ofType:[NSString class]];
- (NSArray*) selectMany:(Selector)transform;
Projects each element of a sequence to an NSArray
and flattens the resulting sequences into one sequence.
This is an interesting one! This is similar to the select
method, however the selector must return an NSArray
, with the select-many operation flattening the returned arrays into a single sequence.
Here's a quick example:
NSArray* data = @[@"foo, bar", @"fubar"];
NSArray* components = [data selectMany:^id(id string) {
return [string componentsSeparatedByString:@", "];
}];
A more useful example might use select-many to return all the order-lines for an array of orders.
- (NSArray*) distinct;
Returns distinct elements from a sequence. This simply takes an array of ties, returning an array of the distinct (i.e. unique) values in source order.
Here's an example:
NSArray* names = @[@"bill", @"bob", @"bob", @"brian", @"bob"];
NSArray* distinctNames = [names distinct];
// returns bill, bob and brian
- (id) aggregate:(Accumulator)accumulator;
Applies an accumulator function over a sequence. This method transforms an array into a single value by applying an accumulator function to each successive element.
Here's an example that creates a comma separated list from an array of strings:
NSArray* names = @[@"bill", @"bob", @"brian"];
id aggregate = [names aggregate:^id(id item, id aggregate) {
return [NSString stringWithFormat:@"%@, %@", aggregate, item];
}];
// returns "bill, bob, brian"
Here's another example that returns the largest value from an array of numbers:
NSArray* numbers = @[@22, @45, @33];
id biggestNumber = [numbers aggregate:^id(id item, id aggregate) {
return [item compare:aggregate] == NSOrderedDescending ? item : aggregate;
}];
// returns 45
- (id) firstOrNil;
Returns the first element of an array, or nil if the array is empty.
- (id) lastOrNil;
Returns the last element of an array, or nil if the array is empty
- (NSArray*) skip:(NSUInteger)count;
Returns an array that skips the first 'n' elements of the source array, including the rest.
- (NSArray*) take:(NSUInteger)count;
Returns an array that contains the first 'n' elements of the source array.
- (BOOL) any:(Condition)condition;
Tests whether any item in the array passes the given condition.
As an example, you can check whether any number in an array is equal to 25:
NSArray* input = @[@25, @44, @36];
BOOL isAnyEqual = [input any:^BOOL(id item) {
return [item isEqualToNumber:@25];
}];
// returns YES
- (BOOL) all:(Condition)condition;
Tests whether all the items in the array pass the given condition.
As an example, you can check whether all the numbers in an array are equal to 25:
NSArray* input = @[@25, @44, @36];
BOOL areAllEqual = [input all:^BOOL(id item) {
return [item isEqualToNumber:@25];
}];
// returns NO
- (NSDictionary*) groupBy:(Selector)groupKeySelector;
Groups the items in an array returning a dictionary. The groupKeySelector
is applied to each element in the array to determine which group it belongs to.
The returned dictionary has the group values (as returned by the key selector) as its keys, with an NSArray
for each value, containing all the items within that group.
As an example, if you wanted to group a number of strings by their first letter, you could do the following:
NSArray* input = @[@"James", @"Jim", @"Bob"];
NSDictionary* groupedByFirstLetter = [input groupBy:^id(id name) {
return [name substringToIndex:1];
}];
// the returned dictionary is as follows:
// {
// J = ("James", "Jim");
// B = ("Bob");
// }
- (NSDictionary*) toDictionaryWithKeySelector:(Selector)keySelector;
- (NSDictionary*) toDictionaryWithKeySelector:(Selector)keySelector valueSelector:(Selector)valueSelector;
Transforms the source array into a dictionary by applying the given keySelector and (optional) valueSelector to each item in the array. If you use the toDictionaryWithKeySelector:
method, or the toDictionaryWithKeySelector:valueSelector:
method with a nil
valueSelector, the value for each dictionary item is simply the item from the source array.
As an example, the following code takes an array of names, creating a dictionary where the key is the first letter of each name and the value is the name (in lower case).
NSArray* input = @[@"Frank", @"Jim", @"Bob"];
NSDictionary* dictionary = [input toDictionaryWithKeySelector:^id(id item) {
return [item substringToIndex:1];
} valueSelector:^id(id item) {
return [item lowercaseString];
}];
// result:
// (
// F = frank;
// J = jim;
// B = bob;
// )
Whereas in the following there is no value selector, so the strings from the source array are used directly.
NSArray* input = @[@"Frank", @"Jim", @"Bob"];
NSDictionary* dictionary = [input toDictionaryWithKeySelector:^id(id item) {
return [item substringToIndex:1];
}];
// result:
// ()
// F = Frank;
// J = Jim;
// B = Bob;
// )
This section provides a few brief examples of each of the API methods.
- (NSDictionary*) where:(KeyValuePredicate)predicate;
Filters a dictionary based on a predicate.
The following example uses filters a dictionary to remove any keys that are equal to their value.
NSDictionary* result = [input where:^BOOL(id key, id value) {
return [key isEqual:value];
}];
- (NSDictionary*) select:(KeyValueSelector)selector;
Projects each key-value pair in a dictionary into a new form. Each key-value pair is transformed by a 'selector' into a new form, which is then used to populate the values of the output dictionary.
The following example takes a dictionary which has string values, returning a new dictionary where each value is the first character of the source string.
NSDictionary* result = [input select:^id(id key, id value) {
return [value substringToIndex:1];
}];
- (NSArray*) toArray:(KeyValueSelector)selector;
Projects each key-value pair in a dictionary into a new form, which is used to populate the output array.
The following example takes a dictionary which has string values, returning an array which concatenates the key and value for each item in the dictionary.
NSDictionary* input = @{@"A" : @"Apple", @"B" : @"Banana", @"C" : @"Carrot"};
NSArray* result = [input toArray:^id(id key, id value) {
return [NSString stringWithFormat:@"%@, %@", key, value];
}];
// result:
// (
// "A, Apple",
// "B, Banana",
// "C, Carrot"
// )