In my macOS Objective-C application, I have created a subclass of NSMutableSet. What I want to achieve is a NSMutableSet that does not use isEqual: as the comparing strategy. Specifically, The set will contain objects of type NSRunningApplication, and I want the set to work based on the equality of the objects bundle identifiers. Following is my implementation:
Header file:
#import <Cocoa/Cocoa.h>
NS_ASSUME_NONNULL_BEGIN
@interface BundleIdentifierAwareMutableSet : NSMutableSet
@property (atomic, strong) NSMutableSet *backStorageMutableSet;
@property (atomic, strong) NSMutableArray *backStorageMutableArray;
@end
NS_ASSUME_NONNULL_END
Implementation file:
#import "BundleIdentifierAwareMutableSet.h"
@implementation BundleIdentifierAwareMutableSet
@synthesize backStorageMutableSet;
- (instancetype)init {
self = [super init];
if (self) {
self.backStorageMutableSet = [[NSMutableSet alloc] init];
self.backStorageMutableArray = [[NSMutableArray alloc] init];
}
return self;
}
- (NSUInteger)count {
return [self.backStorageMutableArray count];
}
- (NSRunningApplication *)member:(NSRunningApplication *)object {
__block NSRunningApplication *returnValue = nil;
[self.backStorageMutableArray enumerateObjectsUsingBlock:^(NSRunningApplication * _Nonnull app, NSUInteger __unused idx, BOOL * _Nonnull stop) {
if ([app.bundleIdentifier isEqualToString:[object bundleIdentifier]]) {
returnValue = app;
if (![app isEqual:object]) {
NSLog(@"An ordinary set would have not considered the two objects equal.");
}
*stop = YES;
}
}];
return returnValue;
}
- (NSEnumerator *)objectEnumerator {
self.backStorageMutableSet = [NSMutableSet setWithArray:self.backStorageMutableArray];
return [self.backStorageMutableSet objectEnumerator];
}
- (void)addObject:(NSRunningApplication *)object {
NSRunningApplication *app = [self member:object];
if (app == nil) {
[self.backStorageMutableArray addObject:object];
}
}
- (void)removeObject:(NSRunningApplication *)object {
NSArray *snapShot = [self.backStorageMutableArray copy];
[snapShot enumerateObjectsUsingBlock:^(NSRunningApplication * _Nonnull currentApp, NSUInteger __unused idx, BOOL * _Nonnull __unused stop) {
if ([[currentApp bundleIdentifier] isEqualToString:[object bundleIdentifier]]) {
[self.backStorageMutableArray removeObject:currentApp];
if (![currentApp isEqual:object]) {
NSLog(@"An ordinary set would have not considered the two objects equal.");
}
}
}];
}
This seems to work, and indeed, When applicable, Xcode logs that an ordinary NSMutableSet would have not considered two members equal. I would like to bring this implementation to the Production App, but I am afraid I have not considered something important, since this is the first time I subclass NSMutableSet. For example, I am worried about the following method:
- (NSEnumerator *)objectEnumerator {
self.backStorageMutableSet = [NSMutableSet setWithArray:self.backStorageMutableArray];
return [self.backStorageMutableSet objectEnumerator];
}
This is the only use I do of the backStorageMutableSet since the rest is backed to the array. Is this fine or can bring troubles ? Will other parts of the subclass bring problems ? Any help will be greatly appreciated. Thanks
Don't do this. Subclassing collections should be the last resort. It can have implications on performance, ... Try to use highest possible abstraction and go down if it doesn't work for you for some reason.
Wrapper object
Wrap the
NSRunningApplicationin another object and provide your ownhash&isEqual:methods.Application.h:Application.m:Toll-free bridging &
CFMutableSetRefCFSetis bridged with theNSSet,CFMutableSetis bridged with theNSMutableSet, etc. It means that you can create a set via Core Foundation API and then use it asNSSetfor example. Core Foundation is a powerful framework which exposes more stuff to you.You can provide a custom set of callbacks for the
CFSet.There're predefined sets of callbacks like:
Which means that you're not forced to provide all of them, but you're free to modify just some of them. Let's prepare two callback functions:
You can use them in this way:
&