Creazione di un'istanza di classe personalizzata da NSDictionary
-
06-07-2019 - |
Domanda
Ho la sensazione che questa sia una domanda stupida, ma lo farò comunque ...
Ho una raccolta di oggetti NSDictionary
le cui coppie chiave / valore corrispondono a una classe personalizzata che ho creato, chiamalo MyClass
. Esiste una procedura facile o "best practice" metodo per fare sostanzialmente qualcosa come MyClass * instance = [
map NSDictionary
proprietà su MyClass
];
? Ho la sensazione di dover fare qualcosa con NSCoding
o NSKeyedUnarchiver
, ma piuttosto che inciampare da solo, immagino che qualcuno là fuori potrebbe essere in grado di indicarmi la giusta direzione.
Soluzione
Il metodo -setValuesForKeysWithDictionary: insieme a -dictionaryWithValuesForKeys: è quello che vuoi usare.
Esempio:
// In your custom class
+ (id)customClassWithProperties:(NSDictionary *)properties {
return [[[self alloc] initWithProperties:properties] autorelease];
}
- (id)initWithProperties:(NSDictionary *)properties {
if (self = [self init]) {
[self setValuesForKeysWithDictionary:properties];
}
return self;
}
// ...and to easily derive the dictionary
NSDictionary *properties = [anObject dictionaryWithValuesForKeys:[anObject allKeys]];
Altri suggerimenti
Non c'è allKeys
su NSObject. Dovrai creare una categoria aggiuntiva su NSObject come di seguito:
NSObject + PropertyArray.h
@interface NSObject (PropertyArray)
- (NSArray *) allKeys;
@end
NSObject + PropertyArray.m
#import <objc/runtime.h>
@implementation NSObject (PropertyArray)
- (NSArray *) allKeys {
Class clazz = [self class];
u_int count;
objc_property_t* properties = class_copyPropertyList(clazz, &count);
NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count ; i++) {
const char* propertyName = property_getName(properties[i]);
[propertyArray addObject:[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]];
}
free(properties);
return [NSArray arrayWithArray:propertyArray];
}
@end
Esempio:
#import "NSObject+PropertyArray.h"
...
MyObject *obj = [[MyObject alloc] init];
obj.a = @"Hello A"; //setting some values to attributes
obj.b = @"Hello B";
//dictionaryWithValuesForKeys requires keys in NSArray. You can now
//construct such NSArray using `allKeys` from NSObject(PropertyArray) category
NSDictionary *objDict = [obj dictionaryWithValuesForKeys:[obj allKeys]];
//Resurrect MyObject from NSDictionary using setValuesForKeysWithDictionary
MyObject *objResur = [[MyObject alloc] init];
[objResur setValuesForKeysWithDictionary:objDict];
Supponendo che la tua classe sia conforme al Key-Value Coding , è possibile utilizzare quanto segue: (definito come categoria su NSDictionary per comodità):
// myNSDictionaryCategory.h:
@interface NSDictionary (myCategory)
- (void)mapPropertiesToObject:(id)instance
@end
// myNSDictionaryCategory.m:
- (void)mapPropertiesToObject:(id)instance
{
for (NSString * propertyKey in [self allKeys])
{
[instance setValue:[self objectForKey:propertyKey]
forKey:propertyKey];
}
}
Ed ecco come lo useresti:
#import "myNSDictionaryCategory.h"
//...
[someDictionary mapPropertiesToObject:someObject];
Se stai facendo questo genere di cose, hai a che fare con JSON e probabilmente dovresti dare un'occhiata a Mantle https://github.com/Mantle/Mantle
Otterrai quindi un metodo conveniente DictionaryValue
[anObject dictionaryValue];
Basta aggiungere la categoria per NSObject per ottenere la rappresentazione del dizionario dagli oggetti personalizzati (nel mio caso utilizzando solo nella serializzazione JSON):
// NSObject+JSONSerialize.h
#import <Foundation/Foundation.h>
@interface NSObject(JSONSerialize)
- (NSDictionary *)dictionaryRepresentation;
@end
// NSObject+JSONSerialize.m
#import "NSObject+JSONSerialize.h"
#import <objc/runtime.h>
@implementation NSObject(JSONSerialize)
+ (instancetype)instanceWithDictionary:(NSDictionary *)aDictionary {
return [[self alloc] initWithDictionary:aDictionary];
}
- (instancetype)initWithDictionary:(NSDictionary *)aDictionary {
aDictionary = [aDictionary clean];
self.isReady = NO;
for (NSString* propName in [self allPropertyNames]) {
[self setValue:aDictionary[propName] forKey:propName];
}
//You can add there some custom properties with wrong names like "id"
//[self setValue:aDictionary[@"id"] forKeyPath:@"objectID"];
self.isReady = YES;
return self;
}
- (NSDictionary *)dictionaryRepresentation {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSArray *propertyNames = [self allPropertyNames];
id object;
for (NSString *key in propertyNames) {
object = [self valueForKey:key];
if (object) {
[result setObject:object forKey:key];
}
}
return result;
}
- (NSArray *)allPropertyNames {
unsigned count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableArray *rv = [NSMutableArray array];
unsigned i;
for (i = 0; i < count; i++) {
objc_property_t property = properties[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
[rv addObject:name];
}
//You can add there some custom properties with wrong names like "id"
//[rv addObject:@"objectID"];
//Example use inside initWithDictionary:
//[self setValue:aDictionary[@"id"] forKeyPath:@"objectID"];
free(properties);
return rv;
}
@end
Inoltre, puoi vedere che la mia soluzione non funzionerà con oggetti personalizzati con oggetti o matrici nidificati. Per gli array: basta cambiare le righe di codice nel metodo DictionaryRepresentation
:
if (object) {
if ([object isKindOfClass:[NSArray class]]) {
@autoreleasepool {
NSMutableArray *array = [NSMutableArray array];
for (id item in (NSArray *)object) {
[array addObject:[item dictionaryRepresentation]];
}
[result setObject:array forKey:key];
}
} else {
[result setObject:object forKey:key];
}
}