dispatch_semaphore_wait blocked my network request callback - ios

-(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.

Related

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");
}];

ReactiveCocoa after catch the error, the button signal not get triggered again

If I add a UIControlEventTouchUpInside signal to a doneButton, and call an API, if the API fails, the catch will be called. But if I try to click the button again, the button control event does not get triggered.
- (void)viewDidLoad {
[super viewDidLoad];
[[[[[self.doneButton rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x) {
[SVProgressHUD show];
}] flattenMap:^RACStream *(id value) {
return [[HttpService sharedService] updateImageData:UIImageJPEGRepresentation(self.signatureImageView.image, 0.5)];
}] catch:^RACSignal *(NSError *error) {
[SVProgressHUD showErrorWithStatus:error.localizedDescription];
return [RACSignal empty];
}] subscribeNext:^(id x) {
[SVProgressHUD dismiss];
[self.navigationController popToRootViewControllerAnimated:YES];
}];
}
I think this thread will help. https://github.com/ReactiveCocoa/ReactiveCocoa/issues/1218
A signal will automatically be unsubscribed to if it fails / errors. You can use - retry, however that will simply keep trying your operation until is doesn't fail, which, if there is a perpetual issue will just loop indefinitely.
Wrapping this condition in a flattenMap will capture the issue without unsubscribing the initial rac_signalForControlEvents observation.
See mdieps comment in the thread above on GitHub, and maybe do something like.
[[[[self.doneButton rac_signalForControlEvents:UIControlEventTouchUpInside] doNext:^(id x) {
[SVProgressHUD show];
}] flattenMap:^RACStream *(id value) {
return [[[HttpService sharedService] updateImageData:UIImageJPEGRepresentation(self.signatureImageView.image, 0.5)]
catch:^RACSignal *(NSError *error) {
[SVProgressHUD showErrorWithStatus:error.localizedDescription];
return [RACSignal empty];
}];
}] subscribeNext:^(id x) {
[SVProgressHUD dismiss];
[self.navigationController popToRootViewControllerAnimated:YES];
}];
I've not actually constructed a test with this code. Just guessing based on what you might have in your HttpService Class.
You can use RACCommand to solve this problem.
RACCommand *doneCommand =
[[RACCommand alloc] initWithSignalBlock:^RACSignal *(NSString *selected) {
return [[[self updateImageSignal]
doCompleted:^{
[SVProgressHUD dismiss];
[self.navigationController popToRootViewControllerAnimated:YES];
}] doError:^(NSError *error) {
[SVProgressHUD showErrorWithStatus:error.localizedDescription];
}];
}];
self.doneButton.rac_command = doneCommand;
Now create RACSignal that send success and error according to your request.
-(RACSignal *)updateImageSignal {
#weakify(self)
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
#strongify(self)
[[HttpService sharedService] updateImageData:UIImageJPEGRepresentation(self.signatureImageView.image, 0.5)
complete:^(BOOL success) {
if(success)
[subscriber sendNext:#(success)];
else
[subscriber sendError:nil];
[subscriber sendCompleted];
}];
return nil;
}];
}
Hope it will help you. And If you have any question then feel free to ask.

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);
}];

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

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();
}];
}

Creating block queues with completion

I want to create 2 async queues with completion blocks and after finished this blocks I want to run some action. I can not achieve it with this code. Where my bad?
dispatch_queue_t queue = dispatch_queue_create("com.company.queue", 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// block 1
dispatch_group_async(group, queue, ^{
[[WebRequests sharedInstance] request:#{#"type" : [NSNumber numberWithInt:request_uploadAdv], #"adv" : adv} withCompletion:^(id response) {
BOOL success = [response boolValue];
NSLog(#"done1 text");
// block 1 Done
}];
});
// block 2 //картинки
dispatch_group_async(group, queue, ^{
[self getImagesForAdv:adv completion:^(NSArray *images) {
[[WebRequests sharedInstance] uploadPhotos:images completion:^(BOOL success) {
uploadImagesSuccess = YES;
NSLog(#"done1 2\n");
// block 2 Done
}];
}];
});
dispatch_group_notify(group, queue, ^{
printf("all tasks are finished!\n");
});
First, you're missing a }); in there somewhere. Second, there's no need for the outer dispatch_group_async call anyway. Assuming it's there because you want these things to execute with background priority, you can do this instead:
dispatch_queue_t queue = dispatch_queue_create("com.company.queue", 0);
dispatch_set_target_queue(queue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
dispatch_group_t group = dispatch_group_create();
// block 1
dispatch_group_async(group, queue, ^{
[[WebRequests sharedInstance] request:#{#"type" : [NSNumber numberWithInt:request_uploadAdv], #"adv" : adv} withCompletion:^(id response) {
BOOL success = [response boolValue];
NSLog(#"done1 text");
// block 1 Done
}];
});
// block 2 //картинки
dispatch_group_async(group, queue, ^{
[self getImagesForAdv:adv completion:^(NSArray *images) {
[[WebRequests sharedInstance] uploadPhotos:images completion:^(BOOL success) {
uploadImagesSuccess = YES;
NSLog(#"done1 2\n");
// block 2 Done
}];
}];
});
dispatch_group_notify(group, queue, ^{
printf("all tasks are finished!\n");
});

Resources