Save own Class with NSCoder - iphone

I'm trying to store some custom class/data to a file in my iPhone/iPad app.
I have a Class RSHighscoreList
#interface RSHighscoreList : NSObject {
NSMutableArray *list;
}
which contains objects of RSHighscore in the list
#interface RSHighscore : NSObject {
NSString *playerName;
NSInteger points;
}
When I try to store all to file
- (void)writeDataStore {
RSDataStore *tmpStore = [[RSDataStore alloc] init];
_tmpStore.highscorelist = self.highscorelist.list;
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:tmpStore forKey:kDataKey];
[archiver finishEncoding];
[data writeToFile:[self dataFilePath] atomically:YES];
[archiver release];
[data release];
}
#interface RSDataStore : NSObject <NSCoding, NSCopying> {
NSMutableArray *highscorelist;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:highscorelist forKey:#"Highscorelist"];
}
The app will crash with an error message
-[RSHighscore encodeWithCoder:]: unrecognized selector sent to instance 0x573cc20
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[RSHighscore encodeWithCoder:]: unrecognized selector sent to instance 0x573cc20'
I wonder why the error tells about RSHighscore, even if that is 'wrapped'. Does anyone have a good idea?

RSDataStore has an -encodeWithCoder: method, but (according to the error message) RSHighscore doesn't. You need to implement the NSCoding protocol for every class you're serializing.
#implementation RSHighscore
static NSString *const kPlayerName = #"PlayerName";
static NSString *const kPoints = #"Points";
-(id)initWithCoder:(NSCoder *)decoder {
if ((self=[super init])) {
playerName = [[decoder decodeObjectForKey:kPlayerName] retain];
points = [decoder decodeIntegerForKey:kPoints];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:playerName forKey:kPlayerName];
[encoder encodeInt:points forKey:kPoints];
}
...
If the base class of RSHighscore is ever changed to something other than NSObject, the -initWithCoder: method might need to be changed to call [super initWithCoder:decoder] rather than [super init]. Alternatively, add <NSCoding> to NSObject and change RSHighscore's -initWithCoder: now.
#interface NSObject (NSCoding)
-(id)initWithCoder:(NSCoder*)decoder;
-(void)encodeWithCoder:(NSCoder*)encoder;
#end
#implementation NSObject (NSCoding)
-(id)initWithCoder:(NSCoder*)decoder {
return [self init];
}
-(void)encodeWithCoder:(NSCoder*)encoder {}
#end
#implementation RSHighscore
-(id)initWithCoder:(NSCoder *)decoder {
if ((self=[super initWithCoder:decoder])) {
playerName = [[decoder decodeObjectForKey:kPlayerName] retain];
points = [decoder decodeIntegerForKey:kPoints];
}
return self;
}
...

The class you're going to encode or initWithCoder should conform to <NSCoding> protocol
So you just should add this in your interface, otherwise indeed the runtime will not recognize the selector as it's the part of <NSCoding> protocol

Related

Writing NSMutableArrray to file an then reading it

