I've attempted to implement a 1-to-1 dictionary in Objective-C. I'm probably missing some convenient methods that one might commonly want to use.
These are both declared in the same .h
file and implemented in the same .m
file. I think if much more code is added to either of these, splitting them into their own files would probably be preferred, but remaining in the same files allows me to hide some implementation details that I might not otherwise be able to hide.
The goal here is to make the object familiar and feel very similar to an NSDictionary
(or NSMutableDictionary
) but allow 1-to-1 mapping, so that for each key there is exactly one value, and for each value there is exactly one key.
BiDiDictionary.h
Immutable:
@interface BiDiDictionary : NSObject
- (instancetype)init;
- (instancetype)initWithDictionary:(NSDictionary *)dictionary;
+ (instancetype)dictionary;
+ (instancetype)dictionaryWithDictionary:(NSDictionary *)dictionary;
- (id<NSCopying>)objectForKey:(id<NSCopying>)key;
- (id<NSCopying>)keyForObject:(id<NSCopying>)object;
@property (readonly) NSArray *allKeys;
@property (readonly) NSArray *allObjects;
@end
Mutable:
@interface BiDiMutableDictionary : BiDiDictionary
- (void)setObject:(id<NSCopying>)object forKey:(id<NSCopying>)key;
- (void)setKey:(id<NSCopying>)key forObject:(id<NSCopying>)object;
- (void)removeObjectForKey:(id<NSCopying>)key;
- (void)removeKeyForObject:(id<NSCopying>)object;
- (void)removeAllObjects;
@end
BiDiDictionary.m
Immutable:
@implementation BiDiDictionary {
@protected NSMutableDictionary *_keyValueDict;
@protected NSMutableDictionary *_valueKeyDict;
}
- (instancetype)init {
return [self initWithDictionary:nil];
}
- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
self = [super init];
if (self) {
_keyValueDict = [NSMutableDictionary dictionary];
_valueKeyDict = [NSMutableDictionary dictionary];
for (id key in [dictionary allKeys]) {
if (!_keyValueDict[key]) {
id value = dictionary[key];
if (![value conformsToProtocol:@protocol(NSCopying)]) {
NSException *ex = [NSException exceptionWithName:@"InvalidArgumentException"
reason:@"Values must conform to NSCopying protocol"
userInfo:@{@"InvalidArgument":value}];
@throw ex;
} else if (_valueKeyDict[value]) {
NSException *ex = [NSException exceptionWithName:@"DuplicateValueException"
reason:@"Duplicate value found in the initilization dictionary"
userInfo:@{@"Duplicate":value}];
@throw ex;
} else {
_keyValueDict[key] = value;
_valueKeyDict[value] = key;
}
}
}
}
return self;
}
+ (instancetype)dictionary {
return [[self alloc] init];
}
+ (instancetype)dictionaryWithDictionary:(NSDictionary *)dictionary {
return [[self alloc] initWithDictionary:dictionary];
}
- (id)objectForKey:(id)key {
return _keyValueDict[key];
}
- (id)keyForObject:(id)object {
return _valueKeyDict[object];
}
- (NSArray *)allKeys {
return [_keyValueDict allKeys];
}
- (NSArray *)allObjects {
return [_valueKeyDict allKeys];
}
@end
Mutable:
@implementation BiDiMutableDictionary
- (void)setObject:(id<NSCopying>)object forKey:(id<NSCopying>)key {
if (![(id<NSObject>)key conformsToProtocol:@protocol(NSCopying)]) {
NSException *ex = [NSException exceptionWithName:@"InvalidArgumentException"
reason:@"Keys must conform to NSCopying protocol"
userInfo:@{@"InvalidArgument":key}];
@throw ex;
}
if (![(id<NSObject>)object conformsToProtocol:@protocol(NSCopying)]) {
NSException *ex = [NSException exceptionWithName:@"InvalidArgumentException"
reason:@"Objects must conform to NSCopying protocol"
userInfo:@{@"InvalidArgument":object}];
@throw ex;
}
[self removeObjectForKey:key];
[self removeKeyForObject:object];
_keyValueDict[key] = object;
_valueKeyDict[object] = key;
}
- (void)setKey:(id<NSCopying>)key forObject:(id<NSCopying>)object {
@try {
[self setObject:object forKey:key];
}
@catch (NSException *ex) {
// rethrowing to hide some implementation details
@throw;
}
}
- (void)removeObjectForKey:(id<NSCopying>)key {
if (![(id<NSObject>)key conformsToProtocol:@protocol(NSCopying)]) {
NSException *ex = [NSException exceptionWithName:@"InvalidArgumentException"
reason:@"Keys must conform to NSCopying protocol"
userInfo:@{@"InvalidArgument":key}];
@throw ex;
}
id object = _keyValueDict[key];
if (object) {
[_valueKeyDict removeObjectForKey:object];
}
[_keyValueDict removeObjectForKey:key];
}
- (void)removeKeyForObject:(id<NSCopying>)object {
if (![(id<NSObject>)object conformsToProtocol:@protocol(NSCopying)]) {
NSException *ex = [NSException exceptionWithName:@"InvalidArgumentException"
reason:@"Objects must conform to NSCopying protocol"
userInfo:@{@"InvalidArgument":object}];
@throw ex;
}
id key = _valueKeyDict[object];
if (key) {
[_keyValueDict removeObjectForKey:key];
}
[_valueKeyDict removeObjectForKey:object];
}
- (void)removeAllObjects {
[_keyValueDict removeAllObjects];
[_valueKeyDict removeAllObjects];
}
@end
Aha! The rare, but not entirely pointless NSException
class!