When the code below gets run (once it enters the method), there is a few seconds of freeze. I can't even scroll up or down. I am trying to remove that by using block but even with the code below it still freezes. Here is the code:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
dispatch_queue_t downloadQueu= dispatch_queue_create("downloader",NULL);
dispatch_async(downloadQueu, ^{
NSDictionary *placeDictionary =
[self.topPlaces objectAtIndex:self.tableView.indexPathForSelectedRow.row];
dispatch_async(dispatch_get_main_queue(), ^{
[[segue destinationViewController] setPhotoList:[FlickrFetcher photosInPlace:placeDictionary
maxResults:50]
withTitle:[[sender textLabel] text]];
});
});
}
You probably want to create your dispatch queue once, rather than on every call to this method. Also note that you can't use indexPathForSelectedRow from a background thread (i.e. your block); you need to take that and store it in a variable in your method, outside of your block. You may also have trouble with [topPlaces objectAtIndex:], unless you take care about thread safety.
Related
I have some code that I got from a book that uses performSelectorOnMainThread. I am finding that it is freezing the app in some cases and want to change it to a background thread using GCD:
This is my code:
[self performSelectorOnMainThread:#selector(fetchData:) withObject:data waitUntilDone:YES];
Should changing it to GCD background be as simple as
[self dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
(fetchData:) withObject:data
}];
That is currently throwing error expected ']'
Not quite. It needs to be:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self fetchData:data];
});
Note that dispatch_async is a function call, not a method call. It takes a block as the 2nd parameter. The code inside the block is where you put the actual code you want called on the background.
Following TDD I'm developing an iPad app that downloads some info from the internet and displays it on a list, allowing the user to filter that list using a search bar.
I want to test that, as the user types in the search bar, the internal variable with the filter text is updated, the filtered list of items is updated, and finally the table view receives a "reloadData" message.
These are my tests:
- (void)testSutChangesFilterTextWhenSearchBarTextChanges
{
// given
sut.filterText = #"previous text";
// when
[sut searchBar:nil textDidChange:#"new text"];
// then
assertThat(sut.filterText, is(equalTo(#"new text")));
}
- (void)testSutReloadsTableViewDataAfterChangeFilterTextFromSearchBar
{
// given
sut.tableView = mock([UITableView class]);
// when
[sut searchBar:nil textDidChange:#"new text"];
// then
[verify(sut.tableView) reloadData];
}
NOTE: Changing the "filterText" property triggers right now the actual filtering process, which has been tested in other tests.
This works OK as my searchBar delegate code was written as follows:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
self.filterText = searchText;
[self.tableView reloadData];
}
The problem is that filtering this data is becoming a heavy process that right now is being done on the main thread, so during that time the UI is blocked.
Therefore, I thought of doing something like this:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *filteredData = [self filteredDataWithText:searchText];
dispatch_async(dispatch_get_main_queue(), ^{
self.filteredData = filteredData;
[self.tableView reloadData];
});
});
}
So that the filtering process occurs in a different thread and when it has finished, the table is asked to reload its data.
The question is... how do I test these things inside dispatch_async calls?
Is there any elegant way of doing that other than time-based solutions? (like waiting for some time and expect that those tasks have finished, not very deterministic)
Or maybe I should put my code on a different way to make it more testable?
In case you need to know, I'm using OCMockito and OCHamcrest by Jon Reid.
Thanks in advance!!
There are two basic approaches. Either
Make things synchronous only while testing. Or,
Keep things asynchronous, but write an acceptance test that does resynchronizing.
To make things synchronous for testing only, extract the code that actually does work into their own methods. You already have -filteredDataWithText:. Here's another extraction:
- (void)updateTableWithFilteredData:(NSArray *)filteredData
{
self.filteredData = filteredData;
[self.tableView reloadData];
}
The real method that takes care of all the threading now looks like this:
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSArray *filteredData = [self filteredDataWithText:searchText];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateTableWithFilteredData:filteredData];
});
});
}
Notice that underneath all that threading fanciness, it really just calls two methods. So now to pretend that all that threading was done, have your tests just invoke those two methods in order:
NSArray *filteredData = [self filteredDataWithText:searchText];
[self updateTableWithFilteredData:filteredData];
This does mean that -searchBar:textDidChange: won't be covered by unit tests. A single manual test can confirm that it's dispatching the right things.
If you really want an automated test on the delegate method, write an acceptance test that has its own run loop. See Pattern for unit testing async queue that calls main queue on completion. (But keep acceptance tests in a separate test target. They're too slow to include with unit tests.)
Albite Jons options are very good options most of the time, sometime it creates less cluttered code when doing the following. For example if your API has a lot small methods that are synchronised using a dispatch queue.
Have a function like this (it could be a method of your class as well).
void dispatch(dispatch_queue_t queue, void (^block)())
{
if(queue)
{
dispatch_async(queue, block);
}
else
{
block();
}
}
Then use this function to call the blocks in your API methods
- (void)anAPIMethod
{
dispatch(dispQueue, ^
{
// dispatched code here
});
}
You would usually initialise the queue in your init method.
#implementation MyAPI
{
dispatch_queue_t dispQueue;
}
- (instancetype)init
{
self = [super init];
if (self)
{
dispQueue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
}
return self;
}
Then have a private method like this, to set this queue to nil. It is not part of your interface, the API consumer will never see this.
- (void) disableGCD
{
dispQueue = nil;
}
In your test target you create a category to expose the GCD disabling method:
#interface TTBLocationBasedTrackStore (Testing)
- (void) disableGCD;
#end
You call this in your test setup and your blocks will be called directly.
The advantage in my eyes is debugging. When a test case involves a runloop so that blocks are actually called, the problem is that there has to be a timeout involved. This timeout is usually quite short because you don't want to have tests that last long if the they run into the timeout. But having a short timeout means your test runs into the timeout when debugging.
I'm trying to perform the acquisition of data from the internet on the load of my view. To not lag the UI, I'm performing the HTML download and parsing by using
[self performSelectorInBackground:#selector(alertThreadMethod) withObject:nil];
which checks to see if there is an alert online. In order to display the information on the view however, iOS says that I need to use the main thread. So i call the display code right after:
[self performSelectorInBackground:#selector(alertThreadMethod) withObject:nil];
[self loadAlert];
In doing this, the [self loadAlert]; actually runs before the selector in the background (it is faster). Because of this, it does not have the information that the selector in the background is supposed to provide it.
How can I ensure that [self loadAlert]; runs after? Or is there a better way to do this?
You can either move loadAlert invocation into the alertThreadMethod or use Grand Central Dispatch serial queues, e.g.,
dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(queue, ^{
[self alertThreadMethod];
[self loadAlert];
});
dispatch_release(queue);
Or, if loadAlert is updating the UI, since you do UI updates in the main queue, you'd do something like:
dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", NULL);
dispatch_async(queue, ^{
[self alertThreadMethod];
dispatch_async(dispatch_get_main_queue(), ^{
[self loadAlert];
});
});
dispatch_release(queue);
By the way, if you're just doing this one task in background, rather than creating your own serial queue, you might just use one of the existing background queues. You only need to create a queue if you need the serial nature (i.e. you're going to be numerous dispatch_async calls and you can't have them running concurrently). But in this simple case, this might be even a little more efficient, bypassing the creating and releasing of the serial queue:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[self alertThreadMethod];
dispatch_async(dispatch_get_main_queue(), ^{
[self loadAlert];
});
});
In your alertThreadMethod, after you have your information, call the method performSelectorOnMainThread:withObject:waitUntilDone: and pass it a selector to your loadAlert method.
-(void)alertThreadMethod
{
// get your information here
performSelectorOnMainThread:#selector(loadAlert) withObject:nil waitUntilDone:NO
}
Currently i'm having trouble using two functions in my -(void)viewDidLoad, both of these functions uses NSUrlRequest to send HTTPPost to a webservice to recieve data.
It works fine untill [self bar] decides to kick in before [self foo] is completely finished. So, is there any smart way of checking if [self bar] is completely finished before starting [self foo]?
-(void)viewDidLoad{
[self foo]; // initiates a nsxmlparsercall to a webservice to get values.
[self bar]; // relies on the values recieved from [self foo] to make it's own call.
/* However, [self bar] always crashes before initiating it's request.
/* It crashes when the variables that are being sent with the poststring
/* are being set, as they are null.
/* Which means that the `[self foo]` doesnt get completed before starting [self bar];
}
I might be very off at this point, i've even considered overriding -(void)viewDidload and setting a bool to control when it's ok to fire the second function, but that seems like super poor coding..
Any suggestions and/or tips on how to point me in the right direction will be highly appreciated. Thanks in advance.
I best place to put your function will be one of the delegate methods of nsxmlparser that is
- (void)parserDidEndDocument:(NSXMLParser *)parser
{
[self bar];
}
This fine if you are parsing the response on a background thread and it doesn't matter if the function bar is called on main thread or background thread.
But if you want to call the bar function specifically on main thread then you can use this function
[self performSelectorOnMainThread:#SEL(bar) withObject:nil waitUntilDone:YES];
you mean in [self foo] function you want to parse some thing and when its completely parsed then you want to call [self bar]; function right?
okay then you can fire a notification when parsing gets completed. in by this notification you can call the method you want.
I'm trying to get data from a website- xml. Everything works fine.
But the UIButton remains pressed until the xml data is returned and thus if theres a problem with the internet service, it can't be corrected and the app is virtually unusable.
here are the calls:
{
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
if(!appDelegate.XMLdataArray.count > 0){
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[appDelegate GetApps]; //function that retrieves data from Website and puts into the array - XMLdataArray.
}
XMLViewController *controller = [[XMLViewController alloc] initWithNibName:#"MedGearsApps" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
[controller release];
}
It works fine, but how can I make the view buttons functional with getting stuck. In other words, I just want the UIButton and other UIButtons to be functional whiles the thing works in the background.
I heard about performSelectorInMainThread but I can't put it to practice correctly.
You don’t understand the threading model much and you’re probably going to shoot yourself in the foot if you start adding asynchronous code without really understanding what’s going on.
The code you wrote runs in the main application thread. But when you think about it, you don’t have to write no main function — you just implement the application delegate and the event callbacks (such as touch handlers) and somehow they run automatically when the time comes. This is not a magic, this is simply a Cocoa object called a Run Loop.
Run Loop is an object that receives all events, processes timers (as in NSTimer) and runs your code. Which means that when you, for example, do something when the user taps a button, the call tree looks a bit like this:
main thread running
main run loop
// fire timers
// receive events — aha, here we have an event, let’s call the handler
view::touchesBegan…
// use tapped some button, let’s fire the callback
someButton::touchUpInside
yourCode
Now yourCode does what you want to do and the Run Loop continues running. But when your code takes too long to finish, such as in your case, the Run Loop has to wait and therefore the events will not get processed until your code finishes. This is what you see in your application.
To solve the situation you have to run the long operation in another thread. This is not very hard, but you’ll have to think of a few potential problems nevertheless. Running in another thread can be as easy as calling performSelectorInBackground:
[appDelegate performSelectorInBackground:#selector(GetApps) withObject:nil];
And now you have to think of a way to tell the application the data has been loaded, such as using a notification or calling a selector on the main thread. By the way: storing the data in the application delegate (or even using the application delegate for loading the data) is not very elegant solution, but that’s another story.
If you do choose the performSelectorInBackground solution, take a look at a related question about memory management in secondary threads. You’ll need your own autorelease pool so that you won’t leak autoreleased objects.
Updating the answer after some time – nowadays it’s usually best to run the code in background using Grand Central Dispatch:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// No explicit autorelease pool needed here.
// The code runs in background, not strangling
// the main run loop.
[self doSomeLongOperation];
dispatch_sync(dispatch_get_main_queue(), ^{
// This will be called on the main thread, so that
// you can update the UI, for example.
[self longOperationDone];
});
});
Use NSURLConnection's connectionWithRequest:delegate: method. This will cause the specified request to be sent asynchronously. The delegate should respond to connection:didReceiveResponse: and will be sent that message once the response is completely received.
You can make use of a background operation that gets pushed into the operation queue:
BGOperation *op = [[BGOperation alloc] init];
[[self operationQueue] addOperation:op];
[op release];
I've created specific "commands" that get executed in the background:
#implementation BGOperation
# pragma mark Memory Management
- (BGOperation *)init
{
if ((self = [super init]) != nil)
/* nothing */;
return self;
}
- (void)dealloc
{
self.jobId = nil;
[super dealloc];
}
# pragma mark -
# pragma mark Background Operation
- (void)main
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[appDelegate GetApps];
[pool release];
return;
}
#end
After completion it might be a good idea to send a notification to the main thread because the internal database has been changed.
It looks as if you might be using NSURLConnection inside your getApps method. If so, you should convert it to an asynchronous call.