I have a NSMutableArray feed.leagues which has two objects of <MLBLeagueStandings: 0xeb2e4b0>
I want to write it to a file and then read it from the file. This is what I have done:
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:feed.leagues forKey:#"feed.leagues"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
self.feed.leagues = [decoder decodeObjectForKey:#"feed.leagues"];
}
return self;
}
-(void)saveJSONToCache:(NSMutableArray*)leaguesArray {
NSString *cachePath = [self cacheJSONPath];
[NSKeyedArchiver archiveRootObject:feed.leagues toFile:cachePath];
NSMutableArray *aArray = [NSKeyedUnarchiver unarchiveObjectWithFile:cachePath];
NSLog(#"aArray is %#", aArray);
}
-(NSString*)cacheJSONPath
{
NSString *documentsDirStandings = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *cacheJSONPath = [NSString stringWithFormat:#"%#/%#_Standings.plist",documentsDirStandings, sport.acronym];
return cacheJSONPath;
}
Your object : MLBLeagueStandings should be serializable and respond to NSCoding protocole :
#interface MLBLeagueStandings : NSObject <NSCoding>{
}
Now in your MLBLeagueStandings class file (.m) add the following methods :
- (id)initWithCoder:(NSCoder *)decoder;
{
self = [super initWithCoder:decoder];
if(self)
{
yourAttribute = [decoder decodeObjectForKey:#"MY_KEY"]
//do this for all your attributes
}
}
- (void)encodeWithCoder:(NSCoder *)encoder;
{
[encoder encodeObject:yourAttribute forKey:#"MY_KEY"];
//do this for all your attributes
}
In fact if you want to write an object to a file (in your case it's an array), all the object contained in this array have to conform to the NSCoding protocole.
Moreover if you want example : here is a good tutorial
Hope it will help you.
NB : if you want to encode/decode primitive type (int, float etc...) use :
[encode encodeInt:intValue forKey:#"KEY"];
(more information on apple doc)

Delegate Methods not called in Class Instance

I'm creating an instance of a class called S3ObjectController (S3OC) that has one method and four delegate methods. I create an instance of my S3OC, call an instance method from the S3OC Class (which I know fires from NSLog statements) but none of the associated delegate methods are called within the S3OC class. I have the delegate set to self in the method and the delegate declared properly in the .h header.
Thoughts? just to be clear, it's the (void)request methods in the .m file below I'm thinking should be called that aren't. I'm getting EXC BAD ACCESS errors. Is self getting released by ARC?
The entire .m file of the S3OC class is below:
#import "S3ObjectController.h"
#implementation S3ObjectController
#synthesize string;
#synthesize s3GOR, s3Client;
-(void)method
{
NSLog(#"Method Called");
s3Client = [[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY];
s3GOR = [[S3GetObjectRequest alloc]initWithKey:string withBucket:[Constants pictureBucket]];
[s3GOR setDelegate:self];
[s3Client getObject:s3GOR];
NSLog(#"Method Finished");
}
-(void)request:(AmazonServiceRequest *)request didFailWithError:(NSError *)error
{
NSLog(#"Error %#",error);
}
-(void)request:(AmazonServiceRequest *)request didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"Response Key %#", response);
}
-(void)request:(AmazonServiceRequest *)request didReceiveData:(NSData *)data
{
NSLog(#"ObjectRequestKey = %#",request);
}
-(void)request:(AmazonServiceRequest *)request didCompleteWithResponse:(AmazonServiceResponse *)response
{
NSLog(#"Final Delegate Method");
}
Here's the header:"
#interface S3ObjectController : NSObject <AmazonServiceRequestDelegate>{
NSMutableData *responseData;
NSString *string;
AmazonS3Client *s3Client;
S3GetObjectRequest *s3GOR;
}
-(void)method;
#property (nonatomic, strong) NSString *string;
#property (nonatomic, strong) S3GetObjectRequest *s3GOR;
#property (nonatomic, strong) AmazonS3Client *s3Client;
#end
Finally, here's how I call the method in another class:
for (NSString *name in nameArray){
#try {
S3ObjectController *localS3 = [[S3ObjectController alloc]init];
localS3.string = name;
[localS3 method];
NSLog(#"called");
}
I think your suspicions about ARC are true. Because delegate properties are usually weak references, they aren't enough to keep the object from being released.
Make an NSArray that's an iVar and add the S3ObjectController to it. If the delegates still don't fire, you know it's something else...
Edit:
so declare an NSMutableArray in the header of the class that contains your for loop, initialize it somewhere like this:
myArray = [NSMutableArray arrayWithCapacity:0];
then use it like this:
for (NSString *name in nameArray){
#try {
S3ObjectController *localS3 = [[S3ObjectController alloc]init];
localS3.string = name;
[localS3 method];
[myArray addObject:localS3];
NSLog(#"called");
}
}

How to serialize a class in IOS sdk (Objective-c)?

How to serialize the following class in objective-c so that it can be used with SBJson?
I get "JSON serialisation not supported for Animal" error when I use this code.
Can someone point out where I am going wrong?
The contents of Animal.h file is as below
#import <UIKit/UIKit.h>
#interface Animal : NSObject<NSCoding> {
NSString *name;
NSString *description;
NSString *imageURL;
}
#property (nonatomic, retain) NSString *name;
#property (nonatomic, retain) NSString *description;
#property (nonatomic, retain) NSString *imageURL;
-(id)initWithName:(NSString *)n description:(NSString *)d url:(NSString *)u;
#end
The contents of Animal.m file is as below
#import "Animal.h"
#implementation Animal
#synthesize name, description, imageURL;
-(id)initWithName:(NSString *)n description:(NSString *)d url:(NSString *)u {
self.name = n;
self.description = d;
self.imageURL = u;
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if(self = [super initWithCoder:aDecoder])
{
name = [[aDecoder decodeObjectForKey:#"name"] retain];
description = [[aDecoder decodeObjectForKey:#"description"] retain];
imageURL = [[aDecoder decodeObjectForKey:#"imageURL"] retain];
}
return [self initWithName:name description:description url:imageURL];
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[super encodeWithCoder:encoder];
[encoder encodeObject:name forKey:#"name"];
[encoder encodeObject:description forKey:#"description"];
[encoder encodeObject:imageURL forKey:#"imageURL"];
}
#end
Make your custom class conform to NSCoding protocol and then serialize it.
For more info, visit the Apple documentation
Also, this link will also help you.
As suggested in this link, archive your custom class to NSData and serialize that as provided in the Apple documentation.
Edit
Make your Animal.m as follows:
#import "Animal.h"
#implementation Animal
#synthesize name, description, imageURL;
-(id)initWithName:(NSString *)n description:(NSString *)d url:(NSString *)u {
self = [super init];
if( self )
{
self.name = n;
self.description = d;
self.imageURL = u;
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if( self )
{
self.name = [aDecoder decodeObjectForKey:#"name"];
self.description = [aDecoder decodeObjectForKey:#"description"];
self.imageURL = [aDecoder decodeObjectForKey:#"imageURL"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:name forKey:#"name"];
[encoder encodeObject:description forKey:#"description"];
[encoder encodeObject:imageURL forKey:#"imageURL"];
}
#end
To actually answer your question on how to do it using SBJson: Implement the proxyForJson method. Unless you are serializing an NSArray or NSDictionary you must override this method for serialization to work.
You can see that this is the case by looking at the SBJson source code (in SBJsonWriter.m):
- (NSData*)dataWithObject:(id)object {
...
if ([object isKindOfClass:[NSDictionary class]])
ok = [streamWriter writeObject:object];
else if ([object isKindOfClass:[NSArray class]])
ok = [streamWriter writeArray:object];
else if ([object respondsToSelector:#selector(proxyForJson)])
return [self dataWithObject:[object proxyForJson]];
else {
self.error = #"Not valid type for JSON";
return nil;
}
...
}
}
Implement proxyForJson in Animal.m like this (not tested):
- (NSDictionary*) proxyForJson
{
return [NSDictionary dictionaryWithObjectsAndKeys:self.name, #"name",
self.description, #"description",
self.imageURL, #"imageURL",
nil];
}
This open source project JSONCoding makes the whole process pretty simple, using the new sdk class in conjunction with the NSCoding protocol.
Check the newly introduced NSJSONSerialization class:
NSJSONSerialization class
I think you can check out this if it helps you: Make a Custom Class Serializable
Please check this Property List Programming Guide - Serializing a Property List
and the similar post here:
Make a Custom Class Serializable in Objective-c/iPhone?
Object serialization in XML format using Obj-C / iPhone SDK
See also https://github.com/jsonmodel/jsonmodel
Magical Data Modeling Framework for JSON - allows rapid creation of
smart data models. You can use it in your iOS, macOS, watchOS and tvOS
apps.
This is also the chosen library for the Objective-c Swagger client.

NSMutableArray for Object which has NSString property causes memory leak

I hope to add objects to a NSMutableArray "myArray", The NSMutableArray is the array for FileObj which has a NSString property "fileName"
#import <UIKit/UIKit.h>
#interface FileObj : NSObject {
NSString *fileName;
}
-(void) setfileName:(NSString *)s ;
-(NSString *) getfileName ;
#end
//
// File.m//
#import "File.h"
#implementation FileObj
-(void) setfileName:(NSString *)s ;
{
fileName=s;
}
-(NSString *) getfileName ;
{
return fileName;
}
#end
I initialize the myArray here:
NSMutableArray *temarray;
temarray=[[NSMutableArray alloc] init];
self.myArray=temarray;
[temarray release];
the codes to add object to myArray
FileObj *newobj=[[FileObj alloc]init ];
NSString *fieldValue2 = [[NSString alloc] initWithUTF8String:#"aaaa"];
[newobj setfileName:fieldValue2];
[myArray addObject:newobj];
[fieldValue2 release]; //**if I enabled the line, it will cause crash**
//**if I disable the line, it will cause memory leak**
[newobj release];
Welcome any comment
Thanks
interdev
First you should look into ObjC naming conventions. There is no -get methods in ObjC. It's also a good idea to prefix your classes with your own 2 letters (like NS).
Your setter value assignment is invalid and the NSString initialization unnecessary.
I would strongly recommend introductory material to you!
#interface MYFileObject : NSObject {
NSString *_fileName;
}
- (void)setFileName:(NSString *)theString;
- (NSString *)fileName;
#end
and the implementation
#implementation MYFileObject
- (void)setFileName:(NSString *)theString {
[_fileName release];
_fileName = [theString copy];
}
- (NSString *)fileName {
return [[_fileName copy] autorelease];
}
- (void)dealloc {
[_fileName release];
[super dealloc];
}
#end
You would add an object like this...
NSMutableArray *myAry = [[NSMutableArray alloc] init];
MYFileObject *obj = [[MYFileObject alloc] init];
[obj setFileName:#"thefilename.txt"];
[myAry addObject:obj];
[obj release];
I would recommend using properties instead of defining your own getters/setters.
You could also use the NSMutableArrays' designated initializers for fast array creation.
Look here for how to use properties: http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/ObjectiveC/Articles/ocProperties.html
Why bother with getters and setters? Use declared property already!
#interface FileObj : NSObject {
NSString *fileName;
}
#property(retain,nonatomic) NSString* fileName; // <---
#end
...
#implementation FileObj
#synthesize fileName; /// <---
-(void)dealloc {
[fileName release]; // Remember to release the object on dealloc.
[super dealloc];
}
#end
...
FileObj *newobj=[[FileObj alloc] init];
NSString *fieldValue2 = [[NSString alloc] initWithUTF8String:#"aaaa"];
newobj.fileName = fieldValue2; /// <----
[myArray addObject:newobj];
[fieldValue2 release];
[newobj release];
The crash occurs because the NSString instance is not retained anymore.
A common pattern is to retain NSString properties, either declaratively with #property or by hand.
You should modify the setter like this:
-(void) setfileName:(NSString *)s ;
{
[s retain]; // <- Retain new value
[filename release]; // <- Release old value
fileName=s;
}

Converting a NSObject into NSData

I am having an issue in converting a NSObject into NSData. I have a class which inherits NSObject.
When i tried to convert the object of that particular class into NSData as follows :
NSData *dataOnObject = [NSKeyedArchiver archivedDataWithRootObject:classObject];
but it gives out exception stating that -[classObject encodeWithCoder:]: unrecognized selector sent to instance ..
I have also added the object to a newly created array as
NSMutableArray *wrapperedData = [NSMutableArray arrayWithObject: classObject];
NSData *dataOnObject = [NSKeyedArchiver archivedDataWithRootObject:value];
But still , its giving out exception.
So I need to extract the bytes from the object classObject.
Any help would be greatly appreciated ...
awaiting for your reply ...
You must implement for your own object such as:
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.name forKey:#"name"];
[aCoder encodeInt:self.age forKey:#"age"];
[aCoder encodeObject:self.email forKey:#"email"];
[aCoder encodeObject:self.password forKey:#"password"];
}
BOOL success = [NSKeyedArchiver archiveRootObject:person toFile:archiveFilePath];
and:
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
self.name = [aDecoder decodeObjectForKey:#"name"];
self.age = [aDecoder decodeIntForKey:#"age"];
self.email = [aDecoder decodeObjectForKey:#"email"];
self.password = [aDecoder decodeObjectForKey:#"password"];
}
return self;
}
Person *unarchivePerson = [NSKeyedUnarchiver unarchiveObjectWithFile:archiveFilePath];
You need to implement encodeWithCoder: on your custom class, serializing all of its attributes using the NSCoder passed into it. If its attributes include any more custom classes, they'll need encodeWithCoder: implementing too.
Instead of
NSData *dataOnObject = [NSKeyedArchiver archivedDataWithRootObject:classObject];
it should be
NSData *dataOnObject = [[NSUserDefaults standardUserDefaults] objectForKey:#"someKey"];
But that's just for reading data in that's already been saved. If you want to save an object as NSData then you have this:
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:classObject] forKey:#"someKey"];
But that's not all. Your classObject has to implement the NSCoding protocol and have the two methods encodeWithCoder: and initWithCoder: since it's not an NS object in order for it to work.
you can only archive objects that support the NSCoding protocol
You can convert any object to NSData with the NSCoding protocol.
You can find sample code to do this here:
http://samsoff.es/posts/archiving-objective-c-objects-with-nscoding
This is a example of custom object converted to NSData (so it can be then saved into user defaults)
Create the following files:
Catalog.h
#interface Catalog : NSObject
#property (nonatomic, assign) int pk;
#property (nonatomic, copy) NSString *catalogName;
#property (nonatomic, copy) NSString *catalogDescription;
#property (nonatomic, assign) int catalogEdition;
#property (nonatomic, assign) int catalogTotalPages;
- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;
#end
Catalog.m
#import <Foundation/Foundation.h>
#import "Catalog.h"
#implementation Catalog
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.catalogName forKey:#"catalogName"];
[aCoder encodeObject:self.catalogDescription forKey:#"catalogDescription"];
[aCoder encodeInt:self.catalogEdition forKey:#"catalogEdition"];
[aCoder encodeInt:self.catalogTotalPages forKey:#"catalogTotalPages"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super init]) {
self.catalogName = [aDecoder decodeObjectForKey:#"catalogName"];
self.catalogDescription = [aDecoder decodeObjectForKey:#"catalogDescription"];
self.catalogEdition = [aDecoder decodeIntForKey:#"catalogEdition"];
self.catalogTotalPages = [aDecoder decodeIntForKey:#"catalogTotalPages"];
}
return self;
}
#end
Finally in your controller include header files
#import "Catalog.h"
And add this code to use your object (in this case im saving into user defaults)
Catalog *catalog = [[Catalog alloc] init];
catalog.catalogName = #"catalogName";
catalog.catalogDescription = #"catalogName";
catalog.catalogEdition = 1;
NOTE: in this line of code is where the actual data passing is taking place
//archiving object to nsdata
NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:catalog];
[[NSUserDefaults standardUserDefaults] setObject:encodedObject forKey:#"keyName"];
[[NSUserDefaults standardUserDefaults] synchronize];
In case you want to get your object back from NSData
NSData *nsData = [[NSUserDefaults standardUserDefaults] objectForKey:#"keyName"];
//unarchiving object to nsdata
Catalog *selectedCatalog = [NSKeyedUnarchiver unarchiveObjectWithData: nsData];
Hope this helps!