Altthough I am using NSMutableDictionary for different reasons and in a different way (I am not subclassing it) I found another approach.
I am using NSMutableDictionary, since it nicely serializes and deserializes from/to JSON. To get and set values I am using "wrappers". Those are objects that use the dictionaries as "raw" entities and provide getters and setters to access the values. My getter and setter implementations then simply define the keys (and object types).
There also is a base class which provides the property I am passing those dictionaries to (or get them from).
@protocol PMBase <NSObject>
@property (nonatomic, strong, nullable) NSMutableDictionary<NSString*, id>* entity;
@end
@interface MBase : NSObject<PMBase>
@property (nonatomic, strong, nullable) NSMutableDictionary<NSString*, id>* entity;
@end
inline static NSString* _Nullable mbase_get_string(id<PMBase> _Nonnull const obj,
NSString* _Nonnull const key,
NSString* _Nullable const fallback) {
id const val = [obj.entity objectForKey:key];
return [val isKindOfClass:NSString.class] ? val : fallback;
}
inline static void mbase_set_string(id<PMBase> _Nonnull const obj,
NSString* _Nonnull const key,
NSString* _Nullable const value) {
if (value) {
[obj.entity setObject:value forKey:key];
} else {
[obj.entity removeObjectForKey:key];
}
}
inline static NSNumber* _Nullable mbase_get_number(id<PMBase> _Nonnull const obj,
NSString* _Nonnull const key,
NSNumber* _Nullable const fallback) {
id const val = [obj.entity objectForKey:key];
return [val isKindOfClass:NSNumber.class] ? val : fallback;
}
inline static void mbase_set_number(id<PMBase> _Nonnull const obj,
NSString* _Nonnull const key,
NSNumber* _Nullable const value) {
if (value) {
[obj.entity setObject:value forKey:key];
} else {
[obj.entity removeObjectForKey:key];
}
}
(MBase implementation is no magic, so no code here.)
In the subclasses I simply define additional properties, where I override the getters and setters. Subclasses pretty much implement the accessors and sometimes, they return instances of other MBase
subclasses.
@protocol PMDevice <NSObject>
// Not important here
@end
@interface MDevice : MBase <PMDevice>
@property (nonatomic, strong, nonnull) NSString* deviceName;
@property (nonatomic, assign) MDeviceType deviceType; // phone, tablet...
@end
@implementation MDevice
- (void)setDeviceName:(NSString*)name {
mbase_set_string(self, @"name", name);
}
- (NSString*)deviceName {
return mbase_get_string(self, @"name", NSLocalizedString(@"Unnamed", @"unnamed device placeholder"));
}
- (void)setDeviceType:(MDeviceType)deviceType {
mbase_set_number(self, @"type", [NSNumber numberWithInt:(int)deviceType]);
}
- (MDeviceType)deviceType {
return (MDeviceType)mbase_get_number(self, @"type", [NSNumber numberWithInt:MDeviceTypeOther]).intValue;
}
@end
Now I needed the key, wrappers use to access a specific dictionary value. I simply added the "last used key" as property to my MBase and added that line to the getter and setter inlines:
@protocol PMBase <NSObject>
@property (nonatomic, strong, nullable) NSString* lastUsedKey;
@property (nonatomic, strong, nullable) NSMutableDictionary<NSString*, id>* entity;
@end
@interface MBase : NSObject<PMBase>
@property (nonatomic, strong, nullable) NSString* lastUsedKey;
@property (nonatomic, strong, nullable) NSMutableDictionary<NSString*, id>* entity;
@end
inline static NSString* _Nullable mbase_get_string(id<PMBase> _Nonnull const obj,
NSString* _Nonnull const key,
NSString* _Nullable const fallback) {
obj.lastUsedKey = key;
id const val = [obj.entity objectForKey:key];
return [val isKindOfClass:NSString.class] ? val : fallback;
}
inline static void mbase_set_string(id<PMBase> _Nonnull const obj,
NSString* _Nonnull const key,
NSString* _Nullable const value) {
obj.lastUsedKey = key;
if (value) {
[obj.entity setObject:value forKey:key];
} else {
[obj.entity removeObjectForKey:key];
}
}
inline static NSNumber* _Nullable mbase_get_number(id<PMBase> _Nonnull const obj,
NSString* _Nonnull const key,
NSNumber* _Nullable const fallback) {
obj.lastUsedKey = key;
id const val = [obj.entity objectForKey:key];
return [val isKindOfClass:NSNumber.class] ? val : fallback;
}
inline static void mbase_set_number(id<PMBase> _Nonnull const obj,
NSString* _Nonnull const key,
NSNumber* _Nullable const value) {
obj.lastUsedKey = key;
if (value) {
[obj.entity setObject:value forKey:key];
} else {
[obj.entity removeObjectForKey:key];
}
}
Now, whenever I need information about a key (keys are subject to change if I come across a reason to do so), I am going to access a value getter and then the lastUsedKey
property.