Creating objects in a loop, but ivar / instance variable not keeping state - objective-c

I have a method that creates a dictionary from NSJSONSerialization class. I then enumerate the json, and create objects to store state for each instance.
- (void)fetchedData:(NSData *)responseData {
NSError* error;
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSArray *moviesJson = [json objectForKey:#"movies"];
for(NSDictionary *element in moviesJson)
{
RABMovie *myMovie = [[RABMovie alloc] initWithJson:element];
// RABMovie *myMovie = [RABMovie new];
// myMovie.json = element;
[_myMovieNames addObject: myMovie];
myMovie = nil;
}
[self.movieTableView reloadData];
}
Problem: I want to create my object by passing in element in the allocator, however when I do this, my UITTableView rows all contain the same movie data. It is always the last item, leading me to believe I am working with only one memory address, and the last update affects the whole collection.
If I uncomment the code to use the json as a property instead of a alloc param, I no longer have the issue and all works well. I've also tried creating a completely new NSDictionary via a deep copy of element to no avail.
Does someone know why this is happening? BTW, I am using ARC. -Thanks for the time.
Edit: Added more code. I've included a property movieName to illustrate how I use the ivar _json.
#implementation RABMovie
NSDictionary *_json;
- (id) initWithJson: (NSDictionary*) jsonAsDictionary
{
if (self = [super init])
{
_json = jsonAsDictionary;
}
return self;
}
- (NSString*) movieName
{
return [_json objectForKey:#"title"];
}

I think you meant to declare _json as an instance variable. Instead it's a globally visible (at least within that class) variable - not 100% sure on the scoping rules, but regardless, it's not an instance variable - it's a single variable shared by all instances! Try this instead:
#implementation RABMovie {
NSDictionary *_json;
}
/* ...rest of class */
#end
Putting it inside the curly braces after the #implementation directive makes it an instance variable. Hope this helps!
EDIT: Do you have a property called json on RABMovie already? Then you can skip the instance declaration altogether and the compiler will generate the an instance variable for you. That's probably happening already actually, which is why it works when you go through the property - it's accessing the ivar rather than the "global".

Related

How to pass ivar into a function and set it without losing the reference to the original object

I am passing an ivar (NSMutableArray) into some method. I was expecting that if I modify the object inside the function, it would be reflected outside the function, but in this case I need to set the object; something like the following:
- (void) someMethod:(SMResponseObject *)response onData:(NSMutableArray *)imAnIvar {
imAnIvar = [response objects];
//Some other stuff
}
But I noticed that the memory reference of imAnIvar inside the function changes when I set it, and given that, the actual ivar doesn't change. I understand that the problem is that I'm changing the reference of the object inside the method, so it stops pointing to the ivar and then it points to some other random memory direction.
I thought about one solution to this problem, and it can be to ensure that the ivar is not nil before calling the function and do something like this:
- (void) someMethod:(SMResponseObject *)response onData:(NSMutableArray *)imAnIvar {
NSMutableArray *data = [response objects];
[arrayForTableView removeAllObjects];
for(id element in data){
[imAnIvar addObject:element];
}
//Some other stuff
}
So I use the original object instead of setting it directly. The problem is that in order for this to work I need to ensure that the ivar is not nil, which I think is not clean, because I'll need to do something like this on every call to the method:
if(!_ivar){
//alloc it
}
So my question is: Is there a way to force the local scope variable to point to the original variable even if I'm setting it? if not, is there any cleaner way to make this work?
Do you mean this?
- (void)setFoo:(SomeClass **)objPtr
{
*objPtr = someOtherObject;
}
// call it as:
SomeClass *foo = someObject;
NSLog(#"Before: %#", foo);
[self setFoo:&foo];
NSLog(#"After: %#", foo);
Why not use a getter for the array so that you need not check for the array being nil while using it?
-(NSMutableArray *)iAmAnIvar {
if(_iAmAnIvar == nil) {
_iAmAnIvar = [NSMutableArray array];
}
return _iAmAnIvar;
}
And when you have to set a value to the array, as you mentioned in your question, you could use
[self.iAmAnIvar removeAllObjects];
[self.iAmAnIvar addObject:someObj];
I believe you can use the - (id)copy; function of NSObject
so your code might look like this:
- (void)someFunction:(NSString *)someArg
{
NSString *str = [someArg copy];
}

NSKeyedArchiver and NSKeyedUnarchiver with NSMutableArray

I'm hoping this isn't something to do with the fact that I'm using a Mutable array here, but this one is baffling me so it wouldn't surprise me if that were the case.
BACKGROUND:
I have made a small database which is essentially an NSMutableArray containing custom objects, which we can call recordObjects. I set up the array:
database = [[NSMutableArray alloc] init];
and my custom object, called "recordObject" contains the following variables and inits:
NSString *name;
int anInt;
Bool aBool;
I also synthesized methods so I can make calls like:
aString = [[database objectAtIndex:someIndex] name];
And added methods to my controller class to add, remove, and select the individual records for display. So far everything works correctly and exactly as expected.
Next, I've set up my recordObject class (subclass of NSObject) to use the NSCoder (by including in the #interface directive, and have added the following custom encoder and decoder methods in the implementation file:
-(void) encodeWithCoder: (NSCoder *) encoder {
[encoder encodeObject: name forKey: #"recordName"];
[encoder encodeInt: anInt forKey: #"recordInteger"];
[encoder encodeBool: aBool forKey: #"recordBool"];
}
-(id) initWithCoder: (NSCoder *) decoder {
name = [decoder decodeObjectForKey: #"recordName"];
anInt = [decoder decodeIntForKey: #"recordInteger"];
aBool = [decoder decodeBoolForKey: #"recordBool"];
}
In order to write the file, I have used the following:
[NSKeyedArchiver archiveRootObject:database toFile:myPath];
When I run the program, everything APPEARS to work correctly. The encoder method is called for each of the records in the array and the file is written to disk. Opening the file with TextEdit shows that the data is there (though mostly unintelligible to me.)
THE PROBLEM:
Here's where I run into a snag.
I added the following code to LOAD the file into my database array:
database = [NSKeyedUnarchiver unarchiveObjectWithFile:myPath];
When I run the program again, this time Loading the database, it APPEARS to work correctly. My first test was to use the NSMutableArray count method:
x = [database count];
The result was that X is filled with the correct number of records in the file. If there were 5 records when I saved the database, X is set to 5 after loading the database on the next execution of the program.
Now, here's the big problem:
The program crashes if I try to use ANY of my accessor methods. For example, if I try to use the following after loading the database:
aString = [[database objectAtIndex:someIndex] name];
the program crashes and returns the following error in the console:
Program received signal: “EXC_BAD_ACCESS”.
sharedlibrary apply-load-rules all
My interpretation is that the data is not being loaded and initialized into the database array correctly for some reason, but for the life of me I can't figure out where I've gone wrong here.
As a side note, everything I've implemented came from Stephen G. Kochan's book "Programming in Objective-C"
Any ideas would be greatly appreciated!
There are a few problems with your code.
Your initWithCoder: method is not fully implemented. You must call [super init] and return self. You must also copy or retain the string object, otherwise it will be autoreleased:
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if(self)
{
name = [[decoder decodeObjectForKey: #"recordName"] copy];
anInt = [decoder decodeIntForKey: #"recordInteger"];
aBool = [decoder decodeBoolForKey: #"recordBool"];
}
return self;
}
The other problem is with this line:
database = [NSKeyedUnarchiver unarchiveObjectWithFile:myPath];
That is fine, except for two things:
You're not holding a reference to the object, so it will be
autoreleased.
NSKeyedArchiver returns an immutable object, in this case an NSArray and not an NSMutableArray.
You need to do this:
database = [[NSKeyedUnarchiver unarchiveObjectWithFile:myPath] mutableCopy];
That will both retain the object (because it's a copy) and make the object an NSMutableArray.
It doesn't appear that you're initializing the recordObjects in -initWithCoder:
Try something like this:
-(id) initWithCoder: (NSCoder *) decoder {
self = [super init];
if (self){
name = [decoder decodeObjectForKey: #"recordName"] copy];
anInt = [decoder decodeIntForKey: #"recordInteger"];
aBool = [decoder decodeBoolForKey: #"recordBool"];
}
return self;
}
The data is there when you archive it but you're not properly unarchiving it.
Sounds like a memory management issue to me. EXC_BAD_ACCESS usually means that you're trying to access an object that has been deallocated. unarchiveObjectWithFile: returns an autoreleased object, so you have to retain it if you want to keep it around, either with an explicit retain or by assigning it to a retained property.
I was having the same issue when unarchiving a custom object
self.calTable = [[NSKeyedUnarchiver unarchiveObjectWithFile:calibrationFile] objectForKey:#"calTable"];
Based on Rob's answer I changed to
self.calTable = [[[NSKeyedUnarchiver unarchiveObjectWithFile:calibrationFile] mutableCopy] objectForKey:#"calTable"];
and it fixed all errors.

Where do I create global variables for an iOS app?

Here is my code:
I want to be able to create a global NSMutableArray that can store Budget* objects that can then be written to a .pList file... I'm only learning what pLists are, and I am a bit hazy about how to implement them...
Where am I going wrong here?
- (IBAction)btnCreateBudget:(id)sender
{
Budget *budget = [[Budget alloc] init];
budget.name = self.txtFldBudgetName.text;
budget.amount = [self.txtFldBudgetAmount.text intValue];
// Write the data to the pList
NSMutableArray *anArray = [[NSMutableArray alloc] init]; // I want this to be a global variable for the entire app. Where do I put this?
[anArray addObject:budget];
[anArray writeToFile:[self dataFilePath] atomically:YES];
/* As you can see, below is where I test the code. Unfortunately,
every time I run this, I get only 1 element in the array. I'm assuming
that this is because everytime the button is pressed, I create a brand new
NSMutableArray *anArray. I want that to be global for the entire app. */
int i = 0;
for (Budget * b in anArray)
{
i++;
}
NSLog(#"There are %d items in anArray",i);
}
-(NSString *) dataFilePath
{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [path objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"BudgetData.plist"];
}
edit: I'd like to add that I am creating the anArray array so that it can be accessible by other views. I understand that this can be done with NSNotification? or Should I do this the appDelegate classes? The end goal is to have the anArray object populate a UITableView that is in a separate View.
Just put the declaration outside the method instead of inside it.
NSMutableArray *anArray = nil;
- (IBAction)btnCreateBudget:(id)sender
{
...
if ( anArray == nil )
anArray = [[NSMutableArray alloc] init];
...
}
If it's only used inside the one file, make it "static" instead to prevent name collisions with other files:
static NSMutableArray *anArray = nil;
If it's only used inside the one method, make it "static" and put it inside that method:
- (IBAction)btnCreateBudget:(id)sender
{
static NSMutableArray *anArray = nil;
...
if ( anArray == nil )
anArray = [[NSMutableArray alloc] init];
...
}
Note that people usually use some kind of naming convention for global variables, like "gArray", to easily differentiate them from local variables, instance variables, or method parameters.
Global variable is not necessary in this case. You can do something like this:
Read old data to mutable array (initWithContentsOfFile:).
Add new record to the array.
Save the array to same file.
But the second problem in your code is that if your Budget class is not a property list type (NSString, NSData, NSArray, or NSDictionary objects) writeToFile: will not save it sucessfully.
You need to make sure that your Budget class invokes NSCoder and then the NSCoder initWithCoder: and NSCoder decodeWithCoder: methods. Otherwise, writeToFile: will not work for you NSObject class.
But I digress. The answer to the original question should be the following.
In your .h file you need to do the following.
#interface WhateverClassName : UIViewController
{
NSMutableArray *anArray;
}
#property(nonatomic, retain) NSMutableArray *anArray;
#end
Then, you need to make sure you #synthesize the NSMutableArray so that you don't get any freaky warnings. This is done just after the #implementation line in your .m file.
Then, within the function that you want it to be allocated into memory, simply do the following.
anArray = [[NSMutableArray alloc] initWithObjects:nil];
This is now a global variable. It is global in the sense that it can be used from any function and is not limited to use in one function.
If you would like to have data accessible to the entire application or context ("global"), you can use a singleton. However, do this with care and make sure it is actually necessary and appropriate. I would suggest doing plenty of reading up on it prior to any implementation of a singleton. Carter Allen has a good basic implementation here.
According to "The end goal is to have the anArray object populate a UITableView that is in a separate View" you wouldn't need to write anything to a file, database or singleton. Just set the object. Like stated by Sebastien Peek.
If you wish for offline data storage, look into sqlite, json, plist , etc

Obj-c, how do I create function which will populate an NSDictionary and gain a value from the function?

I've been reading about NSArrays and NSDictionaires and I think I need the later. I'm trying to populate an object from a small database table. So I can access the string values via a record id. I have to do this several times so putting it into an object makes sense.
I have the basics...
- (void)viewDidLoad {
// WORKING START
NSMutableDictionary *dictCategories = [[NSMutableDictionary alloc] init];
[dictCategories setValue:#"Utility" forKey:#"3"];
[dictCategories setValue:#"Cash" forKey:#"5"];
NSString *result;
result = [dictCategories objectForKey:#"3"];
NSLog(#"Result=%#", result);
// WORKING END
// Can't get this bit right, current error Request for member
// 'getCategories' in something not a structure or union
NSMutableDictionary *dictCategories2 = self.getCategories;
NSLog(#"Result2=%#", [dictCategories2 objectForKey:#"5"]);
[super viewDidLoad];
}
-(NSMutableDictionary*)getCategories {
NSMutableDictionary *dictCategories = [[NSMutableDictionary alloc] init];
[dictCategories setValue:#"Utility" forKey:#"3"];
[dictCategories setValue:#"Cash" forKey:#"5"];
return dictCategories;
}
you are calling the method wrong,try [self getCategories]
You're not being clear on what isn't working, but a few things that are obviously wrong (JonLOo might be spot on though) ...
Firstly. You're using the wrong methods, or at least there's a better one -- setValue:forKey: should/could be setObject:forKey: instead. This might be one of the reasons for your issue.
Secondly. You're over-allocating and not releasing properly. dictCategories2 in your viewDidLoad will vanish into the void and bring with it the allocated memory for dictCategories defined in the getCategories method. An easy standard fix for this is to change
NSMutableDictionary *dictCategories = [[NSMutableDictionary alloc] init];
in getCategories into
NSMutableDictionary *dictCategories = [NSMutableDictionary dictionary];
It will be autoreleased using the latter method by the system.
Thirdly. You want to read up on #property. Instead of getFoo, setBar, the Ob-C standard is to use #properties to (pre)define setters and getter methods. You can then override these to populate default data into your methods when appropriate. You also (probably) want to store the dictionary in your interface as an instance variable, rather than letting it be deallocated all the time. Example of a #property implementation that does this:
#interface foo {
NSMutableDictionary *ingredients;
}
#property (nonatomic, retain) NSMutableDictionary *ingredients;
#end
// ....
#implementation foo
#synthesize ingredients;
// ...
// the #synthesize command above will create getter and setter methods for us but
// we can override them, which we need to do here
- (NSMutableDictionary *)ingredients
{
if (ingredients != nil) {
// we've already got an ingredients variable so we just return it
return ingredients;
}
// we need to create ingredients
ingredients = [[NSMutableDictionary alloc] init];
[ingredients setObject:#"foo" forKey:#"bar"]
return ingredients;
}
In the viewDidLoad method (or anywhere else where you think ingredients might not have been initialized yet), you would do e.g.
NSMutableDictionary *dict = self.ingredients;
Anywhere else you can opt to use just ingredients without self, but if it's nil, your method will never be called, and you will get nil thrown at you.
This is useful in many cases, and is necessary if we want to ever read or write the ingredients variable from outside of our class. It's outside of what you're asking about, but I brought it up because you're trying to do something similar with self.getCategories.
Hope that helps.

NSMutableDictionary error

I want to use NSMutableDictionary to cache some data i will use later. My custom object is following:
#interface MyData : NSObject {
NSRange range;
NSMutableArray *values;
}
#property (nonatomic, retain) NSMutableArray *values;
and implement:
- (id)init {
if (self = [super init]) {
values = [[NSMutableArray alloc] init];
}
return self;
}
and when i wanna cache it, i use it like this:
NSMutableDictionary *cache = [[NSMutableDictionary alloc] init];
NSString *key = #"KEY";
MyData *data = [[MyData alloc] init];
// save some data into data
[data.values addObject:"DATA1"];
[data.values addObject:"DATA2"];
//... ...
[cache setObject:data forKey:key];
My questions is the count of cache.values is zero when i retrieve this object later as follow:
[cache objectForKey:#"KEY"];
i can retrieve "data" and the object's memory address is the same as the address when i put it into cache.
what's wrong? i need some kind guys help, any info is helpful. thanks
As Carl Norum pointed out, you're passing C strings to addObject:. addObject:, as its name suggests, requires a pointer to a Cocoa object; a C string is a pointer to characters. You need to pass NSString objects there; for literal strings, this simply requires prefixing them with #: "Fred" is a constant C string, whereas #"Fred" is a constant NSString object.
Is cache an instance variable? It looks like it's not; it appears to be a local variable, which means you're creating a new dictionary object every time. That's why there's nothing you've added previously (to previous dictionaries) in the new one. It also means you're leaking those previous dictionaries, since you're not releasing them (not in the code you showed, anyway).
Make cache an instance variable and only create the dictionary when you don't already have one (i.e., when cache == nil). Creating the dictionary in your init method is one good way. And make sure you manage its lifetime appropriately, so you don't leak and/or crash.
First of all your objects your adding don't look right it should have an # before the string. Like #"DATA1"
Second when you add an object to a dictionary or an array it does not make an actual copy of it. It just creates a pointer to it so if those objects are destroyed or moved somewhere also they are also gone out of your dictionary. A better way to make a cache of your values would be to copy the objects like so:
MyData* cache = [[MyData alloc] init];
for (int i = 0; i < [data.values count]; i ++){{
[cache.values addObject:[NSString stringWithString:[data.values objectAtIndex:i]]];
}
Don't use a dictionary in this situation.