Is it safe to call XCTestExpectation fulfill method on background thread? - ios

I am using XCTestExpectation in a lot of tests and sometimes (very randomly) some expectations are not fulfilled (although I am sure they should be).
While investigating this problem I have noticed that some expectations are fulfilled in a main thread and some are fulfilled in a background thread. And so far these problems are with the ones fulfilled in a background thread.
Is it safe to fulfill expectations from a background thread? I could not find any explicit information about that.
Below is an example of how I use XCTestExpectation:
__block XCTestExpectation *expectation = [self expectationWithDescription:#"test"];
[self doSomethingAsyncInBackgroundWithSuccess:^{
[expectation fullfill];
}];
[self waitForExpectationsWithTimeout:10.0 handler:^(NSError *error) {
expectation = nil;
if (error) {
NSLog(#"Timeout Error: %#", error);
}
}];

It's not documented anywhere that XCTestExpectation is thread-safe. due to there being no official documentation on the matter you can only guess by creating test examples:
- (void)testExpectationMainThread;
{
__block XCTestExpectation *expectation = [self expectationWithDescription:#"test"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
NSLog(#"%#", error);
}];
}
- (void)testExpectationStartMainThreadFulfilBackgroundThread;
{
__block XCTestExpectation *expectation = [self expectationWithDescription:#"test"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, kNilOptions), ^{
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
NSLog(#"%#", error);
}];
}
- (void)testExpectationBackgroundThread;
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, kNilOptions);
__block XCTestExpectation *expectation;
dispatch_sync(queue, ^{
expectation = [self expectationWithDescription:#"test"];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), queue, ^{
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:2 handler:^(NSError * _Nullable error) {
NSLog(#"%#", error);
}];
}
Here it does not crash or cause a problem however due to the lack of official documentation it is probably safer to stick to the same queue to fulfil.
you should really be stubbing the method doSomethingAsyncInBackgroundWithSuccess and provide the app with local "dummy" data.
Your unit tests should not rely on network as it is something which is variable.
You should be executing the completion block of doSomethingAsyncInBackgroundWithSuccess on the main thread (or at least provide a way to call back consistently on the same thread), you can easily do this with GCD.
- (void)doSomethingAsyncInBackgroundWithSuccess:(void (^)(void))completion;
{
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}
or use NSOperationQueue mainQueue
- (void)doSomethingAsyncInBackgroundWithSuccess:(void (^)(void))completion;
{
[NSOperationQueue.mainQueue addOperationWithBlock:^{
completion();
}];
}

Related

dispatch_semaphore_wait blocked my network request callback

-(void)init{
self.sema = dispatch_semaphore_create(1)
}
-(void)main{
//sending one message is fine
[self preSendMessage:#"hi"];
//deadlock happen when sending multiple msg in a short time.
for(int i = 0; i < 10; i++){
[self preSendMessage:#"yo"];
}
}
- (void)preSendMessage:(NSString*)msg
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER);
RACSignal* signal = [self sendMessage:msg];
#weakify(self);
[signal subscribeNext:^(id x) {
} error:^(NSError *error) {
#strongify(self);
dispatch_semaphore_signal(self.sema);
} completed:^{
#strongify(self);
dispatch_semaphore_signal(self.sema);
}];
});
}
Some informations:
In sendMessage function, I am using AFNetworking.
When I remove the semaphore logic, it works fine, I need this because need to control the sequence of sending msgs.
[Update] my semaphore is init with value 1, so It would run at the first time
My Target is to ensure the code inside preSendMessage execute when the previous one is completed/error
Edit: you should use dispatch_semaphore_create(0)
You should put dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER); after dispatch_async(....)
Something like this:
- (void)preSendMessage:(NSString*)msg{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
RACSignal* signal = [self sendMessage:msg];
#weakify(self);
[signal subscribeNext:^(id x) {
} error:^(NSError *error) {
#strongify(self);
dispatch_semaphore_signal(self.sema);
} completed:^{
#strongify(self);
dispatch_semaphore_signal(self.sema);
}];
});
dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER);
}
You are calling dispatch_semaphore_wait in the wrong order. Your code subscription does not get executed, because your semaphore is in a wait state. You should call your dispatch_semaphore_wait right after dispatching to background thread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
RACSignal* signal = [self sendMessage:msg];
#weakify(self);
[signal subscribeNext:^(id x) {
} error:^(NSError *error) {
#strongify(self);
dispatch_semaphore_signal(self.sema);
} completed:^{
#strongify(self);
dispatch_semaphore_signal(self.sema);
}];
});
dispatch_semaphore_wait(self.sema, DISPATCH_TIME_FOREVER);
You tell your app to wait for the semaphore, so your app waits for the semaphore. It doesn't execute any code after the wait call.
You need to set up everything, including the callbacks that will signal, and then you wait for the signal.

