Initialize instance variables as key/value pair from NSDictionary? - objective-c

I have my NSObject subclass's public interface defined as,
#interface MyObject : NSObject
{
NSString *_key1;
NSString *_key2;
NSString *_key3;
}
- (id)initWithDict:(NSDictionary *)dict;
#end
Is there are trick to implement the initWithDict: method so that I can pass a NSDictionary defined as
{"key1" : "value1", "key2" : "value2", "key3" : "value3"}
and the init method can set the related instance variables with their corresponding "value"?

You have to use KVC.
NSDictionary *dict = #{#"key1" : #"value1", #"key2" : #"value2", #"key3" : #"value3"};
for (NSString *key in [dict allKeys]) {
[self setValue:dict[key] forKey:key];
}

In addition to DrummerB answer, if you have nested object like below,
#interface InnerObject : NSObject
{
NSString *innerKey1;
}
#interface MyObject : NSObject
{
NSString *key1;
NSString *key2;
NSString *key3;
InnerObject *innerObj;
}
You can set value to the iVar innerKey1 also by below method:
MyObject *obj = [[MyObject alloc]init];
obj.innerObj = [[InnerObject alloc]init];
[obj setValue:yourValue forKeyPath:innerObj.innerKey1];

You can also initialize your instance variables/properties from a dictionary like this:
[self setValuesForKeysWithDictionary:dictionary];
Here is the official Apple Documentation on setValuesForKeysWithDictionary:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Protocols/NSKeyValueCoding_Protocol/#//apple_ref/occ/instm/NSObject/setValuesForKeysWithDictionary:

Related

how to define a two dimensional const array of strings in objective C

