私はさまざまな理由でさまざまな方法でNSMutableDictionaryを使用していますが(私はそれをサブクラス化していない)、別のアプローチを見つけました。
NSMutableDictionaryを使用しています。これは、JSONとの間で適切にシリアル化および逆シリアル化できるためです。値を取得して設定するには、「ラッパー」を使用しています。これらは、ディクショナリを「生の」エンティティとして使用し、値にアクセスするためのゲッターとセッターを提供するオブジェクトです。次に、ゲッターとセッターの実装は、キー(およびオブジェクトタイプ)を定義するだけです。
これらの辞書を渡す(または取得する)プロパティを提供する基本クラスもあります。
@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.)
サブクラスでは、追加のプロパティを定義するだけで、ゲッターとセッターをオーバーライドします。サブクラスはほとんどアクセサーを実装し、場合によっては他のMBase
サブクラスのインスタンスを返します。
@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
ここでキーが必要になりました。ラッパーは特定のディクショナリ値にアクセスするために使用します。「最後に使用したキー」をプロパティとしてMBaseに追加し、その行をゲッターとセッターのインラインに追加しました。
@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];
}
}
これで、キーに関する情報が必要なときはいつでも(そうする理由に出くわした場合、キーは変更される可能性があります)、値ゲッターにアクセスしてからlastUsedKey
プロパティにアクセスします。