UI not updating in main thread - ios

This is really driving me nuts.
I have a button, and if that button is touched it will call a method that updates the UI. Here's the method in question:
- (void)loadLevelWithImagePath:(NSString *)imagePath difficulty:(int)difficulty modelName:(NSString *)modelName
{
// do stuffs
}
Except that it doesn't.
However when I enclose the whole method body in:
dispatch_async(dispatch_get_main_queue(), ^{
// do stuffs
}
It works!
However, I am baffled because when I put a breakpoint in the method, according to the debugger it is already in the main thread. Also If I put these 2 checks:
- (void)loadLevelWithImagePath:(NSString *)imagePath difficulty:(int)difficulty modelName:(NSString *)modelName
{
NSLog(#"%d", [NSThread currentThread] == [NSThread mainThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%d", [NSThread currentThread] == [NSThread mainThread]);
// do stuffs
}
}
Both returns TRUE!
So my question is, why is the UI not updating? Thanks a lot!

It could be a nil problem, however, you provide too little info to know for sure. The reason it works with dispatch_async and doesn't without is not necessarily the thread you're calling the methods from, it could be that, at the time when you call the code, some of your UI objects are nil. When you call dispatch_async you add the job to the queue but since all dispatch queues are first-in, first-out data structures, you actually add the job at the end of the run loop, which potentially gives time for initialisation (wherever that is done)

Related

Wait for value to change in thread Objective C

I have a thread call in objective C and i want once this thread ends i want to return a value ;the value will be changed inside the thread
So the method must not return the value unless the tread ends
Here is the code i use:
[NSThread detachNewThreadSelector:#selector(CheckBeforePrint2) toTarget:self withObject:nil];
This is My Full Code
- (NSString *) getStatusPrinter:(NSString *) printerSerialNumber
{
self.printerSN = printerSerialNumber;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *Result = #"-1";
[NSThread sleepForTimeInterval:2.0f];
[NSThread detachNewThreadSelector:#selector(CheckBeforePrint) toTarget:self withObject:Result];
[pool release];
return Result;
}
Now i want to wait for the value of Result and return it i am using
cocoa
And i am returning the value to another app
Can anyone help in that.
Thanks
What you are doing here requires the use of a semaphore for example. If there is nothing more to it than you are providing here then a completion block to a background running method is the best way to do it. See option 2 below
Either way, why do you want the parent thread (the thread dispatching a new thread) to wait for another thread? If the parent thread is waiting, it is locked until the dispatched thread is done (according to your requirement). This situation is redundant because the whole point of dispatching another thread is so that the parent thread can continue with other things. Unless of course the parent thread needs to wait for multiple threads, then it makes sense to lock it.
Having said that, its best to just let the dispatching thread / parent thread do the processing that you are dispatching on to another thread. Im only saying this given the details you have provided.
OPTION 1 use a semaphore
Use a semaphore to lock and unlock parent thread
-(void)getStatusPrinter()
{
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[NSThread detachNewThreadSelector:#selector(checkBeforePrint2) toTarget:self withObject: semaphore];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[self print]; // this will run after semaphore is unlocked
}
-(void)checkBeforePrint2:(dispatch_semaphore_t)sem
{
//this is within child thread
//do some processing,
dispatch_semaphore_signal(sem);//unlock semaphore
}
But, as I mentioned before, this situation seems redundant because the parent thread waits (therefore unusable) for child thread; why can't it just do the work itself...
OPTION 2 use completion block (PREFERABLE)
Use a completion block that you pass to the child thread. This allows the parent thread to continue. If it is the main thread it remains free for UI stuff.
-(void)getStatusPrinter()
{
[self checkBeforePrint2WithCompletion: ^{
[self print];
}];
//continue with more stuff
}
-(void)checkBeforePrint2WithCompletion:(void (^ __nullable)(void))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//do something before executing completion code
if(completion){
completion();
}
});
}
Disclaimer: there may be typos in this code as it was not written in an editor/ IDE. Please comment if any.
UPDATE in response to added details.
Okay, for the fact that you said you need to return the result to another application, that means the entry thread at getStatusPrinter can not be allowed to return after dispatching a new thread. If you really need to create a new thread for CheckBeforePrint then the entry thread has to wait. That to me is pointless. You can simply run everything on the entry thread.
If you are using openURL:options:completionHandler: then the entry thread doesn't need to wait. The value of result can be passed back within the completion block.
Please refer to Apple's documentation on openURL with a completion handle

iOS ObjC: Why is dispatch_sync on mainThread not working while app is in background receiving an APNs fetch?

In my app I use the following method to check for values of certain variables which are meant to be accessed on the main thread only.
Now that I began to implement APNs and when my app is woken by APNs it seems that code execution (in background) is always stuck at the point indicated using comments:
- (void) xttSyncOnMainThread:(void (^)(void))prmBlock {
if (![NSThread isMainThread]) {
dispatch_queue_t mtQueue = dispatch_get_main_queue(); // will be executed
// execution is stuck here
dispatch_sync(mtQueue, prmBlock); // won't be executed
} else {
prmBlock();
}
}
Do I need to move all code to non-MT queues or am I missing something else?
Thanks a lot!
Because dispatch_sync on main queue cause deadlock.
More information about dispatch_sync and main queue is for example here:
dispatch_sync on main queue hangs in unit test
Why dispatch_sync( ) call on main queue is blocking the main queue?
Can you just use dispatch_async method ?
why are you passing prmBlock to dispatch_sync
usually it is like
dispatch_sync(dispatch_get_main_queue(), ^(void) {
// write the code that is to be executed on main thread
});
But if you use disptch_sync it will wait for the block to complete execution and then return. If you don't want to block the execution use
dispatch_async(dispatch_get_main_queue(), ^(void) {
// write the code that is to be executed on main thread
});
Ok, after some more testing I found that in my case (while the code in the question works just fine) the problem came from accidently calling the completionhandler from the APNs delegate too soon.
- (void) xttSyncOnMainThread:(void (^)(void))prmBlock {
dispatch_async(dispatch_get_main_queue(), ^{
//code here to perform
});
}

Switch back to the main thread in ReactiveCocoa

I'm new to ReactiveCocoa, and therefore could miss something obvious.
I have 2 operations scheduled on the background thread, and after they complete I want to update the UI and for this I need to switch back to the main thread, but I have no idea how.
Here is snippet of code.
-(RACSignal *)executeSigninSignal {
return [[[self.services getAuthenticationService]
authenticationSignalFor:self.username andPassword:self.password]
//Return user if exists
flattenMap:^RACStream *(STUser *user) {
return [[[[self services] getContactsLoadService]
contactsLoadSignalForUser:user]
//Return user contacts
doNext:^(NSArray *contacts) {
STContactsListViewModel *contactsViewModel =
[[STContactsListViewModel alloc] initWithContactsLoadResults:contacts services:self.services];
[self.services pushViewModel:contactsViewModel];
}];
}];
}
authenticationSignalFor: and contactsLoadSignalForUser: are RACSignals that are delivered on the background thread and I want to execute contents of doNext block on the main thread.
How can I declare that this block should be executed on the main thread?
Use the deliverOnMainThread method before the doNext: method.

How can I get back into my main processing thread?

I have an app that I'm accessing a remote website with NSURLConnection to run some code and then save out some XML files. I am then accessing those XML Files and parsing through them for information. The process works fine except that my User Interface isn't getting updated properly. I want to keep the user updated through my UILabel. I'm trying to update the text by using setBottomBarToUpdating:. It works the first time when I set it to "Processing Please Wait..."; however, in the connectionDidFinishLoading: it doesn't update. I'm thinking my NSURLConnection is running on a separate thread and my attempt with the dispatch_get_main_queue to update on the main thread isn't working. How can I alter my code to resolve this? Thanks! [If I need to include more information/code just let me know!]
myFile.m
NSLog(#"Refreshing...");
dispatch_sync( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self getResponse:#"http://mylocation/path/to/file.aspx"];
});
[self setBottomBarToUpdating:#"Processing Please Wait..."];
queue = dispatch_queue_create("updateQueue", DISPATCH_QUEUE_CONCURRENT);
connectionDidFinishLoading:
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Contacts..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Emails..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}
In my connectionDidFinishLoading: I would try something like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^ {
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Contacts..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
dispatch_async(dispatch_get_main_queue(),^ {
[self setBottomBarToUpdating:#"Updating Emails..."];
});
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}
});
Then all that file access is happening in a background queue so the main queue is not locked up. The main queue will also complete this call to connectionDidFinishLoading much more quickly, since you're throwing all the hard work onto the default queue instead, which should leave it (and the main thread) ready to accept your enqueuing of the updates to the UI which will be done by the default queue as it processes the block you just enqueued to it.
The queue handover becomes
main thread callback to connectionDidFinishLoad:
rapid handoff to default global queue releasing main thread
eventual hand off to main queue for setBottomBarToUpdating: calls
performing main queue blocks on main thread to properly update UI
eventual completion of blocks on main queue
eventual completion of blocks on default queue
You've increased concurrency (good where you've good multi-core devices) and you've taken the burden of I/O off the main thread (never a good place for it) and instead got it focused on user interface work (the right place for it).
Ideally you woud run the NSURLConnection run loop off the main thread too, but this will might be enough for you to get going.
Which run loop are you running the NSURLConnection in? If it's the main loop, you're queueing up the setBottomBarToUpdating: calls behind the work you're already doing, hence the probable reason why you're not seeing the UI update.
You could also give performSelectorOnMainThread try like so:
if ([response rangeOfString:#"Complete"].location == NSNotFound]) {
// failed
} else {
//success
[self performSelectorOnMainThread:#selector(setBottomBarToUpdating) withObject:#"Updating Contacts..." waitUntilDone:false];
[self updateFromXMLFile:#"http://thislocation.com/path/to/file.xml"];
[self performSelectorOnMainThread:#selector(setBottomBarToUpdating) withObject:#"Updating Emails..." waitUntilDone:false];
[self updateFromXMLFile:#"http://thislocation.com/path/to/file2.xml"];
}

setNeedsDisplay not working inside a block

I'm using CMMotionManager for retrieving accelerometer data. The thing is that the accelerometer data gets printed periodically, the instance variables are changed in the view, but the view doesn't get redrawn. I have checked that hv is not nil and that everything is hooked. Is there a problem with calling setNeedsDisplay within a block?
-(void) viewDidAppear:(BOOL) animated
{
[super viewDidAppear: animated];
[motionManager startAccelerometerUpdatesToQueue:motionQueue withHandler:
^(CMAccelerometerData *accelerometerData, NSError *error)
{
NSLog(#"%#",accelerometerData);
HypnosisView *hv = (HypnosisView *) [self view];
hv.xShift = 10.0 * accelerometerData.acceleration.x;
hv.yShift = -10.0 * accelerometerData.acceleration.y;
[hv setNeedsDisplay];
}];
}
It's because your calling a UI method on a thread different from the main thread.
Add this to your block:
dispatch_async(dispatch_get_main_queue(), ^{
[hv setNeedsDisplay];
});
Remember that any method dealing with user interface elements must be called from the main thread.
I have done the same in other blocks and it did work, though not with the callback you use here. Maybe the block is not executed on the main thread? You can check that with:
NSLog(#"Main thread? %d", [NSThread isMainThread]);
If it is not, you could force setNeedsDisplay to run on the main thread.
Just wanted to test #cocoahero's answer. I tried to call setTitle for a UIButton in a non main thread. And the title did changed. I tried this both targeting iOS 5.1 and 7.0 using Xcode 5.0.1. Also I called setNeedsDisplay from a non main thread and it worked too.
[NSThread isMainThread] was how I made sure that my calls were not from main thread. I am not sure calling from non main thread was the cause to your problem but at least there are other possibilities there. You can take a look at my answer for another question.

Resources