I have declared the following constant which works:
NSString * const LETTER_SELECTED[] = {#"A",#"B",#"C",#"D",#"E",#"F"};
and now I want to declare a similar constant but with dimension two which does not work:
NSString * const LETTER_SELECTED[][] = {
{#"A",#"uc"},
{#"b",#"lc"},
{#"c",#"lc"},
{#"d",#"lc"},
{#"E",#"uc"},
{#"f",#"lc"}};
I am a C programmer, any idea how to declare this in Objective-C ?
Thanks in advance
This is what an NSArray looks like in objective-c.
#interface MyClass ()
#property (nonatomic, strong) NSArray *my2DArrayOfStrings;
#end
#implementation MyClass
- (instancetype)init {
self = [super init];
self.my2DArrayOfStrings = #[
#[ #"A", #"uc" ],
#[ #"b", #"lc" ],
#[ #"c", #"lc" ],
#[ #"d", #"lc" ],
];
return self;
}
#end
However, in your example it seems like what you really want is an NSDictionary.
#interface MyClass ()
#property (nonatomic, strong) NSDictionary *stringDictionary;
#end
#implementation MyClass
- (instancetype)init {
self = [super init];
self.stringDictionary = #{
#"A" : #"uc",
#"b" : #"lc",
#"c" : #"lc",
#"d" : #"lc",
};
return self;
}
#end

Creating Array with properties for each item in Objective-c

tell me please, how do I create an array in which each element will have a number of properties. For example:
array:
|
|-item 1 ( property_1-"Name1", property_2-"LastName1", property_3-"Age1");
|-item 2 ( property_1-"Name2", property_2-"LastName2", property_3-"Age2");
|-item 3 ( property_1-"Name3", property_2-"LastName1", property_3-"Age2");
|-…
In this case, the different elements of an array can have one and the same property, such as in the code posted above - "item 3" has the "property 2" is the same as in "item 1", and "property 3" is the same as in "item 2"
Tell me, please, how best to do it and, if not difficult, write a simple example or a link to some tutorial.
Thank you in advance)
There are two methods that I would suggest:
1. Use a class to store all the properties
Here's an example:
#interface Wrapper : NSObject
#property (nonatomic, assign) NSString* property_1;
#property (nonatomic, assign) NSString* property_2;
#property (nonatomic, assign) NSString* property_3;
#end
Then you can use it as a dictionary:
NSString* value=[myWrapperInstance valueForKey: #"property_1"];
But here comes the alternative solution:
1. Use a NSDictionary to store all the properties
NSDictionary* dict= #{ #"property_1" : #"Name1" ,#"property_2" : #"Name2", #"property_3" : #"Name3" };
Then the solution comes easy:
NSMutableArray* objects=[NSMutableArray new];
for(int i=0; i<N; i++)
{
NSDictionary* dict= #{ #"property_1" : #"Name1" ,#"property_2" : #"Name2", #"property_3" : #"Name3" };
[objects addObject: dict];
}
Sounds like you want an NSArray of NSDictionary objects:
NSDictionary *dict1 = [NSDictionary dictionaryWithObjectsAndKeys:#"Name1", #"property_1", #"LastName1", #"property_2", #"Age1", #"property_3", nil);
NSDictionary *dict2 = [NSDictionary dictionaryWithObjectsAndKeys:#"Name2", #"property_1", #"LastName2", #"property_2", #"Age2", #"property_3", nil);
NSDictionary *dict3 = [NSDictionary dictionaryWithObjectsAndKeys:#"Name3", #"property_1", #"LastName3", #"property_2", #"Age3", #"property_3", nil);
NSArray *array = [NSArray arrayWithObjects:dict1, dict2, dict3, nil];
If you want to update this later then you should use NSMutableDictionary and NSMutableArray, respectively.
If you are not using ARC, the you need to release objects when you are finished with them.

Using setValuesForKeysWithDictionary with child objects and JSON

I have a json string
{"name":"test","bar":{"name":"testBar"}}
In objective c I have an object
#interface Foo : NSObject {
}
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) Bar * bar;
#end
And I just synthesize those properties. And I have a child object with synthesized properties.
#interface Bar : NSObject {
}
#property (nonatomic, retain) NSString * name;
#end
Then here is the code where I'm trying to get into the Foo object where response is the json string above:
SBJsonParser *json = [[SBJsonParser new] autorelease];
parsedResponse = [json objectWithString:response error:&error];
Foo * obj = [[Foo new] autorelease];
[obj setValuesForKeysWithDictionary:parsedResponse];
NSLog(#"bar name %#", obj.bar.name);
This throws an exception on the NSLog statement of:
-[__NSCFDictionary name]: unrecognized selector sent to instance 0x692ed70'
But if I change the code to it works:
NSLog(#"bar name %#", [obj.bar valueForKey:#"name"]);
I'm confused at why I can't do the first example, or am I doing something wrong?
Have you tried this?
// Foo class
-(void)setBar:(id)bar
{
if ([bar class] == [NSDictionary class]) {
_bar = [Bar new];
[_bar setValuesForKeysWithDictionary:bar];
}
else
{
_bar = bar;
}
}
-setValuesForKeysWithDictionary: isn't smart enough to recognize that the value of the key "bar" should be an instance of Bar. It's assigning an NSDictionary to that property. Thus, when you ask for the property "name," the dictionary can't field that request. However, an NSDictionary does know how to handle -valueForKey:, so it happens to work in that case.
So you need to use something smarter than -setValuesForKeysWithDictionary: to populate your objects.

performSelectorInBackground with multiple params

How can I call a method with multiple params like below with performSelectorInBackground?
Sample method:
-(void) reloadPage:(NSInteger)pageIndex firstCase:(BOOL)firstCase;
The problem is that performSelectorInBackground:withObject: takes only one object argument. One way to get around this limitation is to pass a dictionary (or array) of arguments to a "wrapper" method that deconstructs the arguments and calls your actual method:
- (void)callingMethod {
NSDictionary * args = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInteger:pageIndex], #"pageIndex",
[NSNumber numberWithBool:firstCase], #"firstCase",
nil];
[self performSelectorInBackground:#selector(reloadPageWrapper:)
withObject:args];
}
- (void)reloadPageWrapper:(NSDictionary *)args {
[self reloadPage:[[args objectForKey:#"pageIndex"] integerValue]
firstCase:[[args objectForKey:#"firstCase"] boolValue]];
}
- (void)reloadPage:(NSInteger)pageIndex firstCase:(BOOL)firstCase {
// Your code here...
}
This way you're only passing a "single" argument to the backgrounding call, but that method can construct the multiple arguments you need for the real call (which will take place on the same backgrounded thread).
I've just found this question and wasn't happy with any of the answers. In my opinion neither make good use of the tools available, and passing around arbitrary information in arrays and dictionaries generally worries me.
So, I went and wrote a small NSObject category that will invoke an arbitrary selector with a variable number of arguments:
Category Header
#interface NSObject (NxAdditions)
-(void)performSelectorInBackground:(SEL)selector withObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION;
#end
Category Implementation
#implementation NSObject (NxAdditions)
-(void)performSelectorInBackground:(SEL)selector withObjects:(id)object, ...
{
NSMethodSignature *signature = [self methodSignatureForSelector:selector];
// Setup the invocation
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
invocation.target = self;
invocation.selector = selector;
// Associate the arguments
va_list objects;
va_start(objects, object);
unsigned int objectCounter = 2;
for (id obj = object; obj != nil; obj = va_arg(objects, id))
{
[invocation setArgument:&obj atIndex:objectCounter++];
}
va_end(objects);
// Make sure to invoke on a background queue
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithInvocation:invocation];
NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init];
[backgroundQueue addOperation:operation];
}
#end
Usage
-(void)backgroundMethodWithAString:(NSString *)someString array:(NSArray *)array andDictionary:(NSDictionary *)dict
{
NSLog(#"String: %#", someString);
NSLog(#"Array: %#", array);
NSLog(#"Dict: %#", dict);
}
-(void)someOtherMethod
{
NSString *str = #"Hello world";
NSArray *arr = #[#(1337), #(42)];
NSDictionary *dict = #{#"site" : #"Stack Overflow",
#"url" : [NSURL URLWithString:#"http://stackoverflow.com"]};
[self performSelectorInBackground:#selector(backgroundMethodWithAString:array:andDictionary:)
withObjects:str, arr, dict, nil];
}
Well, I have used this:
[self performSelectorInBackground:#selector(reloadPage:)
withObject:[NSArray arrayWithObjects:pageIndex,firstCase,nil] ];
for this:
- (void) reloadPage: (NSArray *) args {
NSString *pageIndex = [args objectAtIndex:0];
NSString *firstCase = [args objectAtIndex:1];
}
with performSelectorInBackground you can only pass one argument, so make a custom object for this method to hold your data, itll be more concise than an ambiguous dictionary or array. The benefit of this is you can pass the same object around when done containing several return properties.
#import <Foundation/Foundation.h>
#interface ObjectToPassToMethod : NSObject
#property (nonatomic, strong) NSString *inputValue1;
#property (nonatomic, strong) NSArray *inputArray;
#property (nonatomic) NSInteger returnValue1;
#property (nonatomic) NSInteger returnValue2;
#end
and pass that object to your method:
ObjectToPassToMethod *obj = [[ObjectToPassToMethod alloc] init];
obj.inputArray = #[];
obj.inputValue1 = #"value";
[self performSelectorInBackground:#selector(backgroundMethod:) withObject:obj];
-(void)backgroundMethod:(ObjectToPassToMethod*)obj
{
obj.returnValue1 = 3;
obj.returnValue2 = 90;
}
make sure to clean up the object when done to prevent memory leaks

Save / Write NSMutableArray of objects to disk?

Initially I thought this was going to work, but now I understand it won't because artistCollection is an NSMutableArray of "Artist" objects.
#interface Artist : NSObject {
NSString *firName;
NSString *surName;
}
My question is what is the best way of recording to disk my NSMutableArray of "Artist" objects so that I can load them the next time I run my application?
artistCollection = [[NSMutableArray alloc] init];
newArtist = [[Artist alloc] init];
[newArtist setFirName:objFirName];
[newArtist setSurName:objSurName];
[artistCollection addObject:newArtist];
NSLog(#"(*) - Save All");
[artistCollection writeToFile:#"/Users/Fgx/Desktop/stuff.txt" atomically:YES];
EDIT
Many thanks, just one final thing I am curious about. If "Artist" contained an extra instance variable of NSMutableArray (softwareOwned) of further objects (Applications) how would I expand the encoding to cover this? Would I add NSCoding to the "Applications" object, then encode that before encoding "Artist" or is there a way to specify this in "Artist"?
#interface Artist : NSObject {
NSString *firName;
NSString *surName;
NSMutableArray *softwareOwned;
}
#interface Application : NSObject {
NSString *appName;
NSString *appVersion;
}
many thanks
gary
writeToFile:atomically: in Cocoa's collection classes only works for property lists, i.e. only for collections that contain standard objects like NSString, NSNumber, other collections, etc.
To elaborate on jdelStrother's answer, you can archive collections using NSKeyedArchiver if all objects the collection contains can archive themselves. To implement this for your custom class, make it conform to the NSCoding protocol:
#interface Artist : NSObject <NSCoding> {
NSString *firName;
NSString *surName;
}
#end
#implementation Artist
static NSString *FirstNameArchiveKey = #"firstName";
static NSString *LastNameArchiveKey = #"lastName";
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self != nil) {
firName = [[decoder decodeObjectForKey:FirstNameArchiveKey] retain];
surName = [[decoder decodeObjectForKey:LastNameArchiveKey] retain];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:firName forKey:FirstNameArchiveKey];
[encoder encodeObject:surName forKey:LastNameArchiveKey];
}
#end
With this, you can encode the collection:
NSData* artistData = [NSKeyedArchiver archivedDataWithRootObject:artistCollection];
[artistData writeToFile: #"/Users/Fgx/Desktop/stuff" atomically:YES];
Take a look at NSKeyedArchiver. Briefly :
NSData* artistData = [NSKeyedArchiver archivedDataWithRootObject:artistCollection];
[artistData writeToFile: #"/Users/Fgx/Desktop/stuff" atomically:YES];
You'll need to implement encodeWithCoder: on your Artist class - see Apple's docs
Unarchiving (see NSKeyedUnarchiver) is left as an exercise for the reader :)