I'm working on cline-server app. I'm getting JSON objects as response from the server,
then I'm converting JSON to NSDictionary. Now I need to map NSDictionary to custom data object.
So I have created BasicDataObject class that has:
#pragma mark - Inits
- (id)initWithDictionary:(NSDictionary *)dictionary {
self = [super init];
if (self) {
[self setValuesForKeysWithDictionary:dictionary];
}
return self;
}
#pragma mark - Service
- (id)valueForUndefinedKey:(NSString *)key {
NSArray *allKeys = [self allKeys];
id returnObject = nil;
BOOL keyFound = NO;
for (NSString *propertyName in allKeys) {
if ([propertyName isEqualToString:key]) {
id object = [self performSelector:NSSelectorFromString(key)];
returnObject = object ? object : [NSNull null];
keyFound = YES;
break;
}
}
if (!keyFound) {
#throw [NSException exceptionWithName:NSUndefinedKeyException reason:[NSString stringWithFormat:#"key '%#' not found", key] userInfo:nil];
}
return returnObject;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSString *capitalizedString = [key stringByReplacingCharactersInRange:NSMakeRange(0,1)
withString:[[key substringToIndex:1] capitalizedString]];
NSString *setterString = [NSString stringWithFormat:#"set%#:", capitalizedString];
[self performSelector:NSSelectorFromString(setterString) withObject:value];
}
- (void)setNilValueForKey:(NSString *)key {
object_setInstanceVariable(self, key.UTF8String, 0);
}
- (NSArray *)allKeys {
unsigned int propertyCount = 0;
objc_property_t *properties = class_copyPropertyList(self.class, &propertyCount);
NSMutableArray *propertyNames = [NSMutableArray array];
for (unsigned int i = 0; i < propertyCount; ++i) {
objc_property_t property = properties[i];
const char *name = property_getName(property);
[propertyNames addObject:[NSString stringWithUTF8String:name]];
}
free(properties);
return propertyNames;
}
Each data object is a subclass of this class, so it could be initialised from NSDictionary.
If some data object subclass needs some custom initialisation, I'm overriding it's:
- (id)initWithDictionary:(NSDictionary *)dictionary
Is it correct/good approach, or do I need to add something more?
This is an OK way to do this. Ive found that rarely are the json keys the most appropriate names for my object's properties, so most people's code that I've seen manually sets each property on their object so they can 1) name it whatever they want 2) convert to primitive values from the json dictionary, which will always contain objects. (Eg NSNumber -> float)
Related
This question already has answers here:
NSDictionary with ordered keys
(9 answers)
Closed 6 years ago.
I am passing NSDictionary to my function as a parameter. I want it's key and values to be in order as I inserted.
for eg. expected output is:
mutable dict:{
zKey1 = Value1;
fKey2 = Value2;
aKey3 = Value3;
}
I have tried following ways to create and set value for keys.
NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc]init];
[mutableDict setObject:#"Value1" forKey:#"zKey1"];
[mutableDict setObject:#"Value2" forKey:#"fKey2"];
[mutableDict setObject:#"Value3" forKey:#"aKey3"];
NSMutableDictionary *dic2=[[NSMutableDictionary alloc]initWithObjectsAndKeys:#"1004",#"userId",#"cucumber",#"domain",#"168d5c02f ",#"token",#"1004",#"userId1",#"cucumber",#"domain1",#"168d5c02f ",#"token1", nil];
NSDictionary * dict = [NSDictionary
dictionaryWithObjects:#[#"Ravi",#"33",#"India",#"India"]
forKeys:#[#"name",#"age",#"location",#"country"]];
NSArray *sortedKeys = [[dict allKeys] sortedArrayUsingSelector: #selector(compare:)];
NSMutableArray *sortedValues = [NSMutableArray array];
for (NSString *key in sortedKeys) {
[sortedValues addObject: [dict objectForKey: key]];
}
NSString *obj1=#"1004";
NSString *obj2=#"cucumber";
NSString *obj3=#"168d5c02f";
NSString *key1=#" userId";
NSString *key2=#"domain";
NSString *key3=#"token";
NSLog(#"dict %#",dict);
NSDictionary *dict8 =[NSDictionary
dictionaryWithObjects:#[obj1,obj2,obj3]
forKeys:#[key1,key2,key3]];
But nothing has worked I am always getting output as
mutable dict:{
aKey3 = Value3;
fKey2 = Value2;
zKey1 = Value1;
}
dict8 {
domain = cucumber;
token = 168d5c02f;
userId = 1004;
}
dict {
age = 33;
country = India;
location = India;
name = Ravi;
}
dic= {
domain = cucumber;
domain1 = cucumber;
token = "168d5c02f ";
token1 = "168d5c02f ";
userId = 1004;
userId1 = 1004;
}
It is always sorting values according to alphabetical order of keys. Many people say that NSDictionary is an unsorted container. But it does gets sorted. Need help desperately. Thank you in advance.
NSDictionary is not ordered by default. It will always be without any order. To create an ordered dictionary, you will need to override the existing form of the Data structure. You can read This tutorial to achieve your end.
To summarize the tutorial (Because everyone hates link-only answers and links can die any time):
NSDictionary stores its keys in a hash table, which is unordered by design. Since this lack of order is fundamental to the hash table storeage, you have to perform subclassing of NSMutableDictionary (and hence reimplementation of the storage).
In your .h file
//
// OrderedDictionary.h
// OrderedDictionary
//
// Created by Matt Gallagher on 19/12/08.
// Copyright 2008 Matt Gallagher. All rights reserved.
//
// Permission is given to use this source code file without charge in any
// project, commercial or otherwise, entirely at your risk, with the condition
// that any redistribution (in part or whole) of source code must retain
// this copyright and permission notice. Attribution in compiled projects is
// appreciated but not required.
//
#import <Cocoa/Cocoa.h>
#interface OrderedDictionary : NSMutableDictionary
{
NSMutableDictionary *dictionary;
NSMutableArray *array;
}
- (void)insertObject:(id)anObject forKey:(id)aKey atIndex:(NSUInteger)anIndex;
- (id)keyAtIndex:(NSUInteger)anIndex;
- (NSEnumerator *)reverseKeyEnumerator;
#end
In your .m file:
//
// OrderedDictionary.m
// OrderedDictionary
//
// Created by Matt Gallagher on 19/12/08.
// Copyright 2008 Matt Gallagher. All rights reserved.
//
// Permission is given to use this source code file without charge in any
// project, commercial or otherwise, entirely at your risk, with the condition
// that any redistribution (in part or whole) of source code must retain
// this copyright and permission notice. Attribution in compiled projects is
// appreciated but not required.
//
#import "OrderedDictionary.h"
NSString *DescriptionForObject(NSObject *object, id locale, NSUInteger indent)
{
NSString *objectString;
if ([object isKindOfClass:[NSString class]])
{
objectString = (NSString *)[[object retain] autorelease];
}
else if ([object respondsToSelector:#selector(descriptionWithLocale:indent:)])
{
objectString = [(NSDictionary *)object descriptionWithLocale:locale indent:indent];
}
else if ([object respondsToSelector:#selector(descriptionWithLocale:)])
{
objectString = [(NSSet *)object descriptionWithLocale:locale];
}
else
{
objectString = [object description];
}
return objectString;
}
#implementation OrderedDictionary
- (id)init
{
return [self initWithCapacity:0];
}
- (id)initWithCapacity:(NSUInteger)capacity
{
self = [super init];
if (self != nil)
{
dictionary = [[NSMutableDictionary alloc] initWithCapacity:capacity];
array = [[NSMutableArray alloc] initWithCapacity:capacity];
}
return self;
}
- (void)dealloc
{
//This method is pre-ARC. Manual Release commands don't work now.
//[dictionary release];
//[array release];
//[super dealloc];
}
- (id)copy
{
return [self mutableCopy];
}
- (void)setObject:(id)anObject forKey:(id)aKey
{
if (![dictionary objectForKey:aKey])
{
[array addObject:aKey];
}
[dictionary setObject:anObject forKey:aKey];
}
- (void)removeObjectForKey:(id)aKey
{
[dictionary removeObjectForKey:aKey];
[array removeObject:aKey];
}
- (NSUInteger)count
{
return [dictionary count];
}
- (id)objectForKey:(id)aKey
{
return [dictionary objectForKey:aKey];
}
- (NSEnumerator *)keyEnumerator
{
return [array objectEnumerator];
}
- (NSEnumerator *)reverseKeyEnumerator
{
return [array reverseObjectEnumerator];
}
- (void)insertObject:(id)anObject forKey:(id)aKey atIndex:(NSUInteger)anIndex
{
if (![dictionary objectForKey:aKey])
{
[self removeObjectForKey:aKey];
}
[array insertObject:aKey atIndex:anIndex];
[dictionary setObject:anObject forKey:aKey];
}
- (id)keyAtIndex:(NSUInteger)anIndex
{
return [array objectAtIndex:anIndex];
}
- (NSString *)descriptionWithLocale:(id)locale indent:(NSUInteger)level
{
NSMutableString *indentString = [NSMutableString string];
NSUInteger i, count = level;
for (i = 0; i < count; i++)
{
[indentString appendFormat:#" "];
}
NSMutableString *description = [NSMutableString string];
[description appendFormat:#"%#{\n", indentString];
for (NSObject *key in self)
{
[description appendFormat:#"%# %# = %#;\n",
indentString,
DescriptionForObject(key, locale, level),
DescriptionForObject([self objectForKey:key], locale, level)];
}
[description appendFormat:#"%#}\n", indentString];
return description;
}
#end
You can Download Matt Gallagher's orderedDictionary here.
I have a Array of custom objects with object having following properties optionID,OptionText. I want to get comma separated string for the optionID property. What would be the best approach to do this in iOS SDK.
for example NSString CommaSeperted = #"1,3,5" etc.
Category to NSArray:
#implementation NSArray(CustomAdditions)
- (NSString *)commaSeparatedStringWithSelector:(SEL)aSelector
{
NSMutableArray *objects = [NSMutableArray array];
for (id obj in self)
{
if ([obj respondsToSelector:aSelector]) {
IMP method = [obj methodForSelector:aSelector];
id (*func)(id, SEL) = (void *)method;
id customObj = func(obj, aSelector);
if (customObj && [customObj isKindOfClass:[NSString class]]) {
[objects addObject:customObj];
}
}
}
return [objects componentsJoinedByString:#","];
}
#end
Example:
#implementation NSDictionary(Test)
- (NSString*)optionID
{
return [self objectForKey:#"optionID"];
}
- (NSString*)OptionText
{
return [self objectForKey:#"OptionText"];
}
#end
NSArray *customObjects = #[#{#"optionID": #"id1", #"OptionText": #"text1" }, #{#"optionID" : #"id2", #"OptionText": #"text2"}];//List of Your custom objects
NSString *commaSeparatedOptionIDs = [customObjects commaSeparatedStringWithSelector:NSSelectorFromString(#"optionID")];
NSString *commaSeparatedOptionTexts = [customObjects commaSeparatedStringWithSelector:NSSelectorFromString(#"OptionText")];
Try this
NSString *commaSeparatedStringOfID = #"";
for (CustomClass *object in yourArray){
commaSeparatedStringOfID = [commaSeparatedStringOfID stringByAppendingString:[NSString stringWithFormat:#"%#,"]];
}
// removing last comma
commaSeparatedStringOfID = [commaSeparatedStringOfID substringToIndex:[commaSeparatedStringOfID length]-1];
commaSeparatedStringOfID will be your required string.
I have two arrays. First contains custom objects. Now I want to copy all object of first array in another array. For that I am using below code.
Arays.
arr_post=[[NSMutableArray alloc]init];
copy_arr_user_post=[[NSMutableArray alloc]init];
am adding the objects into them like this.
for(i=0;i<[arr_main count];i++)
{
Post *obj=[[Post alloc]init];
obj.name=#"abc";
obj.category=#"social";
[arr_post addObject:obj];
}
Now I am copying to another array like this
[arr_post addObject:user_post];
Post *objectCopy = [user_post copy]; //create a copy of our object
[copy_arr_user_post addObject: objectCopy]; //insert copy into other array
In Post.h
#interface Post : NSObject<NSCopying>
In Post.m
- (id)copyWithZone:(NSZone *)zone
{
// Copying code here.
Post *another =[[[self class] allocWithZone:zone] init];
another.id=self.id;
another.category=self.category;
return another;
}
But it does not copy objects I get null value. Why?
One method that I find faster than NSCopyng
-Create an NSObject category with this two method
#import <objc/runtime.h>
-(id)deepCopy
{
NSArray *tmpArray = #[self];
NSData *buffer = [NSKeyedArchiver archivedDataWithRootObject:tmpArray];
return [NSKeyedUnarchiver unarchiveObjectWithData:buffer][0];
}
- (NSMutableArray *)allProperties
{
NSMutableArray *props = [NSMutableArray array];
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList([self class], &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
//Excluding all readOnly properties
unsigned int numOfAttributes;
objc_property_attribute_t *propertyAttributes = property_copyAttributeList(property, &numOfAttributes);
BOOL foundReadonly = NO;
for ( unsigned int ai = 0; ai < numOfAttributes; ai++ )
{
switch (propertyAttributes[ai].name[0]) {
case 'T': // type
break;
case 'R': // readonly
foundReadonly = YES;
break;
case 'C': // copy
break;
case '&': // retain
break;
case 'N': // nonatomic
break;
case 'G': // custom getter
break;
case 'S': // custom setter
break;
case 'D': // dynamic
break;
default:
break;
}
}
free(propertyAttributes);
if (!foundReadonly)
{
NSString *propertyName = [[NSString alloc] initWithCString:property_getName(property) encoding:NSASCIIStringEncoding];
[props addObject:propertyName];
}
}
free(properties);
return props;
}
-Make your object conforms to NSCoding
#pragma mark - NSCoding
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self)
{
NSArray *keys = [self allProperties];
for (NSString *key in keys)
{
[self setValue:[decoder decodeObjectForKey:key] forKey:key] ;
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
NSArray *keys = [self allProperties];
for (NSString *key in keys)
{
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
-Import the category
Now you are able to copy any kinds of object
MYObject *copy = [originalObject deepCopy];
NSArray *arrayWithCopiedObjects = [originalArray deepCopy];
etc....
Try
NSMutableArray * arr_post=[[NSMutableArray alloc]init];
NSMutableArray * copy_arr_user_post=[[NSMutableArray alloc]init];
for(int i=0;i<3;i++)
{
Post *obj=[[Post alloc]init];
obj.name=#"abc";
obj.category=#"social";
[arr_post addObject:obj];
}
[arr_post enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[copy_arr_user_post addObject:[obj copy]];
}];
Post * temp = [arr_post objectAtIndex:0];
temp.name = #"123";
NSLog(#"%#",arr_post);
NSLog(#"%#",copy_arr_user_post);
And log
2015-10-23 13:25:31.994 OCTest[1784:130931] (
"123 social",
"abc social",
"abc social"
)
2015-10-23 13:25:31.995 OCTest[1784:130931] (
"abc social",
"abc social",
"abc social"
)
I add description for debugging
-(NSString *)description{
return [NSString stringWithFormat:#"%# %#",self.name,self.category];
}
The following code is in my implementation file:
NSMutableArray *courseArray;
- (IBAction)btnClick:(id)sender
{
NSDictionary *courseNames;
if(![_txtBox.text isEqual:#""]) //if not empty
{
courseNames = [self retrieveCourseNamesForSemester:_txtBox.text];
for (NSString *key in courseNames)
{
NSString *val = [NSString stringWithFormat:#"%#-%#",key,[courseNames objectForKey:key]];
_txtView.text = val;
#try
{
[courseArray addObject:val];
}
#catch(NSException *e)
{
NSLog(#"Exception: %# for value = %#", e, val);
}
}
}
[_coursePicker reloadAllComponents];
_coursePicker.hidden=false;
[_txtBox resignFirstResponder];
}
Where you see the call to NSLog(), I get the following error message:
2014-03-29 00:02:25.830 WebServiceTest[44646:60b] Exception: -[__NSArrayI addObject:]: unrecognized selector sent to instance 0x8d82c30 for value = 73-522-Course Name
EDIT: Also, courseArray is populated with sample data in viewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
courseArray = #[#"Australia (AUD)", #"China (CNY)",
#"France (EUR)", #"Great Britain (GBP)", #"Japan (JPY)"];
}
Is there somewhere I should be defining that courseArray will take NSString objects?
The code in viewDidLoad creates an immutable array. You need to make a mutable copy, like this
(void)viewDidLoad
{
[super viewDidLoad];
courseArray = [#[#"(AUD)", #"(CNY)", #"(EUR)"] mutableCopy];
}
Try this code,
for (NSString *key in courseNames)
{
NSString *val = [NSString stringWithFormat:#"%#-%#",key,[courseNames objectForKey:key]];
_txtView.text = val;
if ([CourseArray count]==0)
{
CourseArray= [NSMutableArray arrayWithObject:val];
}
else
{
[CourseArray addObject:val];
}
}
So I'm new to iOS, but I'm a bit baffled by the complexity of a simple task. I'm trying to store my custom NSObject class called 'Vehicle' in NSUserDefaults. Obviously, this can't be done, so I'll need to encode it to NSDATA first. Fine.
But that means that I need to encode each property of the class as well in the decode...
Inside my Vehicle class...
- (void) encodeWithCoder: (NSCoder *) coder
{
[coder encodeInt: x forKey: #"x"];
[coder encodeInt: y forKey: #"y"];
[coder encodeInt: direction forKey: #"direction"];
} // encodeWithCoder
- (id) initWithCoder: (NSCoder *) coder
{
if (self = [super init]) {
x = [coder decodeIntForKey: #"x"];
y = [coder decodeIntForKey: #"y"];
direction = [coder decodeIntForKey: #"direction"];
}
return (self);
} // initWithCoder
If I end up adding a new property to the vehicle class, I've got to add the encode and decode logic too. This is the same for creating a copy of a class using CopyWithZone. This leaves 3 or 4 areas where adding a new property to a class can go wrong.
I currently program mostly in LabVIEW, and we have the ability to take a class, and feed it to an encoder, which will do all the versioning and property manipulation automatically.
So I guess my question is:
Is this not heard of in iOS?
If it's not possible, is there a way to enumerate through all properties in a class and write a function to do this automatically.
You can use the objective-c runtime to find all the properties of an object and decode them, but I wouldn't recommend it. If you'd ilke, I can create a simple example for you.
EDIT: Here's an example:
#import <objc/runtime.h>
void decodePropertiesOfObjectFromCoder(id obj, NSCoder *coder)
{
// copy the property list
unsigned propertyCount;
objc_property_t *properties = class_copyPropertyList([obj class], &propertyCount);
for (int i = 0; i < propertyCount; i++) {
objc_property_t property = properties[i];
char *readonly = property_copyAttributeValue(property, "R");
if (readonly)
{
free(readonly);
continue;
}
NSString *propName = [NSString stringWithUTF8String:property_getName(property)];
#try
{
[obj setValue:[coder decodeObjectForKey:propName] forKey:propName];
}
#catch (NSException *exception) {
if (![exception.name isEqualToString:#"NSUnknownKeyException"])
{
#throw exception;
}
NSLog(#"Couldn't decode value for key %#.", propName);
}
}
free(properties);
}
void encodePropertiesOfObjectToCoder(id obj, NSCoder *coder)
{
// copy the property list
unsigned propertyCount;
objc_property_t *properties = class_copyPropertyList([obj class], &propertyCount);
for (int i = 0; i < propertyCount; i++) {
objc_property_t property = properties[i];
char *readonly = property_copyAttributeValue(property, "R");
if (readonly)
{
free(readonly);
continue;
}
NSString *propName = [NSString stringWithUTF8String:property_getName(property)];
#try {
[coder encodeObject:[obj valueForKey:propName] forKey:propName];
}
#catch (NSException *exception) {
if (![exception.name isEqualToString:#"NSUnknownKeyException"])
{
#throw exception;
}
NSLog(#"Couldn't encode value for key %#.", propName);
}
}
free(properties);
}
__attribute__((constructor))
static void setDefaultNSCodingHandler()
{
class_addMethod([NSObject class], #selector(encodeWithCoder:), imp_implementationWithBlock((__bridge void *)[^(id self, NSCoder *coder) {
encodePropertiesOfObjectToCoder(self, coder);
} copy]), "v#:#");
class_addMethod([NSObject class], #selector(initWithCoder:), imp_implementationWithBlock((__bridge void *)[^(id self, NSCoder *coder) {
if ((self = [NSObject instanceMethodForSelector:#selector(init)](self, #selector(init))))
{
decodePropertiesOfObjectFromCoder(self, coder);
}
return self;
} copy]), "v#:#");
}
This allows you to encode any object that exposes enough properties to reconstruct itself.