Execute dispatch_async(dispatch_get_main_queue() from xctest - ios

I am trying to write test, that stubs the requests with OHHTTPStubs and then it should load the UI. The stub part is working, but the problem is, that test and UI loading are both executed on the main thread, so this block of loading ViewController never gets executed. Thanks for the tips in advance. Have a great day.
dispatch_block_t mainBlock = ^{
WDSomeVC *viewController = [[WDSomeVC alloc] initWithData:data andStyle:self.style];
viewController.delegate = self;
[self switchRootController:viewController withCompletion:nil];
};
dispatch_async(dispatch_get_main_queue(), mainBlock);

I solved it using this code.
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:timeToWait]];

Related

Dropbox DBRestClient/DBRestClientDelegate execute on a thread

I am using the dropbox API with DBRESTClient and DBRestClientDelegate in Dropbox-iOS-SDK
My issue is that I need these to run on a background thread.
When I call the [restClient loadMetadata] I do not get a response to - restClient:loadedMetadata: unless I begin call from the main thread.
Is there a simple workaround/library that I can use which will allow a delegate response on a thread ? I have tried Dropblocks which uses blocks but no luck.
I noticed "Make sure you call DBRestClient methods from the main thread or a thread that has a run loop. Otherwise the delegate methods won't be called." on the Dropbox Page
https://www.dropbox.com/developers-v1/core/start/ios
I used a runloop and it functions on a thread now
This is using a completion block Similarly you could use the delegate to set the flag to NO
self.inQuery = YES;
[self loadMetaDataWithPath:rootFolder mediaType:#(mediaType) completion:^(BOOL complete) {
self.inQuery = NO;
}];
#autoreleasepool {
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
DDLogError(#"currentRunLoop %#",[NSDate date]);
}while(self.inQuery);
}

Objective C- Trouble updating UI on main thread