How to subscribe to completion of RACCommand

RACSignal *s1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"s1");
[subscriber sendCompleted];
});
return nil;
}];
RACSignal *s2 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(#"s2");
[subscriber sendCompleted];
});
return nil;
}];
self.command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
return [s1 then:^RACSignal * _Nonnull{
return s2;
}];
}];
[self.command execute:nil];
[self.command.executionSignals subscribeCompleted:^{
NSLog(#"completed");
}];
I found that completed did not excute. How to make it work?
RACCommand.executionSignals is one signal that belongs to the RACCommand instance.
On this signal, the command will send another RACSignal as value for each execution of the command.
With your snippet
[self.command.executionSignals subscribeCompleted:^{
NSLog(#"Command was deallocated");
}];
you are observing the RACSignal<RACSignal> of the command - this signal will only complete, when the RACSignal is deallocated.
If you really need to observe the command, you will need to do the following:
[self.command.executionSignals subscribeNext:^(RACSignal *innerSignal) {
[innerSignal subscribeCompleted:^{
NSLog(#"One execution of the command completed");
}];
}];
This subscribeNext will be called with each execute of the RACCommand and the innerSignal is the signal you want to observe.
However, if you actually want to know when a single execution is completed, you can subscribe directly to the signal thats returned from the execute call:
[[self.command execute:nil] subscribeCompleted:^{
NSLog(#"This execution of the command completed");
}];

How to Custom group notify for blocks of functions

Asynchronous block be called in function, add these multiple functions into a group,process all results of blocks when all block execute completely.Is there a common and smart method to implement?
like dispatch_group_notify ,completionBlock
dispatch_group_t _blockTaskGroup;
.....
if (!_blockTaskGroup) {
_blockTaskGroup = dispatch_group_create();
}
dispatch_group_notify(_blockTaskGroup, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSLog(#"finally!");
});
....
dispatch_group_enter(_blockTaskGroup);
[XXXXX computeInBackground:xx completion:^{
NSLog(#"1 done");
dispatch_group_leave(group);
}];
//
dispatch_group_enter(_blockTaskGroup);
[XXXXX computeInBackground:xx completion:^{
NSLog(#"2 done");
dispatch_group_leave(_blockTaskGroup);
}];
//
dispatch_group_enter(_blockTaskGroup);
[XXXXX computeInBackground:xx completion:^{
NSLog(#"3 done");
dispatch_group_leave(_blockTaskGroup);
}];

Would I use dispatch in block for MBProgressHud?

In the MBProgressHud documentation it states to use this inside of a dispatch like so:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do something...
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
});
Which is completely understandable considering you don't want it to boggle up the main thread. But could I just do this instead when using a block:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[object deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (error)
{
}
else
{
[MBProgressHUD hideHUDForView:self.view animated:YES];
}
}];
Or would I still have to use dispatch?
Change your code to be like this:
[MBProgressHUD showHUDAddedTo:self.view animated:YES];
[object deleteInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
NSLog(#"Am I running on the main thread: %#", [NSThread isMainThread] ? #"YES": #"NO");
if (error)
{
}
else
{
}
}];
if it logs "YES" then you don't need to run [MBProgressHUD hideHUDForView:self.view animated:YES]; on the main thread, otherwise you need to use
dispatch_async(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
});
Update:
blocks are run on whatever thread they've been called from, notice following example:
- (void)viewDidLoad {
[super viewDidLoad];
[self longRunningProcessWithCompletionBlock:^{
NSLog(#"Is running on the main thread? %#", [NSThread isMainThread] ? #"YES" : #"NO");
}];
}
- (void)longRunningProcessWithCompletionBlock:(void (^)(void))completionBlock {
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this is running on the concurrent thread, so does completionBlock() as it has been called on a concurrent thread.
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:3];
completionBlock();
});
}
So Basically the result of above will be "Is running on the main thread? NO"
Again I have exact same call on viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self longRunningProcessWithCompletionBlock:^{
NSLog(#"Is running on the main thread? %#", [NSThread isMainThread] ? #"YES" : #"NO");
}];
}
But this time, I'm calling completionBlock of longRunningProcessWithCompletionBlock on the main thread as follow:
- (void)longRunningProcessWithCompletionBlock:(void (^)(void))completionBlock {
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
[NSThread sleepForTimeInterval:3];
//notice the difference, this time we are calling the completionBlock on the main thread, so that block will be running on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock();
});
});
}
This time because we have called the completion block on the main thread, the result will be Is running on the main thread? YES
So in a nutshell, block does not guarantee that they are getting executed on the main thread! but they can guarantee that they will be executed on whatever thread they've been called from.
In your case Parse.com developers are calling the completion handler block of deleteInBackgroundWithBlock on the main thread and that's why you saw that log was "yes".So you just need to call [MBProgressHUD hideHUDForView:self.view animated:YES]; without dispatch_async(dispatch_get_main_queue(), ^{ }); (as it is already on the main thread and this is an extra unnecessary step)

