I am a newbie. I am using Grand Central Dispatch to populate an array (student_temp) on another thread. That part is working fine. The problem is I cannot pass the array to a class property (student_Array) where it is used throughout the class. I can't get the array back on the main thread.
it works fine until I get back tot he main thread and I can't pass student_temp into student_Array (the property) either inside or outside of GCD.
What am I doing wrong, or is there a better to populate the array property using GCD?
Thank you for your help. And please try to explain in non-technical language if possible I am new at this.
- (void)viewDidLoad
{
[super viewDidLoad];
R2LFetcher *studentFetch = [[R2LFetcher alloc] init];
__block NSMutableArray *student_temp = [[NSMutableArray alloc] init];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
//long-running code goes hereā¦
student_temp = [studentFetch fetchToStudentArray];
dispatch_async(dispatch_get_main_queue(), ^{
// code the updates the main thread (UI) here...
student_Array = student_temp;
});
});
student_Array = student_temp;
A couple of reactions:
In the last line of your code, you're setting student_Array to student_temp. Clearly that line makes no sense because you're populating student_temp asynchronously. And you're opening yourself up to synchronization issues if you're trying to simultaneously access the save variable in two queues. Don't bother to assign student_Array to student_temp at the end of viewDidLoad, but rather just do it inside the nested dispatch_async calls.
Inside the block, you're populating and setting student_temp. It probably makes more sense to make that variable scoped within that block, avoiding temptation to access it from outside that block as well as simplifying your code because the __block qualifier is no longer needed.
This block is running asynchronously, so when you update student_Array in the main queue, you might want to update your UI at the same time (e.g. reload the tableview or whatever). Perhaps you're doing that already and just removed it for the sake of brevity, but I just wanted to make sure.
Thus:
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
dispatch_async(queue, ^{
R2LFetcher *studentFetch = [[R2LFetcher alloc] init];
// long-running code goes here, for example ...
NSMutableArray *student_temp = [studentFetch fetchToStudentArray];
dispatch_async(dispatch_get_main_queue(), ^{
student_Array = student_temp;
// code the updates the main thread (UI) here, for example...
[self.tableView reloadData];
});
});
}
You should be able to add objects to student_Array directly from your block. Unlike stack variables, properties and ivars don't get copied when used inside a block. Instead, self gets retained in the block, and the property is referenced through it.
Of course, you need to be aware of concurrency issues, e.g. if you need to access the data from the main thread as well. For that, you probably still want to have this at the end of your async GCD block:
// done populating the data
dispatch_async(dispatch_get_main_queue(), ^{
// update the UI
}
Related
First post - please be gentle.
I am trying to port a remote control application from Android over to iPhone and am having some difficulty with the intra-thread communication using dispatch_async on a method in the main view.
The application has a separate ControlPanel class which takes care of communications to a remote client. The ControlPanel comms object is instantiated as a background thread which takes care of continuous network comms to a device and I need the background thread to pass simple text strings to the UI thread - updating a textfield.
I have looked at many examples, but none seem to deal with definitions and I've been going round in circles with this. How do I define the method in the ControlPanel class ? I have tried various ways of defining, but not making any headway. Am I missing something simple ? The updateKeypadDisplay in the viewDidLoad was suggested as the way to do this, but I don't understand the syntax enough to see where I am going wrong.
/*
* In the ControlPanel.m class the thread does stuff and wants to update the UI:
*/
NSString * kpString = #"String Updated By Process";
dispatch_async(dispatch_get_main_queue(), ^{
self.updateKeypadDisplay(kpString); // << This call gives an error - property not found on object of type ControlPanel
});
/*
* In the ControlPanel.h header :
*/
- (void)updateKeypadDisplay:(void (^)(NSString *kpString))block;
/*
* In the ViewController .m
*/
- (void)viewDidLoad
{
[super viewDidLoad];
kpdisplay.text = #"Initial String";
backgroundQueue = dispatch_queue_create("com.sss.tt.bgqueue", NULL);
dispatch_async(backgroundQueue, ^{
self.panelobj = [[ControlPanel alloc ]init];
[self.panelobj connectPanel];
});
// This was suggested as the way to define the method to update the main UI kpdisplay text field, but
// I cannot get the syntax right. I'm also unsure the definitions are correct in the headers.
//
// [ControlPanel updateKeypadDisplay::^(NSData *kpString) {
// self.kpdisplay.text = kpString;
// }
}
When you want to update the UI from a background thread you need to call dispatch async to the Main Thread (which is where all UI is handled).
dispatch_async(dispatch_get_main_queue(), ^(void){
NSLog(#"I can update UI elements here!");
});
I have an IOS project (ARC disabled) which has several view controllers. One particular controller initialises a member object of type MyClass, however when the view controller is dismissed, I'm calling a cleanup method for the object which uses a thread (using dispatch-async) to make some time consuming operations and then when these operations are done im executing a [self release] on the main queue for the object. Is this a good practise, will it cause any errors? Below is a similar example to what im doing:
#implementation ViewController
- (void)viewDidLoad
{
myObj = [[MyClass alloc] init];
}
-(void)viewWillDisappear
{
[myObj cleanup];
}
#end
//myClass
#implementation MyClass
- (void)cleanup()
{
dispatch_queue_t myQueue = dispatch_queue_create ("MyClassDeallocQueue", NULL);
dispatch_async(myQueue, ^{
//time consuming operations
dispatch_async(dispatch_get_main_queue(), ^{
[self release];
});
});
}
#end
Is this a good practise, will it cause any errors?
Currently, your code has an unbalanced retain/release. That is definitely an error (over release).
"Is it good practice?" - well, I don't know what you are trying to accomplish. But if your goal is to keep self alive, until after the block is executed, it is already accomplished purely through the fact that self will be captured. So, strictly a release is not needed.
However, if you NOT explicitly release self on the main thread, you introduce a subtle bug: it might happen that the block has the last reference to self, and since it may execute on some arbitrary thread, it will release self on this non-main thread. And this is forbidden: UIKit methods (including dealloc) MUST be called on the main thread!
Thus, it might make sense:
[self retain];
dispatch_async(myQueue, ^{
// time consuming operation, which captures `self`
[self doSomething];
...
// ensure that `dealloc` will be executed on the main thread, if
// last reference is held by the block:
dispatch_async(dispatch_get_main_queue(), ^{
[self release];
});
});
or shorter:
dispatch_async(myQueue, ^{
// time consuming operation, which captures `self`
[self doSomething];
...
// ensure that `dealloc` will be executed on the main thread, if
// last reference is held by the block:
dispatch_async(dispatch_get_main_queue(), ^{
[self self];
});
});
Edit:
It's an interesting question, whether the "short" version is actually tread-safe or has a race:
Suppose, self will be released in the block executed on myQueue, as the effect of capturing self before it will be retained in the same bock as an effect of capturing self for the block executed on the main queue. Then, we have an issue. Comments appreciated.
In my app, I have a UITableViewController.
Its tableView is divided in 3 sections.
I download datas for each of those sections from my server. To do this, I have 3 functions (for example f1 f2 and f3). Each updates a corresponding NSArray, used as data source for my table.
Now what I want is to reload datas using this functions and refresh my tableView once this 3 functions are done, but without disturbing the user.
I'm not used with asynchronous request, blocks, threads etc... and I'm looking for tips.
Actually, here is what I do :
-(void)viewDidLoad
{
//some settings
[NSTimer scheduledTimerWithTimeInterval:15.0 target:self selector:#selector(reloadDatas) userInfo:nil repeats:YES];
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
[self reloadDatas];
});
}
-(void)reloadDatas
{
dispatch_queue_t concurrentQueue = dispatch_get_main_queue();
dispatch_async(concurrentQueue, ^{
[self f1];
[self f2];
[self f3];
[myDisplayedTable reloadData];
});
}
-(void)f1
{
//load datas with a url request and update array1
}
-(void)f2
{
//load datas with a url request and update array2
}
-(void)f3
{
//load datas with a url request and update array3
}
But here, my tableView is "frozen" until it is refreshed.
I don't care about the order of execution of f1 f2 and f3, but I need to wait for this 3 functions to be done before refresh my tableView.
Thanks for your help.
EDIT
Thanks for all your answers.
Here is the working solution :
As mros suggets, I removed the dispatch queue from the viewDidLoad, and replace in reloadDatas:
dispatch_queue_t concurrentQueue = dispatch_get_main_queue();
with
dispatch_queue_t mainThreadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
And finally, I reload my table in a main thread
dispatch_async(dispatch_get_main_queue(), ^{ [myDisplayedTable reloadData]; });
So your "background thread" is actually your main thread. You have to use dispatch_get_global_queue and specify a priority to actually get a different thread. Also, the dispatch async in viewDidLoad is useless as all view controller lifecycle methods are called in the main thread. I would recommend doing something as follows in your f1, f2 and f3 methods:
Start by launching an asynchronous url request, then in the completion block, update arrayX and reload a particular section of your tableview. This way all three requests can happen simultaneously and the table just updates the necessary data when each one finishes. Alternatively, if you only want to reload once, just replace the concurrentQueue variable you have with a background thread and then perform [tableView reloadData] on the main thread.
The previous answers are absolutely right. However your implementation of reloadDatas & viewDidLoad is a bit problematic.
Just to clarify:
You want to complete the time consuming data loading stuff in a background thread, then update the UI/Cells when your data is ready on the main thread.
Like so:
-(void)viewDidLoad
{
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.my.backgroundQueue", NULL);
dispatch_async(concurrentQueue, ^{
[self reloadDatas];
});
}
-(void)reloadDatas
{
// Expensive operations i.e pull data from server and add it to NSArray or NSDictionary
[self f1];
[self f2];
[self f3];
// Operation done - now let's update our table cells on the main thread
dispatch_queue_t mainThreadQueue = dispatch_get_main_queue();
dispatch_async(mainThreadQueue, ^{
[myDisplayedTable reloadData]; // Update table UI
});
}
One other thing. Pulling data from a server and updating table cells is pretty common.
No need for queues or timers here.
Here's an alternative structure.
Say you're pulling mp3's from your server :
Your model class is : Music.h/m
Your Model manager is : MusicManager.h/m (Singleton) - it will contain an array of music objects - that singleton is basically your datasource;
and finally your UItableViewController : MusicTableVC.h/m
In MusicManager.h/m : You have an NSMutableArray which will be loaded with Music.h objects that you've pull from the server. You can do that as soon as you app loads without even waiting for the TableViewController.
Inside MusicManager you have a few helper methods to add or remove items from the mutableArray and provide the count and of course your networking methods.
Finally : Post a notification in your network code. Your UITableViewController should listen/observe that notification and "reload" accordingly.
[[NSNotificationCenter defaultCenter] postNotificationName:#"NewMusicAdded" object:nil];
You query data from your server, parse the data into Music objects add them to your NSMutable array and post a notification to let the table update itself.
Pretty standard recipe.
In reloadDatas method you should change this line:
dispatch_queue_t concurrentQueue = dispatch_get_main_queue();
To:
dispatch_queue_t concurrentQueue = dispatch_queue_create("some queue", NULL);
But when you call [myDisplayedTable reloadData], you need to call this operation in the main queue.
dispatch_async(dispatch_get_main_queue(), ^{ [myDisplayedTable reloadData]; });
Assume we have one UIVewcontroller, call it A, in the viewdidload of that VC we add to it two UIViewcontrollers( B,C ). now to make the UI smooth in the Viewdidload of A we do some GCD work
dispatch_queue_t queue = dispatch_queue_create("CustomQueue", NULL);
dispatch_async(queue, ^{
// Create views, do some setup here, etc etc
// Perform on main thread/queue
dispatch_async(dispatch_get_main_queue(), ^{
// this always has to happen on the main thread
[self.view addSubview:myview1];
[self.view addSubview:myview2];
[self.view addSubview:myview3];
});
});
Now based on this code, am I guaranteed that the views will be added in the same order? view 1 , then 2 , then 3?
I am noticing that arbitrarily some views shows up before others !!
Your problem is almost certainly this part:
dispatch_async(queue, ^{
// Create views, do some setup here, etc etc
You cannot do anything view-related (or really anything UIKit-related) on a background thread. Period.
I'm looking for a common and elegant way to manage interfaces update.
I know that user interface code must be run in main thread, so when i need some computation o network task i use GDC with this pattern:
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(aQueue, ^() {
//Backgroud code
dispatch_sync(dispatch_get_main_queue(), ^{
//Update the UI
}
}
The problem with this code is that i need always check if user has changed view during my computation, so the code is like:
dispatch_sync(dispatch_get_main_queue(), ^{
if (mylabel != nil) && ([mylabel superview] != nil) {
mylabel.text = _result_from_computation_;
}
}
There is some best ways?
Thanks.
You pretty well have it. However, in case you want to do more reading or want a more thorough explanation of what's going on...
You should read the Apple Docs Grand Central Dispatch (GCD) Reference and watch the WWDC 2012 video, Session 712 - Asynchronous Design Patters with Blocks, GCD and XPC.
If you're working with iOS, you can disregard XPC (interprocess communication) as it's not supported by the current OS version (6.1 at the time of this writing).
Example: Load a large image in the background and set the image when completed.
#interface MyClass ()
#property (strong) dispatch_block_t task;
#end
#implementation MyClass
- (void)viewDidLoad {
self.task = ^{
// Background Thread, i.e., your task
NSImage *image = [[NSImage alloc] initWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
// Main Thread, setting the loaded image
[view setImage:image];
});
});
}
- (IBAction)cancelTaskButtonClick:(id)sender { // This can be -viewWillDisappear
self.task = nil; // Cancels this enqueued item in default global queue
}
- (IBAction)runTaskButtonClick:(id)sender {
// Main Thread
dispatch_queue_t queue;
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, self.task);
}
In order to cancel and reload the interface later, all you have to do is set the dispatch_block_t variable to nil.
Perhaps more specifically to your problem, this example piece of code deals with Reading Data from a Descriptor, i.e., either the disk or network.
Typically, you would use the Call-Callback pattern which essentially gets a background thread, executes a task, and when completed calls another block to get the main thread to update the UI.
Hope this helps!
You can check the view window property:
if (myLabel.window) {
// update label
}
this is redundant if (label != nil) since if label is nil, then all label properties will also be nil (or zero) and setting them will not raise an exception.