NSUserDefaults for self created classes - objective-c

I'm having a problem in a usually very simple quest, I want to create a custom class containing an integer, a NSString and a NSDate. However, in my example I only use a NSString to show my problem when storing objects.
This is my class header:
#import
#interface MyClass : NSObject <NSCoding> {
NSString * myString;
}
#property (retain) NSString * myString;
#end
and my body:
#import "MyClass.h"
#implementation MyClass
#synthesize myString;
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
myString = [coder decodeObjectForKey:#"myString"];
}
return self;
}
- (id)init {
self = [super init];
if (self) {
myString = #"Hi";
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject: myString forKey:#"myString"];
}
- (void)dealloc {
myString = nil;
[super dealloc];
}
#end
My ViewController contains two buttons called Create and Read, as well as a UILabel called label.
#import <UIKit/UIKit.h>
#interface UserDefaultsTestViewController : UIViewController {
IBOutlet UILabel *label;
}
- (IBAction)pushCreate;
- (IBAction)pushLoad;
#end
I will only show the functions I've edited here:
#import "UserDefaultsTestViewController.h"
#import "MyClass.h"
- (IBAction)pushCreate {
MyClass * myClass = [[MyClass alloc] init];
NSData * data = [NSKeyedArchiver archivedDataWithRootObject:myClass];
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:data forKey:#"Test"];
[myClass release];
}
- (IBAction)pushLoad {
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
NSData * atchive = [defaults objectForKey:#"Test"];
MyClass * myClass = [NSKeyedUnarchiver unarchiveObjectWithData:atchive];
label.text = myClass.myString;
}
However, if I run my code, I get either a EXC_BAD_ACCESS error or it displays it correctly the first time and crashes when doing it a second time, again with the same error.
What am I doing wrong?

You want to do:
self.myString = #"Hi";
Or:
myString = #"Hi";
[myString retain];
Likewise, your dealloc should have:
self.myString = nil;
or
[myString release];

Related

Loading NSTableView from unarchived data

I'm trying to implement saving of user preferences in my OS X app. I have an NSTableView which stores its data to my .plist file, but I have troubles loading same data back to my NSTableView.
My model object:
#import <Foundation/Foundation.h>
#interface UserKeys : NSObject <NSCoding>
#property (nonatomic, copy) NSString *userKeyName;
#property (nonatomic, copy) NSString *userApi;
#property (nonatomic, copy) NSString *userCode;
#end
and its implementation:
#import "UserKeys.h"
#implementation UserKeys
- (id)init
{
self = [super init];
if (self) {
//
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.userKeyName = [aDecoder decodeObjectForKey:#"userKeyName"];
self.userApi = [aDecoder decodeObjectForKey:#"userApi"];
self.userCode = [aDecoder decodeObjectForKey:#"userCode"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)enCoder {
[enCoder encodeObject:self.userKeyName forKey:#"userKeyName"];
[enCoder encodeObject:self.userApi forKey:#"userApi"];
[enCoder encodeObject:self.userCode forKey:#"userCode"];
}
#end
My preferences view controller header:
#import <Cocoa/Cocoa.h>
#import "MASPreferencesViewController.h"
#import "UserKeys.h"
#interface PreferencesApiKeysViewController : NSViewController <MASPreferencesViewController>
#property (strong) NSMutableArray *allKeys;
#end
and its implementation:
#import "PreferencesApiKeysViewController.h"
#import "UserKeys.h"
#interface PreferencesApiKeysViewController ()
#property (weak) IBOutlet NSTableView *keysTableView;
#end
#implementation PreferencesApiKeysViewController
- (id)initWithNibName:(NSString *)nibName bundle:(NSBundle *)bundle {
if ((self = [super initWithNibName:nibName bundle:bundle])) {
self.allKeys = [NSMutableArray array];
}
return self;
}
// delegating....
//
- (void)viewWillAppear {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:#"UserKeys"];
if (data != nil) {
NSArray *oldArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
if (oldArray != nil) {
self.allKeys = [[NSMutableArray alloc] initWithArray:oldArray];
} else {
self.allKeys = [NSMutableArray array];
}
}
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
// some code...
return cellView;
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return [self.allKeys count];
}
The error I'm getting is:
-[UserKeys count]: unrecognized selector sent to instance 0x100534df0
When I put a breakpoint in - (void)viewWillAppear, I can see that data has around 250 bytes, but this oldArray seems to be empty. I understand that I'm doing some major mistake here with self.allKeys so that my table is not aware how many rows it should create, but after lots of attempts to get proper data, I'm really out of ideas why is this happening.
So, how to properly unarchive my data and present it in NSTableView?
(I'm using Xcode 4.6)
It is because the object you appear to be dearchiving is a pointer to an object of class UserKeys, rather than an object that is or descends from NSArray. When -initWithAray: hits your oldArray pointer to UserKeys, it doesn't know, or doesn't care what class it is, it simply sends it a -count message so as to ascertain the size it must grow to.

Map coordinates not decoding

In my app, I am trying to save the pins that are on the map so that they are there when the user opens the app after it is terminated. I have conformed my mkAnnotation class to NSCoding, and implemented the two required methods. The annotations are all stored in a NSMutableArray in a singleton class, so I am really just trying to save the array in the singleton class. Everything is being encoded fine, but I do not think they are being decoded. Here is some code:
This is my MKAnnotation class:
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>
#interface MapPoint : NSObject <MKAnnotation, NSCoding>
{
}
- (id)initWithAddress:(NSString*)address
coordinate:(CLLocationCoordinate2D)coordinate
title:(NSString *)t;
#property (nonatomic, readwrite) CLLocationCoordinate2D coordinate;
//This is an optional property from MKAnnotataion
#property (nonatomic, copy) NSString *title;
#property (nonatomic, readonly, copy) NSString *subtitle;
#property (nonatomic) BOOL animatesDrop;
#property (nonatomic) BOOL canShowCallout;
#property (copy) NSString *address;
#property (nonatomic, copy) NSString *imageKey;
#property (nonatomic, copy) UIImage *image;
#end
#implementation MapPoint
#synthesize title, subtitle, animatesDrop, canShowCallout, imageKey, image;
#synthesize address = _address, coordinate = _coordinate;
-(id)initWithAddress:(NSString *)address
coordinate:(CLLocationCoordinate2D)coordinate
title:(NSString *)t {
self = [super init];
if (self) {
_address = [address copy];
_coordinate = coordinate;
[self setTitle:t];
NSDate *theDate = [NSDate date];
subtitle = [NSDateFormatter localizedStringFromDate:theDate
dateStyle:NSDateFormatterShortStyle
timeStyle:NSDateFormatterShortStyle];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_address forKey:#"address"];
NSLog(#"ENCODING coordLatitude %f coordLongitude %f ", _coordinate.latitude, _coordinate.longitude);
[aCoder encodeDouble:_coordinate.longitude forKey:#"coordinate.longitude"];
[aCoder encodeDouble:_coordinate.latitude forKey:#"coordinate.latitude"];
[aCoder encodeObject:title forKey:#"title"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
[self setAddress:[aDecoder decodeObjectForKey:#"address"]];
NSLog(#"DECODING coordLatitude %f coordLongitude %f ", _coordinate.latitude, _coordinate.longitude);
_coordinate.longitude = [aDecoder decodeDoubleForKey:#"coordinate.longitude"];
_coordinate.latitude = [aDecoder decodeDoubleForKey:#"coordinate.latitude"];
[self setTitle:[aDecoder decodeObjectForKey:#"title"]];
}
return self;
}
#end
Here is my singleton class:
#import <Foundation/Foundation.h>
#class MapPoint;
#interface Data : NSObject
{
NSMutableArray *_annotations;
}
#property (retain, nonatomic) NSMutableArray *annotations;
+ (Data *)singleton;
- (NSString *)pinArchivePath;
- (BOOL)saveChanges;
#end
#implementation Data
#synthesize annotations = _annotations;
+ (Data *)singleton {
static dispatch_once_t pred;
static Data *shared = nil;
dispatch_once(&pred, ^{
shared = [[Data alloc] init];
shared.annotations = [[NSMutableArray alloc]init];
});
return shared;
}
- (id)init {
self = [super init];
if (self) {
NSString *path = [self pinArchivePath];
_annotations = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if (!_annotations) {
_annotations = [[NSMutableArray alloc]init];
}
}
return self;
}
- (NSString *)pinArchivePath {
NSArray *cachesDirectories = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *cachesDirectory = [cachesDirectories objectAtIndex:0];
return [cachesDirectory stringByAppendingPathComponent:#"pins.archive"];
}
- (BOOL)saveChanges {
NSString *path = [self pinArchivePath];
return [NSKeyedArchiver archiveRootObject:[Data singleton].annotations
toFile:path];
}
#end
In my viewDidLoad method on the map view controller, I try and place the annotations in the singleton array on the map with this:
for (MapPoint *mp in [Data singleton].annotations) {
[_worldView addAnnotation:mp];
}
The main problem is in the singleton method in these lines:
dispatch_once(&pred, ^{
shared = [[Data alloc] init];
shared.annotations = [[NSMutableArray alloc]init]; //<-- problem line
});
The shared = [[Data alloc] init]; line decodes and initializes the annotations array.
Then the shared.annotations = [[NSMutableArray alloc]init]; line re-creates and re-initializes the annotations array thus discarding the just-decoded annotations so the singleton always returns an empty array.
Remove the shared.annotations = [[NSMutableArray alloc]init]; line.
As already mentioned in the comment, the other minor issue, which causes simply confusion, is the placement of the NSLog where the coordinate is being decoded. The NSLog should be after the decode is done.

Saving the title of a button so it can be accessed in another view (Objective-C)

I'm trying to save the name of a button using a singleton so that the name can be accessed in another view to play a video with the same name. However, I'm getting the error: SIGABRT. I don't really see what's wrong with my code. Any ideas?
#import "List.h"
#import "MyManager.h"
#import "Video.h"
#implementation ExerciseList
-(IBAction) goToVideo:(UIButton *) sender{
MyManager *sharedManager = [MyManager sharedManager];
sharedManager.vidName = [[sender titleLabel] text];
Video *videoGo = [[Video alloc] initWithNibName: #"Video" bundle: nil];
[self.navigationController pushViewController: videoGo animated: YES];
[videoGo release];
}
Here is my .h and .m for MyManager:
#import <foundation/Foundation.h>
#interface MyManager : NSObject {
NSMutableArray *workouts;
NSString *vidName;
}
#property (nonatomic, retain) NSMutableArray *workouts;
#property (nonatomic, retain) NSString *vidName;
+ (id)sharedManager;
#end
#import "MyManager.h"
static MyManager *sharedMyManager = nil;
#implementation MyManager
#synthesize workouts;
#synthesize vidName;
#pragma mark Singleton Methods
+ (id)sharedManager {
#synchronized(self) {
if (sharedMyManager == nil)
sharedMyManager = [[self alloc] init];
}
return sharedMyManager;
}
- (id)init {
if ((self = [super init])) {
workouts = [[NSMutableArray alloc] init];
vidName = [[NSString alloc] init];
}
return self;
}
-(void) dealloc{
self.workouts = nil;
self.vidName = nil;
[super dealloc];
}
#end
You should access the title of the button
sharedManger.vidName = [sender currentTitle];
However you are not using ARC so also check where your vidName property is retain or copy.
if it is not retain or copy then you can use this code also
if(sharedManger.vidname != nil){
[sharedManger.vidName release];
sharedManger.vidName = nil;
}
sharedManger.vidName = [[sender currentTitle] retain];

NSKeyedArchiver and description method : app crashes

i tried to test NSKeyedArchiver, but my code seems to be wrong. I then tried only with a NSLog, but the NSLog returns (null) if i alloc-init my var "model" in the init method (in the controller), and it makes the app crash if i put it in viewDidLoad.
Can someone have a look and tell me if something is wrong?
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#class Model;
#interface AAViewController : UIViewController {
Model *model;
}
#property (nonatomic, retain) Model *model;
//+(BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path;
#end
______
#import "AAViewController.h"
#import "Model.h"
#implementation AAViewController
#synthesize model;
- (id) init {
self = [super init];
if (self) {
model = [[Model alloc] initWithName:#"thomas" age:13];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
//self.model = [[Model alloc] initWithName:#"thomas" age:13];
NSLog (#"%#", self.model);
/*
if (![NSKeyedArchiver archiveRootObject:model toFile:#"test.archive"]){
NSLog (#"erreur");
[model release];
}
*/
NSLog(#"success !");
}
- (void)viewDidUnload {
//model = nil;
}
- (void)dealloc {
[model release];
[super dealloc];
}
#end
______
#import <Foundation/Foundation.h>
//#interface Model : NSObject <NSCoding>
#interface Model : NSObject {
NSString *name;
int age;
}
#property (nonatomic, copy) NSString *name;
#property (nonatomic) int age;
- (id) initWithName:(NSString *)theName age:(int)theAge;
/*
- (id) initWithCoder:(NSCoder *)encoder;
- (void) encodeWithCoder:(NSCoder *)decoder;
*/
#end
______
#import "Model.h"
#implementation Model
#synthesize name, age;
- (id) initWithName:(NSString *)theName age:(int)theAge {
self = [super init];
if (self) {
name = [theName copy];
age = theAge;
}
return self;
}
- (void) dealloc {
[name release];
[super dealloc];
}
/*
- (id) initWithCoder:(NSCoder *)decoder{
self = [super init];
if (self) {
name = [[decoder decodeObjectForKey:#"nom"] retain];
age = [decoder decodeIntForKey:#"age"];
}
return self;
}
- (void) encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:name forKey:#"nom"];
[encoder encodeInt:age forKey:#"age"];
}
*/
- (NSString *) description {
return [NSString stringWithFormat:#"the name is : %#, %# years old", name, age];
}
#end
Your app is crashing because this line is wrong:
return [NSString stringWithFormat:#"the name is : %#, %# years old", name, age];
age is an int, not an object pointer. Needs to be:
return [NSString stringWithFormat:#"the name is : %#, %d years old", name, age];
I don't think there's anything obviously wrong with your encode/decode code.

Why "copy" is not being invoked?

I have the following object:
#interface SomeObject : NSObject
{
NSString *title;
}
#property (copy) NSString *title;
#end
And I have another object:
#interface AnotherObject : NSObject{
NSString *title;
}
#property (copy) NSString *title;
- (AnotherObject*)init;
- (void)dealloc;
- (void) initWithSomeObject: (SomeObject*) pSomeObject;
+ (AnotherObject*) AnotherObjectWithSomeObject (SomeObject*) pSomeObject;
#end
#implementation AnotherObject
#synthesize title
- (AnotherObject*)init {
if (self = [super init]) {
title = nil;
}
return self;
}
- (void)dealloc {
if (title) [title release];
[super dealloc];
}
-(void) initWithSomeObject: (SomeObject*) pSomeObject
{
title = [pSomeObject title]; //Here copy is not being invoked, have to use [ [pSomeObject title] copy]
}
+ (AnotherObject*) AnotherObjectWithSomeObject (SomeObject*) pSomeObject;
{
[pSomeObject retain];
AnotherObject *tempAnotherObject = [ [AnotherObject alloc] init];
[tempAnotherObject initWithSomeObject: pSomeObject];
[pSomeObject release];
return tempAnotherObject;
}
#end
I do not understand, why copy is not being invoked when I am assigning "title = [pSomeObject title]". I always thought if i set "copy" in property it is always going to be invoked. I have a error in my code or I don't understand something?
Thank you in advance.
For a setter to get called you need to use the dot notation.
self.title = [pSomeObject title];
or...to use the dot notation for pSomeObject too
self.title = pSomeObject.title;