Problem with saving custom object into NSUserDefaults - iphone

I have a custom object class with the following .m:
#implementation FolderObject
#synthesize folderTitle, folderContents; //title is NSString, contents is array
- (id)init
{
self = [super init];
if (self) {
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:foldersContents forKey:#"foldersContents"];
[coder encodeObject:folderTitle forKey:#"folderTitle"];
}
- (id)initWithCoder:(NSCoder *)coder {
self = [[FolderObject alloc] init];
if (self != nil)
{
foldersContents = [coder decodeObjectForKey:#"foldersContents"];
folderTitle = [coder decodeObjectForKey:#"folderTitle"];
}
return self;
}
#end
Here is how I use the folder (in some other class):
FolderObject *newFolder=[FolderObject alloc];
newFolder.folderTitle=[textView text];
newFolder.folderContents=[[NSMutableArray alloc]init];
[folders addObject:newFolder];
Here is how I save and retrieve the custom object from NSUserDefaults:
-(void)viewDidDisappear:(BOOL)animated
{
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:folders] forKey:#"folders"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
-(void)viewWillAppear:(BOOL)animated
{
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArrayFolders = [currentDefaults objectForKey:#"folders"];
if (dataRepresentingSavedArrayFolders != nil)
{
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArrayFolders];
if (oldSavedArray != nil) {
folders = [[NSMutableArray alloc] initWithArray:oldSavedArray];
}
else {
folders = [[NSMutableArray alloc] init];
}
}
else folders=[[NSMutableArray alloc] init];
And finally here is where the issue is:
FolderObject *newFolder=[folders objectAtIndex:indexPath.row];
[folderTitleLabel setText:newFolder.folderTitle];
On the second line, I get an error during runtime, but only after I exit the app and come back. When I add objects to the folders array and call the above, no problems. But if I exit the app and come back, then problems:
-[__NSArrayM isEqualToString:]: unrecognized selector sent to instance 0x4b9e400
2011-09-02 08:09:24.290 MyApp[43504:b303] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayM isEqualToString:]: unrecognized selector sent to instance 0x4b9e400'

Inside of initWithCoder: for your FolderObject you are not properly retaining the values which will result in your crash. Make sure you use the property like this.
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self != nil)
{
//dot notation (self.) will properly retain or copy your string
//as long as it is declared as retain or copy (I recommend copy)
self.foldersContents = [coder decodeObjectForKey:#"foldersContents"];
self.folderTitle = [coder decodeObjectForKey:#"folderTitle"];
}
return self;
}

The problem seems to be in your initWithCoder: method:
foldersContents = [coder decodeObjectForKey:#"foldersContents"];
folderTitle = [coder decodeObjectForKey:#"folderTitle"];
The values returned by decodeObjectForKey: are not retained for you, and as you are assigning them to ivars directly instead of via the (presumably) retain-declared properties, they are not being retained there either. So they get auto-released the next time the autorelease pool is drained. By the time you get around to trying to use it, it just so happens that the memory location formerly used for the title is now occupied by an __NSArrayM object; were things to work out slightly differently, you'd get a more straightforward EXC_BAD_ACCESS crash.

#Joe is right about needing to retain the objects but why are you using
self = [[FolderObject alloc] init];
instead of
self = [super init];
Your code will leak an chunk of memory each time it's called?
I don't know if it's causing your crash (I suspect not) but it's certainly interesting.

Related

NSZombieEnabled prevents my app from crashing

So I've been debugging like a mad men using NSZombiesEnabled and NSZombies in Instruments. However when running the app using zombies it seems to resolve my issue. When I run the app without NSZombiesEnabled or NSZombies in instruments it crashes. Any idea on how to deal with this?
So the issue is that I am releasing something twice, but can't seem to find where I am doing this. Turning on NSZombieEnabled won't help as the program runs fine without telling me where I am over releasing.
So I think I kind of know where it's crashing, I have this globalArray Singleton class that I am creating:
extern NSString * const kClearDataSource;
#interface AHImageDataSource : NSObject
+ (AHImageDataSource *)sharedDataSource;
- (void) clearDataSource;
- (void) addObject:(id) object;
- (void) addObject:(id)object atIndex:(int) index;
- (int) count;
- (id) objectAtIndex:(int) index;
#end
NSString * const kClearDataSource = #"clearDataSource";
#interface AHImageDataSource()
{
NSMutableArray * imageDataSource_;
}
#property (nonatomic, retain) NSMutableArray * imageDataSource_;
#end
#implementation AHImageDataSource
#synthesize imageDataSource_;
+ (AHImageDataSource *)sharedDataSource {
static AHImageDataSource *_sharedClient = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedClient = [[self alloc] init];
});
return _sharedClient;
}
- (id)init {
self = [super init];
if (!self) {
return nil;
}
NSMutableArray * temp = [[NSMutableArray alloc] initWithCapacity:200];
self.imageDataSource_ = temp;
[temp release];
return self;
}
-(void) clearDataSource
{
if ([self.imageDataSource_ count] > 0){
[self.imageDataSource_ removeAllObjects];
}
}
- (void) addObject:(id) object
{
[self.imageDataSource_ addObject:object];
}
- (void) addObject:(id)object atIndex:(int) index
{
[self.imageDataSource_ insertObject:object atIndex:index];
}
- (int) count
{
return [self.imageDataSource_ count];
}
- (id) objectAtIndex:(int) index
{
if (index >= 0 && index < [self.imageDataSource_ count]){
return [self.imageDataSource_ objectAtIndex:index];
}
return nil;
}
- (void) dealloc
{
[super dealloc];
[imageDataSource_ release];
}
#end
at one point of the code I am trying to remove all of the objects in the array and then adding some stuff in. When that happen the crashed happened.
This part of the code crashes the second time it was executed:
NSArray *arr = [response valueForKey:#"data"];
if ([arr count] > 0){
[[AHImageDataSource sharedDataSource] clearDataSource];
}
for (NSDictionary * data in arr){
AHInstagramImageData * imgData = [[AHInstagramImageData alloc] initWithData:data];
[[AHImageDataSource sharedDataSource] addObject:imgData];
[imgData release];
}
You should definitely not do [super dealloc] first in your -dealloc method. It must come last.
Go go Product -> Analyze. The messages displayed will give you the solution or an idea.
Your app crashes when an object that has been deallocated is sent a message. NSZombiesEnabled prevents your app from crashing because it holds on to all deallocated objects (and thus leaks everything). It will print a message in the console when a deallocated object is sent a message (which would normally crash your app). Something to the affect of "message 'bar' sent to deallocated object 'foo'" (or something like that). It does not actually pause execution of your app.
When you've passed the point where you know your app generally crashes, check the console log for a message similar to the one above.

encodeWithCoder is not called in derived class of the NSMutableDictionary

Basically I am using some open source code called OrderedDictionary that is derived from NSMutableDictionary. Then I want to save the ordered dictionary data to NSUserDefaults by adding encode and decode method to the OrderedDictionary class. However, I realized the encode and decode methods are not being called, as a result, the decoded dictionary is no longer ordered. Below is my code:
#interface OrderedDictionary : NSMutableDictionary <NSCopying, NSCoding>
{
NSMutableDictionary *dictionary;
NSMutableArray *array;
}
In the implementation file:
/**
* encode the object
**/
- (void)encodeWithCoder:(NSCoder *)coder
{
[super encodeWithCoder:coder];
[coder encodeObject:dictionary forKey:#"dictionary"];
[coder encodeObject:array forKey:#"array"];
}
/**
* decode the object
*/
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self != nil)
{
dictionary = [coder decodeObjectForKey:#"dictionary"];
array = [coder decodeObjectForKey:#"array"];
}
return self;
}
Quick example code for using this:
dictionary = [[OrderedDictionary alloc] init];
[dictionary setObject:#"one" forKey:#"two"];
[dictionary setObject:#"what" forKey:#"what"];
[dictionary setObject:#"7" forKey:#"7"];
NSLog(#"Final contents of the dictionary: %#", dictionary);
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"myDictionary"] == nil)
{
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:dictionary]
forKey:#"myDictionary"];
}
else
{
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *savedDictionary = [currentDefaults objectForKey:#"myDictionary"];
if (savedDictionary != nil)
{
OrderedDictionary *oldData = [NSKeyedUnarchiver unarchiveObjectWithData:savedDictionary];
if (oldData != nil)
{
NSLog(#"Final contents of the retrieved: %#", oldData);
}
}
}
The thing is, the final retrievedDictionary does NOT have the original data order and the encode and decode methods are not called at all.
Thanks for any help in advance! :)
There's an NSObject method called -classForCoder that you need to override in OrderedDictionary. From the docs:
classForCoder
Overridden by subclasses to substitute a class other than its own during coding.
-(Class)classForCoder
Return Value
The class to substitute for the receiver's own class during coding.
Discussion
This method is invoked by NSCoder. NSObject’s
implementation returns the receiver’s class. The private subclasses of
a class cluster substitute the name of their public superclass when
being archived.
That last line is the key. So, in OrderedDictionary.m:
- (Class)classForCoder
{
return [self class]; // Instead of NSMutableDictionary
}
Also, if you're not using ARC, make sure you retain the objects coming back from -decodeObjectForKey. As rob mayoff mentions below, you shouldn't call [super initWithCoder:] (or [super encodeWithCoder:'). I also changed the key strings to avoid collisions.
- (id)initWithCoder:(NSCoder *)coder
{
if (self != nil)
{
dictionary = [[coder decodeObjectForKey:#"OrderedDictionary_dictionary"] retain];
array = [[coder decodeObjectForKey:#"OrderedDictionary_array"] retain];
}
return self;
}
You are possibly creating a new OrderedDictionary with the wrong initializer, initWithDictionary:. Try this instead:
OrderedDictionary *oldData = [NSKeyedUnarchiver unarchiveObjectWithData: savedDictionary];
if (oldData != nil)
{
NSLog(#"Final contents of the retrieved: %#", oldData);
}
Make sure initWithDictionary: expects OrderedDictionary as an argument. My guess is that it expects an NSDictionary.
Edit: the code to save the defaults should include something like this:
OrderedDictionary *myDict = ...;
NSData* data = [NSKeyedArchiver archivedDataWithRootObject: myDict];
[[NSUserDefaults standardDefaults] setObject: data forKey: #"myDictionary"];

How to access a string in a class stored in an array?

I have a string (titleName) stored in a class (newNoteBook) stored in an array (myLibrary). I was trying to access it, but I only get a (null) printed in the log.
What am I doing wrong?
-(void) setupLibrary {
NoteBook *newNoteBook = [[NoteBook alloc] init];
newNoteBook.titleName = #"TEST";
NSLog(#"titleName:%#", newNoteBook.titleName); // this prints TEST in the log
[myLibrary addObject:newNoteBook];
NSLog(#"titleName:%#", [[self.myLibrary objectAtIndex:0] titleName]); // this prints (null) in the log)
}
There is nothing fancy in my class... simply:
#interface NoteBook : NSObject {
NSString *titleName; }
#property (nonatomic, retain) NSString *titleName;
#end
#implementation NoteBook
#synthesize titleName;
Try this
NSLog(#"titleName:%#", ((NoteBook *)[self.myLibrary objectAtIndex:0]).titleName);
Possible reasons:
myLibrary (the instance variable) is nil;
self.myLibrary is nil or its backing instance variable isn’t myLibrary;
[self.myLibrary objectAtIndex:0] is not the same object as newNoteBook because there was at least one other element in self.myLibrary.
Edit: you need to create a new mutable array and assign it to your property/instance variable myLibrary:
self.myLibrary = [NSMutableArray array];
or
myLibrary = [[NSMutableArray alloc] init];
Where you should this depend on how your class is used. If an instance of your class should always have valid myLibrary, a good place to do that is in -init:
- (id)init {
self = [super init];
if (self) {
myLibrary = [[NSMutableArray alloc] init];
}
return self;
}
Alternatively, if you want to lazily create myLibrary only when -setupLibrary is executed, create it in that method:
-(void) setupLibrary {
self.myLibrary = [NSMutableArray array];
NoteBook *newNoteBook = [[NoteBook alloc] init];
…
}
Don’t forget to release it in your -dealloc method:
- (void)dealloc {
[myLibrary release];
[super dealloc];
}
I think you are not type casting object from array -
NSLog(#"titleName:%#", [(NoteBook*)[self.myLibrary objectAtIndex:0] titleName]);
and you should alloc your array before adding object to it -
myLibrary = [[NSMutableArray alloc] init];
NSLog(#"titleName:%#", [self.myLibrary objectAtIndex:0].titleName);
Is correct as they said before you don't need to cast.

iPhone - NSMutableArray inside a Custom Object. When to release?

When should I be releasing [self.activeLocations], which is an NSMutableArray inside my custom object? I am also getting memory leaks in initWithValue.
Also the Location object below. Am I calling this and releasing this properly?
Method in Custom Object.m:
- (id)initWithValue:(NSString *)value {
if ((self = [super init])) {
self.couponId = [value valueForKey:#"couponId"];
self.couponName = [value valueForKeyPath:#"couponName"];
self.qrCode = [value valueForKeyPath:#"description"];
self.companyName = [value valueForKeyPath:#"companyName"];
self.categoryName = [value valueForKeyPath:#"categoryName"];
self.distance = [value valueForKeyPath:#"distance"];
NSDictionary *activeLocationsDict = [value valueForKeyPath:#"activeLocations"];
//self.activeLocations = [[[NSMutableArray alloc] init] autorelease];
self.activeLocations = [NSMutableArray array];
for (id val in activeLocationsDict) {
// Add JSON objects to array.
Location *l = [[Location alloc] initWithValue:val];
[self.activeLocations addObject:l];
[l release];
}
}
return self;
}
- (void)dealloc {
[super dealloc];
couponId = nil;
couponName = nil;
qrCode = nil;
companyName = nil;
categoryName = nil;
distance = nil;
activeLocations = nil;
}
My Custom Object.h
#interface Coupon : NSObject {
NSNumber *couponId;
NSString *couponName;
NSString *qrCode;
NSString *companyName;
NSString *categoryName;
NSString *distance;
NSMutableArray *activeLocations;
}
#property (nonatomic, copy) NSNumber *couponId;
#property (nonatomic, copy) NSString *couponName;
#property (nonatomic, copy) NSString *qrCode;
#property (nonatomic, copy) NSString *companyName;
#property (nonatomic, copy) NSString *categoryName;
#property (nonatomic, copy) NSString *distance;
#property (nonatomic, retain) NSMutableArray *activeLocations;
- (id)initWithValue:(NSString *)value;
This is how I'm using the above initWithValue:
- (NSMutableArray *)createArrayOfCoupons:(NSString *)value {
NSDictionary *responseJSON = [value JSONValue];
// Loop through key value pairs in JSON response.
//NSMutableArray *couponsArray = [[NSMutableArray alloc] init] autorelease];
NSMutableArray *couponsArray = [NSMutableArray array];
for (id val in responseJSON) {
// Add JSON objects to array.
Coupon *c = [[Coupon alloc] initWithValue:val];
[couponsArray addObject:c];
[c release];
}
return couponsArray;
}
I get memory leaks on initWithValue in the above method as well...
Location Custom Object:
- (id)initWithValue:(NSString *)value {
if ((self = [super init])) {
self.locationId = [value valueForKeyPath:#"locationId"];
self.companyName = [value valueForKeyPath:#"companyName"];
self.street1 = [value valueForKeyPath:#"street1"];
self.street2 = [value valueForKeyPath:#"street2"];
self.suburb = [value valueForKeyPath:#"suburb"];
self.state = [value valueForKeyPath:#"state"];
self.postcode = [value valueForKeyPath:#"postcode"];
self.phoneNo = [value valueForKeyPath:#"phoneNo"];
self.latitude = [value valueForKeyPath:#"latitude"];
self.longitude = [value valueForKeyPath:#"longitude"];
}
return self;
}
- (void)dealloc {
[super dealloc];
locationId = nil;
companyName = nil;
street1 = nil;
street2 = nil;
suburb = nil;
state = nil;
postcode = nil;
phoneNo = nil;
latitude = nil;
longitude = nil;
}
- (id)init {
....
}
Get rid of this. It does nothing.
- (id)initWithValue:(NSString *)value {
[super init];
There's a specific pattern you should use for initialization:
- (id)initWithValue:(NSString *)value {
if (( self = [super init] )) {
// everything except the return
}
return self;
}
Finally, to answer your actual question, assuming you're using retain with your property, there's two places you'll need to release.
Here's the first:
self.activeLocations = [[NSMutableArray alloc] init];
Why: [[NSMutableArray alloc] init] makes your code own the object by retaining it. But the property set also claims ownership by retaining it. You don't really want this NSMutableArray owned by the code and your custom object, you want it owned by your object.
My suggestion is to just use this:
self.activeLocations = [NSMutableArray array];
The second place is in your dealloc:
- (void)dealloc {
self.activeLocations = nil;
// ...and everything else you've set as a property using retain
[super dealloc];
}
(Personally, I've gone back and forth on whether to use dot notation in dealloc rather than [activeLocations release];. I'm favouring setting to nil using the property now, which puts all the memory management rules in a single location.)
Apple has a great document on memory management you should read: Memory Management Programming Guide: Object Ownership and Disposal.
First of all, your overridden -init method is completely unnecessary because by default when a method is invoked, the runtime will perform an upward traversal of the inheritance hierarchy until the specified method is found, so it will find NSObject's -init method and invoke it.
Second, you should invoke release on all of your owned properties (ones with copy or retain) in your overridden -dealloc method.
Third, in your case, when you call the property setter passing in an object that is already owned locally, you must send the object the release message after invoking the setter to correctly hand off ownership of the object to the receiver.
There are two ways to do this:
One way is to create an object that you own using alloc, copy or new, and then invoke the property setter, passing in that object, then send it the release message.
Another way is to pass in an autoreleased object to the property setter, which will then retain or copy its argument and thereby obtain ownership
The answer to when you should be releasing it is a question of whether or not the activeLocations array and all the elements in that array (remember each element in the array is retained by the array itself) are necessary throughout the lifetime of the Location object.
If you use the activeLocations array for some temporary purpose, for example in a method or chain of methods, then don't need it again, or you plan to refresh its members at some later time when you need it next, then it makes sense to release the array (and its elements, which is automatic) when you're done using it, in whatever function last uses the array. You will use the convention
self.activeLocations = nil;
to let the runtime system release the array and set the member to nil.
If, on the other hand, the activeLocations array data is mandatory for the Locations object to function and must exist as long as the Location object exists, then you will want to release the array inside the dealloc method of the Location object, for example:
- (void) dealloc {
[activeLocations release];
[super dealloc];
}
As it happens, you're pretty much always going to want to release member objects such as activeLocations in a dealloc method. This ensures that when the Location object is released the members it contains are cleaned up. Remember that Objective-C does not call methods on null pointers, so if you have previously set activeLocations to nil the call in dealloc is a safe no-op.
Given then that you'll always set things up to release in dealloc, now you really just have to ask yourself if you need a release/recreate phase somewhere in your object lifecycle (again, determined by frequency-of-use requirements).
It depends on what you're asking. In the initWithValue: method that you've shared, you are double-retaining the array. It should be released or autoreleased once within initWithValue:.
The array should be released a second time in the custom object's dealloc method.

IPhone - copyWithZone leak

Testing my app on the device it returns a leak whe i call the copy of a custom object ande i can't understand why.
this is the call:
NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:5];
for (SinglePart *sp in [copyFrom partList]) {
[arr addObject:[sp copy]];
}
self.partList = arr;
[arr release];
this is the method:
- (id)copyWithZone:(NSZone *)zone {
SinglePart *copy = [[[self class] allocWithZone:zone] initWithSinglePart:self];
[copy loadImage];
return copy;
}
this is the method that is called by copyWithZone:
- (id)initWithSinglePart:(SinglePart *)copyFrom {
if (self = [super init]) {
self.imagePath = [copyFrom.imagePath copy];
self.color = [UIColor colorWithCGColor:copyFrom.color.CGColor];
self.hasOwnColor = copyFrom.hasOwnColor;
self.blendingMode = copyFrom.blendingMode;
}
return self;
}
copy returns a new object with retain count 1. Meaning you need to release the new object, which you are not doing.
NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:5];
for (SinglePart *sp in [copyFrom partList]) {
SingPart *theCopy = [sp copy];
[arr addObject:theCopy];
[theCopy release];
}
self.partList = arr;
[arr release];
Even your custom copyWithZone: method inits an object, but does not autorelease it, which is the expected behavior of a copy method. Copy must be balanced just like a retain or init, meaning you must balance it with release at some point.
Lastly, your initWithSinglePart: method leaks the imagePath as well. In this case if you declare the imagePath property as copy instead of retain then you don't need to do this manually at all. Then you simply assign the value and let the property setter do it for you.
// Header
#property (copy) NSString *imagePath;
// Now this will do the copy for you
self.imagePath = copyFrom.imagePath;
Also, is the property imagePath defined with retain or copy semantics?
If so you need to add an autorelease here:
self.imagePath = [[copyFrom.imagePath copy] autorelease];
because the default setter will retain/copy it too.
So, you either need to autorelease, or omit the "self." to bypass the default setter.
You are making a copy of sp and then adding it to the array. The array then retains the object so your retain count is now 2.
In the end you release arr, thus making the retain count of it's items 1.
You should either add another release to the sp objects, or not use copy.
Try this:
self.partList = [NSMutableArray arrayWithCapacity:5];
for (SinglePart *sp in [copyFrom partList]) {
[arr addObject:sp];
}