My app have a Trip entity which has a to-many relationship to Spot entity, and I need to load trip and its spot list in the background thread. It works fine in debug version, but in release version, the spot list is empty! So I dug a little and found, it cannot work in release version unless I set the compiler optimization level to -O0. I think it may be a bug of the compiler.
Is there any suggestion to make it work for higher optimization level, or I have to release a non-optimized app? Thanks!
Here is the code:
Main thread
[self performSelectorInBackground:#selector(calcRoute:) withObject:self.trip.objectID];
Background thread
- (void)calcRoute:(NSManagedObjectID *)tripId
{
//get trip entity
CoreDataHelper * helper = nil; //responsible for init model, persistentStore and context
TripEntity * t = nil;
helper = [[CoreDataHelper alloc] initWithAnother:mainThreadHelper]; //only take the resource name and storeURL
t = (TripEntity *)[helper.context objectWithID:tripId]; //retrieve trip
if (0 == t.spotList.count) {
[self performSelectorOnMainThread:#selector(drawRouteFailed) withObject:nil waitUntilDone:FALSE];
return; //I got here!
}
//...
}
Finally I solved the problem. The reason is: I 'used' the main thread trip entity. Here is the actual code:
- (void)calcRoute:(NSManagedObjectID *)tripId
{
//get trip entity
CoreDataHelper * helper = nil; //responsible for init model, persistentStore and context
TripEntity * t = nil;
if (self.backgroundDrow) { //sometimes I don't need background drawing
helper = [[CoreDataHelper alloc] initWithAnother:mainThreadHelper]; //only take the resource name and storeURL
t = (TripEntity *)[helper.context objectWithID:tripId]; //retrieve trip
} else
t = self.mainThreadTrip; //HERE is the problem!!!
if (0 == t.spotList.count) {
[self performSelectorOnMainThread:#selector(drawRouteFailed) withObject:nil waitUntilDone:FALSE];
return; //I got here!
}
//...
}
After I comment t = self.mainThreadTrip;, the background loading becomes OK!
But I still don't get the point! Would someone tell me the real reason? Thanks a lot!
Related
I am needing to use #autoreleasepool because I Am getting some high memory usage. The issue is, the objects that are taking up memory are created in one method on a background thread, then sent to a second method where they are used to update the UI.
- (void)createObjects {
TestObject = *test = [[TestObject alloc] init];
[self updateUIWithTestObject:test];
}
- (void)updateUIWithTestObject:(TestObject *)testObject {
// Update UI
self.textLabel.text = testObject.text;
}
Where would I be able to place the #autoreleasepool so that I can release the objects that I have created?
Ran allocation profiler on my code. Found following problem on the inserted code. Could someone please point me what is wrong in the code. Added the picture so as too show the color coding.
Code for initializing the point is as below:
#autoreleasepool {
if(!coordString){
return nil;
}
if([coordString length]<3){
return nil;
}
__weak NSArray* coords=[coordString componentsSeparatedByString:#","];
if(nil != coords && ([coords count]==2)){
self = [super init];
if(nil != self){
self.coordX= [[coords objectAtIndex:0] doubleValue];
self.coordY = [[coords objectAtIndex:1] doubleValue];
return self;
}else {
return nil;
}
}else{
return nil;
}
}
Please suggest where the problem might be.
Allocation snapshot indicates the persistance memory.
There is no straight and clear solution one should expect on the memory related issues such as raised by my question above as there is no clear error is committed in the code. Leaks not picking anything specific. Hence #Avi above gave me a hint that the "Persistent" indicates the abandoned allocations. So his comment above gave me a direction that I need to look into the problems elsewhere.
Someone who hasn't watched, should spend time understanding handling of memory issues using Instrument on the link https://developer.apple.com/videos/play/wwdc2012-242/
More important is to create the "dealloc" function for you such classes and create deallocation code for your code and set the pointers to nil. In my case above I made sure to do that using following code:
-(void) dealloc{
if(nil != self.arrVertices && [self isKindOfClass:[SPCPlane2D class]]){
[self.arrVertices removeAllObjects];
}
self.arrVertices=nil;
}
With few hours of digging further I have got the instrument showing as below
I am facing an issue dealing with when I download large data (using url pagination) from the internet and sav it in core data. During saving of the data my apps GUI becomes frozen. After saving the data my app works normally. I need your help with how can I remove the app freezing up issue.
I am downloading the data using NSURLConnection delegate method which is asynchronous. In connectionDidFinishLoading method I am inserting the data in core data. This is the code inside connectionDidFinishLoading method.
[Customer insertCustomerWithList:data];
[self.delegate connectPermissionServiceSuccessful:method];
Here on insertCustomerWithList method application becomes frozen.
If I use the dispatch_async GCD approach then the application starts to crash by giving this error.
ERROR*** Collection <NSCFSet: 0x7c2282e0> was mutated while
being enumerated.
This is the code with GCD.
dispatch_queue_t coreDataThread = dispatch_queue_create("com.YourApp.YourThreadName", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(coreDataThread, ^{
[Customer insertCustomerWithList:data];
dispatch_async(dispatch_get_main_queue(), ^{
[self.delegate connectPermissionServiceSuccessful:kMethod];
});
});
This is the insertCustomerWithList method which is inserting the data in core data.
+ (BOOL) insertCustomerWithList:(NSArray*)customerList
`{NSError *error = nil;
BOOL success = YES;
Customer *obj = nil;
NSManagedObjectContext *managedObjectContext = [NSManagedObjectContext managedObjectContext];`
if (managedObjectContext == nil)
{
success = NO;
}
#try
{
for (NSInteger index = 0; index < customerList.count ; index ++)
{
NSDictionary* customerInfo = customerList[index];
obj = [self getCustomerByID:customerInfo[#"_id"]];
if (obj == nil)
{
obj = [NSEntityDescription insertNewObjectForEntityForName:kEntityName
inManagedObjectContext:managedObjectContext];
[obj updateCustomer:customerInfo];
}
else
{
[obj updateCustomer:customerInfo];
}
}
}
#catch (NSException *exception)
{
NSLog(#"__ERROR__%#__", exception.reason);
}
[managedObjectContext save:&error];
return success;
`}`
Please help with how I can fix this issue.
You should be using NSManagedObjectContext's performBlock APIs rather than creating your own thread.
The following setup will not block your UI:
Root context (background) - save to persistent store
Main context (foreground) - child of Root, used on UI
Worker context (background) - child of Main, do heavy loading
The result block of your web call should create a worker context and insert the new data. Then the save() method will "push" the changes up to the main context, so if your UI needs to display the new data, that is already possible. The main context's save() pushes the changes to the root context and only when you call save() on the root context the changes are written to the database.
Use the block APIs for the background operations and you will be fine.
It's hard to give a specific answer, as you don't show how your Core Data stack is arranged.
For a basic app, the simplest approach is to create a persistent background context (NSPrivateQueueConcurrencyType) which is connected to the persistent store. For the main thread, create a child of your background context with NSMainQueueConcurrencyType.
You do the writing on the background context by issuing it a performBlock message. You can be notified in the child context by setting up an NSFetchedResultsController.
I need to do this : alloc objects until memory warning is called and then release all objects. But I have some problems. How can I do this? I need code example because the problem is that : the code. I have a class that doesn't use ARC. This class has a method which alloc N objects that are saved into an array. I need the memory is filled until didReceiveMemoryWarning is called because this is the only method to "free" RAM memory on iOS. Then, I will release all. I think the cleaner memory apps for the iPhone on the App Store use this trick to "free" the memory.
Thanks in advance
You'll have to fill in the missing details but here is what I have used before. Credit goes to who/where ever I found it. This will work on ARC and non ARC projects. I have found that usually you get 2-3 warnings before you're completely dead. Good luck. Dinner length is how much of a chunk that gets allocated each time. if you want more fine grained memory control change the size.
-(IBAction)startEatingMemory:(id)sender
{
if(self.belly == nil){
self.belly = [NSMutableArray array];
}
self.paused = false;
[self eatMemory];
}
- (IBAction)pauseEat:(id)sender {
self.paused = true;
[[self class]cancelPreviousPerformRequestsWithTarget:self selector:#selector(eatMemory) object:nil];
}
- (IBAction)stopEatingMemory:(id)sender {
[self pauseEat:self];
[self.belly removeAllObjects];
[[self class] cancelPreviousPerformRequestsWithTarget:self selector:#selector(eatMemory) object:nil];
}
-(void)eatMemory
{
unsigned long dinnerLength = 1024 * 1024;
char *dinner = malloc(sizeof(char) * dinnerLength);
for (int i=0; i < dinnerLength; i++)
{
//write to each byte ensure that the memory pages are actually allocated
dinner[i] = '0';
}
NSData *plate = [NSData dataWithBytesNoCopy:dinner length:dinnerLength freeWhenDone:YES];
[self.belly addObject:plate];
[self performSelector:#selector(eatMemory) withObject:nil afterDelay:.1];
}
-(void)didReceiveMemoryWarning
{
[self pauseEat:self];
<#Could release all here#>
[super didReceiveMemoryWarning];
}
I would edit/subclass the class that doesn't use ARC to either use ARC, or add a method to it that releases the N objects.
I need some help in using objects from Core Data with GCD; I seem to get NSManagedObjects that are aren't faulted into the main thread, even when I access their properties. Would appreciate some help.
This is what I'm doing: on launch, I need to load a list of Persons from the Core Data DB, do some custom processing in the background, then reload the table to show the names. I am following the guidelines for Core Data multi-threading by only passing in the objectIDs into the GCD queues. But when I reload the tableview on the main thread, I never see the name (or other properties) displayed for the contacts, and on closer inspection, the NSManagedObjects turn out to be faults on the main thread, even though I access various properties in cellForRowAtIndexPath. The name property is visible in the background thread when I NSLog it; and it's also showing correctly on the main thread in NSLogs in cellForRowAtIndexPath. But they don't show in the tableView no matter what I do. I tried accessing the name property using the dot notation, as well as valueForKey, but neither worked.
Here's my code …. it's called from the FRC initializer:
- (NSFetchedResultsController *)fetchedResultsController
{
if (__fetchedResultsController != nil)
{
return __fetchedResultsController;
}
__fetchedResultsController = [self newFetchedResultsControllerWithSearch:nil]; // creates a new FRC
[self filterAllContactsIntoDictionary: __fetchedResultsController];
return [[__fetchedResultsController retain] autorelease];
}
- (void) filterAllContactsIntoDictionary: (NSFetchedResultsController *) frc
{
NSArray *fetchedIDs = [[frc fetchedObjects] valueForKey:#"objectID"];
NSArray *fetched = [frc fetchedObjects];
if (filterMainQueue == nil) {
filterMainQueue = dispatch_queue_create("com.queue.FilterMainQueue", NULL);
}
dispatch_async(self.filterMainQueue, ^{
NSManagedObjectContext *backgroundContext = [[[NSManagedObjectContext alloc] init] autorelease];
[backgroundContext setPersistentStoreCoordinator:[[self.fetchedResultsController managedObjectContext] persistentStoreCoordinator]];
NSMutableArray *backgroundObjects = [[NSMutableArray alloc] initWithCapacity: fetchedIDs.count];
// load the NSManagedObjects in this background context
for (NSManagedObjectID *personID in fetchedIDs)
{
Person *personInContext = (Person *) [backgroundContext objectWithID: personID];
[backgroundObjects addObject:personInContext];
}
[self internal_filterFetchedContacts: backgroundObjects]; // loads contacts into custom buckets
// done loading contacts into character buckets ... reload tableview on main thread before moving on
dispatch_async(dispatch_get_main_queue(), ^{
CGPoint savedOffset = [self.tableView contentOffset];
[self.tableView reloadData];
[self.tableView setContentOffset:savedOffset];
});
});
}
What am I doing wrong here? Is there any other way to explicitly make the Person objects fire their faults on the main thread? Or am I doing something wrong with GCD queues and Core Data that I'm not aware of?
Thanks.
Why not take the easy route, since you are not saving anything new ?
Instead of creating an extra context for the background thread and working with IDs, use the main managedObjectContext in the background thread after locking it.
for example:
- (void) filterAllContactsIntoDictionary: (NSFetchedResultsController *) frc
{
if (filterMainQueue == nil) {
filterMainQueue = dispatch_queue_create("com.queue.FilterMainQueue", NULL);
}
dispatch_async(self.filterMainQueue, ^{
NSManagedObjectContext *context = ... // get the main context.
[context lock]; // lock the context.
// do something with the context as if it were on the main thread.
[context unlock]; // unlock the context.
dispatch_async(dispatch_get_main_queue(), ^{
CGPoint savedOffset = [self.tableView contentOffset];
[self.tableView reloadData];
[self.tableView setContentOffset:savedOffset];
});
});
}
This works for me when I call a method with performSelectorInBackground, so I guess it should work for GCD dispatch too.
Well, mergeChangesFromContextDidSaveNotification: is your friend. You'll need to tell the MOC on the main thread that there have been changes elsewhere. This will do the trick.
Here's Apple's documentation. To quote from there:
This method refreshes any objects which have been updated in the other context, faults in any newly-inserted objects, and invokes deleteObject:: on those which have been deleted.
EDIT: original answer removed, OP is not fetching in the background
I looked closer at your code and it doesn't look like you are doing anything that will change data and/or affect the context on the main thread.
You have a fetchedResultsController on the main thread. Presumably, this is working and your table is populating with data. Is this true?
When filterAllContentsIntoDictionary is invoked, you pass an array of the fetchedResultsController's current objectIDs to a background thread and do some processing on them (presumably filtering them based on some criteria) but you are not changing data and saving backgroundContext.
internalFilterFetchedContents is a black box. Without knowing what you intend for it to do, hard to say why it's not working.
When this is done, you reload the table on the main thread.
You haven't made any changes to the store, the context, or the fetchedResultsController so of course, the table shows the same data it did before. The missing details to help further are:
Is your tableView showing correct data from the fetchedResultsController to begin with? If not, most likely your only problem is in handling the tableView delegate and dataSource methods and the rest of this isn't really relevant.
What do you intend to happen in filterAllContentsIntoDictionary and internalFilterFetchedContents?
If your intent is to filter the data as displayed by the fetchedResultsController not sure you need to do anything in the background. If you modify the fetchRequest and do performFetch again your table will reload based on the new results.
I you need more help, please answer my questions, add more relevant code to your post and let me know if I'm missing anything wrt the problem and what you're trying to accomplish.
good luck!