How can i wait until function get all data from alamofire get request?
GetData.swift file:
import Foundation
import Alamofire
import SwiftyJSON
import ObjectMapper
func getStartData() -> Void {
let sharedBranch = BranchSingleton.sharedInstance
let sharedArticle = ArticleSingleton.sharedInstance
Alamofire.request(.GET, Config().apiBranch)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let branch = Mapper<Branch>().map(obj.1.rawString()!)
sharedBranch.addBranch(branch!)
}
}
Alamofire.request(.GET, Config().apiArticle)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let article = Mapper<Article>().map(obj.1.rawString()!)
sharedArticle.addArticle(article!)
}
}
}
ViewController.swift file:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getStartData() // need to wait until all requests are finished then do print
print(sharedArticle.articleList)
}
}
SingletonObj.swift file:
import Foundation
class BranchSingleton {
var branchList: [Branch] = []
class var sharedInstance: BranchSingleton {
struct Static {
static let instance: BranchSingleton = BranchSingleton()
}
return Static.instance
}
func addBranch(branch: Branch) {
branchList.append(branch)
}
}
class ArticleSingleton {
var articleList: [Article] = []
class var sharedInstance: ArticleSingleton {
struct Static {
static let instance: ArticleSingleton = ArticleSingleton()
}
return Static.instance
}
func addArticle(article: Article) {
articleList.append(article)
}
}
i need to wait until getStartData() finish, then pring singleton array..
How can i do that?
This getStartData contains more than 2 requests, but i just gave example with 2..
You're asking a non-question. There is no reason to "wait". Nor can you. You just do what you do, asynchronously. Meanwhile the interface must stay active; the user must be able to continue to work. Thus there is nothing to "wait" for.
Now, if the question is, how can you send a signal in some elegant way to the rest of your app when all of the requests are done, one good answer is to use NSProgress. All the different requests can contribute to a common NSProgress object. The nice thing is that its fractionCompleted is observable with KVO, so when it comes greater-than-or-equal-to 1.0, you're done.
But you don't actually need the NSProgress; you could just increment or decrement an instance variable that's KVO-observable (being careful about threading, of course). If you know there are n processes, then you could just start a variable at n and have each process decrement it when it completes; a didSet observer on the variable can then take action when we hit zero.
The point is: you don't "wait": you just have all the different activities contribute to some common central value that "knows" when this means we've "finished" and can then take action.
As #Matt says, you can't, and shouldn't, try to wait until Alamofire is done with your request. That's like hiring somebody to run an errand for so you can work and then stopping everything and sitting by the door until they get back. You might as well have run the errand yourself.
Dropping the analogy, you might as well have performed the task synchronously. However, synchronous networking is a very bad idea. It freezes the UI until the network request is complete, which can be a very long wait if something goes wrong.
An async method like Alamofire's request method takes a completion block, a block of code that should be run when the work is finished.
The request method returns immediately, before the request has even been sent to the server, much less completed.
Instead of waiting around for the request to complete, you should refactor your getStartData method to take a completion handler, and use that to respond once the work is done:
func getStartData(completion: () -> void) -> Void {
let sharedBranch = BranchSingleton.sharedInstance
let sharedArticle = ArticleSingleton.sharedInstance
Alamofire.request(.GET, Config().apiBranch)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let branch = Mapper<Branch>().map(obj.1.rawString()!)
sharedBranch.addBranch(branch!)
}
}
Alamofire.request(.GET, Config().apiArticle)
.responseJSON { request, response, result in
let jsonObj = SwiftyJSON.JSON(result.value!)
for obj in jsonObj {
let article = Mapper<Article>().map(obj.1.rawString()!)
sharedArticle.addArticle(article!)
}
//At this point the Alamofire .GET request for Config().apiArticle
//is complete. Call our completion block (passed in as a parameter)
completion()
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
getStartData()
{
//This is a "trailing closure", a block of code passed to getStartData
print("At this point, we've finished getting our data from Alamofire.")
print(sharedArticle.articleList)
}
}
}
Note that your getStartData method makes 2 Alamofire.request() commands in a row. If the second request requires that the first request be finished then you will need to restructure that code so that the second Alamofire request is inside the completion block for the first call. (That's more editing than I'm in the mood to do at the moment.)
Related
I'd like to handle a series of network calls in my app. Each call is asynchronous and flatMap() seems like the right call. However, flatMap processes all arguments at the same time and I need the calls to be sequential -- the next network call starts only after the previous one is finished. I looked up an RxSwift answer but it requires concatMap operator that Combine does not have. Here is rough outline of what I'm trying to do, but flatMap fires all myCalls at the same time.
Publishers.Sequence(sequence: urls)
.flatMap { url in
Publishers.Future<Result, Error> { callback in
myCall { data, error in
if let data = data {
callback(.success(data))
} else if let error = error {
callback(.failure(error))
}
}
}
}
After experimenting for a while in a playground, I believe I found a solution, but if you have a better idea, please share. The solution is to add maxPublishers parameter to flatMap and set the value to max(1)
Publishers.Sequence(sequence: urls)
.flatMap(maxPublishers: .max(1)) // <<<<--- here
{ url in
Publishers.Future<Result, Error> { callback in
myCall { data, error in
if let data = data {
callback(.success(data))
} else if let error = error {
callback(.failure(error))
}
}
}
}
You can also use prepend(_:) method on observable which creates concatenated sequence which, I suppose is similar to Observable.concat(:) in RxSwift.
Here is a simple example that I tried to simulate your use case, where I have few different sequences which are followed by one another.
func dataTaskPublisher(_ urlString: String) -> AnyPublisher<(data: Data, response: URLResponse), Never> {
let interceptedError = (Data(), URLResponse())
return Publishers.Just(URL(string: urlString)!)
.flatMap {
URLSession.shared
.dataTaskPublisher(for: $0)
.replaceError(with: interceptedError)
}
.eraseToAnyPublisher()
}
let publisher: AnyPublisher<(data: Data, response: URLResponse), Never> = Publishers.Empty().eraseToAnyPublisher()
for urlString in [
"http://ipv4.download.thinkbroadband.com/1MB.zip",
"http://ipv4.download.thinkbroadband.com/50MB.zip",
"http://ipv4.download.thinkbroadband.com/10MB.zip"
] {
publisher = publisher.prepend(dataTaskPublisher(urlString)).eraseToAnyPublisher()
}
publisher.sink(receiveCompletion: { completion in
print("Completed")
}) { response in
print("Data: \(response)")
}
Here, prepend(_:) operator prefixes the sequence and so, prepended sequences starts first, completes and next sequence start.
If you run the code below, you should see that firstly 10 MB file is download, then 50 MB and at last 1 MB, since the last prepended starts first and so on.
There is other variant of prepend(_:) operator which takes array, but that does not seem to work sequentially.
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.
I'm learning Test Driven Development in Swift. I hit a wall when I realized the delegate pattern I regularly use for asynchronous requests is difficult to test. I've learned that if something's difficult to test, the design pattern behind the implementation could probably be better. This is confusing me because I think the delegate pattern I'm using is common and I'm wondering how others have dealt with this issue.
The pattern:
I wrote a service, which executes an asynchronous request in a static function which takes a delegate instance. The delegate instance conforms to a protocol which requires implementation of a success and failure method. I've contrived an example which hits Google.com. Please ignore the Type safety issues in this example. The actual code I'm running to hit an endpoint and parse JSON is safer. I just wanted to come up with a very small snippet of code to depict the issue that's causing difficulty while testing:
protocol GoogleServiceDelegate {
func gotGoogle(str: String);
func gotError(str: String);
}
struct GoogleService {
static func getGoogle(delegate: GoogleServiceDelegate) {
let url: NSURL! = NSURL(string: "http://google.com")
NSURLSession.sharedSession().dataTaskWithURL(url) { data, response, error in
if let data = data {
let str: NSString! = NSString(data: data, encoding: NSUTF8StringEncoding)
delegate.gotGoogle(str as String)
} else {
delegate.gotError("\(error)")
}
}
}
}
Here's the test which illustrates the problem:
class AsyncTestingTests: XCTestCase {
func testExample() {
let responseExpectation = expectationWithDescription("Got google response!")
struct GoogleDelegate: GoogleServiceDelegate {
func gotGoogle(str: String) {
// expectations about response
responseExpectation.fulfill()
}
func gotError(str: String) {
// expectations about error
responseExpectation.fulfill()
}
}
let myGoogleServiceDelegate = GoogleDelegate()
GoogleService.getGoogle(myGoogleServiceDelegate)
waitForExpectationsWithTimeout(5) { _ in
print("Never got a response from Google :(")
}
}
}
The problem arises at the two .fulfill() lines. I get the following error from Xcode:
Struct declaration cannot close over value 'responseExpectation' defined in outer scope
I understand the error, but am unsure what to adjust... Is there a workaround for this which I can use in the test, or is there a better (easily testable) pattern for asynchronous callbacks than what I am attempting? If you know of a better testable solution, would you mind taking the time to write down an example?
Yes, you can not close over variables defined outside of struct, to workaround, we need to use closures/functions and pass it to the struct. Methods in struct can invoke it when they receive the response.
func testExample() {
let responseExpectation = expectationWithDescription("Got google response!")
//Let a function capture the fulfilling of the expectation
func fullFillExpectation(){
responseExpectation.fullFill()
}
struct GoogleDelegate: GoogleServiceDelegate {
var fullFiller : (()->Void)!
func gotGoogle(str: String) {
// expectations about response via invoke the closure
fullFiller()
}
func gotError(str: String) {
// expectations about error - invoke the closure
fullFiller()
}
}
//Create the delegate with full filler function.
let myGoogleServiceDelegate = GoogleDelegate(fullFiller: fullFillExpectation)
GoogleService.getGoogle(myGoogleServiceDelegate)
waitForExpectationsWithTimeout(5) { _ in
print("Never got a response from Google :(")
}
}
}
PS: I could not test this, please test and let me know.
I have my class named "Service" where inside I do a lot of GET/POST request with Alamofire, an example of request id this
func requestDocuments(){
request(.POST, "http://example.com/json/docs")
.responseJSON { (_, _, JSON, error) in
if error == nil{
var response = JSON as NSArray
println("array document: \(response)")
//**** HERE I WANT PASS VALUE TO MY VIEW CONTROLLER
}
else{
}
}
}
and from my viewcontroller:
let service = Service.sharedInstance
service.requestDocuments()
What can I use? delegate method? or what?
what is the best solution in swift?
func requestDocuments(completion:(data:NSArray?)){
request(.POST, "http://example.com/json/docs")
.responseJSON { (_, _, JSON, error) in
if error == nil{
var response = JSON as NSArray
println("array document: \(response)")
//**** HERE I WANT PASS VALUE TO MY VIEW CONTROLLER
completion(data:response)
}
else{
completion(data:nil)
}
}
}
var reqDoc = requestDocuments(){ (data) -> Void in
if let _data = data {
dispatch_async(dispatch_get_main_queue()) {
//Do something with data
}
}
}
I think closures is the best solution.
Yes. There are 3 main ways to do this. The idea is you want to send off a call to a class, in this case, for networking, and have it come back sometime later and do something.
Delegates+Protocols are fantastic for this:
http://iosdevelopertips.com/objective-c/the-basics-of-protocols-and-delegates.html
As are Blocks
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html
The other popular way is KVO but that is not ideal for your example.
I will use closure in swift,
For example
class Service{
func requestDocuments(completion:(response:AnyObject)->()){
//After network is done
completion(response:data)
}
}
Then here to use
service.requestDocuments { (response) -> () in
//Here you can get response async
}
Use Delegate its the best approach.
Please see below example where delegation approach is demostrated
AFNetworking 2.0 - How to pass response to another class on success
Another approach would be NSNotification
I am writing an application that depends on data from various sites/service, and involves performing calculations based on data from these different sources to produce an end product.
I have written an example class with two functions below that gathers data from the two sources. I have chosen to make the functions different, because sometimes we apply different authentication methods depending on the source, but in this example I have just stripped them down to their simplest form. Both of the functions use Alamofire to fire off and handle the requests.
I then have an initialisation function, which says if we have successfully gathered data from both sources, then load another nib file, otherwise wait up to for seconds, if no response has been returned, then load a server error nib file.
I've tried to make this example as simple as possible. Essentially. This is the kind of logic I would like to follow. Unfortunately it appears this does not currently work in its current implementation.
import Foundation
class GrabData{
var data_source_1:String?
var data_source_2:String?
init(){
// get data from source 1
get_data_1{ data_source_1 in
println("\(data_source_1)")
}
// get data from source 2
get_data_2{ data_source_1 in
println("\(data_source_1)")
}
var timer = 0;
while(timer<5){
if((data_source_1 == nil) && (data_source_2 == nil)){
// do nothing unless 4 seconds has elapsed
if (timer == 4){
// load server error nib
}
}else{
// load another nib, and start manipulating data
}
// sleep for 1 second
sleep(1)
timer = timer+1
}
}
func get_data_1(completionHandler: (String) -> ()) -> () {
if let datasource1 = self.data_source_1{
completionHandler(datasource1)
}else{
var url = "http://somewebsite.com"
Manager.sharedInstance.request(.GET, url).responseString {
(request, response, returnedstring, error) in
println("getting data from source 1")
let datasource1 = returnedstring
self.data_source_1 = datasource1
completionHandler(datasource1!)
}
}
}
func get_data_2(completionHandler: (String) -> ()) -> () {
if let datasource2 = self.data_source_2{
completionHandler(datasource2)
}else{
var url = "http://anotherwebsite.com"
Manager.sharedInstance.request(.GET, url).responseString {
(request, response, returnedstring, error) in
println("getting data from source 2")
let datasource2 = returnedstring
self.data_source_2 = datasource2
completionHandler(datasource2!)
}
}
}
}
I know that i could put the second closure within the first inside the init function, however, I don't think this would be best practice and I am actually pulling from more than 2 sources, so the closure would be n closures deep.
Any help to figuring out the best way to checking if multiple data sources gave a valid response, and handling that appropriately would be much appreciated.
Better than that looping process, which would block the thread, you could use dispatch group to keep track of when the requests were done. So "enter" the group before issuing each of the requests, "leave" the group when the request is done, and set up a "notify" block/closure that will be called when all of the group's tasks are done.
For example, in Swift 3:
let group = DispatchGroup()
group.enter()
retrieveDataFromURL(url1, parameters: firstParameters) {
group.leave()
}
group.enter()
retrieveDataFromURL(url2, parameters: secondParameters) {
group.leave()
}
group.notify(queue: .main) {
print("both requests done")
}
Or, in Swift 2:
let group = dispatch_group_create()
dispatch_group_enter(group)
retrieveDataFromURL(url1, parameters: firstParameters) {
dispatch_group_leave(group)
}
dispatch_group_enter(group)
retrieveDataFromURL(url2, parameters: secondParameters) {
dispatch_group_leave(group)
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
print("both requests done")
}
The other approach is to wrap these requests within an asynchronous NSOperation subclass (making them cancelable, giving you control over constraining the degree of concurrency, etc.), but that's more complicated, so you might want to start with dispatch groups as shown above.