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!");
});
Related
This is specific to iOS 8. I noticed that there was a severe delay after calling AVCaptureSession stopRunning and I'm very confused as to why this is happening.
The relevant code:
static dispatch_queue_t _captureQueue;
#implementation myClass {
AVCaptureSession *_session;
}
- (id)init {
self = [super init];
if (self) {
// standard AVCaptureSession setup (I can put actual code in if need be)
static dispatch_once_t queueOnce;
dispatch_once(&queueOnce, ^{
_captureQueue = dispatch_queue_create("com.myclass.captureQueue", DISPATCH_QUEUE_SERIAL);
});
}
}
- (void)start {
if (!_session) return;
dispatch_async(_captureQueue, ^{
[self->_session startRunning];
});
}
- (void)stop {
if (!_session) return;
// stopMethod#1
//[_session stopRunning];
// stopMethod#2
//dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// [self->_session stopRunning];
//});
// stopMethod#3
dispatch_async(_captureQueue, ^{
[self->_session stopRunning];
});
}
stopMethod#3 is the way that I've always stopped AVCaptureSessions in the past and it is the method that is currently causing the delay I'm seeing. I was wondering if there was a long running method on the _captureQueue and that was causing the delay when stopRunning was sent to it, but upon using stopMethod#2 I saw the exact same delay. I finally tried stopping the session on the main thread and I had no delay at all. Remember all this is on iOS 8. I grabbed a device with iOS 7 on it and using all three stopMethods I received no delay (I'm just taking pictures so I'm assuming there isn't a lot to shut down).
This is very confusing to me because the documentation says
- (void)stopRunning
Discussion
This method is used to stop the flow of data from the inputs to the outputs
connected to the AVCaptureSession instance that is the receiver. This method
is synchronous and blocks until the receiver has completely stopped running.
And almost every SO post on this topic says NOT to call stopRunning on the main thread. Is this some sort of weird iOS 8 bug or am I missing something?
I'm writing an iOS app that is getting data from a server. I have several ViewControllers. I used to load data for that viewcontroller under the viewDidLoad method
-(void)ViewDidload
{
[self loadData];
}
-(void)loadData
{
//calling to webservice caller class
}
But this reduces the app's performance. What is the best method to load data within a viewcontroller? For webservice callings, I have a seperate class. Within my loadData method I call to that particular method inside the webservice calling class.
This is going to block my UI.
What do you mean with "this reduces the app performance". Your app is lagging when you are calling your webservice? This is not because you are calling that in viewDidLoad this is because you are doing that in the main thread.
To call your webservice you can use:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
// Call here your web service
dispatch_sync(dispatch_get_main_queue(), ^{
// push here the results to your ViewController
});
});
With this simple solution you are downloading data from your webservice in a separate thread. And pushing the data to your ViewController with the mainThread. This code is not freezing your app. However you will have a moment that nothing happens. This is a good moment to use a UIActivityIndicatorVew.
I guess your interface is lagging.
Try this:
-(void)ViewDidload
{
[NSThread detachNewThreadSelector:#selector(loadData) toTarget:self withObject:nil];
}
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
}
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.
I have a method that builds a package, sends it to a web service, gets a package back, opens it and returns me a nsdictionary. How can I call it on a background queue in order to display a HUD while it requests the data?
You could detach a new thread like following
- (void) fetchData
{
//Show Hud
//Start thread
[NSThread detachNewThreadSelector:#selector(getDataThreaded)
toTarget:self
withObject:nil];
}
- (void) getDataThreaded
{
//Start Fetching data
//Hide hud from main UI thread
dispatch_async(dispatch_get_main_queue(), ^{
//Update UI if you have to
//Hide Hud
});
}
Grand central dispatch (gcd) provides great support for doing what you ask. Running something in the background using gcd is simple:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_NORMAL, 0) ^{
NSDictionary* data = [self fetchAndParseData];
dispatch_async(dispatch_get_main_queue(), ^{
[self dataRetrieved:data];
});
});
This call will return immediately (so your UI will continue to be responsive) and dataRetrieved will be called when the data is ready.
Now, depending on how fetchAndParse data works it may need to be more complicated. If you NSURLConnection or something similar, you might need to create an NSRunLoop to process data callbacks on the gcd thread. NSURLConnection for the most part is asynchronous anyway (though callbacks like didReceiveData will be routed through the UI thread) so you can use gcd only to do the parsing of the data when all the data has been retrieved. It depends on how asynchronous you want to be.
In addition to previous replies, why don't you use NSOperation and NSOperationQueue classes? These classes are abstractions under GCD and they are very simple to use.
I like NSOperation class since it allows to modularize code in apps I usually develop.
To set up a NSOperation you could just subclass it like
//.h
#interface MyOperation : NSOperation
#end
//.m
#implementation MyOperation()
// override the main method to perform the operation in a different thread...
- (void)main
{
// long running operation here...
}
Now in the main thread you can provide that operation to a queue like the following:
MyOperation *op = [[MyOperation alloc] initWithDocument:[self document]];
[[self someQueue] addOperation:op];
P.S. You cannot start an async operation in the main method of a NSOperation. When the main finishes, delegates linked with that operations will not be called. To say the the truth you can but this involves to deal with run loop or concurrent behaviour.
Here some links on how to use them.
http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/
https://developer.apple.com/cocoa/managingconcurrency.html
and obviously the class reference for NSOperation