0

I trying return JSON Dictionary from Alamofire. The problem is that when assigning JSON to an external variable, the assigned value is saved and read only inside the Alamofire.request block, and outside its value is not saved (magic for me). What have I done wrong?

I do this first time. I'm tried use URLSession.shared.dataTask but I also could not return value from task. I noticed that first the breakpoint triggered on the return res line, then on the return json line and then on the print (JSON) line. I do not know why.

struct resultMsg {
    var message: [String:Any] = [:]
    init() { }
    init(message: [String:Any]) {
        self.message = message
    }
}

func sendGET(method: String, data: [String:String]) -> [String:Any] {
    let resultURL: String = rootURI + method
    let url = URL(string: resultURL)!
    var res: [String:Any] = [:]

    Alamofire.request(url, method: .post, parameters: data, encoding: JSONEncoding.default)
        .responseJSON { response in
            print(response)
            if let status = response.response?.statusCode {
                switch(status){
                case 201:
                    print("example success")
                default:
                    print("error with response status: \(status)")
                }
            }
            //to get JSON return value
            if let result = response.result.value {
                let JSON = result as! NSDictionary
                res = JSON as! [String : Any]
                print(JSON) // Here res is contain actualy json
            }
    }
    return res // Here res equal [:]
}

public func Authorize() -> resultMsg {
    let data: [String:String] = ["login":login,
                                 "pass":pass]        
    var json = resultMsg.init()
    json.message = sendGET(method: "auth.php", data: data)
    return json
}
ghost17
  • 1
  • 3

4 Answers4

1

It's because the network request is asynchronous, as they usually are. When you call Alamofire.request(...), the network request starts, and request returns immediately. In the meantime, networking stuff happens in the background. Since request has returned, your sendGET function continues on to the next line immediately.

Later on, when the network request finishes, the completion block gets called. You assign the value to res, but it's too late because sendGET already returned an empty dictionary back when the request started.

Usually this is pretty fast, but it can help to imagine that it's going to take a very long time. Sometimes it will take a very long time, after all. If the network connection times out, it could take several minutes.

There are different ways of dealing with this.

  • In one approach you'd make res a property of the class where this code is. Then your completion block would still assign a value to res, and then call some new code to tell your class that the result is available.
  • In another approach, you would modify the completion closure to pass the JSON data to some other function that uses the data directly. Add some new function that has an argument of type [String : Any], and call this function from the completion closure.

Which one is better depends on what else is going on in your app and how you need to use this data when you get it.

Tom Harrington
  • 69,312
  • 10
  • 146
  • 170
0

Tom Harrington is right. The better approach would be add a completion block to

func sendGET

like

func sendGET(method: String, data: [String:String], completion: ([String:Any]) -> Void)

and call the completion when you get the json, Thats it.

Happy coding

fahu
  • 23
  • 1
  • 6
  • For your version, the compiler asks to add `@escaping` `func sendGET(method: String, data: [String:String], completion: @escaping ([String:Any]) -> Void)` then I add in `sendGET` `completion(res)` and in Authorize() `sendGET(method: "auth.php", data: data){ dict in` `json.message = dict` `}` But break points triggered in same sequence – ghost17 Jan 01 '19 at 13:59
0

In general, I decided that it was too early for me to write Swift programs. I wanted to port my project for iOS written in C#, I decided that the native application would be better, and there are so many pitfalls with these closure. But in C# everything is simpler: just write await before a function, but there it is not always needed.

Thanks to everyone who helped...

UPD 1

I know that this is probably stupid, but only this method works without unnecessary trouble with closures. But the repetitive code will become a lot.

UPD 2

Also unfortunately I had to use GET instead of POST, since in POST the server did not receive any data. I checked it with echo json_encode($_POST);. But I might as well not have used Alamofire.

UPD 3

Finally, I was achieved a better solution by Tom Harrington's advice. Before I found solution like so, but it don't work in DispatchQueue.main.async like as Alamofire.request(I suggested that Alamofire uses main.async). Therefore, I did like here.

UPD 4

And I'm wrong again. Solution in UPD 3 sometimes freezes in loop body. And I'm decided finally learn use own completion, and I did it :)

func sendGET(_ method: String,_ data: [String:String],_ instance: CommonClass, completion : @escaping (Any?)->()) {
    let url = getURL(method: method, data: data)
    Alamofire.request(url, method: .get).responseJSON { response in
        if let status = response.response?.statusCode {
            instance.manageStatus(status)
        }
        if let result = response.result.value {
            completion(result)
        }
    }
}

func someFunc(_ instance: CommonClass) {      
    let data: [String:String] = ["id":String(id),
                                 "route":route]
    sendGET("getTaskData.php", data, instance, completion: { response in
        let JSON = response as! [[String:String]]
        // Do something with JSON
    })
}
ghost17
  • 1
  • 3
0

I will offer you a method. With the help of Swift 4.2, Codable has been started, so you can easily JSON parse and get rid of the code.

Examples Class

public struct Faults: Decodable {

    public enum CodingKeys:String,CodingKey{
        case guid
        case adi
    }

    public let guid:Int
    public let adi:String
}

And json parse (get web services)

 public func fetchFaults(complation: @escaping (Result<[Faults]>) -> Void) {
        let urlString = "http://examples"
       request(urlString, method: .get, encoding: URLEncoding.default, headers: ...)
        .responseData { (response) in
            switch response.result {
            case .success(let data):
                do {
                    let faults = try Decoders.plainDateDecoder.decode([Faults].self, from: data)
                    complation(.success(faults))
                } catch{
                    complation(.failure(Error.serializationError(internal: error)))
                }
            case .failure(let error):
                complation(.failure(Error.networkError(internal: error)))
            }
        }
    }  
ikbal
  • 1,110
  • 12
  • 21