简体   繁体   中英

NSMutableSet with custom isEqual: and hash callbacks

I am trying to create a custom NSMutableSet that doesn't use the standard isEqual: and hash selectors on objects.

Typically I want to use this with Parse. I have a NSMutableSet containing PFObject subclass instances, and I consider them equal if they have the same objectId . I know I could override isEqual: and hash in my PFObject subclass, but I don't want that functionality on all my objects. Besides, Parse uses these methods internally, so I don't want to mess up with them.

Here is what I have come up so far :

#import <Foundation/Foundation.h>

@interface NSMutableSet (Additions)

+ (NSMutableSet *)setWithParseObjectIdIsEqualCallback;

@end

#import "NSMutableSet+Additions.h"

@implementation NSMutableSet (Additions)

static Boolean ParseObjectIdIsEqualCallback(const void *value1, const void *value2)
{
    PFObject *obj1 = (__bridge id)value1;
    PFObject *obj2 = (__bridge id)value2;
    NSCParameterAssert([obj1 isKindOfClass:PFObject.class]);
    NSCParameterAssert([obj2 isKindOfClass:PFObject.class]);
    NSCParameterAssert([obj1 isMemberOfClass:obj2.class]);

    return [obj1.objectId isEqualToString:obj2.objectId];
}

static CFHashCode ParseObjectIdHashCallback(const void *value)
{
    PFObject *object = (__bridge id)value;
    NSCParameterAssert([object isKindOfClass:PFObject.class]);

    return object.objectId.hash;
}

+ (NSMutableSet *)setWithParseObjectIdIsEqualCallback
{
    CFSetCallBacks callbacks = kCFTypeSetCallBacks;
    callbacks.equal = ParseObjectIdIsEqualCallback;
    callbacks.hash  = ParseObjectIdHashCallback;
    CFMutableSetRef set = CFSetCreateMutable(kCFAllocatorDefault, 0, &callbacks);
    return CFBridgingRelease(set);
}

@end

I don't really know if it will work or if it is safe to use, and I'm quite unfamiliar with Core Foundation objects and functions like CFBridgingRelease() .

I think your approach is needlessly complicated: you could provide your own isEqual: and hash implementations in a wrapper class, and wrap your PFObject s in it before placing in the NSMutableSet :

@interface PFObjectWrap {
    PFObject *_obj;
}
-(BOOL)isEqual:(id)other;
-(NSUInteger)hash;
+(PFObject*)wrapped;
-(id)initWithPFObject:(PFObject*)obj;
+(PFObjectWrap)wrap:(PFObject*)obj;
@end

@implementation PFObjectWrap
-(id)initWithPFObject:(PFObject*)obj {
    if (self = [super init]) {
        _obj = obj;
    }
    return self;
}
+(PFObjectWrap)wrap:(PFObject*)obj {
    return [[PFObjectWrap alloc] initWithPFObject:obj];
}
+(PFObject*)wrapped {
    return _obj;
}
-(BOOL)isEqual:(id)other {
   // Put your custom implementation here
}
-(NSUInteger)hash {
   // Put your custom implementation here
}
@end

Now you can wrap your objects into PFObjectWrap before adding to NSMutableSet :

NSMutableSet *set = [NSMutableSet set];
[set addObject:[PFObjectWrap wrap:myObject1]];
[set addObject:[PFObjectWrap wrap:myObject2]];

Of course you also need to wrap before searching NSMutableSet .

Your code looks fine. All bridge casts use the right ownership semantics. I don't see any obvious errors.

I think this solution is better than using a wrapper object.

Even simpler, I made an method on my Parse subclass that lets me treat an NSArray as an NSSet .

- (BOOL)isContainedInArray:(NSArray *)array {
    for (Tag *tag in array) {
        if ([self.name isEqualToString:tag.name]) {
            return YES;
        }
    } return  NO;
}

In the view controller where I use it, run this method to determine whether or not to add it to my NSArray . I originally overwrote isEqual and hash but as you mention, it creates conflicts with Parse's internal logic.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM