How to update a property of a ViewController from data retrieved asynchronously? - iphone

I would like to update a property of my ViewController self.matchedUsers, which takes data from a query that I run asynchronously through a block.
Then somewhere later When I retrieve the count via [self.matchedUsers count], I still get 0, despite knowing that multiple objects was added to my property. My question is, how do I ensure that my property gets updated even when I am retrieving data asynchronously through a block? Thanks!
Update:
For context, here is the block:
//Way earlier in an initializer:
self.matchedUsers = [[NSMutableArray alloc] init];
//In a method much later
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error){
//Code that updates self.matchedUsers with the NSArray objects
dispatch_async(dispatch_get_main_queue(), ^{
[self.matchedUsers addObjectsFromArray: objects];
});
//Question relates to ensure how property could be updated
}
}];

This should work provided you didn't forget to initialize matchedUsers, you check for its value after it's been changed and array does not lose its elements between the time you schedule and execute the block.
However, I would prefer to write a method that can be called from any thread, say
- (void)addUser ...
#synchronized(self.usersToAdd) {
[self.usersToAdd addObjectsFromArray: array];
Enqueue on main thread {
NSArray *addingNow;
#synchronized(self.usersToAdd) {
addingNow = [self.usersToAdd copy];
[self.usersToAdd removeObjects...
}
if (addingNow.count) {
[self.users addObjectsFromArray: addingNow;
[self.tableView insertRowsWithIndexPaths...
}
}
}
}

