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.
Related
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".
I've created a subclass of UIImage (UIImageExtra) as I want to include extra properties and methods.
I have an array that contains instances of this custom class.However when I save the array, it appears the extra data in the UIImageExtra class is not saved.
UIImageExtra conforms to NSCoding, but neither initWithCoder or encodeWithCoder are called, as NSLog statements I've added aren't printed.
My method to save the array looks like this:
- (void)saveIllustrations {
if (_illustrations == nil) {
NSLog(#"Nil array");
return;
}
[self createDataPath];
//Serialize the data and write to disk
NSString *illustrationsArrayPath = [_docPath stringByAppendingPathComponent:kIllustrationsFile];
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:_illustrations forKey:kIllustrationDataKey];
[archiver finishEncoding];
[data writeToFile:illustrationsArrayPath atomically: YES];
}
And the UIImageExtra has the following delegate methods for saving:
#pragma mark - NSCoding
- (void)encodeWithCoder:(NSCoder *)aCoder {
NSLog(#"Encoding origin data!");
[super encodeWithCoder:aCoder];
[aCoder encodeObject:originData forKey:kOriginData];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:(NSCoder *) aDecoder]) {
NSLog(#"Decoding origin data");
self.originData = [aDecoder decodeObjectForKey:kOriginData];
}
return self;
}
My code to create the array in the first place looks like this (in case that offers any clues)
for (NSDictionary *illustrationDict in illustrationDicts) {
NSString *illustrationString = [illustrationDict objectForKey:#"Filename"];
NSNumber *xCoord = [illustrationDict objectForKey:#"xCoord"];
NSNumber *yCoord = [illustrationDict objectForKey:#"yCoord"];
UIImageExtra *illustration = (UIImageExtra *)[UIImage imageNamed:illustrationString];
//Scale the illustration to size it for different devices
UIImageExtra *scaledIllustration = [illustration adjustForResolution];
NSValue *originData = [NSValue valueWithCGPoint:CGPointMake([xCoord intValue], [yCoord intValue])];
[scaledIllustration setOriginData:originData];
[self.illustrations addObject:scaledIllustration];
}
Or am I just going about saving this data the wrong way? Many thanks.
Your code to initialize the array is not actually creating instances of your UIImageExtra subclass.
UIImageExtra *illustration = (UIImageExtra *)[UIImage imageNamed:illustrationString];
returns a UIImage. Casting it doesn't do what you were intending.
UIImageExtra *scaledIllustration = [illustration adjustForResolution];
is still just a UIImage.
One straightforward-but-verbose way to approach this would be to make UIImageExtra a wrapper around UIImage. The wrapper would have a class method for initializing from a UIImage:
+ (UIImageExtra)imageExtraWithUIImage:(UIImage *)image;
And then every UIImage method you want to call would have to forward to the wrapped UIImage instance-- also being careful to re-wrap the result of e.g. -adjustForResolution lest you again end up with an unwrapped UIImage instance.
A more Objective-C sophisticated approach would be to add the functionality you want in a Category on UIImage, and then use method swizzling to replace the NSCoding methods with your category implementations. The tricky part of this (apart from the required Objective-C runtime gymnastics) is where to store your "extra" data, since you can't add instance variables in a category. [The standard answer is to have a look-aside dictionary keyed by some suitable representation of the UIImage instance (like an NSValue containing its pointer value), but as you can imagine the bookkeeping can get complicated fast.]
Stepping back for a moment, my advice to a new Cocoa programmer would be: "Think of a simpler way. If what you are trying to do is this complicated, try something else." For example, write a simple ImageValue class that has an -image method and an -extraInfo method (and implements NSCoding, etc.), and store instances of that in your array.
You can't add objects to an NSArray after init. Use NSMutableArray, that might be the issue.
I am coding Objective-C using the Cocos2D framework, and I have a singleton used for multiple purposes. One new purposes is to get and set character's "states" which are strings. I've recently made an NSDictionary for this purpose, but I have issues with the program freezing up when a method inside the singleton is called.
Here's the singleton code. I'm just leaving in the character state stuff:
.h
#interface ExGlobal : NSObject {
NSArray *charStates_keys;
NSArray *charStates_objects;
NSMutableDictionary *charStates;
}
#property(nonatomic, retain) NSMutableDictionary *charStates;
+(ExGlobal*)sharedSingleton;
- (NSString *)charState:(NSString *)charName;
- (void)set_charState:(NSString *)value forCharName:(NSString *)charName;
#end
.m
#import "ExGlobal.h"
#implementation ExGlobal
#synthesize charStates;
static ExGlobal* _sharedSingleton = nil;
+(ExGlobal*)sharedSingleton {
#synchronized([ExGlobal class]) {
if (!_sharedSingleton) {
[[self alloc] init];
}
return _sharedSingleton;
}
return nil;
}
+(id)alloc {
#synchronized([ExGlobal class]) {
NSAssert(_sharedSingleton == nil, #"Attempted to allocate a second instance of a singleton.");
_sharedSingleton = [super alloc];
return _sharedSingleton;
}
return nil;
}
-(id)init {
self = [super init];
if (self != nil) {
// initialize stuff here
exitName = #"ruinsSkyMid";
sceneChangeKind = #"reborn";
charStates = [[NSMutableDictionary alloc] init];
charStates_keys = [NSArray arrayWithObjects:#"Feathers", #"Hummus", nil];
charStates_objects = [NSArray arrayWithObjects:#"at wall", #"with Feathers", nil];
charStates = [NSMutableDictionary dictionaryWithObjects:charStates_objects forKeys:charStates_keys];
}
return self;
}
- (NSString *)charState:(NSString *)charName{
NSString *value = [charStates objectForKey:charName];
return value;
}
- (void)set_charState:(NSString *)charState forCharName:(NSString *)charName{
[charStates setObject:charState forKey:charName];
}
- (void)dealloc {
//I know it doesn't get called, but just in case
[charStates release];
[super dealloc];
}
#end
It's unclear to me what exactly the issue is when it freezes. When this happens, all I get in the console is:
Program received signal: “EXC_BAD_ACCESS”.
warning: Unable to read symbols for /Developer/Platforms/iPhoneOS.platform/DeviceSupport/4.3.5 (8L1)/Symbols/Developer/usr/lib/libXcodeDebuggerSupport.dylib (file not found).
Previous frame inner to this frame (gdb could not unwind past this frame)
Previous frame inner to this frame (gdb could not unwind past this frame)
Which I'm sure doesn't help finding the issue. I found if I redefine charStates_keys, charStates_objects and charStates inside both the charState and set_charState methods, it seems to work without freezing, except set_charState does not change the state.
It isn't freezing, it is crashing. Hence the EXC_BAD_ACCESS. It looks like your Xcode installation is borked, too, as the two messages following should not happen.
Note that methods should not have _s in the name; not a cause of the problem, but a comment on following convention.
You aren't retaining charStates and that is likely the cause of the crash.
Not an answer as such but I didn't have enough space in the comments field above to post this, but it might be useful.
As bbum already said, your lack of retaining charStates is likely the problem.
If you are confused about when to retain and not retain objects there's a really good book called "Learn Objective-C on the Mac" and I know it's a Mac book but most of it applies to iPhone too. On page 171 of chapter 9 (Memory Management) it talks about the "Memory Management Rules" and how if you are confused about when to retain or not then you don't understand the simple rules of Objective C memory management.
Essentially if you create an object using new, alloc or copy, then the retain count is automatically set to 1 so the object is retained and does not require you to retain it and will require a subsequent release to deallocate.
If you create the object any other way then the object will be an autoreleased object.
Obviously these rules only apply within the standard iOS libraries and can't necessarily be applied to third party libraries.
I recommend anyone who doesn't fully understand memory management in Objective C read this book. I found highly enlightening even for my iPhone work.
Hope that helps/.
As a relative Objective-C beginner, I'm obviously still not grasping certain memory management rules. I can't figure out how to make this not crash:
#interface MyClass { NSArray *playerArray4th; }
- (void) viewDidLoad { playerArray4th = [self getAudioPlayersForSoundFile:#"rimshot" ofType:#"aif"]; }
- (NSArray*) getAudioPlayersForSoundFile:(NSString*)soundFileName ofType:(NSString*)soundFileType {
//code instantiating objects....
NSArray *toRet = [[NSArray alloc] initWithObjects:toRetTickPlayer,toRetTickPlayerCopy,toRetTickPlayerCopy2,toRetTickPlayerCopy3, nil];
return toRet;
}
Then later, in a different function:
NSArray *currentArray = playerArray4th;
[currentArray release];
currentArray = nil;
currentArray = [self getAudioPlayersForSoundFile:fileName ofType:ofType];
And it crashes when trying to access the array again:
- (void) playSound:(NSString*)soundType {
AVAudioPlayer *currentPlayer;
if ([soundType isEqualToString:#"4th"]) {
if (playerArray4thCounter >= [playerArray4th count]) playerArray4thCounter = 0;
NSLog(#"Playing from array: %#",playerArray4th);
currentPlayer = [playerArray4th objectAtIndex:playerArray4thCounter];
playerArray4thCounter++;
}
}
Try to learn about properties and about using getters and setters. Don't take shortcuts unless you know exactly what's going on.
So define the playerArray4th property in your header file:
#property (nonatomic,retain) NSArray *playerArray4th;
And then in your .m file create getter/setter:
#synthesize playerArray4th;
Then, always use self.playerArray4th for assigning and getting the variable. The prior objects will be released when needed.
So this will not leak:
self.playerArray4th = [NSArray arrayWithObjects:#"text",#"text",nil];
self.playerArray4th = [NSArray arrayWithObjects:#"new array",#"text",nil];
because the second assignment releases the first array.
Furthermore, read about using autorelease. In short, if you alloc, copy or new, you should either release or autorelease. There's a lot to read about this here on SO which I will not repeat here now.
Don't forget to put self.playerArray4th = nil; in your dealloc method.
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.