I'm having issues placing a custom object (WSWCMPost) into an NSMutableArray and then accessing the data stored in it later. Below is the relevant code.
Here is "WSWCMPost.h"
#import <Foundation/Foundation.h>
#interface WSWCMPost : NSObject
{
NSString *postBody;
NSString *postTitle;
NSString *postID;
}
#property (nonatomic, retain) NSString *postBody, *postTitle, *postID;
- init;
- (id)initWithID: (NSString*)ID AndBody: (NSString*)body AndTitle: (NSString*)title;
- (NSString*)postBody;
- (NSString*)postTitle;
- (NSString*)postID;
Here is "WSWCMPost.m"
#import "WSWCMPost.h"
#implementation WSWCMPost
#synthesize postBody, postTitle, postID;
- (id)init {
self = [super init];
if(self) {
postID = #"none";
postBody = #"none";
postTitle = #"none";
}
}
- (id)initWithID: (NSString*)ID AndBody: (NSString*)body AndTitle: (NSString*)title {
postTitle = title;
postID = ID;
postBody = body;
}
#end
And here is the "viewDidLoad" method that is causing my issues
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.detailViewController = (WSWCMDetailViewController *)[[self.splitViewController.viewControllers lastObject] topViewController];
// getting an NSString
NSLog(#"Pulling saved blogs...");
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:#"wswcmt1"];
if (dataRepresentingSavedArray != nil)
{
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if (oldSavedArray != nil)
_objects = [[NSMutableArray alloc] initWithArray:oldSavedArray];
else
_objects = [[NSMutableArray alloc] init];
}
NSLog(#"Pulled saved blogs...");
NSLog(!_objects ? #"Yes" : #"No");
#try {
NSLog(#"_objects description: %#",[_objects description]);
NSLog(#"_objects[0] postID: %#",[[_objects objectAtIndex:0] postID]);
}
#catch (NSException *exception) {
NSLog(#"Caught exception %#", exception);
NSLog(#"Objects doesnt exist, allocating memory...");
_objects = [[NSMutableArray alloc] init];
WSWCMPost *testPost = [[WSWCMPost alloc] initWithID:#"noID" AndBody:#"noBody" AndTitle:#"noTitle"];
[_objects insertObject:testPost atIndex:0];
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_objects] forKey:#"wswcmt1"];
}
if (!_objects ) {
NSLog(#"Objects doesnt exist...");
_objects = [[NSMutableArray alloc] init];
WSWCMPost *testPost = [[WSWCMPost alloc] initWithID:#"dne" AndBody:#"Dne" AndTitle:#"DNe"];
[_objects insertObject:testPost atIndex:0];
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:_objects] forKey:#"wswcmt"];
}
[self refreshButton:nil];
}
And finally, here is the output
2012-06-25 22:39:49.345 WSWCM[4406:907] Pulling saved blogs...
2012-06-25 22:39:49.352 WSWCM[4406:907] Pulled saved blogs...
2012-06-25 22:39:49.355 WSWCM[4406:907] Yes
2012-06-25 22:39:49.356 WSWCM[4406:907] _objects description: (null)
2012-06-25 22:39:49.358 WSWCM[4406:907] _objects[0] postID: (null)
2012-06-25 22:39:49.360 WSWCM[4406:907] Objects doesnt exist...
2012-06-25 22:39:49.363 WSWCM[4406:907] Refresh Triggered...
I think that is all of the relevant code. If i forgot anything let me know please. This issue has been bothering me for hours...
While I'm not positive why it's giving you NSStrings instead of just blowing up normally, the problem seems to stem from the fact that your custom class, WSWCMPost, does not conform to the NSCoding protocol. Make sure that your custom objects implement this protocol if you want to store them in NSUserDefaults, since it doesn't know how to serialize the data otherwise.
To be more exact, you'll have to add these methods to your class implementation:
- (id)initWithCoder:(NSCoder *)coder {
self = [self initWithID:[coder decodeObjectForKey:#"id"] AndBody:[coder decodeObjectForKey:#"body"] AndTitle:[coder decodeObjectForKey:#"title"]];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[encoder encodeObject:postID forKey:#"id"];
[encoder encodeObject:postBody forKey:#"body"];
[encoder encodeObject:postTitle forKey:#"title"];
}
This will allow the data to be serialized by NSCoder. Once you've done this, you should clear all the information currently stored by NSUserDefaults to make sure that it doesn't contain any more NSStrings, but then everything should work properly. Of course, you'll have to update these two methods if you change the data stored by your WSWCMPost object.
Another thing to mention, you're having collisions with your getters/setters and their respective instance variables. So your implementation is:
interface
#interface WSWCMPost : NSObject
{
NSString *postBody; // don't need to do these anymore for properties
NSString *postTitle;
NSString *postID;
}
#property (nonatomic, retain) NSString *postBody, *postTitle, *postID;
implementation
#implementation WSWCMPost
#synthesize postBody, postTitle, postID;
- (id)init {
self = [super init];
if(self) {
postID = #"none"; // not prefixing your variables with 'self' so they are not getting retained
postBody = #"none";
postTitle = #"none";
}
}
#end
Here's how you should be writing those out:
interface
/** NOTE: No need to specify your instance variables here anymore, just the properties */
#interface WSWCMPost : NSObject
#property (nonatomic, retain) NSString *postID;
#property (nonatomic, retain) NSString *postTitle;
#property (nonatomic, retain) NSString *postBody;
implementation
#implementation WSWCMPost
/** Now you specify the corresponding instance variable name alongside the property name */
#synthesize postBody=_postBody, postTitle=_postTitle, postID=_postID;
- (id)init {
self = [super init];
if(self) {
self.postID = #"none"; //getting retained
self.postBody = #"none";
self.postTitle = #"none";
}
}
That would definitely cause data to be released too soon.
So the previous way you could type in self.postID or postID and the compiler wouldn't complain. The difference is when you type postID it is actually setting the member variable and not retaining it... where self.postID will release whatever it is currently set to and retain the new value if it's different.
By declaring your properties the new way, you have to either call the setter as self.postID or set the underlying instance variable as _postID. A lot of early iPhone books had you bang out properties that way and it just ends up causing all sorts of memory issues.
Hope this helps!
UPDATE!!!
You forgot to return self in your constructor ;) I bet that's it
- (id)init {
self = [super init];
if(self) {
self.postID = #"none"; //getting retained
self.postBody = #"none";
self.postTitle = #"none";
}
return self; // THIS IS WHY, you're constructor doesn't return an instance of the class... add this please
}
- (id)initWithID: (NSString*)ID AndBody: (NSString*)body AndTitle: (NSString*)title {
if(( self = [super init] ))
{
self.postTitle = title;
self.postID = ID;
self.postBody = body;
}
return self;
}
Your output definitely shows what was wrong in your code.
2012-06-25 21:51:07.691 WSWCM[4049:907] -[__NSCFString postID]: unrecognized selector sent to instance 0x1d003e80
2012-06-25 21:51:07.696 WSWCM[4049:907] Caught exception -[__NSCFString postID]: unrecognized selector sent to instance 0x1d003e80
These two lines tell you that NSString object does not recognize selector postID. This hint should be enough to find out where you need to see in depth.
See this Storing custom objects in an NSMutableArray in NSUserDefaults for more information.
Related
I have a class that has a NSMutableDictionary as a property:
#interface Alibi : NSObject <NSCopying>
#property (nonatomic, copy) NSMutableDictionary * alibiDetails;
#end
With the following constructor:
- (Alibi *)init
{
self = [super init];
_alibiDetails = [NSMutableDictionary dictionary];
return self;
}
and copy method:
- (Alibi *)copyWithZone:(NSZone *)zone
{
Alibi *theCopy = [[Alibi alloc] init];
theCopy.alibiDetails = [self.alibiDetails mutableCopy];
return theCopy;
}
When I try to call setObject:ForKey: I get a runtime error mutating method sent to immutable object.
I have the Alibi object declared in the view controller as #property (copy, nonatomic) Alibi * theAlibi; and I initialize it with self.theAlibi = [[Alibi alloc] init]; in viewDidLoad.
The line which crashes is:
NSString * recipient;
recipient = #"Boss";
[self.theAlibi.alibiDetails setObject:recipient forKey:#"Recipient"];
Please let me know what I am doing wrong here. I am coding for iOS 5 on iPhone.
You have a 'copy' property, which means exactly that - your NSMutableDictionary will get the -copy method called and return a regular NSDictionary before being assigned to the synthesized instance variable. This thread provides some information on some of your options as to solving this.
For the sake of completing this thread I will include my revised Alibi class below, this works as I require it to. If anyone notices any memory leaks or other issues, that would be appreciated.
#implementation Alibi
NSMutableDictionary *_details;
- (Alibi *)init
{
self = [super init];
_details = [NSMutableDictionary dictionary];
return self;
}
- (NSMutableDictionary *)copyDetails
{
return [_details mutableCopy];
}
- (NSMutableDictionary *)setDetails:(NSMutableDictionary *)value
{
_details = value;
return value;
}
- (void)addDetail:(id)value forKey:(id)key
{
[_details setObject:value forKey:key];
}
- (id)getDetailForKey:(id)key
{
return [_details objectForKey:key];
}
- (Alibi *)copyWithZone:(NSZone *)zone
{
Alibi *theCopy = [[Alibi alloc] init];
theCopy.serverId = [self.serverId copyWithZone:zone];
theCopy.user = [self.user copyWithZone:zone];
theCopy.startTime = [self.startTime copyWithZone:zone];
theCopy.endTime = [self.endTime copyWithZone:zone];
[theCopy setDetails:[self copyDetails]];
return theCopy;
}
#end
The problem lies within the 'initWithCoder' method. When I want to retrieve "Coins_Key" from where I saved it by calling the 'saveData' method in my 'main' class and I pass in the key "self.keyName," the value of keyName is 0.
//Class coins.h
#property (retain) NSString* keyName;
#property (retain) NSString* keyValue;
//Class coins.m
#synthesize keyName;
-(void) saveData:(NSString *)number: (NSString *)keyID
{
self.keyName = keyID;
self.keyValue = number;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
NSLog(#"Encoded keyName: %#", keyName);
[encoder encodeObject:keyValue forKey:keyName];
}
- (id)initWithCoder:(NSCoder *)decoder {
self.keyValue = [decoder decodeObjectForKey:self.keyName];
NSLog(#"Decoded Coins: %#", self.keyValue);
return self;
}
//Class main
[Coins *coin3 = [[Coins alloc] init];
[coin3 saveData:#"6" :#"Coins_Key"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:coin3];
coin3 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
You're not quite grasping the encoder/decoder workflow.
Using the encodeObjectForKey: and decodeObjectForKey: methods properly, you should be passing as an argument the key that should be used to store the value. This key must remain constant.
You should also not require callers to provide the key your Coin object uses to store data. Take this simple example as a more correct/efficient method (assuming I understand the purpose of your class):
// Class Coins.h
#property (assign) int numberOfCoins;
// Class Coins.m
#define NUM_COINS_KEY #"NUM_COINS_KEY"
#synthesize numberOfCoins;
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) { // Use [super initWithCoder:decoder] here if your superclass supports it
self.numberOfCoins = [decoder decodeIntForKey:NUM_COINS_KEY];
NSLog(#"Decoded Coins: %d", self.numberOfCoins);
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
NSLog(#"Encoded keyName: %#", keyName);
[encoder encodeInt:self.numberOfCoins forKey:NUM_COINS_KEY];
}
// Class main
Coins *coin = [[Coins alloc] init];
coin.numberOfCoins = 6;
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:coin];
[coin release]; // If you're just playing around, this is probably overkill, but a good habit
coin = [NSKeyedUnarchiver unarchiveObjectWithData:data];
I'm having a bit of trouble with memory leaks in my objective c code. Could anyone take a look and let me know what they think?
NSStringArray.h
#interface NSStringArray : NSObject {
NSMutableArray *realArray;
}
#property (nonatomic, assign) NSMutableArray *realArray;
-(id)init;
-(void)dealloc;
#end
NSStringArray.m
#import "NSStringArray.h"
#implementation NSStringArray
#synthesize realArray;
-(id)init {
self = [super init];
if ( self != nil ) {
realArray = [[[NSMutableArray alloc] init] retain];
}
return self;
}
-(void)dealloc {
[realArray release];
realArray = nil;
[super dealloc];
}
Factory.m
+(NSStringArray *)getFields:(NSString *)line {
//Divides the lines into input fields using "," as the separator.
//Returns the separate fields from a given line. Strips out quotes & carriage returns.
line = [line stringByReplacingOccurrencesOfString:#"\"" withString:#""];
line = [line stringByReplacingOccurrencesOfString:#"\r" withString:#""];
NSStringArray *fields = [[NSStringArray alloc] init];
for (NSString *field in [line componentsSeparatedByString:#","]) {
[fields.realArray addObject:field];
[field release];
}
return [fields autorelease];
}
The Leaks tool is saying that the leak occurs when fields is allocated, and when I am adding field string to the fields array.
Also, this function is getting called each line of a file that I'm parsing.
Any tips would be helpful.
Thanks!
This line does a double retain:
realArray = [[[NSMutableArray alloc] init] retain];
it is enough
realArray = [[NSMutableArray alloc] init];
In this piece of code, you break the memory management rules.
for (NSString *field in [line componentsSeparatedByString:#","]) {
[fields.realArray addObject:field];
[field release];
}
You do not own the object pointed at by field so you must not release it.
You have overreleased field so the last object to release it (the autorelease pool in your case) is releasing an already dealloc'd object.
From the docs:
An allocation message does other important things besides allocating
memory:
It sets the object’s retain count to one (as described in “How Memory
Management Works”).
Therefore, you don't need to retain something that you've just alloc'ed.
Adding to Felz answer above. Use self.realArray when allocating array
self.realArray = [[NSMutableArray alloc] init];
Because you have created a property for the array so it is better to use "self"
You could also take advantage of the properties in objective C to make
more clear and efficient your code:
NSStringArray.h
#interface NSStringArray : NSObject {
}
#property (nonatomic, retain) NSMutableArray *realArray;
#end
NSStringArray.m
#import "NSStringArray.h"
#implementation NSStringArray
#synthesize realArray = _realArray;
-(id)init {
self = [super init];
if (self) {
self.realArray = [NSMutableArray array];
}
return self;
}
-(void)dealloc {
[_realArray release];
[super dealloc];
}
Now, with the modifier retain of the property realArray you can use
[NSMutableArray array] that return an autorelease mutable array.
The retain properties manage the retain/release stuff by themselves.
You don't need to use the realArray = nil; line. You've already deallocated
the property.
Hope this can help.
I am trying to save an object in NSUserDefaults but i have not been successful in it. Scenario is that, I have an object syncObject of class SyncObjct. I have implemented following methods in SyncObject class
- (NSMutableDictionary*)toDictionary;
+ (SyncObject*)getFromDictionary :(NSMutableDictionary*)dictionary;
- (id) initWithCoder: (NSCoder *)coder;
- (void) encodeWithCoder: (NSCoder *)coder;
And also the SyncObject class is NSCoding class. Now SyncObject class contains an NSMutableArray with name jobsArray. This array contains objects of class JobsObject. I implemented same above methods for this class as well to make it coding compliant. In toDictionary method of SyncObject class, i am storing this array in dictionary by writing following line.
[dictionary setValue:jobsArray forKey:#"jobsArray"];
and I am retrieving it in getFromDictionary method of SyncOject class by writing following line.
+ (SyncObject*)getFromDictionary :(NSMutableDictionary*)dictionary
{
NSLog(#"SyncObject:getFromDictionary");
SyncObject *syncObject = [[SyncObject alloc] init];
#try
{
syncObject.noOfJobs = [[dictionary valueForKey:#"noOfJobs"] intValue];
syncObject.totalJobsPerformed = (TotalJobsPerformed*)[dictionary valueForKey:#"totalobsPerformed"];
syncObject.jobsArray = [dictionary valueForKey:#"jobsArray"];
}
#catch (NSException * e)
{
NSLog(#"EXCEPTION %#: %#", [e name], [e reason]);
}
#finally
{
}
return syncObject;
}
And also I am storing syncObject in NSUserDefault by writing following lines.
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:syncObject];
[userStorage setObject:myEncodedObject forKey:SYNC_OBJECT];
I am retrieving the object from NSUserDefaults by writing following lines
NSData *myEncodedObject = [userStorage objectForKey: SYNC_OBJECT];
syncObject = (SyncObject*)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
Problem is that i am not able to retrieve jobsArray correctly. Some invalid object is returned and app crashes when trying to access it. Please can anyone tell me the reason of this problem?
Best Regards
I believe the problem is that NSUserDefaults does not support storing of custom object types.
From Apple's documentation when using -setObject: The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.
You can review the remaining documentation directly from Apple's NSUserDefault Class Reference manual.
Custom class .h
#import <Foundation/Foundation.h>
#interface Person : NSObject
#property(nonatomic, retain) NSArray *names;
#property(strong,nonatomic)NSString *name;
#end
custom class .m
#import "Person.h"
#implementation Person
#synthesize names;
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:self.name forKey:#"name"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
//decode properties, other class vars
self.name = [decoder decodeObjectForKey:#"name"];
}
return self;
}
#end
To create Person class object and store and retrieve your stored data from NSUserDefaults
Stored your object :
#import "ViewController.h"
#import "Person.h"
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UITextField *textBox;
#end
NSMutableArray *arr;
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)saveButton:(id)sender {
Person *person =[Person new];
person.name = _textBox.text;
arr = [[NSMutableArray alloc]init];
[arr addObject:person];
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:arr];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:encodedObject forKey:#"name"];
[defaults synchronize];
}
- (IBAction)showButton:(id)sender {
_label.text = #"";
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *storedEncodedObject = [defaults objectForKey:#"name"];
NSArray *arrStoreObject = [NSKeyedUnarchiver unarchiveObjectWithData:storedEncodedObject];
for (int i=0; i<arrStoreObject.count; i++)
{
Person *storedObject = [arrStoreObject objectAtIndex:i];
_label.text = [NSString stringWithFormat:#"%# %#", _label.text , storedObject.name];
}
}
#end
i'm posting notification in this manner:
...
IVSession *newSession = [[[IVSession alloc] initWithDictionary:propertyDict] autorelease];
NSDictionary *notifParams = [NSDictionary dictionaryWithObject:newSession forKey:#"session"];
NSNotification *newSessionNotif = [NSNotification notificationWithName:IVNewSessionNotificaiton object:self userInfo:notifParams];
...
IVSession interface:
#interface IVSession : IVMappableObject {
NSString *_ssid;
NSNumber *_uid;
}
#property (nonatomic,retain) NSString *sessionID;
#property (nonatomic,retain) NSNumber *userID;
and init method:
- (id)initWithDictionary:(NSDictionary*)dict
{
if ((self = [super init]))
{
NSDictionary *mapping = [self elementToPropertyMappings];
for (NSString *key in mapping)
[self setValue:[dict objectForKey:key] forKey:[mapping objectForKey:key]];
}
return self;
}
but at the method, called for this notification, i'm receiving broken newSession object - its properties ssid and uid are invalid summaries:
-(void)didOpenSession:(NSNotification *)newSession
{
if (receivedSession)
[receivedSession release];
receivedSession = [[newSession userInfo] objectForKey:#"session"];
}
where is my fault?
code looks ok to me... have you verified that the IVSession object contains what you expect it to, after assembling things, but prior to posting the notification?