Reference NSManagedObject entity from inside NSValueTransformer - ios

I'm using NSValueTranformer to encrypt certain Core Data attributes. This all works fine, except I need to be able to use a different encryption key depending on the NSManagedObject. Is there anyway I can access this entity from within my transformer class?
The use case is I have multiple users with different passwords that can access different NSManagedObject entities. If I use the same encryption key for all of the objects, someone could just reassign who owns them in the SQL db and they would still decrypt.
Any ideas on the best way to go about this?
Edit:
I should mention I'm doing this in iOS.

Third times the charm? Let me see if I can address your only-transform-when-going-to-disk requirement. Think of this as a hybrid of the other two approaches.
#interface UserSession : NSObject
+ (UserSession*)currentSession;
+ (void)setCurrentSession: (UserSession*)session;
- (id)initWithUserName: (NSString*)username andEncryptionKey: (NSData*)key;
#property (nonatomic, readonly) NSString* userName;
#property (nonatomic, readonly) NSData* encryptionKey;
#end
#implementation UserSession
static UserSession* gCurrentSession = nil;
+ (UserSession*)currentSession
{
#synchronized(self)
{
return gCurrentSession;
}
}
+ (void)setCurrentSession: (UserSession*)userSession
{
#synchronized(self)
{
gCurrentSession = userSession;
}
}
- (id)initWithUserName: (NSString*)username andEncryptionKey: (NSData*)key
{
if (self = [super init])
{
_userName = [username copy];
_encryptionKey = [key copy];
}
return self;
}
- (void)dealloc
{
_userName = nil;
_encryptionKey = nil;
}
#end
#interface EncryptingValueTransformer : NSValueTransformer
#end
#implementation EncryptingValueTransformer
- (id)transformedValue:(id)value
{
UserSession* session = [UserSession currentSession];
NSAssert(session, #"No user session! Can't decrypt!");
NSData* key = session.encryptionKey;
NSData* decryptedData = Decrypt(value, key);
return decryptedData;
}
- (id)reverseTransformedValue:(id)value
{
UserSession* session = [UserSession currentSession];
NSAssert(session, #"No user session! Can't encrypt!");
NSData* key = session.encryptionKey;
NSData* encryptedData = Encrypt(value, key);
return encryptedData;
}
#end
The only tricky part here is that you have to be sure that the current UserSession is set up before you create the managed object context and isn't changed until after the context is saved and deallocated.
Hope this helps.

You can create custom instances of NSValueTransformer subclasses that have state (i.e. the encryption key) and pass them in to -bind:toObject:withKeyPath:options: in the options dictionary using the NSValueTransformerBindingOption key.
You won't be able to set this up in IB directly since IB references value transformers by class name, but you can do it in code. If you're feeling extra ambitious you can set up the bindings in IB and then replace them with different options in code later.
It might look something like this:
#interface EncryptingValueTransformer : NSValueTransformer
#property (nonatomic,readwrite,copy) NSData* encryptionKey;
#end
#implementation EncryptingValueTransformer
- (void)dealloc
{
_encryptionKey = nil;
}
- (id)transformedValue:(id)value
{
if (!self.encryptionKey)
return nil;
// do the transformation
return value;
}
- (id)reverseTransformedValue:(id)value
{
if (!self.encryptionKey)
return nil;
// Do the reverse transformation
return value;
}
#end
#interface MyViewController : NSViewController
#property (nonatomic, readwrite, assign) IBOutlet NSControl* controlBoundToEncryptedValue;
#end
#implementation MyViewController
// Other stuff...
- (void)loadView
{
[super loadView];
// Replace IB's value tansformer binding settings (which will be by class and not instance) with specific,
// stateful instances.
for (NSString* binding in [self.controlBoundToEncryptedValue exposedBindings])
{
NSDictionary* bindingInfo = [self.controlBoundToEncryptedValue infoForBinding: binding];
NSDictionary* options = bindingInfo[NSOptionsKey];
if ([options[NSValueTransformerNameBindingOption] isEqual: NSStringFromClass([EncryptingValueTransformer class])])
{
// Out with the old
[self.controlBoundToEncryptedValue unbind: binding];
// In with the new
NSMutableDictionary* mutableOptions = [options mutableCopy];
mutableOptions[NSValueTransformerNameBindingOption] = nil;
mutableOptions[NSValueTransformerBindingOption] = [[EncryptingValueTransformer alloc] init];
[self.controlBoundToEncryptedValue bind: binding
toObject: bindingInfo[NSObservedObjectKey]
withKeyPath: bindingInfo[NSObservedKeyPathKey]
options: mutableOptions];
}
}
}
// Assuming you're using the standard representedObject pattern, this will get set every time you want
// your view to expose new model data. This is a good place to update the encryption key in the transformers'
// state...
- (void)setRepresentedObject:(id)representedObject
{
for (NSString* binding in [self.controlBoundToEncryptedValue exposedBindings])
{
id transformer = [self.controlBoundToEncryptedValue infoForBinding: NSValueBinding][NSOptionsKey][NSValueTransformerBindingOption];
EncryptingValueTransformer* encryptingTransformer = [transformer isKindOfClass: [EncryptingValueTransformer class]] ? (EncryptingValueTransformer*)transformer : nil;
encryptingTransformer.encryptionKey = nil;
}
[super setRepresentedObject:representedObject];
// Get key from model however...
NSData* encryptionKeySpecificToThisUser = /* Whatever it is... */ nil;
for (NSString* binding in [self.controlBoundToEncryptedValue exposedBindings])
{
id transformer = [self.controlBoundToEncryptedValue infoForBinding: NSValueBinding][NSOptionsKey][NSValueTransformerBindingOption];
EncryptingValueTransformer* encryptingTransformer = [transformer isKindOfClass: [EncryptingValueTransformer class]] ? (EncryptingValueTransformer*)transformer : nil;
encryptingTransformer.encryptionKey = encryptionKeySpecificToThisUser;
}
}
// ...Other stuff
#end

OK. This was bugging me so I thought about it some more... I think the easiest way is to have some sort of "session" object and then have a "derived property" on your managed object. Assuming you have an entity called UserData with a property called encryptedData, I whipped up some code that might help illustrate:
#interface UserData : NSManagedObject
#property (nonatomic, retain) NSData * unencryptedData;
#end
#interface UserData () // Private
#property (nonatomic, retain) NSData * encryptedData;
#end
// These functions defined elsewhere
NSData* Encrypt(NSData* clearData, NSData* key);
NSData* Decrypt(NSData* cipherData, NSData* key);
#interface UserSession : NSObject
+ (UserSession*)currentSession;
- (id)initWithUserName: (NSString*)username andEncryptionKey: (NSData*)key;
#property (nonatomic, readonly) NSString* userName;
#property (nonatomic, readonly) NSData* encryptionKey;
#end
#implementation UserData
#dynamic encryptedData;
#dynamic unencryptedData;
+ (NSSet*)keyPathsForValuesAffectingUnencryptedData
{
return [NSSet setWithObject: NSStringFromSelector(#selector(encryptedData))];
}
- (NSData*)unencryptedData
{
UserSession* session = [UserSession currentSession];
if (nil == session)
return nil;
NSData* key = session.encryptionKey;
NSData* encryptedData = self.encryptedData;
NSData* decryptedData = Decrypt(encryptedData, key);
return decryptedData;
}
- (void)setUnencryptedData:(NSData *)unencryptedData
{
UserSession* session = [UserSession currentSession];
NSAssert(session, #"No user session! Can't encrypt!");
NSData* key = session.encryptionKey;
NSData* encryptedData = Encrypt(unencryptedData, key);
self.encryptedData = encryptedData;
}
#end
#implementation UserSession
static UserSession* gCurrentSession = nil;
+ (UserSession*)currentSession
{
#synchronized(self)
{
return gCurrentSession;
}
}
+ (void)setCurrentSession: (UserSession*)userSession
{
#synchronized(self)
{
gCurrentSession = userSession;
}
}
- (id)initWithUserName: (NSString*)username andEncryptionKey: (NSData*)key
{
if (self = [super init])
{
_userName = [username copy];
_encryptionKey = [key copy];
}
return self;
}
-(void)dealloc
{
_userName = nil;
_encryptionKey = nil;
}
#end
The idea here is that when a given user logs in you create a new UserSession object and call +[UserSession setCurrentSession: [[UserSession alloc] initWithUserName: #"foo" andEncryptionKey: <whatever>]]. The derived property (unencryptedData) accessor and mutator get the current session and use the key to transform the values back and forth to the "real" property. (Also, don't skip over the +keyPathsForValuesAffectingUnencryptedData method. This tells the runtime about the relationship between the two properties, and will help things work more seamlessly.)

Related

Custom object returns NSDictionary as type

my object CCategory.h
#interface CCategory : NSObject
#property(strong, nonatomic) NSNumber * _Nonnull categoryId;
#property(strong, nonatomic) NSNumber * _Nonnull originalId;
#property(strong, nonatomic) NSString * _Nonnull name;
#property(strong, nonatomic) NSString * _Nonnull type;
#property(nonatomic, strong) CCategory * _Nullable parent;
#property (nullable, nonatomic, retain) NSOrderedSet<CCategory *> *children;
- (instancetype _Nonnull )initWithId:(NSNumber *_Nullable)categoryId
andOriginalId:(NSNumber *_Nullable)originalId
andName:(NSString *_Nonnull)name
andType:(NSString *_Nonnull)type
andParent:(CCategory *_Nullable)parent
andChildren:(NSOrderedSet<CCategory *> *_Nullable)children NS_DESIGNATED_INITIALIZER;
#end
CCategory.m
#implementation CCategory
- (instancetype)init {
return [self initWithId:0 andOriginalId:0 andName:#"" andType:#"" andParent:nil andChildren:nil];
}
- (instancetype)initWithId:(NSNumber *)categoryId
andOriginalId:(NSNumber *)originalId
andName:(NSString *)name
andType:(NSString *)type
andParent:(CCategory *)parent
andChildren:(NSOrderedSet<CCategory *> *)children {
self = [super init];
if (self) {
self.categoryId = categoryId;
self.originalId = originalId;
self.name = name;
self.type = type;
self.parent = parent;
self.children = children;
}
return self;
}
#end
This is how I check class type:
CCategory * firstItem = [itemsArray objectAtIndex:0];
CCategory *child = [firstItem.children objectAtIndex:0];
NSString *className = NSStringFromClass([child class]);
NSLog(#"First Item is: %#", className);
firstItem returns type CCategory, child returns type NSDictionary
After receiving from database object contains all data, but children for some reason is the NSDictionary type, not CCategory class type. Why is that? and how can I make children type CCategory?
Because you declare some object of some class doesn't mean that it's of the correct class.
If you write for instance
NSArray *array = [#[#"Hello"] firstObject];
array will be in fact a NSString object.
So, when you parse your response and create your CCategory object from what I guess a NSDictionary object.
That's why children seems to be in fact an NSOrderedSet of NSDictionary and not of CCategory objects.
A possible way to do it, is to call recursively initWithId:andOriginalId:andName:andType:andParent:andChildren: for the children.
So instead of self.children = children;
NSMutableOrderedSet *childrenSet = [[NSMutableOrderedSet alloc] init];
for (NSDictionary *aChildDict in [children array])
{
CCategory *aChild = [CCategory alloc] initWithId:aChildDict[keyWhereThereIsID], etc.]
[childrenSet addObject:aChild];
}
self.children = childrenSet;
But that's more of a hack to set it like that in the init method, because it says children should be NSOrderedSet<CCategory *> *.
So it's up to you, to either rename the method to be clear of what it does and maybe accept a NSOrderedSet<NSDictionary *> * for children instead, parse it before, create another one, etc.
One possible lazy option is to do that:
Rename to andChildren:(NSOrderedSet *)children
NSMutableOrderedSet *childrenSet = [[NSMutableOrderedSet alloc] init];
for (id *aChildObject in [children array])
{
CCategory *aChild = nil;
if ([aChildObject isKindOfClass:[NSDictionary class]]) //Need parsing
{
NSDictionary *aChildDict = (NSDictionary *)aChildObject;
aChild = [CCategory alloc] initWithId:aChildDict[keyWhereThereIsID], etc.];
}
else if ([aChildObject isKindOfClass:[CCategory class]]) //Already a CCategory Object
{
aChild = (CCategory *)aChildObject;
}
else
{
NSLog(#"Ooops, child of wrong class: %#", NSStringFromClass([aChildObject class]);
}
if (aChild) { [childrenSet addObject:aChild]; }
}
self.children = childrenSet;

How to call Objective-C instancetype method in Swift?

I have an Objective-C class which looks like this:
#interface CustomObjectHavingData : NSObject
#property (nonatomic, strong) NSData *objWithData;
- (instancetype)initWithObjHavingData;
#end
and implementation like this
#implementation CustomObjectHavingData
- (instancetype)initWithObjHavingData{
if (self = [super init]) {
NSString *str = #"This is simple string.";
self.objWithData = [str dataUsingEncoding:NSUTF8StringEncoding];
}
return self;
}
#end
Now I want to call this initWithObjHavingData in Swift
var objData = CustomObjectHavingData()
This returns nil to me. Please help how I can call the above init method here.
You are not supposed to write initializer like that in Objective C. Either you should have it just init or then if you are passing argument in constructor then only you can name it otherwise.
Here is how you can do it,
#interface CustomObjectHavingData : NSObject
#property (nonatomic, strong) NSData *objWithData;
- (instancetype)initWithObjHavingData:(NSData *)data;
#end
#implementation CustomObjectHavingData
- (instancetype)initWithObjHavingData:(NSData *)data
{
if (self = [super init]) {
_objWithData = data;
}
return self;
}
#end
In Swift, you can simply call it like this,
let myCustomObject = CustomObjectHavingData(objHavingData: someData)
The name is quite inappropriate though.
If you want to call the init method without any parameter with the requirements I posted in the question, we have to write the init method like this:
#interface CustomObjectHavingData : NSObject
#property (nonatomic, strong) NSData *objWithData;
- (id)init;
#end
And implement it like this
#implementation CustomObjectHavingData
- (instancetype)initWithObjHavingData{
if (self = [super init]) {
NSString *str = #"This is simple string.";
self.objWithData = [str dataUsingEncoding:NSUTF8StringEncoding];
}
return self;
}
#end
#implementation CustomObjectHavingData
- (instancetype)initWithObjHavingData:(NSData *)data
{
if (self = [super init]) {
_objWithData = data;
}
return self;
}
#end
Then, you can call this from swift like this:
var objData = CustomObjectHavingData()
It will by default initialize all the objects.
You can use this :
+ (Common *)sharedInstance
{
static Common *sharedInstance_ = nil;
static dispatch_once_t pred;
dispatch_once(&pred, ^{
sharedInstance_ = [[Common alloc] init];
});
return sharedInstance_;
}
After that for calling
var com_obj : Common!
com_obj = Common.sharedInstance()
com_obj.anyfunc(..)

How to set default value in iOS Mantle model subclass

#interface Entity ()
#property (assign) int searchTotalPagesAll;
#property (assign) int searchTotalPagesIdeas;
#end
#implementation Entity
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"Id": #"entity.id_entity",
#"name": #"entity.name",
#"coverage" : #"entity.coverage",
#"id_city": #"entity.Id_City",
#"cityName":#"entity.city",
#"countryName":#"entity.country",
#"stateName":#"entity.district",
#"countryCode": #"entity.countrycode",
#"keyword1": #"entity.key1",
... etc
Since mantle examples doesn't have a init method, where should I initialize those properties (searchTotalPagesAll, searchTotalPagesIdeas) for default values ? This model has internal methods that need this and several other properties.
Whether you create a Mantle model from JSON or otherwise, the model is initialised with [-initWithDictionary:error:]. In your model class, you can add your defaults to the values used to initialise the model:
- (instancetype)initWithDictionary:(NSDictionary *)dictionaryValue error:(NSError *__autoreleasing *)error {
NSDictionary *defaults = #{
#"searchTotalPagesAll" : #(10),
#"searchTotalPagesIdeas" : #(5)
};
dictionaryValue = [defaults mtl_dictionaryByAddingEntriesFromDictionary:dictionaryValue];
return [super initWithDictionary:dictionaryValue error:error];
}
You can set the default value in init method.
- (instancetype)init
{
self = [super init];
if (self) {
self.searchTotalPagesAll = 1;
self.searchTotalPagesIdeas = 2;
}
return self;
}

iOS: Singleton returning null

I have a singleton and I pass data to it but it returns null can you please help me in my situation. Thanks in advance :)
Here's my code
Card.h
#property (weak,nonatomic) NSString *email;
#property (weak,nonatomic) NSString *fName;
#property (weak,nonatomic) NSString *lName;
#property (weak,nonatomic) NSString *category;
+(Card *)getCard;
Card.m
#synthesize email;
#synthesize fName;
#synthesize lName;
#synthesize category;
static csCard *instance;
+(Card *) getCard
{
#synchronized (self)
{
if(instance == nil)
{
instance = [[Card alloc]init];
}
}
return instance;
}
- (id) init{
self.email = [[NSUserDefaults standardUserDefaults]stringForKey:#"email"];
self.fName = [[NSUserDefaults standardUserDefaults]stringForKey:#"firstName"];
self.lName = [[NSUserDefaults standardUserDefaults]stringForKey:#"lastName"];
self.category = #"TestCategory";
return self;
}
and here's my test code to see if it's working
Test.m
Card *card = [Card getCard];
[card setEmail:self.emailField.text];
NSLog(#"%#",card.email);
but this code give me (null)
Modify your class like this.
Card.h
#property (strong,nonatomic) NSString *email; //Let the modal be strong property
#property (strong,nonatomic) NSString *fName;
#property (strong,nonatomic) NSString *lName;
#property (strong,nonatomic) NSString *category;
+(Card *)getCard;
Card.m
static Card *instance;
+(Card *) getCard
{
#synchronized (self)
{
if(instance == nil)
{
instance = [[Card alloc]init];
}
}
return instance;
}
- (NSString)email{
return [[NSUserDefaults standardUserDefaults]stringForKey:#"email"];
}
- (void)setEmail:(NSString)email{
[[NSUserDefaults standardUserDefaults] setString:email forkey:#"email"];
}
No need of overriding init
in your test class
Card *card = [Card getCard];
[card setEmail:self.emailField.text];
NSLog(#"%#",card.email);
static csCard *instance;
+(csCard *) getCard
{
#synchronized (self)
{
if(instance == nil)
{
instance = [[csCard alloc]init];
}
}
return instance;
}
Replace it with this code
static Card *instance;
+(Card *) getCard
{
#synchronized (self)
{
if(instance == nil)
{
instance = [[Card alloc]init];
}
}
return instance;
}
The Class name Of the instance Object was wrong and In singleton method,the return datatype was also wrong. I think u will understand what I am saying.
+ (Card *)instance {
static Card *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[Card alloc] init];
});
return sharedInstance;
}
It should be work
With the help of what βhargavḯ sujjested u can modify your code as below because
in the line static csCard *instance; u are using csCard i think it is typo so better u can do like this,
#import "Card.h"
static dispatch_once_t onceDispach;
#implementation Card
#synthesize email = _email;
#synthesize fName;
#synthesize lName;
#synthesize category;
static Card *instance = nil; //change this to Card because this instance which is of type Card
+(Card *)getCard
{
dispatch_once(&onceDispach, ^{
instance = [[self alloc] init];//careate Card shared object named instance
});
return instance;
}
- (id) init
{
self.email = [[NSUserDefaults standardUserDefaults]stringForKey:#"email"];
self.fName = [[NSUserDefaults standardUserDefaults]stringForKey:#"firstName"];
self.lName = [[NSUserDefaults standardUserDefaults]stringForKey:#"lastName"];
self.category = #"TestCategory";
return self;
}
#end
- (NSString *)email
{
return _email;
}
- (void)setEmail:(NSString *)email
{
_email = email;
NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
[userDefault setObject:email forKey:#"email"];
}
in the class where u are using this shared instance use like below
- (void)actionMathodCalled
{
Card *card = [Card getCard];
NSLog(#"before saving to defaults->%#",card.email);
[card setEmail:#"happyCoding#ymail.com"];
NSLog(#"after savng to defaults->%#",card.email);
}

Can't restore archived data

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.

Resources