When NSDictionary is added to array, it replaces the previous dictionary - ios

UPDATE: Figured out some stuff and changed code.
When I add my NSDictionary to my array it suddenly replaces the previous dictionary I added last time. I don't know why this is happening. I am using a plist as data storage.
I get a error message like this:
Thread 1:Program received signal: "EXC_BAD_ACCESS".
Init
-(id)init{
self=[super init];
if(self){
dbArray = [[NSMutableArray alloc] init];
}
return self;
}
Adding a new item.
-(void)addNewItem:(NSString *)aString
{
// Creates a mutable dictionary with a anonymous string under the NAME key.
NSDictionary *newString = [[NSDictionary alloc] initWithObjectsAndKeys:aString,#"name", nil];
// Adds the new string to empty dbArray.
[dbArray addObject:(newString)];
NSLog(#"[add]:Added anonymous string to dbArray, under name key.");
// Writes the current dbArray (with the dict) to plist and releases retain counts.
[self writeItem];
[newString release];
}
My method to view my data.
-(void)viewData
{
// View data from the created plist file in the Documents directory.
NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *finalPath = [documentsDirectory stringByAppendingPathComponent:#"data.plist"];
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:finalPath]) {
self.dbArray = [NSMutableArray arrayWithContentsOfFile:finalPath];
}
else {
self.dbArray = [NSMutableArray array];
}
}

instead this
self.dbArray = [[NSMutableArray alloc] init];
use this
if( nil == self.dbArray ) {
self.dbArray = [[NSMutableArray alloc] init];
}
UPDATE: (based on provided code)
you're using different instances of DataObject class for displaying & saving data. Your content is over-written, because you don't load data from file during initialization of each instance; to fix that fast, you need to implement init method of your DataObject class as below:
- (id)init{
self = [super init];
if(self){
[self viewData];
}
return self;
}
the following code from viewDidLoad of ViewController class will crash your application very often:
db = [[DataObject alloc] init];
[db viewData];
[db release];
array = [[NSMutableArray alloc] initWithArray:[db dbArray]];
replace it with
db = [[DataObject alloc] init];
[db viewData];
array = [[NSMutableArray alloc] initWithArray:[db dbArray]];
call [db release] only in dealloc implementation
another problem, that you'll probably arise - is updated data is not displayed when you're back to the main screen; to fix that add the following method implementation to your ViewController.m file:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[db viewData];
self.array = [NSMutableArray arrayWithArray: db.dbArray];
[self.tableView reloadData];
}
also in AddView.m replace the following code
// Dismiss view and reload tableview.
ViewController *vc = [[ViewController alloc] init];
[self dismissModalViewControllerAnimated:YES];
[vc release];
with
// Dismiss view and reload tableview.
[self dismissModalViewControllerAnimated:YES];
Just as advise: see more information about using delegates and passing object instances & copies between objects.

I think you are creating a new Array:
self.dbArray = [[NSMutableArray alloc] init];
You should create the dbArray on the viewDidLoad or on the init of your UIViewController (I am assuming you are using this on an UIViewController)
inside your DataObject do the following:
-(id)init{
self=[super init];
if(self){
self.dbArray = [[NSMutableArray alloc] init];
}
return self;
}

Related

Objective C: After I set property to a value, when I check in debugger, it is still nil

