Friday, May 18, 2012

Blocks (3)

Here is one more simple use of blocks in Objective-C (previous posts here and here).

The docs for NSArray give these three functions using blocks:

enumerateObjectsUsingBlock:
enumerateObjectsWithOptions:usingBlock:
enumerateObjectsAtIndexes:options:usingBlock:


As the reference indicates, these methods require a particular form of block with this signature:

^(id s, NSUInteger i, BOOL *stop)

(Apple uses idx for i). The stop flag is meant to tbe used to bail from an iteration when the first matching object is found. The index is not a loop counter but shows the index of the current object within the array.

// clang blocks2.m -o prog -framework Foundation -fobjc-gc-only
#import <Foundation/Foundation.h>

typedef void (^B) (id s, NSUInteger idx, BOOL *stop);

int main(int argc, char * argv[]){
    B b = ^(id s, NSUInteger i, BOOL *stop) { 
        NSLog(@"i = %lu, pre : s = %@", i, s);
        if ([s caseInsensitiveCompare:@"B"] == 0) {
            NSLog(@"setting stop, s = %@", s);
            *stop = YES;
        }
        NSLog(@"post: s = %@", s);
    };
    
    NSArray *arr = [NSArray arrayWithObjects:@"a",@"B",@"c",@"d",nil];
    [arr enumerateObjectsUsingBlock:b];
    printf("------------------------------\n");
    //NSUint o = NSEnumerationReverse;
    uint o = NSEnumerationReverse;
    [arr enumerateObjectsWithOptions:o
                          usingBlock:b];
    printf("------------------------------\n");
    NSIndexSet *is;
    NSRange R = NSMakeRange(2,2);
    is = [NSIndexSet indexSetWithIndexesInRange:R];
    [arr enumerateObjectsAtIndexes:is
                           options:NO
                        usingBlock:b];
    return 0;
}


> ./prog
2012-05-18 09:02:07.791 prog[382:707] i = 0, pre : s = a
2012-05-18 09:02:07.793 prog[382:707] post: s = a
2012-05-18 09:02:07.794 prog[382:707] i = 1, pre : s = B
2012-05-18 09:02:07.795 prog[382:707] setting stop, s = B
2012-05-18 09:02:07.795 prog[382:707] post: s = B
------------------------------
2012-05-18 09:02:07.796 prog[382:707] i = 3, pre : s = d
2012-05-18 09:02:07.796 prog[382:707] post: s = d
2012-05-18 09:02:07.797 prog[382:707] i = 2, pre : s = c
2012-05-18 09:02:07.797 prog[382:707] post: s = c
2012-05-18 09:02:07.798 prog[382:707] i = 1, pre : s = B
2012-05-18 09:02:07.798 prog[382:707] setting stop, s = B
2012-05-18 09:02:07.799 prog[382:707] post: s = B
------------------------------
2012-05-18 09:02:07.799 prog[382:707] i = 2, pre : s = c
2012-05-18 09:02:07.800 prog[382:707] post: s = c
2012-05-18 09:02:07.800 prog[382:707] i = 3, pre : s = d
2012-05-18 09:02:07.801 prog[382:707] post: s = d

Another NSArray method that uses a block is sortedArrayUsingComparator. Here's an example adapted from the Apple docs, followed by a slightly modified approach.

// clang blocks3.m -o prog -framework Foundation -fobjc-gc-only

#import <Foundation/Foundation.h>

void show(NSArray *a) {
    for (id obj in a) { 
        NSLog(@"%@", [obj description]);
        }   
}

int main(int argc, char * argv[]) {
    NSArray *a;
    a = [NSArray arrayWithObjects:@"1",@"21",@"12",@"11",@"02",nil];
    NSStringCompareOptions o1, o2, o3, o4;
    o1 = NSCaseInsensitiveSearch;
    o2 = NSNumericSearch;
    o3 = NSWidthInsensitiveSearch;
    o4 = NSForcedOrderingSearch;
    NSStringCompareOptions o = o2;
    NSLocale *loc = [NSLocale currentLocale];
    NSComparator cmp = ^(id s1, id s2) {
        NSRange R = NSMakeRange(0, [s1 length]);
        return [s1 compare:s2 options:o range:R locale:loc];
    };
    NSArray *result = [a sortedArrayUsingComparator:cmp];
    show(result);
    
    NSComparator cmp2 = ^(id s1, id s2) {
        NSNumber *x = [NSNumber numberWithInt:[s1 intValue]];
        NSNumber *y = [NSNumber numberWithInt:[s2 intValue]];
        return (NSComparisonResult)[x compare:y];
    };
    result = [a sortedArrayUsingComparator:cmp2];
    show(result);
    return 0;
}

Both versions give the same output:

2012-05-18 13:31:41.071 prog[902:707] 1
2012-05-18 13:31:41.073 prog[902:707] 02
2012-05-18 13:31:41.074 prog[902:707] 11
2012-05-18 13:31:41.074 prog[902:707] 12
2012-05-18 13:31:41.075 prog[902:707] 21

One more NSArray method that uses a block is indexesOfObjectsPassingTest. This example is also adapted from the Apple docs. It's followed by use of a block and enumerateIndexesUsingBlock to enumerate the resulting NSIndexSet.

// clang blocks5.m -o prog -framework Foundation -fobjc-gc-only

#import <Foundation/Foundation.h>

int main(int argc, char * argv[]) {
    NSString *s = [NSString stringWithFormat:@"A B C A B Z G Q"];
    NSArray *a = [s componentsSeparatedByString:@" "];
    NSSet *filterSet = [NSSet setWithObjects:@"A", @"C", @"Q", nil];
    
    BOOL (^test) (id obj, NSUInteger idx, BOOL *stop);
    test = ^ (id obj, NSUInteger idx, BOOL *stop) {
        if (idx < 5) {
            if ([filterSet containsObject:obj]) {
                return YES;
            }
        }
        return NO;
    };
    NSIndexSet *iset = [a indexesOfObjectsPassingTest:test];
    [iset enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
        NSLog(@"%lu", idx);
        } ];
    return 0;
}
Output:
> ./prog
2012-05-18 17:03:26.348 prog[1477:707] 0
2012-05-18 17:03:26.351 prog[1477:707] 2
2012-05-18 17:03:26.351 prog[1477:707] 3