Obj-C простой способ конвертировать из NSObject со свойствами в NSDictionary?
Я столкнулся с чем-то, что в конце концов понял, но думаю, что, возможно, гораздо более эффективный способ выполнить его.
У меня был объект (NSObject, который принял протокол MKAnnation), который обладал рядом свойств (название, субтитры, широта, долгота, информация и т.д.). Мне нужно было передать этот объект другому объекту, который хотел извлечь информацию из него с помощью методов objectForKey, как NSDictionary (потому что это то, что он получал с другого контроллера представления).
В результате я создал новый NSMutableDictionary и использовал setObject: forKey для передачи каждой части жизненно важной информации, а затем просто передал только что созданный словарь.
Был ли способ проще сделать это?
Вот соответствующий код:
// sender contains a custom map annotation that has extra properties...
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:@"showDetailFromMap"])
{
DetailViewController *dest =[segue destinationViewController];
//make a dictionary from annotaion to pass info
NSMutableDictionary *myValues =[[NSMutableDictionary alloc] init];
//fill with the relevant info
[myValues setObject:[sender title] forKey:@"title"] ;
[myValues setObject:[sender subtitle] forKey:@"subtitle"];
[myValues setObject:[sender info] forKey:@"info"];
[myValues setObject:[sender pic] forKey:@"pic"];
[myValues setObject:[sender latitude] forKey:@"latitude"];
[myValues setObject:[sender longitude] forKey:@"longitude"];
//pass values
dest.curLoc = myValues;
}
}
Спасибо за вашу коллективную мудрость.
Вот что я придумал, благодаря людям, ниже...
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:@"showDetailFromMap"])
{
DetailViewController *dest =[segue destinationViewController];
NSArray *myKeys = [NSArray arrayWithObjects:
@"title",@"subtitle",@"info",@"pic",@"latitude",@"longitude", nil];
//make a dictionary from annotaion to pass info
NSDictionary *myValues =[sender dictionaryWithValuesForKeys:myKeys];
//pass values
dest.curLoc = myValues;
}
}
И еще более простое исправление, как показано ниже...
Использование valueForKey вместо объекта для ключа для получения информации.
Ответы
Ответ 1
Если свойства имели те же имена, что и ключи, используемые для доступа к словарю, тогда вы могли бы использовать KVC и имели valueForKey:
вместо objectForKey
.
Например, данный словарь
NSDictionary *annotation = [[NSDictionary alloc] initWithObjectsAndKeys:
@"A title", @"title", nil];
и этот объект
@interface MyAnnotation : NSObject
@property (nonatomic, copy) NSString *title;
@end
Не имело бы значения, имел ли я экземпляр словаря или MyAnnotation
, я мог бы назвать
[annotation valueForKey:@"title"];
Очевидно, что работает и другим способом, например,
[annotation setValue:@"A title" forKey:@"title"];
Ответ 2
Конечно! Используйте objc-runtime и KVC!
#import <objc/runtime.h>
@interface NSDictionary(dictionaryWithObject)
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id) obj;
@end
@implementation NSDictionary(dictionaryWithObject)
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
[dict setObject:[obj valueForKey:key] forKey:key];
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
@end
И вы будете использовать вот так:
MyObj *obj = [MyObj new];
NSDictionary *dict = [NSDictionary dictionaryWithPropertiesOfObject:obj];
NSLog(@"%@", dict);
Ответ 3
Это старое сообщение, и ответ Ричарда Дж. Росса III действительно полезен, но в случае пользовательских объектов (у пользовательского класса есть другой пользовательский объект). Однако иногда свойствами являются другие объекты и т.д., Что делает сериализацию несколько сложной.
Details * details = [[Details alloc] init];
details.tomato = @"Tomato 1";
details.potato = @"Potato 1";
details.mangoCount = [NSNumber numberWithInt:12];
Person * person = [[Person alloc]init];
person.name = @"HS";
person.age = @"126 Years";
person.gender = @"?";
person.details = details;
Чтобы преобразовать эти типы объектов (несколько пользовательских объектов) в словарь, мне пришлось немного модифицировать Ричард Дж. Росс III.
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
Class classObject = NSClassFromString([key capitalizedString]);
if (classObject) {
id subObj = [self dictionaryWithPropertiesOfObject:[obj valueForKey:key]];
[dict setObject:subObj forKey:key];
}
else
{
id value = [obj valueForKey:key];
if(value) [dict setObject:value forKey:key];
}
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
Я надеюсь, что это поможет кому-то. Полный кредит принадлежит Ричарду Дж. Росс III.
Ответ 4
Чтобы завершить метод Ричарда Дж. Росса, он работает с NSArray пользовательского объекта.
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
Class classObject = NSClassFromString([key capitalizedString]);
id object = [obj valueForKey:key];
if (classObject) {
id subObj = [self dictionaryWithPropertiesOfObject:object];
[dict setObject:subObj forKey:key];
}
else if([object isKindOfClass:[NSArray class]])
{
NSMutableArray *subObj = [NSMutableArray array];
for (id o in object) {
[subObj addObject:[self dictionaryWithPropertiesOfObject:o] ];
}
[dict setObject:subObj forKey:key];
}
else
{
if(object) [dict setObject:object forKey:key];
}
}
free(properties);
return [NSDictionary dictionaryWithDictionary:dict];
}
Ответ 5
Есть так много решений и ничего не работает для меня, поскольку у меня была сложная структура вложенных объектов. Это решение принимает вещи от Ричарда и Дэмиена, но импровизирует, поскольку решение Damien привязано к именованию ключей как имена классов.
Вот заголовок
@interface NSDictionary (PropertiesOfObject)
+(NSDictionary *) dictionaryWithPropertiesOfObject:(id)obj;
@end
Вот файл .m
@implementation NSDictionary (PropertiesOfObject)
static NSDateFormatter *reverseFormatter;
+ (NSDateFormatter *)getReverseDateFormatter {
if (!reverseFormatter) {
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
reverseFormatter = [[NSDateFormatter alloc] init];
[reverseFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
[reverseFormatter setLocale:locale];
}
return reverseFormatter;
}
+ (NSDictionary *)dictionaryWithPropertiesOfObject:(id)obj {
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
unsigned count;
objc_property_t *properties = class_copyPropertyList([obj class], &count);
for (int i = 0; i < count; i++) {
NSString *key = [NSString stringWithUTF8String:property_getName(properties[i])];
id object = [obj valueForKey:key];
if (object) {
if ([object isKindOfClass:[NSArray class]]) {
NSMutableArray *subObj = [NSMutableArray array];
for (id o in object) {
[subObj addObject:[self dictionaryWithPropertiesOfObject:o]];
}
dict[key] = subObj;
}
else if ([object isKindOfClass:[NSString class]]) {
dict[key] = object;
} else if ([object isKindOfClass:[NSDate class]]) {
dict[key] = [[NSDictionary getReverseDateFormatter] stringFromDate:(NSDate *) object];
} else if ([object isKindOfClass:[NSNumber class]]) {
dict[key] = object;
} else if ([[object class] isSubclassOfClass:[NSObject class]]) {
dict[key] = [self dictionaryWithPropertiesOfObject:object];
}
}
}
return dict;
}
@end
Ответ 6
Вы также можете использовать категорию NSObject+APObjectMapping
, доступную в GitHub: https://github.com/aperechnev/APObjectMapping
Это легко. Просто опишите правила отображения в своем классе:
#import <Foundation/Foundation.h>
#import "NSObject+APObjectMapping.h"
@interface MyCustomClass : NSObject
@property (nonatomic, strong) NSNumber * someNumber;
@property (nonatomic, strong) NSString * someString;
@end
@implementation MyCustomClass
+ (NSMutableDictionary *)objectMapping {
NSMutableDictionary * mapping = [super objectMapping];
if (mapping) {
NSDictionary * objectMapping = @{ @"someNumber": @"some_number",
@"someString": @"some_string" };
}
return mapping
}
@end
И тогда вы можете легко сопоставить свой объект со словарем:
MyCustomClass * myObj = [[MyCustomClass alloc] init];
myObj.someNumber = @1;
myObj.someString = @"some string";
NSDictionary * myDict = [myObj mapToDictionary];
Также вы можете проанализировать свой объект из словаря:
NSDictionary * myDict = @{ @"some_number": @123,
@"some_string": @"some string" };
MyCustomClass * myObj = [[MyCustomClass alloc] initWithDictionary:myDict];