I am having some trouble updating my UI using performSelectorOnMainThread. Here is my situation. In my viewDidLoad I set up an activity indicator and a label. Then I call a selector to retrieve some data from a server. Then I call a selector to update the UI after a delay. Here's the code:
- (void)viewDidLoad
{
[super viewDidLoad];
self.reloadSchools = [[UIAlertView alloc] init];
self.reloadSchools.message = #"There was an error loading the schools. Please try again.";
self.reloadSchools.title = #"We're Sorry";
self.schoolPickerLabel = [[UILabel alloc]init];
self.schoolPicker = [[UIPickerView alloc] init];
self.schoolPicker.delegate = self;
self.schoolPicker.dataSource = self;
self.server = [[Server alloc]init];
schoolList = NO;
_activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[self.view addSubview:_activityIndicator];
[self.view bringSubviewToFront:_activityIndicator];
[_activityIndicator startAnimating];
[NSThread detachNewThreadSelector: #selector(getSchoolList) toTarget: self withObject: nil];
[self performSelector:#selector(updateUI) withObject:nil afterDelay:20.0];
}
The selector updateUI checks to see if the data was retrieved, and calls a selector on the main thread to update the UI accordingly. Here is the code for these parts:
-(void)updateUI
{
self.schools = [_server returnData];
if(!(self.schools == nil)) {
[self performSelectorOnMainThread:#selector(fillPickerView) withObject:nil waitUntilDone:YES];
}
else {
[self performSelectorOnMainThread:#selector(showError) withObject:nil waitUntilDone:YES];
}
}
-(void)showError {
NSLog(#"show error");
[_activityIndicator stopAnimating];
[self.reloadSchools show];
}
-(void)fillPickerView {
NSLog(#"fill picker view");
schoolList = YES;
NSString *schoolString = [[NSString alloc] initWithData:self.schools encoding:NSUTF8StringEncoding];
self.schoolPickerLabel.text = #"Please select your school:";
self.shoolArray = [[schoolString componentsSeparatedByString:#"#"] mutableCopy];
[self.schoolPicker reloadAllComponents];
[_activityIndicator stopAnimating];
}
When the selector fillPickerView is called the activity indicator keeps spinning, the label text doesn't change, and the picker view doesn't reload its content. Can someone explain to me why the method I am using isn't working to update my ui on the main thread?
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//load your data here.
dispatch_async(dispatch_get_main_queue(), ^{
//update UI in main thread.
});
});
First of all you should not be using detachNewThreadSelector. You should use GCD and submit your background task to an async queue. Threads are costly to create. GCD does a much better job of managing system resources.
Ignoring that, your code doesn't make a lot of sense to me. You submit a method, getSchoolList, to run on a background thread. You don't show the code that you are running in the background.
Then use performSelector:withObject:afterDelay to run the method updateUI on the main thread after a fixed delay of 20 seconds.
updateUI checks for self.schools, which presumably was set up by your background thread, and may or may not be done. If self.schools IS nil, you call fillPickerView using performSelectorOnMainThread. That doesn't make sense because if self.schools is nil, there is no data to fill the picker.
If self.schools is not nil, you display an error, again using performSelectorOnMainThread.
It seems to me that the logic on your check of self.schools is backwards. If it is nil you should display an error and if it is NOT nil you should fill the picker.
Next problem: In both cases you're calling performSelectorOnMainThread:withObject:waitUntilDone: from the main thread. Calling that method from the main thread doesn't make sense.
Third problem: It doesn't make sense to wait an arbitrary amount of time for a background task to run to completion, and then either succeed or fail. You won't have any idea what's going on for the full 20 seconds. If the background task finishes sooner, you'll never know.
Instead, you should have your background task notify the main thread once the task is done. That would be a valid use of performSelectorOnMainThread:withObject:waitUntilDone:, while calling it from the main thread is not. (Again, though, you should refactor this code to use GCD, not using threads directly.
It seems pretty clear that you are in over your head. The code you posted needs to be rewritten completely.

using [NSOperationQueue mainQueue] in XCTests

I'm testing a part of my code using using XCTest that also adds NSOperations on the main queue.
It looks like this:
[NSOperationQueue mainQueue] addOperationAsBlock:^{
// some code happens here
}];
The code runs when running the app on a device or in the simulator but doesn't run at all when running the unit test (I can't get to the debug point on the first line of the block).
calling:
[NSOperationQueue mainQueue] waitUntilAllOperationsAreFinished];
doesn't help as well.
Any suggestions? I think i'm missing some code to initialise the queue.
* EDIT *
Thanks for your answers, I added my resulting code for completeness:
// add as many operations as you'd like to the mainQueue here
__block BOOL continueCondition = YES;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// this should be the last operation
continueCondition = NO;
}];
while (continueCondition) {
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
} // continue your test here
This works because the mainQueue is guaranteed to be non-concurrent so the last operation that's added will be the last one executed - this way you don't even have to change your code to stop the test loop.
Same as IOS -NSRunLoop in XCTest: How Do I Get A Run Loop to Work in A Unit Test?
Also, aquarius / XCTestCase+MNAsynchronousTestCase.h is helpful for it.

BAD EXC in [[NSRunLoop currentRunLoop] runMode:* beforeDate:*];

I'm trying to do some stress testing on my app with a unit test, and I'm running into some problems. Below is my code:
//Stress test api and core data
__block BOOL done = NO;
for (int i = 0; i < 100 ; i++) {
DLog(#"in here");
[viewController createList:testList
success:^(Lists *list) {
DLog(#"in success block : %d", i);
STAssertNotNil(list, #"list is not nil");
done = (i == 99);
}
failure:^(NSError *error) {
DLog(#"in fail block");
}
];
}
#autoreleasepool {
while (!done) {
// This executes another run loop.
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
The problem is, after several iterations, I get a bad access error on the line
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
I've already put the while loop in the #autorelease because of this post. I'm using ARC on the project, so I don't know if that's contributing to the problem..? I need to use the NSRunLoop to force the unit test to wait for the block to complete.
Has anyone run into this problem?
In a similar situation, I did it differently and it worked fine. What I did was in the main test, used dispatch_group_async to the normal queue, and then in the main test waited for the group to finish. That works just fine and you don't mess with the unit test's runloop.
if for some reason the above is not acceptable, then try not using any autorelease pool to see if that works If it does, then move it into the while look. With ARC the need for an autorelease pool is greatly diminished. You can observe memory usage using Instruements too.

What does NSRunLoop do?

I read many posts about NSRunLoop, like this, this, this. But can't figure out what NSRunLoop actually does
What I usually see is a worker thread
wthread = [[NSThread alloc] initWithTarget:self selector:#selector(threadProc) object:nil];
[wthread start];
with a NSRunLoop inside it
- (void)threadProc
{
NSAutoreleasePool* pool1 = [[NSAutoreleasePool alloc] init];
BOOL isStopped = NO;
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!isStopped)
{
{
NSAutoreleasePool* pool2 = [[NSAutoreleasePool alloc] init];
[runloop runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
[pool2 release];
}
}
[pool1 release];
}
And the main thread passes some work to this wthread
[self performSelector:#selector(someWork:) onThread:wthread withObject:nil waitUntilDone:NO];
In term of passing work from the main thread to the worker thread, I see many people do this. Why need NSRunLoop here ? What does it do ?
I read that NSRunLoop is used to manage events, why is there nothing except calling runMode inside threadProc ?
The example you've shown is a Cocoa idiom for creating a thread that will continue to run after the method -threadProc exits. Why?
Because:
the NSRunLoop instance you've created has at least one input source ([NSMachPort port])
you've explicitly started the run loop with runMode:beforeDate
Without adding an input source and explicitly starting the run loop, the thread would terminate.
Parenthetically, although run loops are still vital for managing events and certain asynchronous tasks, I would not view NSThread as the default way of architecting most asynchronous work in a Cocoa app nowadays. GCD is a far cleaner way of encapsulating background work.
EDIT:
Submitting work to a serial queue in GCD:
#interface Foo : NSObject
#end
#implementation Foo {
dispatch_queue_t _someWorkerQueue;
}
- (id)init {
self = [super init];
if( !self ) return nil;
_someWorkerQueue = dispatch_queue_create("com.company.MyWorkerQueue", 0);
return self;
}
- (void)performJob {
dispatch_async(_someWorkerQueue, ^{
//do some work asynchronously here
});
dispatch_async(_someWorkerQueue, ^{
//more asynchronous work here
});
}
#end
Much goes on behind the scene. The reason for this is it provides a way for the thread to stop executing when there are no work items. If you ever used a real time OS, tasks need a place to give up the processor so others can run.
What is not well documented is that when you send performSelector:onThread:..., it's the run loop that queues the message and the wakes up to let the thread process it. If you add log messages to the while loop you can see this happen.
For the really curious there is sample code on github you con get to play around with run loops - add a comment and I'll list a few.

Resources