Is it possible to institute a timeout for SKStoreProductViewController loadProductWithParameters?

I'm currently calling storeViewController loadProductWithParameters via dispatch_async . Is it possible to set a timeout value so it only tries to fetch the results for X seconds and then gives up?
I implemented my own timeout by using with the class method below instead of calling loadProductWithParameters directly. It times out thanks to a dispatch_after and __block variable.
+ (void)loadProductViewControllerWithTimeout:(NSTimeInterval)timeout
storeKitViewController:(SKStoreProductViewController *)storeKitViewController
parameters:(NSDictionary *)parameters
completionHandler:(void (^)(BOOL result, NSError *error))completionHandler {
__block BOOL hasReturnedOrTimedOut = NO;
[storeKitViewController loadProductWithParameters:parameters completionBlock:^(BOOL result, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (!hasReturnedOrTimedOut) {
hasReturnedOrTimedOut = YES;
if (completionHandler) completionHandler(result, error);
}
});
}];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if (!hasReturnedOrTimedOut) {
hasReturnedOrTimedOut = YES;
if (completionHandler) completionHandler(NO, nil); // Or add your own error instead of |nil|.
}
});
}
My latest app update got rejected by Apple because loadProductWithParameters never called its completionBlock and stopped my users from buying songs on iTunes... Hope this helps.
I have acomplished it like so:
__block BOOL timeoutOrFinish = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(30 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
if(!timeoutOrFinish) {
timeoutOrFinish = YES;
[self dismissAndShowError];
}
});
[storeViewController loadProductWithParameters:parameters completionBlock:^(BOOL result, NSError * _Nullable error) {
if(timeoutOrFinish) {
return;
}
timeoutOrFinish = YES;
//[[NetworkManager sharedManager] showNetworkActivityIndicator:NO];
if(error) {
[self dismissAndShowError];
}
}];
[self.view.window.rootViewController presentViewController:storeViewController animated:YES completion:nil];
where dismissAndShowError method runs dismissViewControllerAnimated and shows alert with an error.
Basically, you have a separate timer (30 seconds in my case) that switches a flag. After that time, if store has still not been loaded, I close it and display an error. Otherwise, completion is called (on cancel, finish and error) and handles all actions according to the status.

Resources