iOS threading - callback best practice - objective-c

I wanted to clean up one of my projects and extracted parts of my source that I often reuse, in a single class.
This class handles some requests to a web service, everything is fine so far ;). Until I extracted the code to its own class, I handled those requests with threads and callbacks in the calling class.
Now I have a "best practice" question:
In my code I do something like(simplified):
(void)foo{
Helper *h =[[Helper alloc]init];
[h doRequest];
}
doRequest performs a network action(in its own class)and I have to wait until this is request is finished. So I need a callback or something like this.
Should I simply thread doRequest incl. waituntildone=YES?
Do I have to thread the networking in the Helper class too? Or is it enough to call the method threaded something like this:
[NSThread detachNewThreadSelector:#selector(h doRequest) toTarget:self withObject:nil];
What is the best practice to get a callback from doRequest to the caller class after it has completed it’s tasks so that I can handle the returned values from the web service?
Thanks in advance.
Johannes

Given doRequest does not return until the request is done you could do
- (void)fooCompletion:(void (^)(void))completion {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
Helper *h =[[Helper alloc]init];
[h doRequest];
if (completion) {
dispatch_async(dispatch_get_main_queue(), ^{
// doRequest is done
completion();
});
}
});
}
To call the method:
[self fooCompletion:^{
// do something after doRequest is done
}];

I personally prefer calling performSelectorOnMainThread:withObject:waitUntilDone: at the end of any helper threads that need to send information back.
[self performSelectorOnMainThread:#selector(infoFromService:) withObject:aDictionaryWithInfo waitUntilDone:NO];
- (void)infoFromService:(NSDictionary *)aDictionary {
//Process all the information and update UI
}
Be sure to always use the main thread for any UI updates even if they happen in the middle of the worker thread, for example updating a count of how much information has been downloaded. Use the same technique to call the main thread with the relevant information.

Related

Convert synchronous to async Objective-C

I’m working in a new codebase and I don’t have many people who understand it, so I’m hoping I can get some help. I am updating an interface and some of the synchronous methods are now async which is making it difficult to fit into the current architecture for resolving data.
Currently we have a function map which stores these synchronous methods, then when we want the data we do “call” which executes the block/method and returns the value.
Some code below shows how it currently is.
fnMap[#“vid”] = [[Callback alloc] initWithBlock:^id(id param) {
return #([services getVisitorID]);
}];
… later, to resolve the data
id fnMapVal = [fnMap[key] call:nil];
Here is how a callback and callback block are defined.
typedef id (^CallbackBlock)(id);
#interface Callback : NSObject
#property(copy, nonatomic, readonly) CallbackBlock block;
- (instancetype)initWithBlock:(CallbackBlock)block
- (id)call:(id)param
{
return self.block(param);
}
Now the service needs to call an async method to get the ID so I had to change it to:
- (void)getVisitorID: (nullable void (^) (NSString* __nullable visitorIdentifier)) callback
{
[SDK getUserIdentifier:^(NSString * _Nullable userIdentifier) {
callback(userIdentifier);
}];
}
So the call is:
[services getVisitorID:^(NSString * _Nullable visitorIdentifier) {
}];
I haven’t been able to find a way to fit this into the current architecture. Some options I’ve explored is using a run loop to wait for the async method to finish and keep my interface synchronous but this sounds like a bad idea. I’m for some suggestions on how to fit this in as I’ve never seen something like this before.
You need to use dispatch_queues or NSOperationQueue to run your code off the main thread. Dispatch queues are very low level and good to just fire off async tasks:
// we're going to run the getVisitorID method on a background queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[services getVisitorID:^(NSString * _Nullable visitorIdentifier) {
dispatch_async(dispatch_get_main_queue(), ^(void){
// update the user interface on the main queue
});
}];
});
I prefer using NSOperationQueue because the API is cleaner, and they allow you to do more advanced things like make asynchronous task cancellable:
// create a background queue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:^{
[services getVisitorID:^(NSString * _Nullable visitorIdentifier) {
// get the main queue and add your UI update code to it
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// update the UI from here
}];
}];
}];
Queues are much easier to manage than messing around with run loops. For more info see here: https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html
What you need is the Future/Promise concept. Please don't reinvent the wheel, you will have to cover marathon distance trying to achieve the same functionality of requesting value and asynchronously consuming it.
As one of the favourites please consider PromiseKit. However don't be shy to explore the alternatives
You will quickly find it super delightful building chains of async tasks, mapping their results to different values, combining futures together to get a tuple of multiple resolved values once all are available - all that in a concise, well designed form, live tested in millions of applications.

Test code with dispatch_async calls

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.

How to update UI in a task completion block?