As others have written the problem could be missing initialization of matchedUsers but...
...the problem could also be due to your main thread being blocked. You write that you "somewhere later retrieve the count". If that is within the same method as the one that made the first dispatch you will be in trouble. Consider this code
NSMutableArray *collection = [[NSMutableArray alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSArray *array = [[NSArray alloc] initWithObjects:#"1", nil];
dispatch_async(dispatch_get_main_queue(), ^{
for (NSString *item in array){
[collection addObject:item];
}
NSLog(#"A");
});
});
[NSThread sleepForTimeInterval:5];
NSLog(#"B");
If this is running on the main thread it will output first B on then A (no matter the sleep time), because the block is not run until the method finishes executing. If you on the other hand dispatch to another global queue instead of the main queue it will be A and then B.

Related

Memory Management Headache

I get leaks if I dont put it in dealloc. I get a crash EXC_BAD_ACCESS If I do. I cannot see anything wrong with this code. The bad access is pointed at [events release]. Have I made a mistake in the code below or is Instruments just having a laugh at my expense?
events is an NSArray
#interface EventsViewController : UITableViewController
{
#private
NSArray *events;
}
- (void)viewDidLoad
{
events = [[self getEvents] retain];
}
- (void)dealloc
{
[events release];
[super dealloc];
}
- (NSArray*)getEvents
{
NSMutableArray *response = [[[NSMutableArray alloc] init] autorelease];
//Some sql
while(sqlite3_step(statement) == SQLITE_ROW)
{
Event *event = [[[Event alloc] init] autorelease];
event.subject = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
[response addObject:event];
}
return response;
}
Update
A lot of you are saying the code is fine which is a plus. I dont manipulate events elsewhere - I have removed any code that does to try and single out the crash. Perhaps its in the parent view?
This is the click event that pushes the EventsViewController:
- (void)eventsClick:(id)sender
{
EventsViewController *eventsViewController = [[EventsViewController alloc] initWithNibName:#"EventsViewController" bundle:nil];
eventsViewController.anywhereConnection = anywhereConnection;
eventsViewController.contact = contact;
[[self navigationController] pushViewController:eventsViewController animated:YES];
[eventsViewController release];
}
The crash is actually happening when I return to the parent view. (I think it is considered a parent in this scenario). But perhaps the [eventsViewController release] just triggers dealloc in the EventViewController.
Have you considered just refactoring your code to use ARC? It works with iOS 4 and up and will make your life a lot easier. There are plenty of tutorials out there that will guide you how to do it, and will remove the need to manually figure out the nuances of memory management.
If your Events object has property 'subject' set as assign, then the results of stringWithUTF8String: will not be retained. (Same thing if Events is a C++ object.)
The stringWithUTF8String: method returns an auto-released object that will be released at the next turn of the event loop.
There is a huge difference when you reference a variable via "self", and when you don't.
When you use
events = [[self getEvents] retain];
the memory allocated in getEvents never gets stored in the class property and is basically a leak.
You need to use
self.events = [self getEvents]; // no need to use retain if property is correctly defined.
Then
[events release];
should work fine.
try putting
events = nil;
in dealloc.

update ui while running in a loop

I have a read-only text field that I use as a log display. I have a operation that removes all the files in app's document directory. I want to insert a line of log when I remove each file. But the text field is only updated when the whole operation got finished. How do I fix this?
Here is my code:
NSFileManager *fm = [[[NSFileManager alloc] init] autorelease];
NSError *error = nil;
for (NSString *fileName in array) {
NSString *filePath = [DOCUMENT_PATH_VALUE stringByAppendingFormat:#"/%#", fileName];
[fm removeItemAtPath:filePath error:&error];
if (!error) {
NSString *log = [NSString stringWithFormat:#"removed success: %#", fileName];
[self logThis:log];
}else{
NSString *log = [NSString stringWithFormat:#"remove failed: %#, %#", fileName, [error localizedDescription] ];
[self logThis:log];
-(void)logThis:(NSString*) text{
NSRange range = NSMakeRange([updateLogTextView.text length], [text length]);
updateLogTextView.text = [updateLogTextView.text stringByAppendingFormat:#"%#\n", text];
[updateLogTextView scrollRangeToVisible:range];
}
You need to move your long-running operation into a background thread/queue and make sure that your UI-updating code is always executed on the main thread.
Example:
- (void)processFiles:(NSArray *)array
{
//You need to create your own autorelease pool in background threads:
NSAutoreleasePool *pool = [NSAutoreleasePool new];
//...
NSString *log = ...;
[self performSelectorOnMainThread:#selector(logThis:) withObject:log waitUntilDone:NO];
//...
[pool release];
}
You would then start your operation using:
[self performSelectorInBackground:#selector(processFiles:) withObject:files];
Using Grand Central Dispatch with blocks would be another option.
Refer to the Threading Programming Guide and the Concurrency Programming Guide for more in-depth information.
You need to look into Threading and making that call Asynchronous. You are currently blocking your main thread (assuming it's called from there). You need to separate the two so a separate thread does the heavy operation while your main thread is updated with the new text in the UI.
I've just started ios dev but sounds like you need to redraw that UIText I'm not sure how though I'd assume that when your log writes that you could dealloc your original UIText object the realloc it and it should redraw with the new log message. Only problem is that it will most likely update very very fast so redrawing wouldnt be worth it unless ios has a sleep function?
Something like:
Alloc and init text
Then in for loop after each write or new message in log dealloc your text and realloc and init your text
This should redraw the UIText object and update the text, if it doesnt expect a memory write error/app crash

performSelectorOnMainThread throws message deallocated

While parsing of the twitter data, I used threads to call the main URL to download the data. It does the downloading perfectly but when I hit the back button while the data is downloading it throws performSelectorOnMainThread message deallocated. I know we can use isCancelled but its not working for me yet. Does anyone have come across this issue and have resolved it.
- (void)LoadTwitterData
{
NSString *urlString =#"http://search.twitter.com/search.json?q=tabc&result_type=recent&rpp=2500";
NSURL *url = [NSURL URLWithString:urlString];
NSString *jsonString = [NSString stringWithContentsOfURL:url];
NSDictionary *values = [jsonString JSONValue];
/**** Throws here *****/
[self performSelectorOnMainThread:#selector(didFinishLoadingResults:) withObject:values waitUntilDone:NO];
}
If you spin off a thread using a selector on self, you need to make sure that self is retained for the duration of that thread, otherwise (as in your case) self can be deallocated and your thread will try to call back into a zombie. The easiest way to do this is to pass self to the thread as an argument. If you use performSelectorInBackground:withObject: you should do something like this:
[self performSelectorInBackground:#selector(LoadTwitterData) withObject:self];
Or if you use NSThread you should pass self to the object: initializer argument.
In fact the safest way to use thread methods is to make the method static like this:
+ (void)LoadTwitterData:(id)arg
{
// ...
MyController *self = arg;
// ... do work
[self performSelectorOnMainThread:#selector(didFinishLoadingResults:)
withObject:values waitUntilDone:NO];
}
This way you are unable to access instance variables by accident which avoids various multi-threading issues. Any and all data the thread needs, including the self to callback to, should be passed in as 'arg', which can be an array or dictionary or whatever you need. This way you know that everything the thread needs will be retained for the duration of the thread, and because you aren't accessing instance variables through self, another thread can't go and change them around underneath you.
Another thing you should do in a thread method is wrap the whole thing with an autorelease pool:
- (void)LoadTwitterData
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#try {
// ...
} #finally {
[pool drain];
}
}
If LoadTwitterData: is in a background thread, you need to create an Auto release pool (If you haven't already). Surround your code with-
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
//your code
[pool drain];

iPhone - Grand Central Dispatch invalid mutable array

if I do this
NSMutableArray *allColors;
NSData *dataColor = [dictPLIST objectForKey:#"allColors"];
if (dataColor != nil) {
allColors = [NSMutableArray arrayWithArray:
[NSKeyedUnarchiver unarchiveObjectWithData:dataColor]];
}
dataColor = nil;
my allColors mutable array has valid contents, but if I create a GGC group and do this...
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_group_t group = dispatch_group_create();
__block NSMutableArray *allColors;
dispatch_group_async(group, queue, ^{
NSData *dataColor = [dictPLIST objectForKey:#"allColors"];
if (dataColor != nil) {
allColors = [NSMutableArray arrayWithArray:
[NSKeyedUnarchiver unarchiveObjectWithData:dataColor]];
}
dataColor = nil;
});
// .... other stuff is added to the group
dispatch_group_notify(group, queue, ^{
dispatch_group_async(group, queue, ^{
// if I try to access allColors here, the app crashes
});
});
dispatch_release(group);
am I missing something?
thanks.
You are creating an autoreleased array and the autorelease pool is being drained by GCD sometime between when the first block executes and the second block executes.
Any time you are doing concurrent programming, whether by thread or using GCD, you must always hard retain any object that is to survive beyond one scope of execution.

Memory Management,Adding objects to array

Hi friends I am using this code
-(void)searchItem:(NSMutableArray*)items
{
if ([[[items objectAtIndex:1]objectForKey:#"Success"]isEqualToString:#"True"]) {
NSMutableArray *searcharr=[[NSMutableArray alloc]init];
for (int i=2; i<[items count]; i++)
{
NSAutoreleasePool *pool=[[NSAutoreleasePool alloc]init];
[searcharr addObject:[items objectAtIndex:i]];
NSLog(#"data fetch array is====> /n%#",searcharr);
[pool release];
}
searchItemsArray=[[NSMutableArray alloc]initWithArray:searcharr];
[searcharr release];
searcharr=nil;
}
I have released searchItemsArray in dealloc method.The items array i am getting from a webservice.It contains images and other data.I was using the for loop without NSAutoreleasePool,But when i used the instrument with simulator,i was getting the leak here.I just want to know that the code which i have given here with pool is correct or not.My app was also crashing as i was feeding this data and images in tableview cell.So please help me out.
And one more thing should i always use NSAutorelease pool,while looping ......Thanks
if the method "-(void)searchItem:(NSMutableArray*)items" get's called many times then searchItemsArray will be allocated each time resulting a memory leak. If you call release in the dealloc that means you will release only the instance created at the last call of the method.
you could do something similar to this:
-(void)searchItem:(NSMutableArray*)items
{
if ([[[items objectAtIndex:1]objectForKey:#"Success"]isEqualToString:#"True"])
{
if( nil == searchItemsArray )
searchItemsArray = [[NSMutableArray alloc]init];
else
[searchItemsArray removeAllObjects];
for (int i=2; i<[items count]; i++)
{
[searchItemsArray addObject:[items objectAtIndex:i]];
}
}
If you created the items array with alloc/init then after the call of "-(void)searchItem:(NSMutableArray*)items" you need to release it also.
If in a loop you create many many autoreleased objects that you will not use later then the autorelease pool would be an option.
Every time -autorelease is sent to an object, it is added to the inner-most autorelease pool. When the pool is drained, it simply sends -release to all the objects in the pool.
Autorelease pools are simply a convenience that allows you to defer sending -release until "later". That "later" can happen in several places, but the most common in Cocoa GUI apps is at the end of the current run loop cycle.
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
for(int i = 0; i < 100; i++)
{
NSString *string = [[[NSString alloc] init] autorelease];
/* use the string */
}
[pool drain]; //or release if you don't plan to use the pool later.
The code wasn't compiled sorry for the possible errors.
You can find more info about autorelease pools at this link