1. keroxp

    Posted

    keroxp
Changes in title
+NSArray+Pluck
Changes in tags
Changes in body
Source | HTML | Preview

Underscore.jsにarray.pluckというとても便利なメソッドがある。

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

あまりにも便利すぎるのでObjective-Cでも使いたくなったので実装した。
多分n番煎じだと思う。
cocoaにはvalueForKeyPath:という便利なアクセッサがあるので、入れ子になったNSDictionaryやオブジェクトなどのプロパティも比較できる。

NSArray+Pluck.h
@interface NSArray (Pluck)

- (NSArray*)pluckUsingSelector:(SEL)selector value:(id)value;
- (NSArray*)pluckForKeyPath:(NSString*)keyPath comparingSelector:(SEL)selector value:(id)value;

@end

NSArray+Pluck.m
#import <objc/message.h>

@implementation NSArray (Pluck)

- (NSArray *)pluckUsingSelector:(SEL)selector value:(id)value
{
    return [self pluckForKeyPath:nil comparingSelector:selector value:value];
}

- (NSArray *)pluckForKeyPath:(NSString *)keyPath comparingSelector:(SEL)selector value:(id)value
{
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:self.count];
    for (id obj in self) {
        id target = keyPath ? [obj valueForKeyPath:keyPath] : obj;
        if ([target respondsToSelector:selector]){
            if(objc_msgSend(target, selector, value)) {
                [array addObject:obj];
            }
        }
    }
    return [NSArray arrayWithArray:array];
}

@end

こんな感じで使えます。

NSARray+PluckTests.m
- (void)testBasic
{
    NSArray *a = @[@"a",@"b",@"c",@"ab",@"cd",@"a"];
    NSArray *p = [a pluckUsingSelector:@selector(isEqualToString:) value:@"a"];
    XCTAssert(p.count == 2, );
    for (NSString *s in p) {
        XCTAssert([s isEqualToString:@"a"], );
    }
}

- (void)testKeyPath
{
    NSArray *a = @[
  @{@"hoge": @"hogeval"},
  @{@"fuga" : @"fugaval"},
  @{@"var" : @"varval"},
  @{@"hoga" : @"hogaval"},
  @{@"hoge": @"hogeval2"}
  ];
    NSArray *p1 = [a pluckUsingSelector:@selector(objectForKey:) value:@"hoge"];
    XCTAssert(p1.count == 2, @"key hogeを持っているのは一つだけ");
    NSArray *p2 = [a pluckUsingSelector:@selector(isKindOfClass:) value:[NSDictionary class]];
    XCTAssert(p2.count == a.count, @"全部辞書形");
    NSArray *p3 = [a pluckForKeyPath:@"hoge" comparingSelector:@selector(isEqualToString:) value:@"hogeval"];
    XCTAssert(p3.count == 1, @"key hogeの値がhogevalなのは1つだけ");
}