I've been doing quite a bit of work on a fun little iPhone app. At one point, I get a bunch of player objects from my Persistant store, and then display them on the screen. I also have the options of adding new player objects (their just custom UIButtons) and removing selected players.
However, I believe I'm running into some memory management issues, in that somehow the app is not saving which "players" are being displayed.
Example: I have 4 players shown, I select them all and then delete them all. They all disappear. But if I exit and then reopen the application, they all are there again. As though they had never left. So somewhere in my code, they are not "really" getting removed.
MagicApp201AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
context = [appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *desc = [NSEntityDescription entityForName:#"Player" inManagedObjectContext:context];
[request setEntity:desc];
NSError *error;
NSMutableArray *objects = [[[context executeFetchRequest:request error:&error] mutableCopy] autorelease];
if (objects == nil) {
NSLog(#"Shit man, there was an error taking out the single player object when the view did load. ", error);
}
int j = 0;
while (j < [objects count]) {
if ([[[objects objectAtIndex:j] valueForKey:#"currentMultiPlayer"] boolValue] == NO) {
[objects removeObjectAtIndex:j];
j--;
}
else {
j++;
}
}
[self setPlayers:objects]; //This is a must, it NEEDS to work Objects are all the players playing
So in this snippit (in the viewdidLoad method), I grab the players out of the persistant store, and then remove the objects I don't want (those whose boolValue is NO), and the rest are kept. This works, I'm pretty sure. I think the issue is where I remove the players. Here is that code:
NSLog(#"Remove players");
/**
For each selected player:
Unselect them (remove them from SelectedPlayers)
Remove the button from the view
Remove the button object from the array
Remove the player from Players
*/
NSLog(#"Debugging Removal: %d", [selectedPlayers count]);
for (int i=0; i < [selectedPlayers count]; i++) {
NSManagedObject *rPlayer = [selectedPlayers objectAtIndex:i];
[rPlayer setValue:[NSNumber numberWithBool:NO] forKey:#"currentMultiPlayer"];
int index = [players indexOfObjectIdenticalTo:rPlayer]; //this is the index we need
for (int j = (index + 1); j < [players count]; j++) {
UIButton *tempButton = [playerButtons objectAtIndex:j];
tempButton.tag--;
}
NSError *error;
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
UIButton *aButton = [playerButtons objectAtIndex:index];
[players removeObjectAtIndex:index];
[aButton removeFromSuperview];
[playerButtons removeObjectAtIndex:index];
}
[selectedPlayers removeAllObjects];
NSError *error;
if ([context hasChanges] && ![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
NSLog(#"About to refresh YES");
[self refreshAllPlayers:YES];
The big part in the second code snippet is I set them to NO for currentMultiPlayer. NO NO NO NO NO, they should NOT come back when the view does load, NEVER ever ever. Not until I say so. No other relevant part of the code sets that to YES. Which makes me think... perhaps they aren't being saved. Perhaps that doesn't save, perhaps those objects aren't being managed anymore, and so they don't get saved in. Is there a lifetime (metaphorically) of NSManaged object? The Players array is the same I set in the "viewDidLoad" method, and SelectedPlayers holds players that are selected, references to NSManagedObjects. Does it have something to do with Removing them from the array? I'm so confused, some insight would be greatly appreciated!!
It looks like you're removing your player objects from your various internal arrays, but not deleting them from the NSManagedObjectContext itself.
After a lot more debugging (Quite a bit) I found the issue. The code worked fine, until you "Removed" the first player, or the player in slot 0. I looked at when the players are filtered:
while (j < [objects count]) {
if ([[[objects objectAtIndex:j] valueForKey:#"currentMultiPlayer"] boolValue] == NO) {
[objects removeObjectAtIndex:j];
j--;
}
else {
j++;
}
}
And realized that if the first player is not playing, then j becomes -1, and then it breaks out of the loop and fails to validate the rest of the players, causing the weird bug to have them all back in the game. It had nothing to do with memory management at all, but I did fix some leaks in my effort to find the bug. Thank you!
Related
I am using custom view to show the list of matches in my turn based game. With the custom view I am having issues showing the list of current games the player is involved when the device is offline. But when I check the game center default view the matches show fine even when offline. The code I am using to populate my array is as follows (extracted from the book by Ray Wenderlich)
[GKTurnBasedMatch loadMatchesWithCompletionHandler:^(NSArray *matches, NSError *error)
{
if (error)
{
NSLog(#"%#", error.localizedDescription);
}
else
{
NSMutableArray *myMatches = [NSMutableArray array];
NSMutableArray *otherMatches = [NSMutableArray array];
NSMutableArray *endedMatches = [NSMutableArray array];
for (GKTurnBasedMatch *m in matches)
{
GKTurnBasedMatchOutcome myOutcome;
for (GKTurnBasedParticipant *par in m.participants)
{
if ([par.playerID isEqualToString: [GKLocalPlayer localPlayer].playerID])
{
myOutcome = par.matchOutcome;
}
}
if (m.status != GKTurnBasedMatchStatusEnded && myOutcome != GKTurnBasedMatchOutcomeQuit)
{
if ([m.currentParticipant.playerID
isEqualToString:[GKLocalPlayer localPlayer].playerID])
{
[myMatches addObject:m];
}
else
{
[otherMatches addObject:m];
}
}
else
{
[endedMatches addObject:m];
}
}
// 6
allMyMatches = [[NSArray alloc]initWithObjects:myMatches,otherMatches,endedMatches, nil];
NSLog(#"%#",allMyMatches);
[self.tableView reloadData];
}
}];
Any ideas why this is happening?
loadMatchesWithCompletionHandler: will talk to the Game Center servers and I would expect it to fail if your device is offline. You are checking for error to be not nil. Is error.localizedDescription telling you you're not connected?
My bet would be that the Game Center default view will show you matches that it cached from the last time you were connected. You could do this too, but remember that you would also have to cache the matchData. Not sure how important that would be since you won't be able to submit your turn anyways.
I've been working on adding Core Data into my iPhone app, and I've been running into some very frustrating issues. When I call save on my context, the save returns successfully, however no data is getting added to my database(I am running this on the simulator and looking at the SQLITE file to check).
I am using the MYDocumentHandler class from this post to use a single UIManagedDocument across multiple classes. I run the code in my AppDelegate as follows:
if (!self.document) {
[[MYDocumentHandler sharedDocumentHandler] performWithDocument:^(UIManagedDocument *document) {
self.document = document;
self.context = document.managedObjectContext;
[self loadView];
}];
}
The loadView method setups up my view controllers once the document has been returned. In my view controllers that use the Core Data I again use something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
if(!self.document){
[[MYDocumentHandler sharedDocumentHandler] performWithDocument:^(UIManagedDocument *document) {
self.document = document;
self.context = document.managedObjectContext;
[self loadAll];
}];
}
}
Where the loadAll method setups everything for the view. When I try to save my data, I use the following:
for (int i = 0; i < [jsonArray count]; i++) {
NSDictionary *dictionary = [jsonArray objectAtIndex:i];
ProjectObject *tempProject = [[ProjectObject alloc] initWithDict:dictionary andETag:etag];
[tempAllProjects addObject:[Projects newProject:tempProject withContext:self.context]];
[tempProject release];
}
[self saveWithContext:self.context];
My saveWithContext method looks like this:
- (BOOL) saveWithContext:(NSManagedObjectContext *)context{
NSError *error = nil;
if (context != nil) {
if ([context hasChanges] && ![context save:&error]) {
DLog(#"Unresolved error %#, %#", error, [error userInfo]);
} else{
DLog(#"save was successful");
return YES;
}
}else{
DLog(#"context is nil");
return NO;
}
}
I always get the save was successful message, and I get the proper messages from the MYDocumentHandler file. Unfortunately, the data is simply not making it to the database. The data is definitely stored in the context, but its not going to the database. Any ideas?
Edit:
Here is the code where I create the Entities:
+ (Projects *) newProject:(ProjectObject *)project withContext:(NSManagedObjectContext *)context
{
Projects *newProject = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Projects"];
request.predicate = [NSPredicate predicateWithFormat:#"project_id = %#", project.project_id];
NSArray *results = [context executeFetchRequest:request error:nil];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if([results count] > 0){
newProject = [results objectAtIndex:0];
newProject.account_id = [NSString stringWithFormat:#"%#", [defaults objectForKey:#"account_id"]];
} else {
newProject = [NSEntityDescription insertNewObjectForEntityForName:#"Projects" inManagedObjectContext:context];
}
newProject.account_id = [NSString stringWithFormat:#"%#", [defaults objectForKey:#"account_id"]];
newProject.project_id = project.project_id;
newProject.name = project.name;
newProject.project_description = project.description;
newProject.updated_at = project.updated_at;
newProject.starred = project.starred;
newProject.etag = project.etag;
newProject.synced = [self hasConnection] ? [NSNumber numberWithInt:1] : [NSNumber numberWithInt:0];
return newProject;
}
I found the solution in this post. Not sure if this is the best way to handle it. If there is a better option, please let me know.
You are not saving Manage object context after changing it. I have added the lines into your code.
Please check if it works now.
I am facing an issue in updating to core data using batch updating on a background thread. In the below code I am using main thread to notify user with a progress view and a string which calls a method in appdelegate. But if I am getting bad access error in the NSEntity line in random number of data where I have thousands of objects to update.if I uncomment the NSLOG i have indicated below there is no error, or if i comment the main thread no error, or if I dont update through batch instead if I use bulk update then also no error. IF i comment autorelease pool also the error is appearing. Will some body help me on this please.
Thanks in advance,
Cheers,
Shravan
NSAutoreleasePool *tempPool = [[NSAutoreleasePool alloc] init];
NSUInteger iterator = 1;
for (int i = 0; i < totalNo; i++) {
NSDictionary *alertResult = [[alertResultList objectAtIndex:i] retain];
if (alertResult == nil) {
continue;
}
//managedObjectContext = [appDelegate.managedObjectContext retain];
NSLog(#"Object Count:%u", [[managedObjectContext insertedObjects]count]);
AlertResult *result = (AlertResult *)[NSEntityDescription
insertNewObjectForEntityForName:#"AlertResult"
inManagedObjectContext:managedObjectContext];
[result setUserName:#"A"];
iterator++;
//When count reaches max update count we are saving and draining the pool and resetting the pool
if (iterator == kUploadCount) {
if ([self update] == NO) {
// If unable to update Alert results in the Core Data repository, return
// a custom status code.
statusCode = -1;
}
[managedObjectContext reset];
[tempPool drain];
tempPool = [[NSAutoreleasePool alloc] init];
iterator = 0;
}
//Adding code to change the display string for the lock view to notify user
float count1 = (float)(counter/totalAlerts);
counter = counter + 1.0f;
NSString *dispStr = [NSString stringWithFormat:#"%f",count1];//[NSString stringWithFormat:#"Loading %d out of %d alerts",(i+1),totalAlerts];
NSString *dispMess = [NSString stringWithFormat:#"Alerts %d of %d",(i+1),totalNo];
[self performSelectorOnMainThread:#selector(changeLockScreenMessageWith:) withObject:[NSArray arrayWithObjects:dispStr,dispMess, nil] waitUntilDone:YES];
//NSLog(#"count"); /* If I uncomment this line code runs fine */
[alertResult release];
alertResult = nil;
}
//If count is inbetween the update limit we are updating and we are draining the pool
if (iterator != 0) {
if ([self update] == NO) {
// If unable to update Alert results in the Core Data repository, return
// a custom status code.
statusCode = -1;
}
[managedObjectContext reset];
//[tempPool drain];
}
//Out side the previous method
- (BOOL)update {
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"%#", [error userInfo]);
return NO;
}
return YES;
}
The most likely cause of the kind of crash you're describing is using your managedObjectContext across threads. managedObjectContext is not thread-safe. You must create a new MOC for each thread. I assume managedObjectContext is an ivar; you should never access your ivars directly like this (except in init and dealloc). Always use an accessor to handle memory management for you.
The reason NSLog makes it crash is because NSLog dramatically changes the timing of this function, and you have a race condition.
I am developing an application which upload number of photos and xml file in one attempt. I have two core data table with one-many(jobs & photos) relation. One job may contain number of photos. Once all the photos has uploaded I need to upload xml file which contain photos details. I need to keep track on which photo has upload successfully and update the jobs table's status field as well as photo status. Following code illustrate that.
This works some time. Some time this is not updating jobs table. I do appreciate is anyone can let me know what is wrong with following code.
NSMutableArray *photosForJob=[[NSMutableArray alloc] initWithArray:[fetchedJob.photos allObjects]];
self.manageObjectForJobs = fetchedJob;
__block int count = 0;
dispatch_group_async(group, queue, ^{
for (int i = 0; i < [photosForJob count]; i++)
{
Photos *ph = [photosForJob objectAtIndex:i];
if ([ph.status compare:[NSNumber numberWithBool:NO]] == NSOrderedSame)
{
NSMutableArray *responseArray = [self filePosting:ph.photoName];
self.manageObjectForPhotos = ph;
if ([[responseArray objectAtIndex:0] isEqual:#"200"] && [[responseArray objectAtIndex:1] isEqualToString:ph.photoName])
{
[self.manageObjectForPhotos setValue:[NSNumber numberWithBool:YES] forKey:#"status"];
count++;
}
}
else{
count++;
}
}
if (count == [photosForJob count])
{
if ([status compare:[NSNumber numberWithBool:NO]] == NSOrderedSame)
{
NSMutableArray *responseArray = [self filePosting:xmlFile];
if ([[responseArray objectAtIndex:0] isEqual:#"200"] && [[responseArray objectAtIndex:1] isEqualToString:xmlFile]){
[self.manageObjectForJobs setValue:[NSNumber numberWithBool:YES] forKey:#"status"];
}
}
}
NSError *error;
if (![self.managedObjectContext save:&error]) {
NSLog(#"Job status did updat.... : %#", [error description]);
}
else{
[UIApplication sharedApplication].applicationIconBadgeNumber = [self fetchJobsForBadge];
[photosForJob removeAllObjects];
count = 0;
}
});
Many Thanks
I'm writing an application that uses a UIPickerView and is storing each change to the pickers in core data (the name of the app is Wizard Blood in the iTunes store). I've had some success so far in that my -didSelectRow method successfully adds new objects to core data via the following code:
// Get context, entity, managedobject from the fetchedResultsController
[newManagedObject setValue:[NSDate date] forKey:#"timeStamp"];
//Iterates through each component and sets the value to the appropriate key
for (int j=0; j < [LifeCounter numberOfComponents]; j++) {
NSString *keyName = [NSString stringWithFormat:#"lifeTotal%i",j];
[newManagedObject setValue:[NSNumber numberWithInteger:[LifeCounter selectedRowInComponent:j]] forKey:keyName];
}
NSError *error;
if (![context save:&error]) {
NSLog(#"Error saving entity: %#", [error localizedDescription]);
}
However, in my -viewDidLoad I can't seem to get core data to load values from the latest object. This is what I have so far:
lifeCounterArray = [[NSMutableArray alloc] init];
for ( int i = 200; i >= -200; i-- )
{
NSString *myString = [NSString stringWithFormat:#"%i", i];
[lifeCounterArray addObject:myString];
}
//Hopefully this retrieves the latest values from the core data store
NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
//Iterates through each component, setting it to the stored value in core data
for (int j=0; j < [LifeCounter numberOfComponents]; j++) {
NSString *keyName = [NSString stringWithFormat:#"lifeTotal%i",j];
NSInteger row = [[managedObject valueForKey:keyName] integerValue] +180;
[LifeCounter selectRow:row inComponent:j animated:NO];
}
Now, I'm pretty sure my fetchedResultsController method is working since I can see it creating objects (I can see them in the sqlite database) but maybe I shouldn't even be using a fetchedResultsController since I really only care about the latest object. I'd be happy to post my fetchedResultsController method if needed, thanks for the help in advance.
If you've changed the data since you last queried might you have to delete the fetchedResultsController's cache?
[NSFetchedResultsController deleteCacheWithName:#"(cachename)"];
The cache name is set in initWithFetchRequest.
if that doesn't work, try setting the fetchedResultsController to nil after the db changes which will force it to redo the request.