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.
Related
I simplify the code for ask question.
I want to save more class and load class model file.
But now I simplify parameters to one part (lim) in the model(FlightSettingModel.h).
In the FlightSettingModel.h I have set some parameters in here.
FlightSettingModel.h code below:
#import <Foundation/Foundation.h>
#interface FlightSettingModel : NSObject<NSCoding>
// lim property part
#property float limCurrentVal;
#property float limDefaultVal;
#property float limMaxVal;
#property float limMinVal;
// .... other property part
#end
FlightSettingModel.m code below
#import "FlightSettingModel.h"
#interface FlightSettingModel()
{
}
#end
#implementation FlightSettingModel
-(instancetype)init
{
self = [super init];
if(self)
{
self.limDefaultVal = 3.0;
self.limCurrentVal = 4.0;
self.limMaxVal = 10;
self.limMinVal = 0;
// ... other property part value init .....
}
return self;
}
- (void)setFlightSettingToDefaultValue
{
self.limCurrentVal = self.limDefaultVal;
}
- (void) encodeWithCoder: (NSCoder *)encoder
{
[encoder encodeFloat:self.limCurrentVal forKey:#"limCurrentVal"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
self.limCurrentVal = [decoder decodeFloatForKey:#"limCurrentVal"];
}
return self;
}
#end
Then I have set the singleton SettingData file to initial the FlightSettingModel and other model class.
The SettingData model header like below:
#import <Foundation/Foundation.h>
#import "FlightSettingModel.h"
#interface SettingData : NSObject
#property (nonatomic,strong) FlightSettingModel *flightSettingModel;
+(SettingData*) sharedInstance;
#end
SettingData.m code below:
#import "SettingData.h"
#implementation SettingData
SettingData *sharedInstance;
+(SettingData*) sharedInstance{
if( sharedInstance == nil )
{
sharedInstance = [SettingData new];
}
return sharedInstance;
}
-(id) init{
self = [super init];
if( self )
{
self.flightSettingModel = [FlightSettingModel new];
}
return self;
}
#end
In my storyboard (UI) is like below:
When I click the save button , I want to save the custom class model(FlightSettingModel.h) in the NSKeyedArchiver. When I click the load, I want to load the model from the archiver using NSKeyedUnarchiver and resetting to the slider.
But now,when I drag the slider to other value(ex:10), then I click the save, then I close the app restart the app. I click the load, the slider value will become 0.
I don't know why when I load the value the all value will become 0;
My view controller code .m below:
#import "ViewController.h"
#import "SettingData.h"
#interface ViewController ()
{
NSString *path;
NSString *fileName;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// file manage
path =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0];
fileName = [path stringByAppendingPathComponent:#"flightFile"];
[self setUIValue];
NSLog(#"---");
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (IBAction)saveAction:(UIButton *)sender {
[NSKeyedArchiver archiveRootObject:[SettingData sharedInstance].flightSettingModel toFile:fileName];
}
- (IBAction)loadAction:(UIButton *)sender {
[SettingData sharedInstance].flightSettingModel = (FlightSettingModel*) [NSKeyedUnarchiver unarchiveObjectWithFile: fileName];
[self setUIValue];
NSLog(#"current Value:%.f",[SettingData sharedInstance].flightSettingModel.limCurrentVal);
}
- (IBAction)sliderChangedAction:(UISlider *)sender {
[SettingData sharedInstance].flightSettingModel.limCurrentVal = sender.value;
self.theTextField.text = [NSString stringWithFormat:#"%.f",self.theSlider.value];
}
-(void) setUIValue
{
// setting slider property
self.theSlider.maximumValue = [SettingData sharedInstance].flightSettingModel.limMaxVal;
self.theSlider.minimumValue = [SettingData sharedInstance].flightSettingModel.limMinVal;
self.theSlider.value = [SettingData sharedInstance].flightSettingModel.limCurrentVal;
self.theTextField.text = [NSString stringWithFormat:#"%.f",self.theSlider.value];
}
#end
Have anyone know where my problem in my code?
thank you very much.
If you want to download the complete code(the question code) , I have upload in github
This behavior happens because the [SettingData sharedInstance].flightSettingModel.limMaxVal and the [SettingData sharedInstance].flightSettingModel.limMinVal are zero:
-(void) setUIValue
{
// setting slider property
self.theSlider.maximumValue = [SettingData sharedInstance].flightSettingModel.limMaxVal;
// self.theSlider.maximumValue = 0
self.theSlider.minimumValue = [SettingData sharedInstance].flightSettingModel.limMinVal;
// self.theSlider.minimumValue = 0
self.theSlider.value = [SettingData sharedInstance].flightSettingModel.limCurrentVal;
// [SettingData sharedInstance].flightSettingModel.limCurrentVal = 10
self.theTextField.text = [NSString stringWithFormat:#"%.f",self.theSlider.value];
}
EDIT: You can fix it by adding this:
- (void) encodeWithCoder: (NSCoder *)encoder {
[encoder encodeFloat:self.limCurrentVal forKey:#"limCurrentVal"];
[encoder encodeFloat:self.limMaxVal forKey:#"limMaxVal"];
[encoder encodeFloat:self.limMinVal forKey:#"limMinVal"];
}
- (id)initWithCoder:(NSCoder *)decoder {
if((self = [super init])) {
self.limCurrentVal = [decoder decodeFloatForKey:#"limCurrentVal"];
self.limMaxVal = [decoder decodeFloatForKey:#"limMaxVal"];
self.limMinVal = [decoder decodeFloatForKey:#"limMinVal"];
}
return self;
}
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 have an app that uses a single UIManagedDocument as the data store which I access through a singleton using the method in Justin Driscoll's blog.
This works fine, but when I set my ubiquity keys for the persistentStoreOptions and change state notifications, the app only runs in the sandbox and there is no network activity.
Here is the execution flow ...
1. App opens to a landing screen where the singleton is created.
2. Once the UIManagedDocument is open I check for cloud permissions and if all is good to go and we have a ubiquity container, I overwrite the persistentStoreOptions with the cloud contentNameKey and contentUrlKey options.
Here is the code …
// DataStore.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>
typedef void (^OnDocumentReady) (UIManagedDocument *appDataStore);
#interface DataStore : NSObject
#property (strong, nonatomic) UIManagedDocument *appDataStore;
+ (DataStore *)sharedDocumentHandler;
- (void)performWithDocument:(OnDocumentReady)onDocumentReady;
#end
// DataStore.m
#import "DataStore.h"
#import "HashDefines.h"
#interface DataStore ()
#property (nonatomic)BOOL preparingDocument;
#property (nonatomic, strong) NSFileCoordinator *coordinator;
#property (strong, nonatomic) NSString *localDocumentsPath;
#property (strong, nonatomic) NSURL *localDocumentsURL;
#property (strong, nonatomic) NSURL *localFileURL;
#end;
#implementation DataStore
#synthesize appDataStore = _appDataStore;
#synthesize localDocumentsPath = _localDocumentsPath;
#synthesize localDocumentsURL = _localDocumentsURL;
#synthesize localFileURL = _localFileURL;
static DataStore *_sharedInstance;
#synthesize coordinator = _coordinator;
#define LOCAL_DATA_STORE_FILE_NAME #“QR App DataStore"
#pragma mark - synthesiser overiders
+ (DataStore *)sharedDocumentHandler
{
static dispatch_once_t once;
dispatch_once(&once, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
#pragma mark - Key Paths
#pragma mark Local Documents
- (NSString *) localDocumentsPath
{
if (!_localDocumentsPath) {
_localDocumentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
}
return _localDocumentsPath;
}
- (NSURL *) localDocumentsURL
{
if (!_localDocumentsURL) {
_localDocumentsURL = [NSURL fileURLWithPath:self.localDocumentsPath];
}
return _localDocumentsURL;
}
#pragma mark - File URLs
- (NSURL *) localFileURL: (NSString *) filename
{
if (!filename) return nil;
NSURL *fileURL = [[self localDocumentsURL] URLByAppendingPathComponent:filename];
return fileURL;
}
#pragma mark - the juicy bits
#pragma mark - initialisers
- (id)init
{
self = [super init];
if (self) {
NSLog(#"appDataStore does NOT exist ... building one now");
[self initCoreDataInTheSandbox];
}
return self;
}
- (void)initCoreDataInTheSandbox
{
NSURL *localURL = [self localFileURL:LOCAL_DATA_STORE_FILE_NAME];
self.appDataStore = [[UIManagedDocument alloc] initWithFileURL:localURL];
// Set our document up for automatic migrations
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
self.appDataStore.persistentStoreOptions = options;
NSLog(#"1. DS persistentStoreOptions: %#", self.appDataStore.persistentStoreOptions);
}
- (void)performWithDocument:(OnDocumentReady)onDocumentReady
{
NSLog(#"1. DS Begin performWithDocument: %#", self.appDataStore);
void (^OnDocumentDidLoad)(BOOL) = ^(BOOL success) {
onDocumentReady(self.appDataStore);
NSLog(#"2. DS into the block ... onDocumentReady:");
self.preparingDocument = NO; // release in completion handler
};
if(!self.preparingDocument) {
NSLog(#"3. DS preparing document: dataStore.appDataStore: %#", self.appDataStore);
// "lock", so no one else enter here
self.preparingDocument = YES;
if(![[NSFileManager defaultManager] fileExistsAtPath:[self.appDataStore.fileURL path]]) {
NSLog(#"4. DS creating document: dataStore.appDataStore: %#", self.appDataStore);
[self.appDataStore saveToURL:self.appDataStore.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:OnDocumentDidLoad];
[self.appDataStore openWithCompletionHandler:OnDocumentDidLoad];
} else if (self.appDataStore.documentState == UIDocumentStateClosed) {
NSLog(#"5. DS dataStore.appDataStore.documentState: %d ... closed", self.appDataStore.documentState);
[self.appDataStore openWithCompletionHandler:OnDocumentDidLoad];
} else if (self.appDataStore.documentState == UIDocumentStateSavingError) {
NSLog(#"6. DS dataStore.appDataStore.documentState: %d ... saving error", self.appDataStore.documentState);
[self.appDataStore openWithCompletionHandler:OnDocumentDidLoad];
} else if (self.appDataStore.documentState == UIDocumentStateNormal) {
NSLog(#"7. DS dataStore.appDataStore.documentState: %d ... open", self.appDataStore.documentState);
OnDocumentDidLoad(YES);
}
} else {
// try until document is ready (opened or created by some other call)
NSLog(#"8. DS preparing document - NOT ... trying again");
[self performSelector:#selector(performWithDocument:) withObject:onDocumentReady afterDelay:0.5];
}
NSLog(#"9. DS Exiting performWithDocument: %#", self.appDataStore);
}
#end
// CloudConnector.h
#import "DataStore.h"
#interface CloudConnector : NSObject
- (void)hookUpCloudForDocument:(UIManagedDocument *)document;
(NSManagedObjectContext*)managedObjectContext;
- (NSString *) documentState: (int) state;
#end
#define ICLOUD_TOKEN_KEY #“com.apple.QR-App.UbiquityIdentityToken"
#define CLOUD_IS_UBIQUITOUS #"sweet ubiquity"
#define LOCAL_DATA_STORE_FILE_NAME #“QR App DataStore"
#define CLOUD_DATA_STORE_FILE_NAME #"com~app~QR-App~cloudstore"
//CloudConnector.m
#import "CloudConnector.h"
#import "HashDefines.h"
#interface CloudConnector ()
#property (strong, nonatomic) NSString *localDocumentsPath;
#property (strong, nonatomic) NSURL *localDocumentsURL;
#property (strong, nonatomic) NSURL *localFileURL;
#property (strong, nonatomic) NSURL *ubiquityDataFileURL;
#property (nonatomic)BOOL preparingDocument;
#property (nonatomic, strong) NSFileCoordinator *coordinator;
- (NSURL *) localFileURL: (NSString *) filename;
- (NSURL *) ubiquityDataFileURL: (NSString *) filename;
- (BOOL) isLocal: (NSString *) filename;
- (NSString *) documentState: (int) state;
#end;
#implementation CloudConnector
#synthesize coordinator = _coordinator;
#synthesize localDocumentsPath = _localDocumentsPath;
#synthesize localDocumentsURL = _localDocumentsURL;
#synthesize localFileURL = _localFileURL;
#synthesize ubiquityDataFileURL = _ubiquityDataFileURL;
#pragma mark - synthesiser overiders
#pragma mark - the juicy bits
- (void)checkUbiquityStatus
{
id currentiCloudToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
if (currentiCloudToken) {
NSData *newTokenData =
[NSKeyedArchiver archivedDataWithRootObject: currentiCloudToken];
[[NSUserDefaults standardUserDefaults]
setObject: newTokenData
forKey: ICLOUD_TOKEN_KEY];
} else {
[[NSUserDefaults standardUserDefaults]
removeObjectForKey: ICLOUD_TOKEN_KEY];
}
}
#pragma mark - Key Paths
#pragma mark Local Documents
- (NSString *) localDocumentsPath
{
if (!_localDocumentsPath) {
_localDocumentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
}
return _localDocumentsPath;
}
- (NSURL *) localDocumentsURL
{
if (!_localDocumentsURL) {
_localDocumentsURL = [NSURL fileURLWithPath:self.localDocumentsPath];
}
return _localDocumentsURL;
}
#pragma mark - File URLs
- (NSURL *) localFileURL: (NSString *) filename
{
if (!filename) return nil;
NSURL *fileURL = [[self localDocumentsURL] URLByAppendingPathComponent:filename];
return fileURL;
}
- (NSURL *) ubiquityDataFileURL: (NSString *) filename forContainer: (NSString *) container
{
if (!filename) return nil;
NSURL *fileURL = [[self ubiquityDataURLForContainer:container] URLByAppendingPathComponent:filename];
return fileURL;
}
- (NSURL *) ubiquityDataFileURL: (NSString *) filename
{
return [self ubiquityDataFileURL:filename forContainer:nil];
}
#pragma mark Ubiquity Data
- (NSURL *) ubiquityDataURLForContainer: (NSString *) container
{
return [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:container];
}
- (NSArray *) contentsOfUbiquityDataFolderForContainer: (NSString *) container
{
NSURL *targetURL = [self ubiquityDataURLForContainer:container];
if (!targetURL) return nil;
NSArray *array = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:targetURL.path error:nil];
return array;
}
- (BOOL) isLocal: (NSString *) filename
{
if (!filename) return NO;
NSURL *targetURL = [self localFileURL:filename];
if (!targetURL) return NO;
return [[NSFileManager defaultManager] fileExistsAtPath:targetURL.path];
}
- (NSString *) documentState: (int) state
{
if (!state) return #"Document state is normal";
NSMutableString *string = [NSMutableString string];
if ((state & UIDocumentStateClosed) != 0)
[string appendString:#"Document is closed\n"];
if ((state & UIDocumentStateInConflict) != 0)
[string appendString:#"Document is in conflict"];
if ((state & UIDocumentStateSavingError) != 0)
[string appendString:#"Document is experiencing saving error"];
if ((state & UIDocumentStateEditingDisabled) != 0)
[string appendString:#"Document editing is disbled" ];
return string;
}
#pragma mark - initialisers
- (void)hookUpCloudForDocument:(UIManagedDocument *)document
{
// checking for ubiquity
NSLog(#"checking for ubiquity");
[self checkUbiquityStatus];
// THE FOLLOWING CODE DOESN'T WORK
if ([[NSUserDefaults standardUserDefaults] objectForKey:ICLOUD_TOKEN_KEY]) {
// cloud permissions are good to go.
NSLog(#"cloud permissions are good to go.");
dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
NSURL *url = [[NSFileManager defaultManager]
URLForUbiquityContainerIdentifier: nil];
NSLog(#"bikky url: %#", url);
if (url) { // != nil) {
// Your app can write to the ubiquity container
dispatch_async (dispatch_get_main_queue (), ^(void) {
// On the main thread, update UI and state as appropriate
[self setupCoreDataInTheCloudForDocument:document];
});
}
});
}
}
- (void) setupCoreDataInTheCloudForDocument:(UIManagedDocument *)document
{
NSLog(#"1. cc.setupCoreDataInTheCloudForDocument: %#", document);
NSURL *localURL = [self localFileURL:LOCAL_DATA_STORE_FILE_NAME];
NSURL *cloudURL = [self ubiquityDataFileURL:CLOUD_DATA_STORE_FILE_NAME];
// Set the persistent store options to point to the cloud
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
// [document.fileURL lastPathComponent], NSPersistentStoreUbiquitousContentNameKey,
localURL, NSPersistentStoreUbiquitousContentNameKey,
cloudURL, NSPersistentStoreUbiquitousContentURLKey,
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
document.persistentStoreOptions = options;
NSLog(#"2. cc.document.persistentStoreOptions: %#", document.persistentStoreOptions);
// Register as presenter
self.coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:document];
[NSFileCoordinator addFilePresenter:document];
// // THIS CODE IS INLINE FROM iOS 5 COOK BOOK
// // I EXECUTE THIS IN (void)performWithDocument:(OnDocumentReady)onDocumentReady
//
// // Check at the local sandbox
// if ([self isLocal:LOCAL_DATA_STORE_FILE_NAME])
// {
// NSLog(#"Attempting to open existing file");
// [document openWithCompletionHandler:^(BOOL success){
// if (!success) {NSLog(#"Error opening file"); return;}
// NSLog(#"File opened");
// }];
// }
// else
// {
// NSLog(#"Creating file.");
// // 1. save it out, 2. close it, 3. read it back in.
// [document saveToURL:localURL
// forSaveOperation:UIDocumentSaveForCreating
// completionHandler:^(BOOL success){
// if (!success) { NSLog(#"7. Error creating file"); return; }
// NSLog(#"File created");
// [document closeWithCompletionHandler:^(BOOL success){
// NSLog(#"Closed new file: %#", success ? #"Success" : #"Failure");
//
// [document openWithCompletionHandler:^(BOOL success){
// if (!success) {NSLog(#"Error opening file for reading."); return;}
// NSLog(#"File opened for reading.");
// }];
// }];
// }];
// }
//}
#end
I call the singleton as follows …
- (void)viewDidLoad
{
[super viewDidLoad];
if (!self.codeGeneratorDataStore) {
NSLog(#"MVC.viewDidLoad: Grab local instance of document from data store singleton");
[[DataStore sharedDocumentHandler] performWithDocument:^(UIManagedDocument *document) {
self.codeGeneratorDataStore = document;
// Do stuff with the document, set up a fetched results controller, whatever.
// NSLog(#"IN: _codeGeneratorDataStore.documentState : %u", _codeGeneratorDataStore.documentState);
if (![self.codeGeneratorDataStore.persistentStoreOptions objectForKey:NSPersistentStoreUbiquitousContentNameKey]) {
NSLog(#"MVC.viewDidLoad: We have a document, hooking up cloud now ... \n%#", document);
[self.cloudConnector hookUpCloudForDocument:self.codeGeneratorDataStore];
}
[self setupFetchedResultsController];
}];
NSLog(#"GVC.viewDidLoad: Subscribing to notifications");
[self subscribeToCloudNotifications];
}
[self checkDocumentStatus];
}
Then I check the document status …
- (void)checkDocumentStatus {
NSLog(#"MVC.checkDocumentStatus\n Document: %#\n persistentStoreOptions: %#", self.codeGeneratorDataStore, self.codeGeneratorDataStore.persistentStoreOptions);
if (![[
NSFileManager defaultManager] fileExistsAtPath:[self.codeGeneratorDataStore.fileURL path]]) {
[self.codeGeneratorDataStore saveToURL:self.codeGeneratorDataStore.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
NSLog(#"1. MVC self.codeGeneratorDataStore.documentState: %#", [NSString stringWithFormat:#"%u", self.codeGeneratorDataStore.documentState]);
}];
} else if (self.codeGeneratorDataStore.documentState == UIDocumentStateClosed) {
[self.codeGeneratorDataStore openWithCompletionHandler:^(BOOL succes) {
NSLog(#"2. MVC self.codeGeneratorDataStore.documentState: %#", [NSString stringWithFormat:#"%u", self.codeGeneratorDataStore.documentState]);
}];
} else if (self.codeGeneratorDataStore.documentState == UIDocumentStateNormal) {
NSLog(#"3. MVC self.codeGeneratorDataStore.documentState: %#", [NSString stringWithFormat:#"%u", self.codeGeneratorDataStore.documentState]);
}
}
Setup and register for notifications ...
- (void)subscribeToCloudNotifications
{
// subscribe to the NSPersistentStoreDidImportUbiquitousContentChangesNotification notification
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(documentStateChanged:)
name:UIDocumentStateChangedNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(documentContentsDidUpdate:)
name:NSPersistentStoreDidImportUbiquitousContentChangesNotification
object:nil];
NSLog(#"MVC.subscribeToCloudNotifications: notification subscriptions are good to go");
}
- (void) documentContentsDidUpdate: (NSNotification *) notification
{
NSLog(#"CMVC notification: documentContentsDidUpdate");
NSDictionary *userInfo = notification.userInfo;
[self.codeGeneratorDataStore.managedObjectContext performBlock:^{[self mergeiCloudChanges:userInfo forContext:self.codeGeneratorDataStore.managedObjectContext];}];
}
// When notified about a cloud update, start merging changes
- (void)documentStateChanged: (NSNotification *)notification
{
NSLog(#"CMVC notification: documentStateChanged");
// NSLog(#"Document state change: %#", [CloudHelper documentState:self.codeGeneratorDataStore.documentState]);
UIDocumentState documentState = self.codeGeneratorDataStore.documentState;
if (documentState & UIDocumentStateInConflict)
{
// This application uses a basic newest version wins conflict resolution strategy
NSURL *documentURL = self.codeGeneratorDataStore.fileURL;
NSArray *conflictVersions = [NSFileVersion unresolvedConflictVersionsOfItemAtURL:documentURL];
for (NSFileVersion *fileVersion in conflictVersions) {
fileVersion.resolved = YES;
}
[NSFileVersion removeOtherVersionsOfItemAtURL:documentURL error:nil];
}
}
// Merge the iCloud changes into the managed context
- (void)mergeiCloudChanges:(NSDictionary*)userInfo forContext:(NSManagedObjectContext*)managedObjectContext
{
#autoreleasepool
{
// NSLog(#"Merging changes from cloud");
NSMutableDictionary *localUserInfo = [NSMutableDictionary dictionary];
NSSet *allInvalidations = [userInfo objectForKey:NSInvalidatedAllObjectsKey];
NSString *materializeKeys[] = { NSDeletedObjectsKey, NSInsertedObjectsKey };
if (nil == allInvalidations)
{
// (1) we always materialize deletions to ensure delete propagation happens correctly, especially with
// more complex scenarios like merge conflicts and undo. Without this, future echoes may
// erroreously resurrect objects and cause dangling foreign keys
// (2) we always materialize insertions to make new entries visible to the UI
int c = (sizeof(materializeKeys) / sizeof(NSString *));
for (int i = 0; i < c; i++)
{
NSSet *set = [userInfo objectForKey:materializeKeys[i]];
if ([set count] > 0)
{
NSMutableSet *objectSet = [NSMutableSet set];
for (NSManagedObjectID *moid in set)
[objectSet addObject:[managedObjectContext objectWithID:moid]];
[localUserInfo setObject:objectSet forKey:materializeKeys[i]];
}
}
// (3) we do not materialize updates to objects we are not currently using
// (4) we do not materialize refreshes to objects we are not currently using
// (5) we do not materialize invalidations to objects we are not currently using
NSString *noMaterializeKeys[] = { NSUpdatedObjectsKey, NSRefreshedObjectsKey, NSInvalidatedObjectsKey };
c = (sizeof(noMaterializeKeys) / sizeof(NSString*));
for (int i = 0; i < 2; i++)
{
NSSet *set = [userInfo objectForKey:noMaterializeKeys[i]];
if ([set count] > 0)
{
NSMutableSet *objectSet = [NSMutableSet set];
for (NSManagedObjectID *moid in set)
{
NSManagedObject *realObj = [managedObjectContext objectRegisteredForID:moid];
if (realObj)
[objectSet addObject:realObj];
}
[localUserInfo setObject:objectSet forKey:noMaterializeKeys[i]];
}
}
NSNotification *fakeSave = [NSNotification notificationWithName:NSManagedObjectContextDidSaveNotification object:self userInfo:localUserInfo];
[managedObjectContext mergeChangesFromContextDidSaveNotification:fakeSave];
}
else
[localUserInfo setObject:allInvalidations forKey:NSInvalidatedAllObjectsKey];
[managedObjectContext processPendingChanges];
// [self performSelectorOnMainThread:#selector(performFetch) withObject:nil waitUntilDone:NO];
}
}
and we’re good to go - the app runs but with no network activity. Why aren't the transaction logs being uploaded?
Any assistance would be greatly appreciated here. Its been doing my head in for a week now and i’m at my wit’s end.
I answered this question previously in email, but I'm positing my answer here as well for completeness.
Is this an iOS 7-only app, or does it need to support iOS 5 or 6 as well?
I would not recommend mixing iCloud and Core Data in anything that still has to support iOS 5 and 6. However, the new support for iOS 7 seems pretty solid. I haven’t deployed a multi-million user app with it—so I wouldn’t say it’s battle tested, but it seems to be a lot more robust. In my preliminary testing, it handled everything I threw at it with no problems.
Also, is the sample code that you’re following up-to-date for iOS 7? It looks like it’s following some older patterns. This could cause a lot of problems, since the way the system operates has changed considerably in iOS 7.
For example, in iOS 7, we generally only need to set the NSPersistentStoreUbiquitousContentNameKey. Unless we’re trying to support a legacy core data stack, we don’t need to set the content URL key.
Also, there is a huge difference in how the core data stack is set up under iOS 7. When we first set up an iCloud persistent store, iOS 7 will create a local copy (the fallback store) for us automatically. We can use the store—but the changes will only be stored locally. When the iCloud version is ready, it will then send a NSPersistentStoreCoordinatorStoresWillChangeNotification. We’re supposed to save any changes and then reset our managed object context.
Again, it looks like your code is manually setting up its own fallback store—which may be part of the problem.
If you’re working from old examples, I’d actually recommend throwing that code away and starting over. Watch the “What’s New in Core Data and iCloud” video from WWDC 2013. It does a great rundown on the new technologies, and the current best practices.
My guess is that you’re getting the fallback store, but it’s never switching over to the iCloud store. Look in the console log when you run the app. You should see something like the following:
-[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](754): CoreData: p Ubiquity:
mobile~C9C8554A-BD44-43C3-AC54-603046EF0162:com~freelancemad p science~HealthBeat
Using local storage: 1
Here, “Using local storage: 1” means you’re using the fallback store. In a minute or two (or sometimes longer) you’ll see a similar message with “Using local storage: 0”. That means you’re now using the iCloud store.
Also, when you run the application, be sure to open the Debug Navigator in Xcode. It should have a profiler for iCloud, that shows you whenever data was uploaded to or downloaded from the cloud.
I hope that helps,
-Rich-
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.
Ok, I've been over this a million times in the last week and I just am not getting it. (And yes, I've read Apple's docs.)
I am archiving my object and it appears to be archiving correctly (I can see the file written to the file system and if I examine it I can see my data within). However, when I relaunch my app my data is not being restored. Every example I read tells me how easy this is but I'm just not getting it. One unique thing is that my object is a singleton, it's used for passing data between view controllers.
I'd really appreciate some sage advice. Thanks in advance.
Here's my header:
#import <Foundation/Foundation.h>
#interface SharedAppDataObject : NSObject <NSCoding>
{
NSMutableDictionary *apiKeyDictionary;
NSString *skuFieldText;
NSIndexPath *checkmarkIndex;
}
+ (SharedAppDataObject *)sharedStore;
#property (nonatomic, copy) NSString *skuFieldText;
#property (nonatomic, copy) NSIndexPath *checkmarkIndex;
#property (nonatomic, copy) NSMutableDictionary *apiKeyDictionary;
-(void)setValue:(NSString *)apiKey forKey:(NSString *)name;
-(void)setSkuField:(NSString *)s;
-(void)setCheckmarkIndex:(NSIndexPath *)p;
-(NSMutableDictionary *)apiKeyDictionary;
-(BOOL)saveChanges;
#end
Here's my implementation:
#import "SharedAppDataObject.h"
#implementation SharedAppDataObject
#synthesize skuFieldText;
#synthesize checkmarkIndex;
#synthesize apiKeyDictionary;
//create our shared singleton store
+(SharedAppDataObject *)sharedStore {
static SharedAppDataObject *sharedStore = nil;
if (!sharedStore) {
sharedStore = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]];
if(!sharedStore)
sharedStore = [[super allocWithZone:NULL] init];
}
return sharedStore;
}
-(id) init {
self = [super init];
if (self) {
}
return self;
}
-(void)setValue:(id)apiKey forKey:(NSString *)name {
[apiKeyDictionary setObject:apiKey forKey:name];
}
-(void)setSkuField:(NSString *)s {
skuFieldText = s;
}
-(NSMutableDictionary *)apiKeyDictionary {
return apiKeyDictionary;
}
-(void)setCheckmarkIndex:(NSIndexPath *)p {
checkmarkIndex = p;
}
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:skuFieldText forKey:#"skuFieldText"];
[aCoder encodeObject:checkmarkIndex forKey:#"checkmarkIndex"];
[aCoder encodeObject:apiKeyDictionary forKey:#"apiKeyDictionary"];
}
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
[self setSkuFieldText:[aDecoder decodeObjectForKey:#"skuFieldText"]];
[self setCheckmarkIndex:[aDecoder decodeObjectForKey:#"checkmarkIndex"]];
[self setApiKeyDictionary:[aDecoder decodeObjectForKey:#"apiKeyDictionary"]];
}
return self;
}
+(NSString *)archivePath {
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"bbyo.archive"];
}
-(BOOL)saveChanges {
return [NSKeyedArchiver archiveRootObject:self toFile:[SharedAppDataObject archivePath]];
}
#end
Save method from App Delegate:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
BOOL success = [[SharedAppDataObject sharedStore] saveChanges];
if (success) {
NSLog(#"Saved all the data");
} else {
NSLog(#"Didn't save any of the data");
}
}
Initialize sharedStore = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]]; in application:didFinishLaunchingWithOptions:. This method is used to initialize data structures and restore previous app state.
Also, take out static SharedAppDataObject *sharedStore = nil; from sharedStore. If the save file exists, [ShareAppDataObject sharedStore] will always unarchive the file which is not necessary. It can be unarchived once during initialization.
Here's a post that can answer your problem: http://bit.ly/PJO8fM
I cannot give you the answer but some ideas to figure this out. Taking this line:
sharedStore = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]];
So if the sharedStore is nil, something is wrong - so test for it. If nothing then log the path, and use NSFileManager methods to see if the file is there, its size etc. If you find the file is there and has size, but you cannot unarchive it, that's a problem of course. In that case, add special debug code just after you create the file:
-(BOOL)saveChanges {
BOO ret = [NSKeyedArchiver archiveRootObject:self toFile:[SharedAppDataObject archivePath]];
id foo = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]];
// check if foo is not nil, if its the proper class, etc.
}
If when you save the file you can unarchive it just fine, but cannot on restart of the app, then something is wrong with the file. All this info should point the way to a solution.
Another thought - when you encode the data, log it, just to be sure its not nil - but even if so the unarchive should work.