In my application, I let a progress indicator starts animation before I send a HTTP request.
The completion handler is defined in a block. After I get the response data, I hide the progress indicator from inside the block. My question is, as I know, UI updates must be performed in the main thread. How can I make sure it?
If I define a method in the window controller which updates UI, and let the block calls the method instead of updating UI directly, is it a solution?
Also, if your app targets iOS >= 4 you can use Grand Central Dispatch:
dispatch_async(dispatch_get_main_queue(), ^{
// This block will be executed asynchronously on the main thread.
});
This is useful when your custom logic cannot easily be expressed with the single selector and object arguments that the performSelect… methods take.
To execute a block synchronously, use dispatch_sync() – but make sure you’re not currently executing on the main queue or GCD will deadlock.
__block NSInteger alertResult; // The __block modifier makes alertResult writable
// from a referencing block.
void (^ getResponse)() = ^{
NSAlert *alert = …;
alertResult = [NSAlert runModal];
};
if ([NSThread isMainThread]) {
// We're currently executing on the main thread.
// We can execute the block directly.
getResponse();
} else {
dispatch_sync(dispatch_get_main_queue(), getResponse);
}
// Check the user response.
if (alertResult == …) {
…
}
You probably misunderstood something. Using blocks doesn't mean that your code is running in a background thread. There are many plugins that work asynchronously (in another thread) and use blocks.
There are a few options to solve your problem.
You can check if your code is running in the main thread my using [NSThread isMainThread]. That helps you to make sure that you're not in the background.
You can also perform actions in the main or background by using performSelectorInMainThread:SEL or performSelectorInBackground:SEL.
The app immediately crashes when you're trying to call the UI from a bakcground thread so it's quite easy to find a bug.

Objective-C fast enumeration and asynchronous server operations. Model help?

If I have a method called "-uploadToServer:(Object *)objectToUpload", and a mutable array of several Objects, and I want to upload each object one after the other, how could I best handle this?
There are three important considerations:
Don't want NSOperation because I don't want to deal with threading issues
Need to wait for notification of task completion before continuing
Server calls are asynchronous and non-blocking
Here is some code I already have:
for (Object *task in objectsToUpload) {
[self uploadToServer:task];
//need to wait to get notification that upload completed
}
-(void)uploadToServer:(Object *)objectToUpload {
//perform asynchronous server operation here
//either block callback or delegate to notify
//self that upload finished
}
Seeing the above, how do you think I should handle this?
Don't want NSOperation because I don't want to deal with threading issues
Honestly, I think this is your easiest option. The only other way is to do asynchronous IO and use the run loop.
With NSOperation, you'd need two different kinds of operation called e.g. UploadOperation and NotifyOperation: one to upload an object and one to send a notification to the main thread when everything is done.
Then you'd loop through thwe objects putting them all on an NSOperationQueue in an UploadOperation, each one dependent on the previous one (addDependency:). Finally, you'd put the NotifyOperation on the queue dependent on the last UploadOperation.
The NotifyOperation overrides main as follows
-(void) main
{
[someObjectEgViewController performSelectorOnMainThread: #selector(finishedUpload)
withObject: nil
waitUntilDone: NO];
}
objectsToUpload is an NSMutableArray of tasks
-(void)uploadToServer{
//check if there is a task available
if (objectsToUpload.count > 0){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
//get first task
id nextTask = [objectsToUpload objectAtIndex:0];
//do something
//complete async
dispatch_async(dispatch_get_main_queue(), ^(void) {
//remove completed task
[objectsToUpload removeObject:nextTask];
//complete async upload task, check or notify and or start the next task
BOOL shouldDoNextTask = [self check];
if (shouldDoNextTask){
[self uploadToServer];
}
});
});
}
}
I would suggest you do not need to wait for the task to complete. What you need is to respond to the task's completion.
NSURLConnection will provide a delegate with callback methods.

terminating a secondary thread from the main thread (cocoa)

I'm working on a small app written in objective-c with the help of the cocoa framework and I am having a multithreading issue.
I would really appreciate it if somebody could help me with some guidance on how terminate a secondary(worker) thread from the main thread?
- (IBAction)startWorking:(id)sender {
[NSThread detachNewThreadSelector:#selector(threadMain:) toTarget:self withObject:nil];
}
- (void)threadMain
{
// do a lot of boring, time consuming I/O here..
}
- (IBAction)stop:(id)sender {
// what now?
}
I've found something on apple's docs but what is missing from this example is the part where the runloop input source changes the exitNow value.
Also, I won't be using many threads in my app so I would prefer a simple solution (with less overhead) rather than a more complex one that is able to manage many threads easily, but with more overhead generated (eg. using locks maybe(?) instead of runloops)
Thanks in advance
I think the easiest way is to use NSThread's -(void)cancel method. You'll need a reference to the thread you've created, as well. Your example code would look something like this, if you can do the worker thread as a loop:
- (IBAction)startWorking:(id)sender {
myThread = [[NSThread alloc] initWithTarget:self selector:#selector(threadMain:) object:nil];
[myThread start];
}
- (void)threadMain
{
while(1)
{
// do IO here
if([[NSThread currentThread] isCancelled])
break;
}
}
- (IBAction)stop:(id)sender {
[myThread cancel];
[myThread release];
myThread = nil;
}
Of course, this will only cancel the thread between loop iterations. So, if you're doing some long blocking computation, you'll have to find a way to break it up into pieces so you can check isCancelled periodically.
Also take a look at the NSOperation and NSOperationQueue classes. It's another set of threading classes that make developing a worker thread model very easy to do.