Are objects modified when Archived/Unarchived? - ios

So here is my problem. I am trying to archive an array of objects and when I unarchive them although the count is the same and the objects inside the root object are the same it does not find the specified object in the unarchived array.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"self.vendors.count = %d", self.vendors.count);
[NSKeyedArchiver archiveRootObject:self.vendors toFile:[self vendorsArchivePath]];
NSMutableArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:[self
vendorsArchivePath]];
NSLog(#"array.count = %d", array.count);
NSLog(#"%d", [self.vendors indexOfObject:self.vendors[indexPath.row]]);
NSLog(#"%d", [array indexOfObject:self.vendors[indexPath.row]]);
}
In order to troubleshoot myself; I have the counts of both arrays listed and at the bottom and the index of the object in each array in an NSLog.
Here is the log...
2014-04-27 12:53:04.813 Inventory[12272:907] self.vendors.count = 5
2014-04-27 12:53:04.827 Inventory[12272:907] array.count = 5
2014-04-27 12:53:04.828 Inventory[12272:907] 0
2014-04-27 12:53:04.831 Inventory[12272:907] 2147483647
So my question is: Why in the world is the last NSLog not finding the same object, that I archived and unarchived in the SAME function not being found in the array? Are objects modified when archived and unarchived? Or is there something else that I am missing.
EDIT: Here are my vendor.h and vendor.m files:
//vendor.h
#import <Foundation/Foundation.h>
#interface SELVendor : NSObject <NSCoding>
#property (nonatomic) NSMutableArray *itemsAvailable;
#property (nonatomic) NSString *name;
#property (nonatomic) NSString *phoneNumber;
#property (nonatomic) NSString *email;
#property (nonatomic) NSString *vendorKey;
#end
//vendor.m
#import "SELVendor.h"
#implementation SELVendor
-(instancetype) init {
self = [super init];
if (self) {
self.name = #"Unnamed Vendor";
self.itemsAvailable = [[NSMutableArray alloc] init];
NSUUID *uuid = [[NSUUID alloc] init];
self.vendorKey = [uuid UUIDString];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.name forKey:#"name"];
[aCoder encodeObject:self.itemsAvailable forKey:#"itemsAvailable"];
[aCoder encodeObject:self.phoneNumber forKey:#"phoneNumber"];
[aCoder encodeObject:self.email forKey:#"email"];
[aCoder encodeObject:self.vendorKey forKey:#"vendorKey"];
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
self.name = [aDecoder decodeObjectForKey:#"name"];
self.itemsAvailable = [aDecoder decodeObjectForKey:#"itemsAvailable"];
self.phoneNumber = [aDecoder decodeObjectForKey:#"phoneNumber"];
self.email = [aDecoder decodeObjectForKey:#"email"];
self.vendorKey = [aDecoder decodeObjectForKey:#"vendorKey"];
}
return self;
}
#end

This means that the object wasn't found 2147483647 == NSNotFound
What you're trying to do is save a array of objects, and then read them back out to a varaible. Doing this will create (alloc/init) the objects again so they won't occupy the same location in memory. Depending on how you implement your encoder/decode methods, the object might be exactly the same as the other one (content wise). What you need to do is override isEqual: (and hash) in your class and check the objects in there.
If you're objects have some kind of ID property you can just do:
-(BOOL)isEqual:(id)object
{
if (self == object) {
return YES;
}
if (![object isKindOfClass:[self class]]) {
return NO;
}
return [self.ID isEqual:object.ID];
}
If none of your properties on your object are unique, you can check each one in the isEqual: method.
See this answer for more info.

indexOfObject: uses isEqual: to test whether an object in the array is the "same" as the object you're looking for. When you archive and unarchive an object, the resulting object is not at the same memory address (it is effectively a copy of the original object).
Since the default implementation of isEqual: return YES only for the same object (memory address), this explains the behavior you're seeing. You probably want to override isEqual: (and hash, which you should usually override if you override isEqual:) to check whatever member variables are important for your notion of equality. (Or you can use one of a zillion libraries out there that make this easier, such as Mantle.)

Related

Weak properties & prepareForSegue: one property becomes null and the other one not [duplicate]

Why temp object is not released and set to nil even though it is declared as __week. But in case of Person object its working as expected. Do NSString objects memory life cycle is handled differently? How?
#interface Person : NSObject
#property(nonatomic, strong) NSString *name;
- (instancetype)initWithName:(NSString *)name;
+ (Person *)personWithName:(NSString *)name;
#end
#implementation Person
- (instancetype)initWithName:(NSString *)name {
if (self = [super init]) {
self.name = name;
}
return self;
}
+ (Person *)personWithName:(NSString *)name {
return [[self alloc] initWithName: name];
}
#end
- (void)viewDidLoad {
__weak NSString *temp;
#autoreleasepool {
NSMutableArray *data = [[NSMutableArray alloc] initWithObjects:#"firstString", #"secondString", nil];
temp = data[0];
[data removeObjectAtIndex:0];
}
NSLog(#"%#", temp);//prints firstString instead of null
__weak Person *person ;
#autoreleasepool {
NSMutableArray *persons = [[NSMutableArray alloc] initWithObjects:[Person personWithName:#"Steve"], [Person personWithName:#"Harry"], nil];
person = persons[0];
[persons removeObjectAtIndex:0];
}
NSLog(#"%#", person.name);//prints null as expected because person object will be deallocated,
}
Constant strings are never released. There are other objects that are never released, like certain NSNumber objects (on 64 bit versions, most NSNumber objects).
It makes me wonder what you are up to. What do you want to do if that NSString* becomes nil (which it won't)?

Save NSMutableArray of NSStrings to disk

I have a NSMutableaArray of NSString objects. So i'm using NSKeyedArchiever to save it to disk. So when i try to use
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.EventsList forKey:#"Events"];
}
i got an error
Event encodeWithCoder:]: unrecognized selector sent to instance 0x7fd06b542780
Here's my parts of code:
//-------------------Events.h--------------------------
#interface Event : NSObject
#property (strong,nonatomic) NSString *nameOfEvent;
#property (strong,nonatomic) NSString *dateOfEvent;
#property (strong,nonatomic) NSString *placeOfEvent;
#property int priorityOfEvent;
#end
//---------------Singleton.h ----------------
#interface GlobalSingleton : NSObject <NSCoding, NSCopying> {
NSMutableArray *EventsList;
}
#property (nonatomic,retain) NSMutableArray *EventsList;
+(GlobalSingleton *)sharedFavoritesSingleton;
#end
//----------------Singleton.m------------------------
....
#implementation GlobalSingleton
#synthesize EventsList;
....
....
- (void)encodeWithCoder:(NSCoder *)aCoder {
NSLog (#"%#",EventsList); // not nil
[aCoder encodeObject:self.EventsList forKey:#"Events"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super init])) {
NSMutableArray *temp = [[NSMutableArray alloc] initWithArray:[aDecoder decodeObjectForKey:#"Events"]];
self.EventsList = temp;
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
GlobalSingleton *copy = [[GlobalSingleton allocWithZone:zone] init];
copy.EventsList = self.EventsList;
return copy;
}
#end
I get textdata from Web-server using ASIFormDataRequest in JSON format, and then i add this object to NSMutableArray, which is also a Singleton, so it looks like this:
NSDictionary *responseDict = [responseString JSONValue];
GlobalSingleton *Singleton = [GlobalSingleton sharedFavoritesSingleton];
for (NSDictionary *str in responseDict) {
Event *newEvent = [[Event alloc] init];
newEvent.nameOfEvent = [str objectForKey:#"EventName"];
newEvent.dateOfEvent = [str objectForKey:#"EventDate"];
newEvent.placeOfEvent = [str objectForKey:#"EventPlace"];
[Singleton.EventsList addObject:newEvent];
}
//------------------Save this data stored in NSMutableArray to disk-------------------------
[NSKeyedArchiver archiveRootObject:Singleton toFile:[self save_path]];
So, again, execution stops on this:
[aCoder encodeObject:self.EventsList forKey:#"Events"];
But when i try to code single NSString object everything goes with no errors.
eventList doesn't contain NSStrings, it contains Event objects.
Your Event class needs to implement encodeWithCoder: - as the exception message says, the Event class doesn't implement this method.
Also you should use a lowercase s for singleton as it is an instance, not a class, and you should probably not use singletons.

Trouble setting an NSMutableDictionary inside of another NSMutableDictionary

I need to take information submitted by a user, store that information in an NSMutableDictionary, then store that NSMutableDictionary inside another NSMutableDictionary which is then encoded inside another class. For whatever reason, I can't seem to store the first NSMutableDictionary inside of the other.
I had to slim down the code that's in here due to work rules, so sorry if it seems to be missing anything. I only posted the parts that I'm having trouble with.
UserInfo.h:
#import <Foundation/Foundation.h>
#interface MyPlanInfo : NSObject <NSCoding>
#property (nonatomic, strong) NSMutableDictionary *emergencyDictionary;
#end
UserInfo.m:
#import <Foundation/Foundation.h>
#import "MyPlanInfo.h"
static NSString *emergencyDictionaryKey = #"emergencyDictionaryKey";
#implementation MyPlanInfo
#synthesize emergencyDictionary;
- (id) initWithCoder:(NSCoder *)coder
{
self = [super init];
self.emergencyDictionary = [coder decodeObjectForKey:emergencyDictionaryKey];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:self.emergencyDictionary forKey:emergencyDictionaryKey];
}
#end
infoView.h
#import <UIKit/UIKit.h>
#import "MyPlanInfo.h"
#interface infoView : UIViewController <NSCoding>
{
NSMutableDictionary *emergencyContactInfo;
NSArray *userInfo;
NSArray *userKeys;
NSMutableArray *tempArray;
}
#property (nonatomic, strong) MyPlanInfo *myPlanInfoObject;
-(void)saveUserInfo;
-(void)loadUserInfo;
#end
infoView.m:
#import "infoView.h"
#interface infoView ()
#end
#implementation infoView
static NSString *userInfoKey = #"userInfoKey";
static NSString *userName;
-(void)viewDidLoad
{
[super viewDidLoad];
if(!self.myPlanInfoObject)
{
self.myPlanInfoObject = [[MyPlanInfo alloc] init];
}
[self loadUserInfo];
}
-(void)addToDictionary
{
emergencyContactInfo = [NSMutableDictionary dictionaryWithObjects:userInfo forKeys:userKeys];
if([userInfo count] != 0 || userInfo == nil)
{
self.myPlanInfoObject.emergencyDictionary = [NSMutableDictionary dictionaryWithObject:emergencyContactInfo forKey:userName];
}
[self saveUserInfo];
}
- (void)saveUserInfo
{
NSData *userInfoData = [NSKeyedArchiver archivedDataWithRootObject:self.myPlanInfoObject];
[[NSUserDefaults standardUserDefaults] setObject:userInfoData forKey:userInfoKey];
}
- (void)loadUserInfo
{
NSData *userInfoData = [[NSUserDefaults standardUserDefaults] objectForKey:userInfoKey];
if(userInfoData)
{
self.myPlanInfoObject = [NSKeyedUnarchiver unarchiveObjectWithData:userInfoData];
}
}
#end
In infoView.m, in the addToDictionary method, userInfo is an array of user inputted information, and userKey's is an array of key's. The emergencyContactInfo NSMutableDictionary works just fine, everything is in it, but when I try to set that as an object in a new NSMutableDictionary, for a key, it doesn't work. Everything is nil.
Anyone have any ideas on how what I'm doing wrong?
Edit: If you down vote, please leave a reason as to why so that I can avoid doing whatever I did wrong in the future.
In the following line you’re creating an instance of MyPlanInfo using plain alloc/init:
self.myPlanInfoObject = [[MyPlanInfo alloc] init];
However, at least in the code provided, you haven’t overridden init in MyPlanInfo, but instead, initWithCoder::
- (id) initWithCoder:(NSCoder *)coder
{
self = [super init];
self.emergencyDictionary = [coder decodeObjectForKey:emergencyDictionaryKey];
return self;
}
When you use just plain init, the MyPlanInfo’s emergencyDictionary instance variable will be nil. You should likely add something like the following to MyPlanInfo to override init:
- (id) init
{
if ((self = [super init])) {
emergencyDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
That will assure that the newly created MyPlanInfo instance has a proper NSMutableDictionary that can be manipulated from other classes.

How to create an array outside of a function, and be able to add to it in other functions

It seems if I do something like:
NSMutableArray *randomSelection = [[NSMutableArray alloc] init];
Then this needs to be in a function, and I can't modify it later using a different function.
I tried just instantiating it in the .h file,
#interface ViewController:
{
NSMutableArray *Values;
}
But then when I try to append to it during runtime, nothing happens. I try to append to it with this:
int intRSSI = [RSSI intValue];
NSString* myRSSI = [#(intRSSI) stringValue];
[Values addObject:myRSSI];
But the array remains empty when I do this.
How can I fix this?
The recommended way is to create a property;
// ViewController.h
#interface ViewController : UIViewController
{
}
#property (nonatomic, strong) NSMutableArray *values;
#end
Then override the getter for that property, to lazy-initialize it, i.e. the array will be allocated and initialized on first call of the NSMutableArray property's getter:
// ViewController.m
#interface ViewController ()
#end
#implementation ViewController
- (NSMutableArray *)values
{
if (!_values) {
_values = [[NSMutableArray alloc] init];
}
return _values;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//int intRSSI = [RSSI intValue];
//NSString *myRSSI = [#(intRSSI) stringValue];
//[self.values addObject:myRSSI];
// Keep it simple:
[self.values addObject:RSSI];
}
#end

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