I have a property that I am initializing in viewDidLoad, that is declared in my header file. In viewDidLoad I declare it here:
- (void)viewDidLoad {
[super viewDidLoad];
viewControllerList = [NSMutableArray new];
vc = [self.storyboard instantiateViewControllerWithIdentifier:#"moreSpeedyCafeViewController"];
[self loadDefaults];
self.dataSource = self;
In the loadDefaults method, I am iterating through an array and setting its properties. As I step through in the debugger, the values are nil even after being set. The values in the dictionary are not nil, I have checked that. I'm not sure why this is happening.
for (NSMutableDictionary *newDict in array) {
tempDict = newDict;
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:[[tempDict objectForKey:#"ImageUrls"]objectAtIndex:0]]];
vc.dmsImageView.image = [UIImage imageWithData:imageData];
vc.dmsDescriptionLabel.text = [tempDict objectForKey:#"Title"];
vc.dmsLinkText.text = [tempDict objectForKey:#"LinkText"];
vc.index = [array indexOfObject:tempDict];
[viewControllerList addObject:vc];
}

use the autorelease to release,the app will crash

I write an app (using no-arc)named Album which as the iPhone's native "Photo".
My Question:
1.
(please look the attached file name:1)when clicking the button of "+",then inputing some string and clicking the button of "save",the app will crash.But if change the code from "NSMutableArray *albumArr = [[[NSMutableArray alloc] init] autorelease];" to "NSMutableArray *albumArr = [[NSMutableArray alloc] init]", the app can work fine.But I think I should use the autorelease to release.
The related code:
// AlbumDB.m
+ (NSMutableArray *)fetchAlbumData
{
#warning why autorelease crash?
NSMutableArray *albumArr = [[[NSMutableArray alloc] init] autorelease];
FMDatabase *db = [FMDatabase databaseWithPath:[self dataBasePath]];
if ([db open]) {
NSString *sqlSelect = #"SELECT * FROM ALBUM";
FMResultSet *result = [db executeQuery:sqlSelect];
while ([result next]) {
AlbumModel *albumModel = [[AlbumModel alloc] init];
albumModel.albumid = [result intForColumn:#"albumid"];
albumModel.albumName = [result stringForColumn:#"albumName"];
[albumArr addObject:albumModel];
[albumModel release];
}
[db close];
}
return albumArr;
}
(please look the attached file name:2)when analyzing the code,I find the potential leak of an object.But in the dealloc,I had released.Why happen?
The related code:
//MainViewController.h
#property (nonatomic, retain) AlbumModel *editingAlbum;
// MainViewController.m
- (void)dealloc
{
[_albumArr release], _albumArr = nil;
self.editingAlbum = nil;
self.detailViewController = nil;
[super dealloc];
}
I think you should learn more about mrc.
In your first case, the albumArr if it is autorelease , it means when the runloop end ,it will be release, so the _albumArr will be nil when you use, you must retain it ,when you set the value to _albumArr.
In the second case, self.editingAlbum = [[AlbumModel alloc] init]; It will casuse the editingAlbum retain cout ==2 .You must change the code to like this:
AlbumModel *temp = [[AlbumModel alloc] init];
self.editingAlbum = temp;
[temp release];

This saved data doesn't appear to be accessible until relaunch iOS

I have a custom NSCoding class which stores and retrieves itself when necessary. However, it doesn't feed data to my table view until giving one entry to the array of custom Person objects inside it and restarting the app, then giving another. The first one disappears, however.
After that, it appears to load okay.
Here is the implementation of the class
#import "DataStorage.h"
#implementation DataStorage
#synthesize arrayOfPeople = _arrayOfPeople;
+ (DataStorage *)sharedInstance
{
static DataStorage *state = nil;
if ( !state )
{
NSData *data =[[NSUserDefaults standardUserDefaults] objectForKey:#"DataStorageBank"];
if (data)
{
state = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
else
{
state = [[DataStorage alloc] init];
}
}
return state;
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
if ([decoder decodeObjectForKey:#"DataStoragePeopleArray"]) {
_arrayOfPeople = [[decoder decodeObjectForKey:#"DataStoragePeopleArray"] mutableCopy];
} else {
_arrayOfPeople = [[NSMutableArray alloc] init];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:_arrayOfPeople forKey:#"DataStoragePeopleArray"];
}
- (void)save
{
NSData *appStateData = [NSKeyedArchiver archivedDataWithRootObject:self];
[[NSUserDefaults standardUserDefaults] setObject:appStateData forKey:#"DataStorageBank"];
}
#end
I add objects to the _arrayOfPeople like so:
Person *person = [[Person alloc] initWithFirstName:firstName personSurname:surname personCompay:company personPosition:position personEmail:email personMobile:mobile personProduct:product];
[[DataStorage sharedInstance].arrayOfPeople addObject:person];
[[DataStorage sharedInstance] save];
And load them into the table view by this:
Person *personAtIndex = [[DataStorage sharedInstance].arrayOfPeople objectAtIndex:indexPath.row];
[_arrayOfPeople addObject:personAtIndex];
cell.textLabel.text = personAtIndex.firstName;
cell.detailTextLabel.text = personAtIndex.surname;
Loading them in to the table view is in the method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
It looks like you only initialize _arrayOfPeople in initWithCoder:. However, if your data does not exist in user defaults already, you use state = [[DataStorage alloc] init] to initialize your shared instance. This does not call initWithCoder: so _arrayOfPeople is nil until after you save and load again, when it is finally initialized as [[NSMutableArray alloc] init]. To fix this, move _arrayOfPeople = [[NSMutableArray alloc] init] out of initWithCoder: and into init. (You could alternatively move it into sharedInstance, but it makes more sense in init since it is not specific to configuring the shared instance.)
Unrelated, but also make sure you synchronize.
- (void)save
{
NSData *appStateData = [NSKeyedArchiver archivedDataWithRootObject:self];
[[NSUserDefaults standardUserDefaults] setObject:appStateData forKey:#"DataStorageBank"];
[[NSUserDefaults standardUserDefaults] synchronize];
}

NSMutableArray Add and Save Objects

New to iOS and am stuck on one issue in regards to adding objects in NSMutable Array and displaying the array on another view within the App. The data displays fine on other view in TableView, but when I add another item to the Array (using code below), it just replaces what was there, not adding to the array.
- (void) postArray {
tableData = [[NSMutableArray alloc] initWithCapacity:10];
tableData = [[NSMutableArray alloc] initWithObjects: nil];
[tableData addObject:favShot]; }
-(NSString *) saveFilePath {
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, TRUE);
return [[path objectAtIndex:0] stringByAppendingPathComponent:#"savefile.plist"]; }
- (void) viewWillDisappear:(BOOL)animated: (UIApplication *) application {
NSArray *values = [[NSArray alloc] initWithObjects:tableData, nil];
[values writeToFile:[self saveFilePath] atomically: TRUE]; }
- (void)viewDidLoad {
tableData = [[NSMutableArray alloc] initWithObjects: nil];
NSString *myPath = [self saveFilePath];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:myPath];
if (fileExists)
{
NSArray *values = [[NSArray alloc] initWithContentsOfFile:myPath];
tableData = [values mutable copy];
}
UIApplication *myApp = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:myApp];
[super viewDidLoad]; }
Thank you.
Every time you call postArray you're creating an instance of NSMutableArray, then throwing it away (leaking it if you aren't using ARC). Then you're creating another instance of NSMutableArray. Then you're adding an object (favShot) to that second instance.
Next time you call postArray it's going to throw away your old array and create 2 new ones.
What you want to do is create the tableData instance when you create the controller instance, or when the view loads. Then, don't set tableData = ... as that will discard the old instance. Just add and remove objects.
edit
- (void) postArray {
[tableData addObject:favShot];
}
- (NSString *)saveFilePath {
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, TRUE);
return [[path objectAtIndex:0] stringByAppendingPathComponent:#"savefile.plist"];
}
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[tableData writeToFile:[self saveFilePath] atomically: TRUE];
}
- (void)viewDidLoad {
NSString *myPath = [self saveFilePath];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:myPath];
if (fileExists)
{
tableData = [[NSMutableArray alloc] initWithContentsOfFile:myPath];
} else {
tableData = [[NSMutableArray alloc] initWithObjects: nil];
}
UIApplication *myApp = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:myApp];
[super viewDidLoad];
}
Try
- (void) viewWillDisappear:(BOOL)animated: (UIApplication *) application
{
NSString *filePath = [self saveFilePath];
NSMutableArray *savedArray = [NSMutableArray arrayWithContentsOfFile:filePath];
if (!savedArray) savedArray = [NSMutableArray array];
[savedArray addObject:tableData];
[savedArray writeToFile:filePath atomically:YES];
}

My NSMutableArray suddenly becomes a CALayer

I think I am hallucinating. I am trying to add some persistence to my Concentration-lke game. I would like to keep track of high scores. I got this partially working for a little while today and now it has all gone kablooie (I think that is the correct iOS terminology). Now, my allHighScores NSMutablearray suddenly becomes a CALayer. I am using NSKeyed Archiving. I have a break point in my file before allHighScores gets loaded with data. When stepping through the application, allHighScores exists as an NSMutableArray - then, at the next step, it suddenly becomes a CA Layer. Huh?
-(id)init
{
self = [super init];
if (self) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
allHighScores = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
if (!allHighScores) {
allHighScores = [[NSMutableArray alloc] init];
}
}
return self;
}
+(FlipHighScoreStore *)sharedStore {
static FlipHighScoreStore *sharedStore = nil;
if (!sharedStore) {
sharedStore = [[super allocWithZone:nil]init];
}
return sharedStore;
}
Somehow, calling NSKeyedUnarchiver changes my allHighScores from an NSMutableArray into a CALayer. I am very confused.
I tried adding a retain to the unarchiving instruction, but that didn't help.
Here is my encoding/decoding code:
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.themeChosen forKey:#"themeChosen"];
[aCoder encodeInt:self.highScore forKey:#"highScore"];
[aCoder encodeInt:self.scoreStartLevel forKey:#"scoreStartLevel"];
[aCoder encodeInt:self.scoreFinishLevel forKey:#"scoreFinishLevel"];
[aCoder encodeObject:scoreDateCreated forKey:#"scoreDateCreated"];}
-(id)initWithCoder:(NSCoder *)aDecoder {
if (self) {
self.themeChosen = [aDecoder decodeObjectForKey:#"themeChosen"];
self.highScore = [aDecoder decodeIntForKey:#"highScore"];
self.scoreStartLevel = [aDecoder decodeIntForKey:#"scoreStartLevel"];
self.scoreFinishLevel = [aDecoder decodeIntForKey:#"scoreFinishLevel"];
scoreDateCreated = [aDecoder decodeObjectForKey:#"scoreDateCreated"];
}
return self;}
UPDATE: The program crashes when a "highscores.archive" file already exists and a save is called again. I can launch the app, look at the high scores - they are there and retrieved happily, but the save code:
-(BOOL)saveHighScores {
NSString *path = [self flipScoreArchivePath];
return [NSKeyedArchiver archiveRootObject:allHighScores toFile:path];}
causes a EXC_BAD_ACCESS. The path is right, so somehow the allHighScores isn't.
The problem here is you aren't retaining the results of the unarchiving. According to the Basic Memory Management Rules, a method by the name of +unarchiveObjectWithFile: will return an autoreleased object. As such, since you are placing it into an ivar, you need to retain this object, or it will get deallocated out from under you.
Although in your case, since you want a mutable array, you actually need to call -mutableCopy since NSKeyedUnarchive will just give you an immutable array.
-(id)init {
if ((self = [super init])) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
allHighScores = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] mutableCopy];
if (!allHighScores) {
allHighScores = [[NSMutableArray alloc] init];
}
}
return self;
}
Your -initWithCoder: isn't calling super. You need to say
if ((self = [super initWithCoder:aDecoder])) {
Have you tried this?
-(id)init {
if ((self = [super init])) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
allHighScores = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] mutableCopy];
if (!allHighScores) {
allHighScores = [[NSMutableArray alloc] init];
}
// All-important new line....
[self setAllHighScores:allHighScores];
}
return self;
}
Edit/Update:
So, here's two versions of what I actually intended in the above example (I'm assuming here that his ivar allHighScores has a corresponding property):
-(id)init {
if ((self = [super init])) {
NSString *path = [self flipScoreArchivePath];
NSLog(#"Path is %#", path);
self.allHighScores = [[NSKeyedUnarchiver unarchiveObjectWithFile:path] mutableCopy];
if (!self.allHighScores) {
self.allHighScores = [[NSMutableArray alloc] init];
}
}
return self;
}
This is the way I'd actually do it:
-(id)init {
if ((self = [super init])) {
NSMutableArray *arr = [[NSKeyedUnarchiver unarchiveObjectWithFile:[self flipScoreArchivePath]] mutableCopy];
if (!arr) arr = [[NSMutableArray alloc] init];
[self setAllHighScores:arr];
}
return self;
}

Resources