Waiting for "finally" clause in PromiseKit to finish - ios

Currently I am using PromiseKit to chain a logic, which is like the following:
[NSURLConnection promise:rq1].then(^(id data1) {
return [NSURLConnection promise:rq2];
}).then(^(id data2) {
return [NSURLConnection promise:rq3];
}).then(^(id data3) {
return [self promiseToDoSomeWorkOnData:data3];
}).finally(^{
[self cleanup];
});
The problem that I am facing is that the method I call in the finally clause is asynchronous, but I have no way to chain the finally method together with the other promises so that any usage of the whole piece of code somewhere else waits also for the finally clause to finish before continuing to a next promise.

Related

Waiting for network call to finish

What I'm trying to achieve is to make a network request and wait for it to finish, so that I can make a decission what should be apps next step.
Normally I would avoid such solution, but this is a rare case in which codebase has a lot of legacy and we don't have enough time to apply necessary changes in order to make things right.
I'm trying to write a simple input-output method with following definition:
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId;
The thing is that in order to perform some validation inside this method I need to make a network request just to receive neccessary information, so I'd like to wait for this request to finish.
First thing that popped in my mind was using dispatch_semaphore_t, so I ended up with something like this:
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
dispatch_semaphore_signal(sema);
} failure:nil];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
return [self.paymentCards firstCardForStatus:status];
}
Everything compiles and runs, but my UI freezes and I actually never receive sempahore's signal.
So, I started playing with dispatch_group_t with exactly the same results.
Look like I might have some problems with where code gets executed, but I don't know how to approach this and get the expected results. When I try wrapping everything in dispatch_async I actually stop blocking main queue, but dispatch_async return immediatelly, so I return from this method before the network request finishes.
What am I missing? Can this actually be acheived without some while hacks? Or am I trying to fight windmills?
I was able to achieve what I want with the following solution, but it really feels like a hacky way and not something I'd love to ship in my codebase.
- (nullable id<UserPaymentCard>)validCardForLocationWithId:(ObjectId)locationId {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
__block BOOL flag = NO;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
flag = YES;
} failure:nil];
});
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !flag){};
return [self.paymentCards firstCardForStatus:status];
}
I guess fetchLocationProviderStatusFor:completion:failure: calls those callbacks in main queue. That's why you get deadlock. It's impossible. We can't time travel yet.
The deprecated NSURLConnection.sendSynchronousRequest API is useful for those instances when you really can't (or just can't be bothered to) do things properly, like this example:
private func pageExists(at url: URL) -> Bool {
var request = URLRequest(url: url)
request.httpMethod = "HEAD"
request.timeoutInterval = 10
var response: URLResponse?
try! NSURLConnection.sendSynchronousRequest(request,
returning: &response)
let httpResponse = response as! HTTPURLResponse
if httpResponse.statusCode != 200 { return false }
if httpResponse.url != url { return false }
return true
}
Currently, your method causes work to be done on the main thread, which freezes the UI. Your solution works, but it would be best to change the method to include a completion block. Then, you could call the completion block at the end of the async block. Here's the example code for that:
- (void)validCardForLocationWithId:(ObjectId)locationId completion:(nullable id<UserPaymentCard> (^)(void))completion {
id<LocationsReader> locationsReader = [self locationsReader];
__block LocationStatus *status = nil;
[locationsReader fetchLocationProviderStatusFor:locationId completion:^(LocationStatus * _Nonnull locationStatus) {
status = locationStatus;
completion([self.paymentCards firstCardForStatus:status]);
} failure:nil];
}

iOS AFNetwork 3.0: Is there a faster way to send multiple API requests and wait until all of it is finished?

