I'm using a singleton class (contactStorage) and a data model (contactModel) to store a list of contacts. I have created a contact object in my viewdidload of my root view controller and attempted to add it to the NSMutableArray but it will not "stick". I have logged the incoming object inside the addContact procedure and it produces accurate output, however, the addObject:c does not add it to the array. Any insight on this?
#import "contactListViewController.h"
#import "contactDetailScreenViewController.h"
#import "ContactModel.h"
#import "contactStorage.h"
#interface contactListViewController ()
#end
#implementation contactListViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
ContactModel* c = [[ContactModel alloc] initWithfName:#"Mike" andlName:#"Deasy" andEmail:#"mid31#pitt.edu" andPhone:#"4127154194"];
[c logContact];
[[contactStorage shared]addContact:c];
[[contactStorage shared]saveToFile];
[c release];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
The code for my singleton:
//
// contactStorage.m
// contactList
//
// Created by dev on 10/23/13.
// Copyright (c) 2013 Deasy, Michael William. All rights reserved.
//
#import "contactStorage.h"
#implementation contactStorage
{
}
#synthesize cList = _cList;
static contactStorage* _myOnlyInstance = nil;
#pragma mark Storage Methods
-(void)addContact: (ContactModel*) c
{
[c logContact];
[self.cList addObject:c];
NSLog(#"%#", _cList);
}
-(ContactModel*)getContact: (NSIndexPath*) index
{
return [self.cList objectAtIndex:index.row];
}
-(NSMutableArray*)deleteContact: (NSIndexPath*) index
{
[self.cList removeObjectAtIndex:index.row];
return self.cList;
}
-(NSMutableArray*)getAllContacts
{
return self.cList;
}
-(void)saveToFile
{
NSString* path = [[self documentsPath] stringByAppendingPathComponent:#"data.txt"];
NSLog(#"%#",path);
[_cList writeToFile:path atomically:YES];
NSLog(#"%#", self.cList);
}
#pragma mark Singleton Create
-(id)init
{
self = [super init];
if (self)
{
NSLog(#"Initing the array");
_cList = [[NSMutableArray alloc] init];
}
return self;
}
+(contactStorage*)shared
{
if (_myOnlyInstance == nil)
{
_myOnlyInstance = [[contactStorage alloc] init];
}
return _myOnlyInstance;
}
-(NSString*) documentsPath
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
return documentsDir;
}
#end
The code for my contactModel:
//
// ContactModel.m
// contactList
//
// Created by dev on 10/23/13.
// Copyright (c) 2013 Deasy, Michael William. All rights reserved.
//
#import "ContactModel.h"
#implementation ContactModel
{
}
#synthesize fName = _fName;
#synthesize lName = _lName;
#synthesize email = _email;
#synthesize phone = _phone;
-(void)logContact
{
NSLog(#"%#", self.fName);
NSLog(#"%#", self.lName);
NSLog(#"%#", self.email);
NSLog(#"%#", self.phone);
}
-(void)dealloc
{
[_fName release];
[_lName release];
[_email release];
[_phone release];
[super dealloc];
}
-(id) initWithfName: (NSString*) fName
andlName: (NSString*) lName
andEmail: (NSString*) email
andPhone: (NSString*) phone
{
self = [super init];
_fName = [[NSString alloc] initWithString:fName];
_lName = [[NSString alloc] initWithString:lName];
_email = [[NSString alloc] initWithString:email];
_phone = [[NSString alloc] initWithString:phone];
return self;
}
#end
NSLog Output:
2013-10-24 12:50:35.573 contactList[3097:a0b] Mike
2013-10-24 12:50:35.574 contactList[3097:a0b] Deasy
2013-10-24 12:50:35.575 contactList[3097:a0b] mid31#pitt.edu
2013-10-24 12:50:35.575 contactList[3097:a0b] 4127154194
2013-10-24 12:50:35.576 contactList[3097:a0b] Initing the array
2013-10-24 12:50:35.576 contactList[3097:a0b] Mike
2013-10-24 12:50:35.576 contactList[3097:a0b] Deasy
2013-10-24 12:50:35.577 contactList[3097:a0b] mid31#pitt.edu
2013-10-24 12:50:35.577 contactList[3097:a0b] 4127154194
2013-10-24 12:50:35.578 contactList[3097:a0b] (
"<ContactModel: 0x8d72720>"
)
2013-10-24 12:50:35.578 contactList[3097:a0b] /Users/dev/Library/Application Support/iPhone Simulator/7.0/Applications/7CFD98F0-C502-49E5-953B-FD43B61EDC38/Documents/data.txt
2013-10-24 12:50:35.579 contactList[3097:a0b] (
"<ContactModel: 0x8d72720>"
)
Clearly, your singleton is successfully adding the ContactModel object to the array of your singleton (as evidenced by your NSLog statement). I assume your question stems from the fact that you're not seeing your file saved.
That's because you're trying to use writeToFile of your NSMutableArray (which tries to save a plist file). If you check the return code of writeToFile, you'll see it failed. This is because you cannot write a plist with an array consisting of custom objects. You might want to use NSKeyedArchiver instead, e.g.:
- (void)saveToFile
{
NSString* path = [[self documentsPath] stringByAppendingPathComponent:#"cList.dat"];
BOOL success = [NSKeyedArchiver archiveRootObject:_cList toFile:path];
NSAssert(success, #"write failed");
}
Anticipating the logical follow-up question, how to read the file, you would use NSKeyedUnarchiver, like so:
-(void)loadFromFile
{
NSString* path = [[self documentsPath] stringByAppendingPathComponent:#"cList.dat"];
self.cList = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
NSAssert(_cList, #"read failed");
}
But, for this to work, you have to make your contact model conform to the NSCoding protocol, namely adding the following methods to that class:
#pragma mark - NSCoding methods
- (NSArray *)propertyNames
{
return #[#"fName", #"lName", #"email", #"phone"];
}
- (id) initWithCoder:(NSCoder *)aDecoder
{
// if `super` conforms to `NSCoding`, then use
//
// self = [super initWithCoder:aDecoder];
//
// in this case, `super` is `NSObject`, so just call `init`
self = [super init];
if (self) {
for (NSString *key in [self propertyNames]) {
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
// if `super` conforms to `NSCoding`, itself, then call `encodeWithCoder` for `super`:
//
// [super encodeWithCoder:aCoder];
//
// in this case, `super` is `NSObject`, so that is not needed
for (NSString *key in [self propertyNames]) {
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
For more information about using archives, see the Archives and Serializations Programming Guide.
Related
I am having trouble with some batch document processing in iOS and was hoping I could get some help here. The process I'm trying to implement uses an input directory on iCloud drive, pulls all of the documents there, and adds one record into the iCloud database for each. Right now, the 'add iCloud' code isn't in here but it's easy enough to do if I pass the right pointers around. I'd like to give the user a progress bar so they can check on processing and get an idea how well it's going along. This a technique I often use in macOS and just assumed it would work ok in iOS but I'm having difficulties.
Now the strange part about this is that if I remove the code that runs the loop on a background thread and run the loader class on the main thread, the process more or less works. The progress bar doesn't work of course (it never gets the main thread back) but the document open code is called the correct number of times, and processes are spawned to open the documents and all of that. When I run it as is, I get an abend that sort of seems to indicate that a allocate or deallocate process is broken. Don't actually get a crash, only a break that constantly loops.
The basic process is as follows:
User selects load option and a target directory from a screen and presses 'go'. As part of the segue, the loader process is instantiated and waits for a bit to begin (there are better ways to do this but they can wait until I get the basic process built)
User is segued to another screen which displays a progress bar. Here is the code for it:
//
// LoadPopover.m
// TestFindIt
//
// Created by Joe Ruth on 7/16/16.
// Copyright © 2016 Joe Ruth. All rights reserved.
//
#import "LoadPopover.h"
#interface LoadPopover ()
#end
#implementation LoadPopover;
#synthesize localSecondViewController, localLoadCloudDBClass, loadProgressBar, screenReloadDirectory;
#synthesize loadedDocuments;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)pressedDoneButton:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
-(void)viewDidAppear:(BOOL)animated {
[localLoadCloudDBClass addObserver:self
forKeyPath:#"iterationcounter"
options:NSKeyValueObservingOptionNew
context:NULL];
[localLoadCloudDBClass addObserver:self
forKeyPath:#"totalcounter"
options:NSKeyValueObservingOptionNew
context:NULL];
loadProgressBar.progress = 0.00;
self.loadedDocuments = [[NSMutableArray alloc] init];
localLoadCloudDBClass.loadedDocuments = self.loadedDocuments;
[localLoadCloudDBClass LoadCloudDBClassRun:self];
}
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)objectchange:(NSDictionary *)changecontext:(void *)context
{
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
int iterationcounter;
int totalcounter;
iterationcounter = (int) localLoadCloudDBClass.iterationcounter;
totalcounter = (int) localLoadCloudDBClass.totalcounter;
if (totalcounter != 0) {
loadProgressBar.progress = (float) iterationcounter / (float) totalcounter;}
else {
loadProgressBar.progress = 0.00;}
}];
}
#end
The segue screen establishes observer for some counter properties for the loader class.
The loader is pretty simple. Right now it's just pages through the iCloud database and starts a document open process for each record in the database. At some point, I'm going to need to get a little fancier with the document open process but right now it's proof of concept.
//
// LoadCloudDBClass.m
// TestFindIt
//
// Created by Joe Ruth on 7/15/16.
// Copyright © 2016 Joe Ruth. All rights reserved.
//
#import "LoadCloudDBClass.h"
#import "Item.h"
#import "SaveObject.h"
#import "SaveObjectUIDocument.h"
#implementation LoadCloudDBClass
#synthesize localFindItDataController, screenReloadDirectory, localLoadPopover, iterationcounter, totalcounter;
#synthesize loadedDocuments;
- (void) LoadCloudDBClassRun:(NSObject *) parameterTestFinditCaller {
dispatch_queue_t backgroundQueue = dispatch_queue_create("Background Queue",NULL);
dispatch_async(backgroundQueue, ^{
NSError *error;
unsigned long check_count;
unsigned long total_count;
NSURL *fileURL;
int stopper;
__block BOOL blockSuccess = NO;
__block BOOL blockCalled = NO;
[self setIterationcounter:(NSInteger) 0];
[self setTotalcounter:(NSInteger) 0];
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *rootURL = [fm URLForUbiquityContainerIdentifier:nil];
NSURL *newFolderTemp = [rootURL URLByAppendingPathComponent:#"Documents" isDirectory:YES];
NSURL *newFolder = [newFolderTemp URLByAppendingPathComponent:screenReloadDirectory isDirectory:YES];
NSNumber *isDirectory;
if (![newFolder getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {return;}
NSArray *theFiles = [fm contentsOfDirectoryAtURL:newFolder
includingPropertiesForKeys:[NSArray arrayWithObject:NSURLNameKey]
options:NSDirectoryEnumerationSkipsHiddenFiles
error:nil];
[self setTotalcounter:(NSInteger) [theFiles count]];
total_count = [theFiles count];
check_count = 0;
while ((check_count < total_count) && (total_count != 0)) {
fileURL = [theFiles objectAtIndex:check_count];
SaveObjectUIDocument *tempdoc = [[SaveObjectUIDocument alloc] initWithFileURL:fileURL];
//tempdoc.loadedDocuments = loadedDocuments;
long set_iterationcounter = iterationcounter + 1;
[self setIterationcounter:(NSInteger) set_iterationcounter];
[tempdoc openWithCompletionHandler:^(BOOL success) {
NSLog (#" try Open");
if (success) {
blockSuccess = success;
blockCalled = YES;
//[self.loadedDocuments addObject:tempdoc];
NSLog(#"Opened");}
else
{NSLog(#"Not Opened");}
}];
blockCalled = NO;
NSDate *loopUntil = [NSDate dateWithTimeIntervalSinceNow:10];
while (!blockCalled && [loopUntil timeIntervalSinceNow] > 0)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:loopUntil];
}
check_count++;
}
stopper=5;
});
}
#end
The document coding is as follows:
Here is the .h file
//
// SaveObject.h
// TestFindIt
//
// Created by Joe Ruth on 1/10/16.
// Copyright © 2016 Joe Ruth. All rights reserved.
//
#import <Foundation/Foundation.h>
#interface SaveObject : NSObject <NSCoding> {
NSData *_sxmlData;
}
- (id)initWithData:(NSData *)in_xmlData;
#property (nonatomic, copy) NSData *sxmlData;
#end
Here is the accompanying .m file
//
// SaveObject.m
// TestFindIt
//
// Created by Joe Ruth on 1/10/16.
// Copyright © 2016 Joe Ruth. All rights reserved.
//
#import "SaveObject.h"
#implementation SaveObject
#synthesize sxmlData = _sxmlData;
- (id)initWithData:(NSData *)in_xmlData {
if ((self = [super init])) {
self.sxmlData = in_xmlData;
}
return self;
}
- (id)init {
return [self initWithData:nil];
}
#pragma mark NSCoding
#define kVersionKey #"Version"
#define kDataKey #"Data"
#define kSaveObjectXMLData #"SaveObjectXMLData"
//- (void)encodeWithCoder:(NSCoder *)encoder {
// [encoder encodeInt:1 forKey:kVersionKey];
// [encoder encodeObject:self.sxmlData forKey:kDataKey];
//}
//- (id)initWithCoder:(NSCoder *)decoder {
// [decoder decodeIntForKey:kVersionKey];
// NSData * outxmlData = [decoder decodeObjectForKey:kDataKey];
// NSLog(#">>>>>>>>>>>>>>>>>>> %#",outxmlData);
// return [self initWithData:outxmlData];
//}
#pragma mark -
#pragma mark NSCoding Protocol
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.sxmlData forKey:kSaveObjectXMLData];
}
- (id)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self != nil) {
self.sxmlData = [coder decodeObjectForKey:kSaveObjectXMLData];
}
return self;
}
#end
Here is the high level document coding .h file
//
// SaveObjectUIDocument.h
// TestFindIt
//
// Created by Joe Ruth on 1/10/16.
// Copyright © 2016 Joe Ruth. All rights reserved.
//
#import <UIKit/UIKit.h>
#class SaveObject;
#interface SaveObjectUIDocument : UIDocument {
SaveObject *_SaveObject;
}
#property (strong, nonatomic) SaveObject *SaveObject;
#end
And the accompanying .m file. Later the iCloud add code would go here and I'd need to make sure the open MOC object address was passed around properly.
//
// Created by Joe Ruth on 1/10/16.
// Copyright © 2016 Joe Ruth. All rights reserved.
//
#import "SaveObjectUIDocument.h"
#import "SaveObject.h"
#define kArchiveKey #"SaveObjectXMLData"
#interface SaveObjectUIDocument ()
#property (nonatomic, strong) NSData * data;
#end
#implementation SaveObjectUIDocument;
#synthesize SaveObject = _SaveObject;
- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
if ([contents length] > 0) {
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:contents];
self.SaveObject = [unarchiver decodeObjectForKey:kArchiveKey];
[unarchiver finishDecoding];}
else {
self.SaveObject = [[SaveObject alloc] initWithData:nil];}
return YES;
}
- (id)contentsForType:(NSString *)typeName error:(NSError *__autoreleasing *)outError {
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:self.SaveObject forKey:kArchiveKey];
[archiver finishEncoding];
return data;
}
- (void)handleError:(NSError *)error userInteractionPermitted:(BOOL)userInteractionPermitted {
NSLog(#"Error: %# userInfo=%#", error.localizedDescription, error.userInfo);
[super handleError:error userInteractionPermitted:userInteractionPermitted];
}
#end
Got a self answer for this. Forced the openWithCompletionHandler: process to the main thread and it worked. Go figure.
I use this code to get the path of a not existing file in documents directory of my application
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:#"restaurants.xml"];
getting this path:
/var/mobile/Containers/Data/Application/50BC3507-39BA-4F7A-86BA-254AB9DA6184/Documents/restaurants.xml
My question: How can I make my class being able to be saved in an array?
function to get path:
- (NSString *)restaurantListPath {
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:#"restaurants.xml"];
return path;
}
function to write array:
- (void)writeRestaurantListToFile:(NSMutableArray *)restaurantList {
[restaurantList writeToFile:[self restaurantListPath] atomically:YES];
}
function to load array:
- (NSMutableArray *)restaurantListOfFile {
NSMutableArray *restaurantList = [NSMutableArray arrayWithContentsOfFile:[self restaurantListPath]];
if (!restaurantList) {
restaurantList = [NSMutableArray array];
}
return restaurantList;
}
I created an array containing 1 JFRestaurant item and saved it with the function above. Then I loaded it with the function above and the array was empty (initialized but with no content).
If there would be an error while writing the file or reading the file the array would be nil not empty. So I think that the content of the array is the problem.
May my class JFRestaurant cannot be wrote to file. I looked at a tutorial and saw, that I need to implement NSCoding protocol to make my class being able to be written to file and did that, but the array is still empty. I also tried with NSKeyedArchiver and NSKeyedUnarchiver, but then I get another error
2014-08-25 16:52:23.969 Restaurants[5577:934385] -[__NSCFArray objectForKey:]: unrecognized selector sent to instance 0x170050860
2014-08-25 16:52:23.970 Restaurants[5577:934385] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFArray objectForKey:]: unrecognized selector sent to instance 0x170050860'
*** First throw call stack:
(0x185866084 0x1954180e4 0x18586d094 0x185869e48 0x18576f08c 0x185741d98 0x18670c9e0 0x18676e170 0x10007d3a0 0x100077cdc 0x189eb39ec 0x189f510b8 0x189f50b0c 0x189f5082c 0x189f507ac 0x189e997a0 0x1897f22a4 0x1897ece90 0x1897ecd34 0x1897ec534 0x1897ec2b8 0x18a125b54 0x18a126a00 0x18a124b84 0x18d96c6ac 0x18581e360 0x18581d468 0x18581b668 0x185749664 0x189f07140 0x189f02164 0x10007b518 0x195a8ea08)
libc++abi.dylib: terminating with uncaught exception of type NSException
JFRestaurant.h:
//
// JFRestaurant.h
// Restaurants
//
// Created by Jonas Frey on 15.08.14.
// Copyright (c) 2014 Jonas Frey. All rights reserved.
//
#import <Foundation/Foundation.h>
#import "JFUtils.h"
#import "JFItem.h"
#interface JFRestaurant : NSObject <NSCoding>
#property (strong, nonatomic) NSString *name;
#property float score;
- (void)addItem:(JFItem *)item;
- (void)removeItem:(JFItem *)item;
- (void)removeItemAtIndex:(NSUInteger)index;
- (void)setItem:(JFItem *)item atIndex:(NSUInteger)index;
- (JFItem *)itemAtIndex:(NSUInteger)index;
- (NSUInteger)indexOfItem:(JFItem *)item;
- (NSInteger)itemCount;
- (NSMutableArray *)fullItemArray;
- (id)initWithName:(NSString *)name;
- (id)initWithName:(NSString *)name items:(NSMutableArray *)items;
#end
JFRestaurant.m
//
// JFRestaurant.m
// Restaurants
//
// Created by Jonas Frey on 15.08.14.
// Copyright (c) 2014 Jonas Frey. All rights reserved.
//
#import "JFRestaurant.h"
#interface JFRestaurant ()
#property (strong, nonatomic) NSMutableArray *items;
- (float)scoreOfItems;
#end
#implementation JFRestaurant
- (id)initWithCoder:(NSCoder *)aDecoder {
NSString *name = [aDecoder decodeObjectForKey:JFKeyRestaurantCoderName];
NSMutableArray *items = [aDecoder decodeObjectForKey:JFKeyRestaurantCoderItems];
return [self initWithName:name items:items];
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:_name forKey:JFKeyRestaurantCoderName];
[aCoder encodeObject:_items forKey:JFKeyRestaurantCoderItems];
}
- (id)init {
self = [super init];
if (self) {
self.name = #"Neues Restaurant";
self.items = [[[JFUtils alloc] init] defaultItems];
self.score = [self scoreOfItems];
}
return self;
}
- (id)initWithName:(NSString *)name {
self = [super init];
if (self) {
self.name = name;
self.items = [[[JFUtils alloc] init] defaultItems];
self.score = [self scoreOfItems];
}
return self;
}
- (id)initWithName:(NSString *)name items:(NSMutableArray *)items {
self = [super init];
if (self) {
self.name = name;
self.items = items;
self.score = [self scoreOfItems];
}
return self;
}
- (void)addItem:(JFItem *)item {
[self.items addObject:item];
self.score = [self scoreOfItems];
}
- (void)removeItem:(JFItem *)item {
[self.items removeObject:item];
self.score = [self scoreOfItems];
}
- (void)removeItemAtIndex:(NSUInteger)index {
[self.items removeObjectAtIndex:index];
self.score = [self scoreOfItems];
}
- (void)setItem:(JFItem *)item atIndex:(NSUInteger)index {
[self.items setObject:item atIndexedSubscript:index];
self.score = [self scoreOfItems];
}
- (JFItem *)itemAtIndex:(NSUInteger)index {
return [self.items objectAtIndex:index];
}
- (NSUInteger)indexOfItem:(JFItem *)item {
return [self.items indexOfObject:item];
}
- (NSInteger)itemCount {
return self.items.count;
}
- (NSMutableArray *)fullItemArray {
return self.items;
}
- (float)scoreOfItems {
float value = 0.0f;
float totalPossible = 0.0f;
for (JFItem *item in self.items) {
value += (4 - (float)item.segmentIndex) * 25.0f;
totalPossible += 100.0f;
}
return value / totalPossible * 100.0f;
}
#end
Thanks for your help
iComputerfreak
I have class for playing sound in app.
I implemented on/off switch(on GUI), for disabling and enablining sound play.
I am using BOOL property for that and this is working.
Now I am trying to implement saving that BOOL (is sound on/off) in file so that next time when app is started state is automatically restored.
For that I am using NSCoding protocol, archiving is working but I have problem with unarchiving.
My app will not start it will just show black screen.
This is my code, only part that I think it is important.
GTN_Sound.h
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h> // for playing sound
#interface GTN_Sound : NSObject <NSCoding>
#property(nonatomic, readwrite, unsafe_unretained) BOOL isSoundOn;
+ (id)sharedManager;
- (void)playWinSound;
- (void)playLoseSound;
#end
GTN_Sound.m
#pragma mark - NSCoding Methods
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeBool:self.isSoundOn forKey:#"isSoundOn"];
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [GTN_Sound sharedManager];
if (self) {
_isSoundOn = [aDecoder decodeBoolForKey:#"isSoundOn"];
}
return self;
}
I think that code is so far so good ?
Continuation of GTN_Sound.m
#pragma mark - itemArchivePath Method
- (NSString *)itemArchivePath
{
// Make sure that the first argument is NSDocumentDirectory
// and not NSDocumentationDirectory
NSArray *documentDirectories =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
// Get the one document directory from that list
NSString *documentDirectory = [documentDirectories firstObject];
return [documentDirectory stringByAppendingPathComponent:#"sound.archive"];
}
#pragma mark - custom seter Method
- (void)setIsSoundOn:(BOOL)theBoolean {
NSLog(#"My custom setter\n");
if(_isSoundOn != theBoolean){
_isSoundOn = theBoolean;
NSString *path = [self itemArchivePath];
[NSKeyedArchiver archiveRootObject:self toFile:path]; // this is doing save
}
}
It is done that for every time when switch on GUI is changed I do the savings.
This look fine from my side, because I am not expecting that user will change this many times.
Now the unarchiving comes and I thin that here are some problems.
#pragma mark - Singleton Methods
+ (id)sharedManager {
static GTN_Sound *sharedMyManager = nil;
// to be thread safe
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] initPrivate];
});
return sharedMyManager;
}
- (instancetype)init
{
#throw [NSException exceptionWithName:#"Singleton"
reason:#"Use +[GTN_Sound sharedManager]"
userInfo:nil];
return nil;
}
// Here is the real (secret) initializer
- (instancetype)initPrivate
{
self = [super init];
if (self) {
NSString *path = [self itemArchivePath]; // do as iVar, for futture
self = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
//_isSoundOn = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
}
return self;
}
I think that problem is in this line
self = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
But have no idea how to fix it.
I have read that when I am doing archiving, that I need to archive all properties of object.
Does this apply to private ivars also ?
Any help is appreciated.
Thanks
You can only archive NSObject inheriting objects. ie. NSNumber
[NSKeyedArchiver archiveRootObject:#YES toFile:path]; //to save
_isSoundOn = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] boolValue]; //to load
I think I am hallucinating. I am trying to add some persistence to my Concentration-lke game. I would like to keep track of high scores. I got this partially working for a little while today and now it has all gone kablooie (I think that is the correct iOS terminology). Now, my allHighScores NSMutablearray suddenly becomes a CALayer. I am using NSKeyed Archiving. I have a break point in my file before allHighScores gets loaded with data. When stepping through the application, allHighScores exists as an NSMutableArray - then, at the next step, it suddenly becomes a CA Layer. Huh?
-(id)init
{
self = [super init];
if (self) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
allHighScores = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if (!allHighScores) {
allHighScores = [[NSMutableArray alloc] init];
}
}
return self;
}
+(FlipHighScoreStore *)sharedStore {
static FlipHighScoreStore *sharedStore = nil;
if (!sharedStore) {
sharedStore = [[super allocWithZone:nil]init];
}
return sharedStore;
}
Somehow, calling NSKeyedUnarchiver changes my allHighScores from an NSMutableArray into a CALayer. I am very confused.
I tried adding a retain to the unarchiving instruction, but that didn't help.
Here is my encoding/decoding code:
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.themeChosen forKey:#"themeChosen"];
[aCoder encodeInt:self.highScore forKey:#"highScore"];
[aCoder encodeInt:self.scoreStartLevel forKey:#"scoreStartLevel"];
[aCoder encodeInt:self.scoreFinishLevel forKey:#"scoreFinishLevel"];
[aCoder encodeObject:scoreDateCreated forKey:#"scoreDateCreated"];}
-(id)initWithCoder:(NSCoder *)aDecoder {
if (self) {
self.themeChosen = [aDecoder decodeObjectForKey:#"themeChosen"];
self.highScore = [aDecoder decodeIntForKey:#"highScore"];
self.scoreStartLevel = [aDecoder decodeIntForKey:#"scoreStartLevel"];
self.scoreFinishLevel = [aDecoder decodeIntForKey:#"scoreFinishLevel"];
scoreDateCreated = [aDecoder decodeObjectForKey:#"scoreDateCreated"];
}
return self;}
UPDATE: The program crashes when a "highscores.archive" file already exists and a save is called again. I can launch the app, look at the high scores - they are there and retrieved happily, but the save code:
-(BOOL)saveHighScores {
NSString *path = [self flipScoreArchivePath];
return [NSKeyedArchiver archiveRootObject:allHighScores toFile:path];}
causes a EXC_BAD_ACCESS. The path is right, so somehow the allHighScores isn't.
The problem here is you aren't retaining the results of the unarchiving. According to the Basic Memory Management Rules, a method by the name of +unarchiveObjectWithFile: will return an autoreleased object. As such, since you are placing it into an ivar, you need to retain this object, or it will get deallocated out from under you.
Although in your case, since you want a mutable array, you actually need to call -mutableCopy since NSKeyedUnarchive will just give you an immutable array.
-(id)init {
if ((self = [super init])) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
allHighScores = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] mutableCopy];
if (!allHighScores) {
allHighScores = [[NSMutableArray alloc] init];
}
}
return self;
}
Your -initWithCoder: isn't calling super. You need to say
if ((self = [super initWithCoder:aDecoder])) {
Have you tried this?
-(id)init {
if ((self = [super init])) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
allHighScores = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] mutableCopy];
if (!allHighScores) {
allHighScores = [[NSMutableArray alloc] init];
}
// All-important new line....
[self setAllHighScores:allHighScores];
}
return self;
}
Edit/Update:
So, here's two versions of what I actually intended in the above example (I'm assuming here that his ivar allHighScores has a corresponding property):
-(id)init {
if ((self = [super init])) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
self.allHighScores = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] mutableCopy];
if (!self.allHighScores) {
self.allHighScores = [[NSMutableArray alloc] init];
}
}
return self;
}
This is the way I'd actually do it:
-(id)init {
if ((self = [super init])) {
NSMutableArray *arr = [[NSKeyedUnarchiver unarchiveObjectWithFile:[self flipScoreArchivePath]] mutableCopy];
if (!arr) arr = [[NSMutableArray alloc] init];
[self setAllHighScores:arr];
}
return self;
}
I am trying to create a NSMutableDictionary in my class. I have read many post in stackoverflow to understand the difference. But now i am totally confused. So any one correct me , which one is the correct way of initialing a NSMutableDictionary in my class . I have to access this dictiionary in many areas of my application .So suggest me the good way of using the variable initialization ...
/// .h file
#interface ActiveFeeds : NSObject {
}
#property (nonatomic, copy) NSMutableDictionary *invDictionary;
#property (nonatomic, retain) NSString *filePath;
#end
#implementation ActiveFeeds
#synthesize filePath;
#synthesize invDictionary;
- (id)init{
self = [super init];
if (self != nil){
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:self.filePath];
self.invDictionary = [dictionary mutableCopy];
dictionary release];
}
return self;
}
/* And use self.invDictionary all in the application */
- (void)setObjectAtKey:(NSMutableDictionary *)objectDic atKey:(NSString *)setKey{
[self.invDictionary setObject:objectDic forKey:setKey];
}
- (void)dealloc {
[self.invDictionary release];
[self.filePath release];
[super dealloc];
}
#end
or like this ....
#interface ActiveFeeds : NSObject {
NSMutableDictionary *invDictionary;
NSString *filePath;
}
#end
#implementation ActiveFeeds
- (id)init{
self = [super init];
if (self != nil){
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] initWithContentsOfFile:filePath];
invDictionary = [dictionary mutableCopy];
[dictionary release];
}
}
return self;
}
/* And use invDictionary all in the application */
- (void)setObjectAtKey:(NSMutableDictionary *)objectDic atKey:(NSString *)setKey{
[invDictionary setObject:objectDic forKey:setKey];
}
- (void)dealloc {
[invDictionary release];
[filePath release];
[super dealloc];
}
#end
Please any one help me to get the correct way of using the variables ....
- (id)initWithFilePath:(NSString *)path{
self = [super init];
if (self != nil){
self.filePath = path;
self.invDictionary = [NSMutableDictionary dictionaryWithContentsOfFile:path];
}
return self;
}
also
- (void)dealloc {
[invDictionary release];
[filePath release];
[super dealloc];
}