Rik
Rik

Reputation: 1987

NSSet of nested subproperty from set

I have a set of someProtocol objects, I'd like to make a set from a NSString nested property on this object:

// Protocol SomeProtocol
@protocol SomeProtocol<NSObject>

@property(nonatomic, readonly) id<SomeSubProtocol> someSubProtocolObject;

@end

// Protocol SomeSubProtocol
@protocol SomeSubProtocol<NSObject>

@property(nonatomic, readonly) NSString *Id;

@end

I have a set of SomeProtocols:

NSSet<id<SomeProtocol>> *setOfSomeSubProtocols;

I'd like to get a NSSet of the Id properties:

NSSet<NSString *> *idSet = ?; // Calculate from arrayOfSomethings.

I've tried:

idSet = [setOfSomeSubProtocols valueForKeyPath @"someSubProtrocolObject.id"];

But I'd prefer something that throws a compiler error if the properties change...

Upvotes: 0

Views: 60

Answers (2)

Larme
Larme

Reputation: 26096

In Swift, I would have suggested to use map(), but it doesn't exists in Objective-C. You can search or implement yourself a version of map in Objective-C.

But, what you can do, is a manual loop, since it's a normal approach of the issue.

__block NSMutableSet *ids = [[NSMutableSet alloc] init];
[setOfSomeSubProtocols enumerateObjectsUsingBlock:^(id<SomeProtocol>  _Nonnull obj, BOOL * _Nonnull stop) {
    [ids addObject:[[obj someSubProtocolObject] identifier]];
}];

It's quite simple and does the job.

You could as said before, implement a map on NSSet, something like this:

@implementation NSSet(Map)

-(NSSet *)mapWithBlock:(id (^)(id))block {
    __block NSMutableSet *set = [[NSMutableSet alloc] init];
    [self enumerateObjectsUsingBlock:^(id  _Nonnull obj, BOOL * _Nonnull stop) {
        id mappedObject = block(obj);
        [set addObject:mappedObject];
    }];
    return set;
}

With call:

NSSet *ids = [setOfSomeSubProtocols mapWithBlock:^id (id<SomeProtocol> object) {
    return [[object someSubProtocolObject] identifier];
}];
NSLog(@"ids: %@", ids);

Upvotes: 1

Eugene Dudnyk
Eugene Dudnyk

Reputation: 6030

Define the following somewhere:

@protocol KeyPath <NSObject> @end
typedef NSString<KeyPath> KeyPath;

NS_INLINE KeyPath *GetKeyPath(__unused unsigned long val, NSString *str) { return (KeyPath *)str; }

#define PRIVATE_KEYPATH(obj, key) GetKeyPath(sizeof(obj.key), @#key)
#define PROTOCOL_KEYPATH(proto, key) PRIVATE_KEYPATH(((id<proto>)nil), key)

then use it like this:

[setOfSomeSubProtocols valueForKeyPath:PROTOCOL_KEYPATH(SomeSubProtocol, Id)];

This will give you a compile error if the type name of SomeSubProtocol or the property name Id changes.

P.S.

id is a bad property name, because it's also a built-in type name. Consider using identifier property name instead.

Upvotes: 1

Related Questions