I am currently using the following method to send GET API requests. This method works, but I was wondering if there is a faster way. All I need regarding requirements is to know when all of the Deleted mail has been synced. Any tips or suggestions are appreciated.
- (void)syncDeletedMail:(NSArray *)array atIdx:(NSInteger)idx {
if (idx < array.count) {
NSInteger idNumber = array[idx];
[apiClient deleteMail:idNumber onSuccess:^(id result) {
[self syncDeletedMail:array atIdx:(idx + 1)];
} onFailure:^(NSError *error){
[self syncDeletedMail:array atIdx:(idx + 1)];
}];
} else {
NSLog(#"finished");
}
}
Edit: I don't care what order it is completed (not sure if it matters in terms of speed), as long as all the API requests come back completed.
You can just send deleteMail requests at once and use dispatch_group to know when all the requests are finished. Below is the implementation,
- (void)syncDeletedMail:(NSArray *)array {
dispatch_group_t serviceGroup = dispatch_group_create();
for (NSInteger* idNumber in array)
{
dispatch_group_enter(serviceGroup);
[apiClient deleteMail:idNumber onSuccess:^(id result) {
dispatch_group_leave(serviceGroup);
} onFailure:^(NSError *error){
dispatch_group_leave(serviceGroup);
}];
}
dispatch_group_notify(serviceGroup,dispatch_get_main_queue(),^{
NSLog(#"All email are deleted!");
});
}
Here you can see all the requests are fired at the same time so it will reduce the time from n folds to 1.
Swift Version of #Kamran :
let group = DispatchGroup()
for model in self.cellModels {
group.enter()
HTTPAPI.call() { (result) in
// DO YOUR CHANGE
switch result {
...
}
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
// UPDATE UI or RELOAD TABLE VIEW etc.
// self.tableView.reloadData()
}
I suppose your request is due to the fact that you might have huge amounts of queued delete requests, not just five or ten of them.
In this case, I'd also try and consider adding a server side API call that allows you to delete more than just one item at a time, maybe up to ten or twenty, so that you could also reduce the overhead of the network traffic you'd be generating (a single GET isn't just sending the id of the item you are deleting but also a bunch of data that will basically sent on and on again for each and every call) by grouping the mails in batches.

How to stub method with response block using OCMock

I have a method that calls a request with a response block inside. I want to stub my response and return fake data. How can this be done?
-(void)method:(NSString*)arg1{
NSURLRequest *myRequest = ...........
[self request:myRequest withCompletion:^(NSDictionary* responseDictionary){
//Do something with responseDictionary <--- I want to fake my responseDictionary
}];
}
- (void)request:(NSURLRequest*)request withCompletion:(void(^)(NSDictionary* responseDictionary))completion{
//make a request and passing a dictionary to completion block
completion(dictionary);
}
If I understand you correctly, you want to mock request:withCompletion: and call the passed completion block.
Here is how I have done this in the past. I've adapted this code to your call, but I cannot check it for compilation errors, but it should show you how to do it.
id mockMyObj = OCClassMock(...);
OCMStub([mockMyObj request:[OCMArg any] completion:[OCMArg any]])).andDo(^(NSInvocation *invocation) {
/// Generate the results
NSDictionary *results = ....
// Get the block from the call.
void (^__unsafe_unretained completionBlock)(NSDictionary* responseDictionary);
[invocation getArgument:&callback atIndex:3];
// Call the block.
completionBlock(results);
});
You will need the __unsafe_unretained or things will go wrong. Can't remember what right now. You could also combine this with argument captures as well if you wanted to verify the passed arguments such as the setup of the request.

Executing functions and blocks through time

I don't understand how Objective-C loop system works. I have function (hope names are right, rather check in code) which executes query from Health Kit. I got my mind blown when I realised that function pass return value before query finishes.
__block bool allBeckuped = true;
HKSampleQuery *mySampleQuery = [[HKSampleQuery alloc] initWithSampleType:mySampleType
predicate:myPredicate
limit:HKObjectQueryNoLimit
sortDescriptors:#[mySortDescriptor]
resultsHandler:^(HKSampleQuery *query, NSArray *results, NSError *error) {
if(!error && results)
{
for(HKQuantitySample *samples in results)///main hk loop
{
allBeckuped = false;
NSLog(#"1");
}
}
}];//end of query
[healthStore executeQuery:mySampleQuery];
NSLog(#"2");
return allBeckuped;
I'm trying to check if there are any new data, but I don't know where to put condition for that, because nslog2 is called before nslog 1.
Any words I should Google up?
Any words I should google up?
You can start with: asynchronous design, blocks, GCD/Grand Central Dispatch should help as well - you're not using it but asynchronous designs often do.
Look at the initWithSampleType: method you are calling, it is an example of a method following the asynchronous model. Rather than return a result immediately, which is the synchronous model you are probably used to, its last argument, resultsHandler:, is a block which the method calls at some future time passing the result of its operation to it.
This is the pattern you will need to learn and follow.
Your method which contains the call to initWithSampleType: cannot return a result (e.g. your allBeckuped) synchronously. So it needs to take a "results handler" block argument, and the block you pass to initWithSampleType: should call the block passed to your method - and so the asynchronous flow of control is weaved.
HTH

ReactiveCocoa, Serialize network requests without fire & forget

I am trying to implement code, so I can serialize network requests, basically, the next request should start only after the first one is done. I also want to subscribe to these requests, so I can handle errors. The code looks like follows:
- (RACSignal * ) sendRequest: (Request *) request{
[[[RACSignal return:nil
deliverOn: [RACScheduler scheduler]
mapReplace: [self.network sendRequest]]; // A different thread is spawned to execute the request
}
and it is called as:
[self sendRequest:request
subscribeNext: ^(id x) {
NSLog(#"Request has been sent");
}];
Note that sendRequest can be called from multiple threads in parallel, so the requests need to be queued.
Putting the requests on the same scheduler, didn't work, as the send happens on another thread, and the next request gets picked up, before the previous is finished.
I also looked at using RACSubject that can help in buffering the requests, but it is good for fire and forget.
I was able to achieve the above using the concat command, therefore it is something like:
- (RACSignal * ) sendRequest: (Request *) request subscriber:(id<RACSubscriber>) subscriber{
[[[RACSignal return:nil
deliverOn: [RACScheduler scheduler]
flattenMap:^RACStream *(id value) {
[self.network sendRequest]]; // A different thread is spawned to execute the request
}]
doNext: ^(id x) {
[subscriber sendNext];
}
[[self sendRequest:request
concat]
subscribeNext: ^(id x) {
NSLog(#"Request has been sent");
}];
It turns out that an NSOperationQueue is unavoidable.
I have made RACSerialCommand to serialize the command execution. It has an interface similar to RACCommand, but with built-in NSOperationQueue to serialize the executions.
Feel free to try it.

Resources