Swift http request use urlSession

北慕城南 提交于 2019-11-28 13:10:48

As mentioned in Rob's comments, the dataTask closure is run asynchronously. Instead of returning the value immediately, you would want to provide a completion closure and then call it when dataTask completes.

Here is an example (for testing, can be pasted to Xcode Playground as-is):

import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true

let HOST = "http://example.org"

public func HTTPRequest(dir: String, param: [String: String]?,  completion: @escaping (String) -> Void) {

    var urlString = HOST + dir + "?"

    if param != nil{
        for currentParam in param! {
            urlString += currentParam.key + "=" + currentParam.value + "&"
        }
    }

    let url = URL(string: urlString)

    let task = URLSession.shared.dataTask(with: url!) { data, response, error in
        guard error == nil else {
            print("ERROR: HTTP REQUEST ERROR!")
            return
        }
        guard let data = data else {
            print("ERROR: Empty data!")
            return
        }
        let responseString = NSString(data: data,encoding: String.Encoding.utf8.rawValue) as! String

        completion(responseString)

    }

    task.resume()

}

let completion: (String) -> Void = { responseString in

    print(responseString)

}

HTTPRequest(dir: "", param: nil, completion: completion)

You need to use completion block instead of returning value because the dataTask closure is run asynchronously, i.e. later, well after you return from your method. You don't want to try to return the value immediately (because you won't have it yet). You want to (a) change this function to not return anything, but (b) supply a completion handler closure, which you will call inside the dataTask closure, where you build responseString.

For example, you might define it like so:

public func HTTPRequest(dir: String, param: [String:String]? = nil, completionHandler: @escaping (String?, Error?) -> Void) {
    var urlString = HOST + dir

    if let param = param {
        let parameters = param.map { return $0.key.percentEscaped() + "=" + $0.value.percentEscaped() }
        urlString += "?" + parameters.joined(separator: "&")
    }

    let url = URL(string: urlString)

    let task = URLSession.shared.dataTask(with: url!) { data, response, error in
        guard let data = data, error == nil else {
            completionHandler(nil, error)
            return
        }
        let responseString = String(data: data, encoding: .utf8)
        completionHandler(responseString, nil)
    }
    task.resume()
}

Note, I'm percent escaping the values in the parameters dictionary using something like:

extension String {

    /// Percent escapes values to be added to a URL query as specified in RFC 3986
    ///
    /// This percent-escapes all characters besides the alphanumeric character set and "-", ".", "_", and "~".
    ///
    /// http://www.ietf.org/rfc/rfc3986.txt
    ///
    /// - Returns: Returns percent-escaped string.

    func percentEscaped() -> String {
        let allowedCharacters = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")

        return self.addingPercentEncoding(withAllowedCharacters: allowedCharacters)!
    }

}

And then you'd call it like so:

HTTPRequest(dir: directory, param: parameterDictionary) { responseString, error in
    guard let responseString = responseString else {
        // handle the error here
        print("error: \(error)")
        return
    }

    // use `responseString` here

    DispatchQueue.main.async {
        // because this is called on background thread, if updating
        // UI, make sure to dispatch that back to the main queue.
    }
}

// but don't try to use `responseString` here
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!