I have a class named Person and created a Person instance "person".
Person *person = [Person personWithName:#"Kyle", andAge:15];
Then I tried to encode it using method archivedDataWithRootObject:requiringSecureCoding:error:.
NSData *personData = [NSKeyedArchiver archivedDataWithRootObject:person
requiringSecureCoding:YES error:nil];
However, the personData always returns nil. Did I miss something?
Person.h
#interface Person : NSObject<NSSecureCoding>
#property (strong, nonatomic) NSString *name;
#property (assign, nonatomic) NSInteger age;
+ (instancetype)personWithName:(NSString *)name andAge:(NSInteger)age;
#end
Person.m
#implementation Person
+ (instancetype)personWithName:(NSString *)name andAge:(NSInteger)age{
Person *p = [Person new];
p.name = name;
p.age = age;
return p;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder]; // error: No visible #interface for 'NSObject' declares the selector 'initWithCoder'
return self;
}
#end
Update (after implementing +supportsSecureCoding in .m):
Class 'Person' has a superclass that supports secure coding, but
'Person' overrides -initWithCoder: and does not override
+supportsSecureCoding. The class must implement +supportsSecureCoding and return YES to verify that its implementation of -initWithCoder: is
secure coding compliant.
What's wrong: Person isn't NSSecureCoding compliant. That's the first thing that pops in mind if you ever played with Archiving Custom Class into Data with NSKeyedArchiver (if it's an old developer, he/she would have said NSCoding, but that's "almost the same", same logic).
What's the deal with that? It's just how to convert Person into NSData and reverse. What's the logic? Do you want to save its properties? How? Etc.
BUT, your biggest mistake as a developer was to totally ignore the error parameter!
NSData *personData = [NSKeyedArchiver archivedDataWithRootObject:person
requiringSecureCoding:YES error:nil];
==>
NSError *archiveError
NSData *personData = [NSKeyedArchiver archivedDataWithRootObject:person
requiringSecureCoding:YES
error:& archiveError];
if (archiveError) {
NSLog(#"Ooops, got error while archiving: %#", archiveError);
}
Then the error would have stated that it was indeed missing the NSSecureCoding compliancy
See the documentation of Archives and Serializations Programming Guide: Encoding and Decoding Objects, you'll see how to implement initWithCoder: (from NSData to Person) and encodeWithCoder: (from Person to NSData).
Applied to your class (and add it to the compliance with: #interface Person : NSObject< NSSecureCoding > for instance):
- (void) encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:_name forKey:#"name"];
[encoder encodeInteger:_age forKey:#"age"];
}
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
_name = [coder decodeObjectForKey:#"name"];
_age = [coder decodeIntegerForKey:#"age"];
}
return self;
}
+ (BOOL)supportsSecureCoding {
return YES;
}
Note that the strings key ("name" & "age" needs to be the same for encode/decode, you can use const, etc.)
Related
I'm trying to store some data in an NSMutableArray. This is my struct:
typedef struct{
int time;
char name[15];
}person;
This is the code to add a person:
person h1;
h1.time = 108000;
strcpy(h1.name, "Anonymous");
[highscore insertObject:[NSValue value:&h1 withObjCType:#encode(person)] atIndex:0];
So, I try to extract in this way:
NSValue * value = [highscore objectAtIndex:0];
person p;
[value getValue:&p];
NSLog(#"%d", p.time);
The problem is that the final log doesn't show me 108000!
What is wrong?
Your code looks correct (and works for me), so I deduce that you aren't initializing highscore. So when you send the insertObject:atIndex: message to it, nothing happens. When you then send the objectAtIndex: method to it, you get nil back. When you send getValue: to the nil NSValue *value, it does nothing, so your person p is left filled with random stack garbage, which is why your NSLog doesn't print 108000.
As stated in my initial comment there rarely is a reason to do this kind of stuff with pure c structs. Instead go with real class objects:
If you're unfamiliar with the syntax below you may want to look at these quick tutorials on ObjC 2.0 as well as read Apple's documentation:
A Quick Objective-C 2.0 Tutorial
A Quick Objective-C 2.0 Tutorial: Part II
Person Class:
// "Person.h":
#interface Person : NSObject {}
#property (readwrite, strong, nonatomic) NSString *name;
#property (readwrite, assign, nonatomic) NSUInteger time;
#end
// "Person.m":
#implementation Person
#synthesize name = _name; // creates -(NSString *)name and -(void)setName:(NSString *)name
#synthesize time = _time; // creates -(NSUInteger)time and -(void)setTime:(NSUInteger)time
#end
Class use:
#import "Person.h"
//Store in highscore:
Person *person = [[Person alloc] init];
person.time = 108000; // equivalent to: [person setTime:108000];
person.name = #"Anonymous"; // equivalent to: [person setName:#"Anonymous"];
[highscore insertObject:person atIndex:0];
//Retreive from highscore:
Person *person = [highscore objectAtIndex:0]; // or in modern ObjC: highscore[0];
NSLog(#"%#: %lu", person.name, person.time);
// Result: "Anonymous: 108000"
To simplify debugging you may also want Person to implement the description method:
- (NSString *)description {
return [NSString stringWithFormat:#"<%# %p name:\"%#\" time:%lu>", [self class], self, self.name, self.time];
}
which will allow you to just do this for logging:
NSLog(#"%#", person);
// Result: "<Person 0x123456789 name:"Anonymous" time:108000>
Reimplement Person as an Objective-C object and reap the benefits:
Person.h:
#interface Person : NSObject
{
int _time;
NSString *_name;
}
#property (assign, nonatomic) int time;
#property (retain, nonatomic) NSString *name;
#end
Person.m:
#import "Person.h"
#interface Person
#synthesize time = _time;
#synthesize name = _name;
- (id)init
{
self = [super init];
if (self != nil)
{
// Add init here
}
return self;
}
- (void)dealloc
{
self.name = nil;
[super dealloc];
}
#end
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.
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 am currently using the pragmatic screencast on Objective-C to help me program in objective-c. I have a background in Java and C++, but I am having a very difficult time getting used to everything in Objective(Mostly because I am not comfortable with the syntax).
Below is the error I am receiving with all the code.
I am also getting a warning in movie.m class as well: Wirtable atomic property 'title'
cannot be pair a synthesized setter/getter with a user defined setter/getter
thanks for your help.
I am receive this error
Current language: auto; currently objective-c
warning: Couldn't find class validation function, calling methods on uninitialized objects may deadlock your program.
Program received signal: “EXC_BAD_ACCESS”.
I ran it through the debugger and the address of movie in the code below is in red
main.m
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Movie *movie = [[Movie alloc] initWithTitle:#"iron man"
andRating:5
andYear:2008];
[movie play];
NSLog(#"our movie is %#", movie);
[pool drain];
return 0;}
Movie.h
interface Movie : NSObject {
NSString *title;
int rating;
int year;
}
- (id)initWithTitle:(NSString *)newTitle
andRating:(int)newRating
andYear:(int) year;
#property(assign) NSString *title;
#property(assign) int rating;
#property(assign) int year;
-(void) play;
#end
Movie.m
#import "Movie.h"
#implementation Movie
#synthesize title;
#synthesize rating;
#synthesize year;
-(id)initWithTitle:(NSString *)newTitle
andRating:(int)newRating
andYear:(int)newYear;
{
self = [super init];
if(nil != self){
self.title = newTitle;
self.rating = newRating;
self.year = newYear;
}
return self;
}
-(NSString *) description{
NSString *oldDescription = [super description];
return [NSString stringWithFormat: #"%# title =%#, rating =%d year=%#",
oldDescription, self.title, self.rating, self.year];
}
- (void)setTitle:(NSString *)newTitle {
title = [newTitle capitalizedString];
}
-(void) play {
NSLog(#"Playing %#", self);
}
You use year=%# when it should be year=%d.
Some more random thoughts:
You should retain or better even copy the title instead of assigning it.
The init method should be named
-(id)initWithTitle:(NSString *)aTitle
rating:(int)aRating
year:(int)aYear;
Don't forget a dealloc method then.
Your title property is an object type and so should in generally be either retain or copy -- in the case of NSString properties, it is traditional to use copy to avoid issues when you're passed an NSMutableString instead.
#property (copy) NSString* title;
Since you explicitly define the setter, you then need to implement this policy yourself, something like this:
- (void)setTitle:(NSString *)newTitle
{
[title release];
title = [[newTitle capitalizedString] copy];
}
You'll also need to include a dealloc method to clean up:
- (void) dealloc
{
[title release];
[super dealloc];
}
Initially I thought this was going to work, but now I understand it won't because artistCollection is an NSMutableArray of "Artist" objects.
#interface Artist : NSObject {
NSString *firName;
NSString *surName;
}
My question is what is the best way of recording to disk my NSMutableArray of "Artist" objects so that I can load them the next time I run my application?
artistCollection = [[NSMutableArray alloc] init];
newArtist = [[Artist alloc] init];
[newArtist setFirName:objFirName];
[newArtist setSurName:objSurName];
[artistCollection addObject:newArtist];
NSLog(#"(*) - Save All");
[artistCollection writeToFile:#"/Users/Fgx/Desktop/stuff.txt" atomically:YES];
EDIT
Many thanks, just one final thing I am curious about. If "Artist" contained an extra instance variable of NSMutableArray (softwareOwned) of further objects (Applications) how would I expand the encoding to cover this? Would I add NSCoding to the "Applications" object, then encode that before encoding "Artist" or is there a way to specify this in "Artist"?
#interface Artist : NSObject {
NSString *firName;
NSString *surName;
NSMutableArray *softwareOwned;
}
#interface Application : NSObject {
NSString *appName;
NSString *appVersion;
}
many thanks
gary
writeToFile:atomically: in Cocoa's collection classes only works for property lists, i.e. only for collections that contain standard objects like NSString, NSNumber, other collections, etc.
To elaborate on jdelStrother's answer, you can archive collections using NSKeyedArchiver if all objects the collection contains can archive themselves. To implement this for your custom class, make it conform to the NSCoding protocol:
#interface Artist : NSObject <NSCoding> {
NSString *firName;
NSString *surName;
}
#end
#implementation Artist
static NSString *FirstNameArchiveKey = #"firstName";
static NSString *LastNameArchiveKey = #"lastName";
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self != nil) {
firName = [[decoder decodeObjectForKey:FirstNameArchiveKey] retain];
surName = [[decoder decodeObjectForKey:LastNameArchiveKey] retain];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:firName forKey:FirstNameArchiveKey];
[encoder encodeObject:surName forKey:LastNameArchiveKey];
}
#end
With this, you can encode the collection:
NSData* artistData = [NSKeyedArchiver archivedDataWithRootObject:artistCollection];
[artistData writeToFile: #"/Users/Fgx/Desktop/stuff" atomically:YES];
Take a look at NSKeyedArchiver. Briefly :
NSData* artistData = [NSKeyedArchiver archivedDataWithRootObject:artistCollection];
[artistData writeToFile: #"/Users/Fgx/Desktop/stuff" atomically:YES];
You'll need to implement encodeWithCoder: on your Artist class - see Apple's docs
Unarchiving (see NSKeyedUnarchiver) is left as an exercise for the reader :)