I have a save method that saves info entered from a ViewController. I put that object with the saved info into an array, and I want to archive that array to a file. The app works fine until unarchive and try to NSLog the info. The app then crashes. Can anyone help me figure out why?
- (IBAction)Save:(UIButton *)sender {
self.homeworkAssignment = [[Homework alloc] init];
self.homeworkAssignment.className = self.ClassNameField.text;
self.homeworkAssignment.assignmentTitle = self.AssignmentTitleField.text;
self.homeworkAssignment.assignmentDiscription = self.DiscriptionTextView.text;
self.homeworkAssignment.pickerDate = self.DatePicker.date;
NSMutableArray *MyHomeworkArray = [[NSMutableArray alloc] init];
[MyHomeworkArray addObject:self.homeworkAssignment];
//Create file
NSString *filePath = [self dataFilePath];
//Archive my object
[NSKeyedArchiver archiveRootObject:MyHomeworkArray toFile:filePath];
//Unarchive my object to check
Homework *archivedHomework = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(#"%# %#", archivedHomework.className, archivedHomework.assignmentTitle);
//^^^^^If I comment this line out, the app does not crash^^^^^^^^^
And this is the method that creates the file
- (NSString*)dataFilePath
{
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [docDir stringByAppendingPathComponent:#"MyHomework.data"];
NSFileHandle *file = [NSFileHandle fileHandleForWritingAtPath:filePath];
if (!file) {
NSLog(#"Attempting to create the file");
if (![[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil]) {
NSLog(#"Failed to create file");
}
else
file = [NSFileHandle fileHandleForWritingAtPath:filePath];
}
return filePath;
}
Here is the output error info:
2013-10-09 20:22:03.524 HW1ARC[902:11303] -[__NSArrayM assignmentTitle]: unrecognized selector sent to instance 0x71c72a0
2013-10-09 20:22:03.524 HW1ARC[902:11303] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayM assignmentTitle]: unrecognized selector sent to instance 0x71c72a0'
* First throw call stack:
(0x1c96012 0x10d3e7e 0x1d214bd 0x1c85bbc 0x1c8594e 0x28df 0x10e7705 0x1b2c0 0x1b258 0xdc021 0xdc57f 0xdb6e8 0x2df1d3 0x1c5eafe 0x1c5ea3d 0x1c3c7c2 0x1c3bf44 0x1c3be1b 0x1bf07e3 0x1bf0668 0x17ffc 0x1dbd 0x1ce5 0x1)
libc++abi.dylib: terminate called throwing an exception
(lldb)
EDIT :
Homework.h
#interface Homework : NSObject
<
NSCoding
>
#property (nonatomic, strong) NSString *className;
#property (nonatomic, strong) NSString *assignmentTitle;
#property (nonatomic, strong) NSString *assignmentDiscription;
#property (nonatomic, strong) NSDate *pickerDate;
#property (nonatomic, strong) UISwitch *Switch;
#end
Homework.m
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.className forKey:#"className"];
[aCoder encodeObject:self.assignmentTitle forKey:#"assignmentTitle"];
[aCoder encodeObject:self.assignmentDiscription forKey:#"assignmentDiscription"];
[aCoder encodeObject:self.pickerDate forKey:#"pickerDate"];
[aCoder encodeObject:self.Switch forKey:#"Switch"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
self.className = [aDecoder decodeObjectForKey:#"className"];
self.assignmentDiscription = [aDecoder decodeObjectForKey:#"assignmentDiscription"];
self.assignmentTitle = [aDecoder decodeObjectForKey:#"assignmentTitle"];
self.pickerDate = [aDecoder decodeObjectForKey:#"pickerDate"];
self.Switch = [aDecoder decodeObjectForKey:#"Switch"];
}
return self;
}
#end
Look at what you are doing. You create a Homework object, add it to an array, then archive the array.
But when you unarchive the data, you assign the unarchived object (which is an array) to a Homework variable. You then attempt to use the array like a Homework object. This is why the error is coming from __NSArrayM (the internal representation of a mutable array).
This code:
//Unarchive my object to check
Homework *archivedHomework = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
NSLog(#"%# %#", archivedHomework.className, archivedHomework.assignmentTitle);
should be:
//Unarchive my object to check
NSMutableArray *homeworkArray = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];
Homework *archivedHomework = homeworkArray[0];
NSLog(#"%# %#", archivedHomework.className, archivedHomework.assignmentTitle);
Show the header of the Homework class, as well as it's implementations of initWithCoder and encodeWithCoder.
(In order for what you are trying to do to work, the Homework class must conform to NSCoding, which means it has to implement those 2 methods.)
Your method dataFilePath doesn't really make sense. It creates a path for a file in the documents directory, which is fine. It also creates an NSFileHandle as a local variable and then doesn't do anything with that file handle. There is no reason to create a file handle. It is not doing you any good, and might prevent your file from writing at all. Just build a path string and then use archiveRootObject: toFile: on the array, like you are doing.
The error you report, unrecognized selector assignmentTitle sent to an NSArray object sounds like you have a zombie (an object that is being deallocated and then has a message sent to it after it's memory is used for another object.)
Is your project ARC? Zombies are unusual with ARC, although still not impossible.
Related
For my iOS application that uses Parse, I need to store an array of custom objects into a PFObject. I tried doing this, and I am getting the error: 'NSInvalidArgumentException', reason: 'Invalid type in JSON write (JSQMessage)'
Is there any possible way to store an array of custom objects in Parse? I can't seem to find a good answer for this.
For your reference, I am using the JSQMessages view controller library at https://github.com/jessesquires/JSQMessagesViewController
The array that I am trying to add to the PFObject is initialized with the code:
[[NSMutableArray alloc] initWithObjects:
[[JSQMessage alloc] initWithText:initialtext sender:[[PFUser currentUser] objectId] date:[NSDate date]],
nil];
If your custom objects conform to the NSCoding protocol then yes, you can save them most definitely.
Here's an example for a Magic: The Gathering set
#interface MTGSet : NSObject <NSCoding>
#property (nonatomic, strong) NSString *name;
#property (nonatomic, strong) NSString *code;
#property (nonatomic, strong) NSDate *releaseDate;
#end
Implementation
#import "MTGSet.h"
#implementation MTGSet
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_name forKey:#"name"];
[aCoder encodeObject:_code forKey:#"code"];
[aCoder encodeObject:_releaseDate forKey:#"releaseDate"];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
return [self initWithJSONDictionary:
#{#"name" : [aDecoder decodeObjectForKey:#"name"],
#"code" : [aDecoder decodeObjectForKey:#"code"],
#"releaseDate" : [aDecoder decodeObjectForKey:#"releaseDate"]}];
}
#end
Usage
MTGSet *set = [MTGSet new];
set.name = #"Magic 2015";
set.code = #"M15";
set.releaseDate = [NSDate date];
NSData *dataFromSet = [NSKeyedArchiver archivedDataWithRootObject:set];
PFObject *object = [PFObject objectWithClassName:#"MyObject"];
object[#"set"] = dataFromSet;
[object save];
MTGSet *unarchivedSet = [NSKeyedUnarchiver unarchiveObjectWithData:object[#"set"]];
NSLog(#"Here's the set: %#", unarchivedSet);
There are libraries out there to make this easier using the objective-c runtime which I recommend.
https://github.com/eladb/Parse-NSCoding
How do I save/read an NSMutableArray filled with objects I made a class for?
This is what I have so far... (where 'ObjectA' represents my class, and 'objects' is an array containing many instances of 'ObjectA')
//Creating a file
//1) Search for the app's documents directory
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//2) Create the full file path by appending the desired file name
NSString *documentFileName = [documentsDirectory stringByAppendingPathComponent:#"save.dat"];
//Load the array
objects = [[NSMutableArray alloc] initWithContentsOfFile: _documentFileName];
if(objects == nil)
{
//Array file didn't exist... create a new one
objects = [[NSMutableArray alloc]init];
NSLog(#"Did not find saved list, Created new list.");
}
else
{
NSLog(#"Found saved list, Loading list.");
}
This will load an array if it exists. It works if the array is filled with property type objects like NSNumbers, I tested that. If I fill it with my own objects, it crashes! This is the error: (where 'objectAInt' is a private int belonging to 'ObjectA')
-[__NSCFNumber objectAInt]: unrecognized selector sent to instance 0x15d417f02013-11-19 17:28:58.645 My Project[1791:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFNumber objectAInt]: unrecognized selector sent to instance 0x15d417f0'
What do I need to do to make my class, 'ObjectA', work with the save/read process like it does with NSNumbers and other NS objects of type (id)?
Thank You!
P.S. My class implements and is NSCoding compliant. If more information is needed please don't hesitate to ask! :)
EDIT -- Here is my ObjectA (per request) (it is a subclass of NSObject)
//
// ObjectA.m
// My Project
//
// Created by Will Battel on 8/8/13.
//
//
#import "ObjectA.h"
#implementation ObjectA
#synthesize objectAString, objectAString2, objectAString3, objectAInt;
-(id)initWithName:(NSString *)_objectAString{
self = [super init];
objectAString = _objectAString;
objectAInt = 2;
objectAString3 = #"$0.00";
return self;
}
-(id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
NSLog(#"Decoding");
[self setObjectAString:[decoder decodeObjectForKey:#"objectAStringKey"]];
[self setObjectAString2:[decoder decodeObjectForKey:#"objectAString2Key"]];
[self setObjectAString3Price:[decoder decodeObjectForKey:#"objectAString3Key"]];
[self setobjectAInt:[decoder decodeIntForKey:#"objectAIntKey"]];
}
return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder {
NSLog(#"Encoding");
[encoder encodeObject:objectAString forKey:#"objectAStringKey"];
[encoder encodeObject:objectAString2 forKey:#"objectAString2Key"];
[encoder encodeObject:objectAString3 forKey:#"objectAString3Key"];
[encoder encodeInt:objectAInt forKey:#"objectAIntKey"];
}
#end
Since the array contains NSCoding compliant objects, you can use the coding methods, like this...
- (NSMutableArray *)instancesFromArchive {
// compute documentFileName using your original code
if ([[NSFileManager defaultManager] fileExistsAtPath:documentFileName]) {
return [NSKeyedUnarchiver unarchiveObjectWithFile:documentFileName];
} else {
return [NSMutableArray array];
}
}
// archives my array of objects
- (BOOL)archiveInstances {
// compute documentFileName using your original code
return [NSKeyedArchiver archiveRootObject:self.objects toFile:documentFileName];
}
NSNumber class is NSCoding compliant. Are your own objects NSCoding compliant?
I need it for my NSCoder classes apparently and it would be useful if I can see a real example of this and its corresponding method.
The reason I feel I need it is when my method tries to encodeObject for an NSString*, it complains saying:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -encodeBytes:length:forKey: only defined for abstract class. Define -[JSONEncoder encodeBytes:length:forKey:]!'
Thanks.
The method in JSONEncoder: (https://github.com/ontometrics/JSONCoding) I couldn't get the static library thing to work, so I'm trying to work directly with the code.
- (void)encodeObject:(id)object {
[self push:object];
[object encodeWithCoder:self];
finalJSONObject = [NSDictionary dictionaryWithObject:[self topObject] forKey:[[[object class] description] camelcaseString]];
[self pop];
}
I think that the JSONEncoder is mainly meant to encode objects from custom classes (which must implement the NSCoding protocol). For example, if you define a class Person with a string property:
#interface Person : NSObject <NSCoding>
#property(strong, nonatomic) NSString *name;
#end
#implementation Person
- (id)initWithCoder:(NSCoder *)coder {
if ((self = [super init])) {
self.name = [coder decodeObjectForKey:#"name"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.name forKey:#"name"];
}
#end
then you can encode an object of this class without problems using JSONEncoder:
JSONEncoder *encoder = [JSONEncoder encoder];
Person *obj = [[Person alloc] init];
obj.name = #"John";
[encoder encodeObject:obj];
NSString *json = [encoder json];
NSLog(#"%#", json);
// Output: {"person":{"name":"John"}}
It works also with some "plain" Foundation types, e.g. with NSNumber, but the output is not very "nice", and I am not sure if JSONEncoder is meant to be used like that:
JSONEncoder *encoder = [JSONEncoder encoder];
NSNumber *obj = #1234;
[encoder encodeObject:obj];
NSString *json = [encoder json];
NSLog(#"%#", json);
// Output: {"__NSCFNumber":{"NS.intval":1234}}
In this case, the encodeInt64:forKey: method of JSONEncoder is called internally.
However, as you noticed, it does not work with NSString:
JSONEncoder *encoder = [JSONEncoder encoder];
NSString *obj = #"Hello world";
[encoder encodeObject:obj];
// Exception: *** -encodeBytes:length:forKey: only defined for abstract class. Define -[JSONEncoder encodeBytes:length:forKey:]!
The reason for the exception is that NSString is NSCoding compliant, but it uses the -encodeBytes:length:forKey: of the coder, and JSONEncoder does not implement this method.
Implementing that method is not too difficult, this is a quick-and-dirty attempt:
- (void)encodeBytes:(const uint8_t *)bytesp length:(NSUInteger)lenv forKey:(NSString *)key
{
NSString *s = [[NSString alloc] initWithBytes:bytesp length:lenv encoding:NSUTF8StringEncoding];
[self setObject:s forKey:key];
}
If you add this code to "JSONEncoder.m", then you can encode a plain NSString without getting an exception, the result looks like this:
{"__NSCFConstantString":{"NS.bytes":"Hello world"}}
But again, I am not sure if JSONEncoder is meant to be used like this.
Short Version:
I define a property with (nonatomic, retain) and assumed that the property would be retained. But unless I call retain when assigning a dictionary to the property, The app crashes with an EXEC BAD ACCESS error.
Long Version:
I have a singleton which has a dictionary. The header is defined like this
#interface BRManager : NSObject {
}
#property (nonatomic, retain) NSMutableDictionary *gameState;
+ (id)sharedManager;
- (void) saveGameState;
#end
In the implementation file, I have a method that's called in the init. This method loads a plist form the bundle and makes a copy of it in the users documents folder on the device.
- (void) loadGameState
{
NSFileManager *fileManger=[NSFileManager defaultManager];
NSError *error;
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doumentDirectoryPath=[pathsArray objectAtIndex:0];
NSString *destinationPath= [doumentDirectoryPath stringByAppendingPathComponent:#"gameState.plist"];
NSLog(#"plist path %#",destinationPath);
if (![fileManger fileExistsAtPath:destinationPath]){
NSString *sourcePath=[[[NSBundle mainBundle] resourcePath]stringByAppendingPathComponent:#"gameStateTemplate.plist"];
[fileManger copyItemAtPath:sourcePath toPath:destinationPath error:&error];
gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath];
}else{
gameState = [NSMutableDictionary dictionaryWithContentsOfFile:destinationPath];
}
}
Now here's how I thought this should work. In the header I define the gameState property with (nonatomic, retain). I assumed (probably incorrectly) that 'retain' meant that the gameState dictionary would be retained. However, I have another method in my singleton (saveGameState) that get's called when the AppDelegate -> 'applicationWillResignActive'.
- (void) saveGameState
{
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doumentDirectoryPath=[pathsArray objectAtIndex:0];
NSString *plistPath = [doumentDirectoryPath stringByAppendingPathComponent:#"gameState.plist"];
[gameState writeToFile:plistPath atomically:YES];
}
This throws an EXEC BAD ACCESS error on gameState. If I modify loadGameState to retain the gameState dictionary, everything works as it should. eg:
gameState = [[NSMutableDictionary dictionaryWithContentsOfFile:sourcePath] retain];
I'm guessing this is the correct behaviour, but why? Does (nonatomic, retain) not mean what I think it means, or is something else at play here?
I've not really grok'd memory management yet, so I stumble on this stuff all the time.
You must use the accessor:
self.gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath];
or (is equivalent to):
[self setGameState:[NSMutableDictionary dictionaryWithContentsOfFile:sourcePath]];
instead of
gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath];
which only sets the ivar without any notion of property.
Where do you declare gameState as an ivar? I'm presuming you do so in the implementation.
The real problem is that in your implementation, you access gameState directly and don't actually invoke the property you've declared. To do so you must send self the appropriate message:
[self gameState]; // invokes the synthesized getter
[self setGameState:[NSMutableDictionary dictionaryWithContentsOfFile:sourcePath]]; // invokes the synthesized setter -- solves your problem
or
whatever = self.gameState; // invokes the getter
self.gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath]; // invokes the synthesized setter -- solves your problem
Make sure you get around to groking that memory management literature... this is a very basic question which, according to the strict rules of StackOverflow, I shouldn't be answering. Good luck!
I archive an array (NSMutableArray) of custom objects that implement the .
Once i load it froma file to a retaining property
#property (nonatomic, retain) NSMutableArray *buddies;
the release count of the object is 2 (correct, it's 1of autorelease + 1 of retain of the property) but then noone releases it and the retain count becames 1, so when i release it i get
-[__NSArrayM retainCount]: message sent to deallocated instance
(i think because the 1 retain count is the autorelease)
Here's the full code:
BuddieListViewController.h
#import <UIKit/UIKit.h>
#import "Buddie.h"
#interface BuddieListViewController : UITableViewController {
IBOutlet NSMutableArray *buddies;
[...]
}
[...]
#property (nonatomic, retain) NSMutableArray *buddies;
[...]
#end
BuddieListViewController.m
#import "BuddieListViewController.h"
#import "Buddie.h"
#import "PreviewViewController.h"
#implementation BuddieListViewController
#synthesize buddies;
[...]
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
[self loadFromDisk];
}
return self;
}
- (void)loadFromDisk {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *appFile = [documentsPath stringByAppendingPathComponent:#"BuddieArchive.ark"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:appFile]) {
self.buddies = [NSKeyedUnarchiver unarchiveObjectWithFile:appFile];
NSLog(#"1- buddies retain count %d (should be 2, 1 + 1autorelease)", [buddies retainCount]);
} else {
self.buddies = [NSMutableArray arrayWithCapacity:1];
}
}
[...]
- (IBAction)cancelledBuddie:(NSNotification *)notification
{
[editingBuddie release];
NSLog(#"2- buddies retain count %d (should be 2, 1 + 1autorelease)", [buddies retainCount]);
[buddies release];
[self loadFromDisk];
[self.tableView reloadData];
}
Has anyone some idea of why this happens?
I can't say it better than this:
The number returned by retainCount is
useless.
Don't rely on it for anything. Use the Leaks tool in Instruments to determine if you're leaking objects.
If you're crashing, it's most likely that you have a zombie. See this video to find out how to use Instruments to find zombies.
If you need to nullify the array, use the property accessor to set it to nil:
self.buddies = nil;
The synthesized implementation takes care of the memory management issues. Try to avoid sending -retain/-release messages directly to instance variables wherever possible and instead allow the property accessors to take care of things for you. It'll save you a lot of trouble.
Rather than releasing buddies why not just do a [self.buddies removeAllObjects] at the beginning of loadFromDisk.