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.
Related
I'm trying to make a restaurant queue app whose first page will be with three buttons, one of them being 'New Reservation'. When clicked on it, it opens a new view that takes in the customers name,phone number and total number of people, the waiter will enter the estimated time for their order and press enter which will save all these values in a plist. I'm referring to youtube videos to create plist, but I have a view controller class in which I initialize the plist and also a NewReservation class that controls the saving data. According to the video I create the plist in viewDidLoad method and also copy few lines of it in the IBAction of the button "enter details". I'm jumbled up and can't access the values in my NewReservation class, please clarify.
Thank you.
ViewController.h
#import <UIKit/UIKit.h>
#class NewReservation;
#interface ViewController : UIViewController
#property (nonatomic , strong) NewReservation *Res;
#end
ViewController.m
#import "ViewController.h"
#import "NewReservation.h"
#interface ViewController ()
#end
#implementation ViewController{
NSMutableArray *phoneNumbers , *name , *noOfPeople , *estTime;
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.Res = [[NewReservation alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"Property List.plist"];
if(![[NSFileManager defaultManager] fileExistsAtPath:plistPath]){
plistPath = [[NSBundle mainBundle] pathForResource:#"Property List" ofType:#"plist"];
}
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSString *errorDesc = nil;
NSPropertyListFormat format;
NSDictionary *temp = (NSDictionary *) [NSPropertyListSerialization propertyListWithData:plistXML options:NSPropertyListMutableContainersAndLeaves format:&format error:&errorDesc];
if(!temp){
NSLog(#"Error Reading plist: %# , format: %lu",errorDesc , format);
}
name = [NSMutableArray arrayWithArray:[temp objectForKey:#"name"]];
phoneNumbers = [NSMutableArray arrayWithArray:[temp objectForKey:#"phoneNumbers"]];
noOfPeople = [NSMutableArray arrayWithArray:[temp objectForKey:#"noOfPeople"]];
estTime = [NSMutableArray arrayWithArray:[temp objectForKey:#"estTime"]];
//Confused how to go further
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
NewReservation.h
#import <UIKit/UIKit.h>
#interface NewReservation : UIViewController{
}
#property (weak, nonatomic) IBOutlet UITextField *name;
#property (weak, nonatomic) IBOutlet UITextField *phoneNumber;
#property (weak, nonatomic) IBOutlet UITextField *noOfPeople;
#property (weak, nonatomic) IBOutlet UITextField *estTime;
- (IBAction)nameReturn:(id)sender;
- (IBAction)enterDetails:(id)sender;
#end
NewReservation.m
#import "NewReservation.h"
#implementation NewReservation
- (IBAction)enterDetails:(id)sender{
if([self.name.text isEqualToString:#""] || [self.phoneNumber.text isEqualToString:#""] || [self.noOfPeople.text isEqualToString:#""] || [self.estTime.text isEqualToString:#""]) {
UIAlertView *error = [[UIAlertView alloc] initWithTitle:#"Oops" message:#"You must complete all fields!" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[error show];
}
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsPath = [paths objectAtIndex:0];
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"Property List.plist"];
//Cant continue here cause its confusing me, i need to add the values and update the list everytime a new 'name,noOfPersons,phoneNumber,estTime
//is entered.
}
#end
An instance variable is unique to a class. By default, only the class and subclasses can access it. Therefore, as a fundamental principal of object-oriented programming, instance variables (ivars) are private—they are encapsulated by the class.
By contrast, a property is a public value that may or may not correspond to an instance variable. If you want to make an ivar public, you'd probably make a corresponding property. But at the same time, instance variables that you wish to keep private do not have corresponding properties, and so they cannot be accessed from outside of the class. You can also have a calculated property that does not correspond to an ivar…
Without a property, ivars can be kept hidden. In fact, unless an ivar is declared in a public header it is difficult to even determine that such an ivar exists.
Your child class have a properties that does not correspond to an ivar
To access the ivar with properties the ivar need to be synthesise properties in from child class.
#implementation NewReservation
#synthesize phoneNumbers, name, noOfPeople, estTime;
// ****
#end
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.
(storyboard image)
http://i.stack.imgur.com/DUZ12.png
Have 3 text fields where user inputs data and saves it. Upon opening the application, if there is any save data, the previous input is displayed within the text fields. Problem is, there is only one set of data, while it needs to be an array with multiple people's information. I would like to instead create a navigation controller with cells with names and upon clicking on them it displays the correlating contact info.
viewcontroller.h
#interface ArchiveViewController : UIViewController
#property (strong, nonatomic) IBOutlet UITextField *name;
#property (strong, nonatomic) IBOutlet UITextField *address;
#property (strong, nonatomic) IBOutlet UITextField *phone;
#property (strong, nonatomic) NSString *dataFilePath;
- (IBAction)saveData:(id)sender;
#end
viewcontroller.m
#interface ArchiveViewController ()
#end
#implementation ArchiveViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSFileManager *filemgr;
NSString *docsDir;
NSArray *dirPaths;
filemgr = [NSFileManager defaultManager];
// Get the documents directory
dirPaths = NSSearchPathForDirectoriesInDomains(
NSDocumentDirectory, NSUserDomainMask, YES);
docsDir = dirPaths[0];
// Build the path to the data file
_dataFilePath = [[NSString alloc] initWithString: [docsDir
stringByAppendingPathComponent: #"data.archive"]];
// Check if the file already exists
if ([filemgr fileExistsAtPath: _dataFilePath])
{
NSMutableArray *dataArray;
dataArray = [NSKeyedUnarchiver
unarchiveObjectWithFile: _dataFilePath];
_name.text = dataArray[0];
_address.text = dataArray[1];
_phone.text = dataArray[2];
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)saveData:(id)sender {
NSMutableArray *contactArray;
contactArray = [[NSMutableArray alloc] init];
[contactArray addObject:self.name.text];
[contactArray addObject:self.address.text];
[contactArray addObject:self.phone.text];
[NSKeyedArchiver archiveRootObject:
contactArray toFile:_dataFilePath];
}
#end
Thank you for your time.
Instead of having an array with 3 elements of text in it and using NSKeyedArchiver, have an array with dictionaries in it and save it with writeToFile:atomically:. This will use the array as a list of 'entries' rather than a list of fields and will save the data in a plist instead of a binary file.
Now, when you read in the array you can display a table view of the entries (just showing the name for example) and then when you show the archive view you would pass the controller the a appropriate dictionary.
For saving, it would be good to use delegation to pass the edit back to the master controller. But it could also be done directly (requires more knowledge in the detail controller) or by notification.
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 can pass basic data between classes, but when I try to pass a NSString* from my UIApplicationDelegate I get an EXC_BAD_ACCESS / NSZombie.
Is there something special I have to do to return an NSObject? Does this have to do with threading? (I thought the atomic setting on the property would take care of that?)
AppDelegate.h:
#interface AppDelegate : NSObject <UIApplicationDelegate> {
NSString * currentNoteName;
}
#property (atomic, assign) NSString *currentNoteName;
#end
AppDelegate.m:
- (void)timerCallback:(NSTimer *)timer {
currentNoteName = [NSString stringWithCString:(tone->freq).c_str() encoding:NSUTF8StringEncoding];
// This works:
NSLog(#"Current Note Name in timerCallback: %#", currentNoteName);
OtherObject.m:
// Returns a Zombie object & EXC_BAD_ACCESS:
NSString *currentNoteName = [appDelegate currentNoteName];
If not using ARC, you must using retain property:
#property (atomic, retain) NSString *currentNoteName;
and assign a value for it, using setter:
self.currentNoteName = [NSString stringWithCString: ...];
and don't forget to release instance of this ivar in your dealloc implementation of AppDelegate:
- (void) dealloc {
[currentNoteName release], currentNoteName = nil;
[super dealloc];
}
you are assigning a value and autoreleasing the NSString instance. Use retain instead.
The probleis is "assign", because the string from " [NSString stringWithCString" is auto-released.
Maybe u can change it to "copy" or "retain". (i think copy is better).