Question

I have two objects:

@interface AObject : NSObject

@property NSArray *bObjects;

@end

 

@interface BObject : NSObject

@property NSString *name;

@end

Using key-value coding on an instance of AObject, I can get the list of bObjects (@"self.bObjects"), and a list of bObjects' names (@"self.bObjects.name").

However, what I want is the name of only the first of bObjects. My gut is that key-value coding should support list subscripting, like this: @"bObjects[0].name".

But that doesn't seem to exist. How do I get a single entity; the name of an AObject's first BObject, using key-value coding?

Footnote: I realized in my last question I was stupidly conflating NSPredicate and KV-coding.

Was it helpful?

Solution

As Martin R mentioned in the comments, currently the best option would be to create a firstBObject property in the AObject class.

AObject.h/m

@class BObject;

@interface AObject : NSObject
+ (AObject*)aObjectWithBObjects:(NSArray*)bObjects;
@property NSArray *bObjects;
@property (nonatomic, readonly) BObject *firstBObject;
@end

@implementation AObject
+ (AObject*)aObjectWithBObjects:(NSArray*)bObjects
{
    AObject *ao = [[self alloc] init];
    ao.bObjects = bObjects;
    return ao;
}
- (BObject*)firstBObject
{
    return [self.bObjects count] > 0 ? [self.bObjects objectAtIndex:0] : nil;
}
@end

BObject.h/m

@interface BObject : NSObject
+ (BObject*)bObjectWithName:(NSString*)name;
@property NSString *name;
@end

@implementation BObject
+ (BObject*)bObjectWithName:(NSString *)name
{
    BObject *bo = [[self alloc] init];
    bo.name = name;
    return bo;
}
@end

Usage:

NSArray *aobjects = @[
                      [AObject aObjectWithBObjects:@[
                       [BObject bObjectWithName:@"A1B1"],
                       [BObject bObjectWithName:@"A1B2"],
                       [BObject bObjectWithName:@"A1B3"],
                       [BObject bObjectWithName:@"A1B4"]
                       ]],
                      [AObject aObjectWithBObjects:@[
                       [BObject bObjectWithName:@"A2B1"],
                       [BObject bObjectWithName:@"A2B2"],
                       [BObject bObjectWithName:@"A2B3"],
                       [BObject bObjectWithName:@"A2B4"]
                       ]],
                      [AObject aObjectWithBObjects:@[
                       [BObject bObjectWithName:@"A3B1"],
                       [BObject bObjectWithName:@"A3B2"],
                       [BObject bObjectWithName:@"A3B3"],
                       [BObject bObjectWithName:@"A3B4"]
                       ]]
                      ];
NSLog(@"%@", [aobjects valueForKeyPath:@"firstBObject.name"]);

Results

(
A1B1,
A2B1,
A3B1
)

OTHER TIPS

So as it turns out, I had the fortune of being able to simply override -valueForKey: in the root class (AObject). It bears repeating that -valueForKeyPath: calls -valueForKey: on every key, which is cool.

Since that might not be applicable to everyone, and this might be too much manipulation of default, expected behavior, this definitely not the "right" answer.

But here it is anyway:

- (id)valueForKey:(NSString *)string
{
    if ([string characterAtIndex: [string length] - 1] == ']') // Trying to subscript
    {
        NSRegularExpression *subscriptRegex = [[NSRegularExpression alloc] initWithPattern: @"([a-zA-Z]+)\\[([0-9]+)\\]"
                                                                                   options: (NSRegularExpressionOptions)0
                                                                                     error: nil];

        NSString *key = [subscriptRegex stringByReplacingMatchesInString: string
                                                                 options: (NSMatchingOptions)0
                                                                   range: NSMakeRange(0, [string length])
                                                            withTemplate: @"$1"];
        id valueForKey = [self valueForKey: key];
        if (!key || !valueForKey || ![valueForKey respondsToSelector: @selector(objectAtIndexedSubscript:)])
            return nil;

        NSInteger index = [[subscriptRegex stringByReplacingMatchesInString: string
                                                                    options: (NSMatchingOptions)0
                                                                      range: NSMakeRange(0, [string length])
                                                               withTemplate: @"$2"] integerValue];
        if ((index < 0) || (index >= [valueForKey count]))
            return nil;

        return [valueForKey objectAtIndexedSubscript: index];
    }

    return [super valueForKey: string];
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top