Waiting for network call to finish - ios

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

Related

Writing Unit Test with asynchronous code in IOS

I need to test my Api calls response but before the block return Unit test got completed
how can i test my APIs
You can use XCTestExpectation for that.
XCTestExpectation *apiCallExpectation = [self expectationWithDescription:#"APICall"];
[apiService apiCall:^(BOOL success) {
XCTAssert(success);
[apiCallExpectation fulfill];
}];
[self waitForExpectationsWithTimeout:5 handler:^(NSError *error) {
[apiCallExpectation closeWithCompletionHandler:nil];
}];
At first, you don't need to call the actual API in unit testing, It shall to be independent and quicker to be completed. This shall be part of integration testing.
Regarding your question, I think you need to use Expectations, and Waiter. Check the following:
func testExample() {
let responseExpectation = expectation(description: "response")
// Your API Call shall be here
DispatchQueue.main.async {
// When you get the response, and want to finalize the expectation
responseExpectation.fulfill()
}
let result = XCTWaiter.wait(for: [responseExpectation], timeout: 15) // ex: 15 seconds to wait for the response for all expectations.
// result possible values are:
//all expectations were fulfilled before timeout.
.completed
//timed out before all of its expectations were fulfilled
.timedOut
//expectations were not fulfilled in the required order
.incorrectOrder
//an inverted expectation was fulfilled
.invertedFulfillment
//waiter was interrupted before completed or timedOut
.interrupted
}
Following is an example i am performing test case on a search api. you need to declare expectation and it will get fulfil once it's done.
func test_UpdateShowSearch_Result() {
let promise = expectation(description: "Status code: 200")
let searchAPI: SearchShowApi = SearchShowApi()
searchAPI.search(query: "") { (statusCode, tvShows ,error) in
if statusCode == 200 {
// reload table
promise.fulfill()
} else if (statusCode == 204){
// show no content
XCTFail("Status code: \(statusCode)")
}
else{
XCTFail("Error: \(String(describing:error?.localizedDescription))")
return
}
}
wait(for: [promise], timeout: 10)
}

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.

Swift code being executed asynchronously even while in completion handler

I'm rather new at swift and have been doing some research on how to answer this question myself since I want to learn, but I am completely stumped.
I have a function which requests data from a server, and after the data is received, a completion handler is executed which parses the data. Within the previously mentioned completion handler, another function is called which is passed a completion handler itself.
For some reason, the function call within the function is being being skipped, and being finished after the first completion handler is fully executed. This might make more sense with the code below:
func loadSites(forceDownload: Bool){
self.inspectionSites = MyData.getLocallyStoredInspectionSites()
if self.inspectionSites.count < 1 || forceDownload {
self.http.requestSites({(sitesAcquired, jsonObject) -> Void in
guard sitesAcquired else{
SwiftOverlays.removeAllBlockingOverlays()
MyAlertController.alert("Unable to acquire sites from server or locally")
return
}
let result = jsonObject
for (_,subJson):(String, JSON) in result!.dictionaryValue {
let site = InspectionSite()
site.name = subJson[self.currentIndex]["name"].string!
site.city = subJson[self.currentIndex]["city"].string!
site.address = subJson[self.currentIndex]["address"].string!
site.state = subJson[self.currentIndex]["state"].string!
site.zip = subJson[self.currentIndex]["zip"].stringValue
site.siteId = subJson[self.currentIndex]["id"].string!
objc_sync_enter(self) //SAW A STACKOVERFLOW POST WITH THIS, THOUGHT IT MIGHT HELP
MyLocation.geoCodeSite(site, callback:{(coordinates) -> Void in
print("YO!!!! GEOCODING SITE!")
self.localLat = coordinates["lat"]!
self.localLon = coordinates["lon"]!
})
objc_sync_exit(self)
for type in subJson[self.currentIndex]["inspection_types"]{
let newType = InspectionType()
newType.name = type.1["name"].string!
newType.id = type.1["id"].string!
site.inspectionTypes.append(newType)
}
site.lat = self.localLat
print("HEYY!!!! ASSIGNING COORDS")
site.lon = self.localLon
let address = "\(site.address), \(site.city), \(site.state) \(site.zip)"
site.title = site.name
site.subtitle = address
MyData.persistInspectionSite(site)
self.currentIndex++
}
self.inspectionSites = MyData.getLocallyStoredInspectionSites()
SwiftOverlays.removeAllBlockingOverlays()
self.showSitesOnMap(self.proteanMap)
})
}else{
SwiftOverlays.removeAllBlockingOverlays()
self.showSitesOnMap(self.proteanMap)
}
}
I added those print statements which print "YOOO" and "HEYYY" just so I could see what was being executed first, and "HEYY" is always first. I just need to make sure that the geocoding always happens before the object is persisted. I saw a stackoverflow post which mentioned objc_sync_enter(self) for synchronous operation, but im not even sure if it's what I need.
This is the function which geocodes the site (incase it helps):
class func geoCodeSite(site: InspectionSite, callback: ((coordinates: Dictionary<String, String>)->Void)?) {
let geocoder = CLGeocoder()
let address: String = "\(site.address), \(site.city), \(site.state) \(site.zip)"
print(address)
geocoder.geocodeAddressString(address, completionHandler: {(placemarks, error) -> Void in
if((error) != nil){
print("Error", error)
}
if let placemark = placemarks?.first {
MyLocation.mLat = String(stringInterpolationSegment:placemark.location!.coordinate.latitude)
MyLocation.mLon = String(stringInterpolationSegment:placemark.location!.coordinate.longitude)
MyLocation.coordinates = ["lat":mLat, "lon":mLon]
print(MyLocation.coordinates)
callback?(coordinates: MyLocation.coordinates)
}
})
}
I think the behaviour your seeing is expected. You have two levels of asynchronous methods:
requestSites
geoCodeSite
Since the geoCodeSite method is also asynchronous, its callback is executed well after the line:
MyData.persistInspectionSite(site)
So your problem is how to wait till all InspectionSites have geocoded before persisting the site, right?
Dispatch groups can be used to detect when multiple asynchronous events have finished, see my answer here.
How to Implement Dispatch Groups
dispatch_groups are used to fire a callback when multiple async callbacks have finished. In your case, you need to wait for all geoCodeSite async callbacks to complete before persisting your site.
So, create a dispatch group, firing off your geoCodeSite calls, and implement the dispatch callback inside of which you can persist your geocoded sites.
var myGroup = dispatch_group_create()
dispatch_group_enter(myGroup)
...
fire off your geoCodeSite async callbacks
...
dispatch_group_notify(myGroup, dispatch_get_main_queue(), {
// all sites are now geocoded, we can now persist site
})
Don't forget to add
dispatch_group_leave(myGroup)
inside the closure of geoCodeSite! Otherwise dispatch_group will never know when your async call finish.

Unit test case for call back methods ios

I have a following method in my app for which I need to write unit test cases.
Can anyone suggest how can I test whether the success block or error block is called.
- (IBAction)loginButtonTapped:(id)sender
{
void (^SuccessBlock)(id, NSDictionary*) = ^(id response, NSDictionary* headers) {
[self someMethod];
};
void (^ErrorBlock)(id, NSDictionary*, id) = ^(NSError* error, NSDictionary* headers, id response) {
// some code
};
[ServiceClass deleteWebService:#“http://someurl"
data:nil
withSuccessBlock:SuccessBlock
withErrorBlock:ErrorBlock];
}
You have to use expectations, a relatively recently introduced API. They were added to solve exactly the problem you are having right now, verifying callbacks of asynchronous methods are called.
Note that you can also set a timeout that will affect the result of the test (slow network connections for example can fire false positives, unless you are checking for slow connections of course, although there are much better ways to do that).
- (void)testThatCallbackIsCalled {
// Given
XCTestExpectation *expectation = [self expectationWithDescription:#"Expecting Callback"];
// When
void (^SuccessBlock)(id, NSDictionary*) = ^(id response, NSDictionary* headers) {
// Then
[self someMethod];
[expectation fulfill]; // This tells the test that your expectation was fulfilled i.e. the callback was called.
};
void (^ErrorBlock)(id, NSDictionary*, id) = ^(NSError* error, NSDictionary* headers, id response) {
// some code
};
[ServiceClass deleteWebService:#“http://someurl"
data:nil
withSuccessBlock:SuccessBlock
withErrorBlock:ErrorBlock];
};
// Here we set the timeout, play around to find what works best for your case to avoid false positives.
[self waitForExpectationsWithTimeout:2.0 handler:nil];
}

How one tests http requests in iOS 8?

In ruby I used to test http requests with vcr gem which recorded the request so the tests didn't send request to real host. Is there anything like this in iOS8 world?
The requests I want to test really need to be recorded since those requests may be outdated in some time and will return some other response
P.S. It would be great if it was some default Apple/iOS approach/library like XCTest for testing in general
What you want is something like OHHTTPStubs or Nocilla or AMY server. All of them essentially use NSURLProtocol to intercept your request and allow you to designate a response. We used OHHTTPStubs but pick the one with the feature set closest to your use case.
Here's an example of an OHHTTPStubs implementation in a unit test for a service that talks to a single REST endpoint:
NSString *loadRoomJSON = #{ #"key" : #"value" }; /* some JSON */
NSNumber identifier = #1;
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
NSString *url = [NSString stringWithFormat:#"v1/user/%#/room", identifier];
XCTAssert([request.URL.relativePath containsString:url], #"Expected certain URL");
return YES;
} andRespond:^OHHTTPStubsResponse *(NSURLRequest *request) {
return [OHHTTPStubsResponse responseWithJSONObject:loadRoomJSON statusCode:200 headers:nil];
}];
XCTestExpectation *loadPromise = [self expectation:#"Room loaded"];
[service loadRoomOnSucceed:^(Room *room) {
// Do your asserts here. For us, the JSON is mapped to an object
// so for example you could assert that the object is mapped correctly
[loadPromise fulfill];
} onFail:^(NSError *error) {
expect(error).to.beNil();
}];
[self waitForExpectationsWithTimeout:1.0 handler:^(NSError *error) {
expect(error).to.beNil();
}];
In reality our tests are shorter since we write wrapper/helpers to make it read better so this is an exploded-out version. Should give you the general idea. OHHTTPStubs (if you use it) has helper functions to load responses directly from files as well.
Im not sure if I understood you correct. But if I understand you right, you should be able to use XCTest to test your request and response.
class Tests:XCTestCase{
func testing(){
var expectation = self.expectationWithDescription("Your request")
var url = NSURL(string: "http://yourUrl.com")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) {(data, response, error) in
if let httpRes = response as? NSHTTPURLResponse {
println("status code=",httpRes.statusCode)
//200 means OK
if httpRes.statusCode == 200 {
println(NSString(data: data, encoding: NSUTF8StringEncoding))
}
}else{
println("error \(error)")
}
}
}
}

Resources