从URLSession返回数据并保存在属性变量中

时间:2018-02-20 16:46:39

标签: ios swift api session

我尝试使用 URLSession.shared.dataTask 从服务器获取一些数据。 它工作正常,但我不能像类变量一样保存结果。 许多答案建议使用 完成处理程序 ,但它对我的任务没有帮助。

这是我的测试代码:

class PostForData {
func forData(completion:  @escaping (String) -> ()) {
    if let url = URL(string: "http://odnakrov.info/MyWebService/api/test.php") {
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        let postString : String = "json={\"Ivan Bolgov\":\"050-062-0769\"}"
        print(postString)
        request.httpBody = postString.data(using: .utf8)
        let task = URLSession.shared.dataTask(with: request) {
            data, response, error in
            let json = String(data: data!, encoding: String.Encoding.utf8)!
                completion(json)
        }
        task.resume()
    }
}
}
class ViewController: UIViewController {
var str:String?
override func viewDidLoad() {
    super.viewDidLoad()
    let pfd = PostForData()

    pfd.forData { jsonString in
        print(jsonString)
        DispatchQueue.main.async {
            self.str = jsonString
        }
    }
    print(str ?? "not init yet")
}
}

1 个答案:

答案 0 :(得分:0)

这个闭包是@escaping(即稍后异步调用),所以你必须将它放在闭包中:

class ViewController: UIViewController {
    @IBOutlet weak var label: UILabel!

    var str: String?

    override func viewDidLoad() {
        super.viewDidLoad()
        let pfd = PostForData()

        pfd.performRequest { jsonString, error in
            guard let jsonString = jsonString, error == nil else {
                print(error ?? "Unknown error")
                return
            }

            // use jsonString inside this closure ...

            DispatchQueue.main.async {
                self.str = jsonString
                self.label.text = jsonString
            }
        }

        // ... but not after it, because the above runs asynchronously (i.e. later)
    }
}

注意,我更改了闭包以返回String?Error?,以便视图控制器可以知道是否发生错误(如果它关心,它可以看到发生了什么类型的错误)。

注意,我将您的forData重命名为performRequest。通常你会使用比这更有意义的名字,但方法名称(在Swift 3及更高版本中)通常应该包含一个动词,表明正在做什么。

class PostForData {
    func performRequest(completion:  @escaping (String?, Error?) -> Void) {
        // don't try to build JSON manually; use `JSONSerialization` or `JSONEncoder` to build it

        let dictionary = [
            "name": "Ivan Bolgov",
            "ss": "050-062-0769"
        ]
        let jsonData = try! JSONEncoder().encode(dictionary)

        // It's a bit weird to incorporate JSON in `x-www-form-urlencoded` request, but OK, I'll do that.

        // But make sure to percent escape it.

        let jsonString = String(data: jsonData, encoding: .utf8)!
            .addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)!

        let body = "json=" + jsonString
        let url = URL(string: "http://odnakrov.info/MyWebService/api/test.php")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.httpBody = body.data(using: .utf8)

        // It's not required, but it's good practice to set `Content-Type` (to specify what you're sending)
        // and `Accept` (to specify what you're expecting) headers.

        request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.setValue("application/json", forHTTPHeaderField: "Accept")

        // now perform the prepared request

        let task = URLSession.shared.dataTask(with: request) { data, _, error in
            guard let data = data, error == nil else {
                completion(nil, error)
                return
            }
            let responseString = String(data: data, encoding: .utf8)
            completion(responseString, nil)
        }
        task.resume()
    }
}

对该例程也有一些修改,特别是:

  1. 在处理服务器响应时,不要使用!强制解包。您无法控制请求是成功还是失败,强制解包操作符会使您的应用程序崩溃。您应该使用guard letif let模式优雅地展开这些选项。

  2. 使用json=...模式,...是JSON字符串,这是非常不寻常的。您可以从中推断出您正在准备application/x-www-form-urlencoded请求,并使用$_POST$_REQUEST获取与json密钥相关联的值。通常,您要么执行真正的JSON请求,要么执行application/x-www-form-urlencoded请求,但不能同时执行这两项请求。但是,在一个请求中同时执行这两项操作会使客户端和服务器代码中的工作量增加一倍。上面的代码遵循原始代码段中的模式,但我建议使用其中一个,但不能同时使用。

  3. 就个人而言,我不会performRequest返回JSON字符串。我建议它实际执行JSON的解析。但是,我再次将其保留在您的代码段中。

  4. 我注意到你以"Ivan Bolgov": "050-062-0769"的形式使用了JSON。我建议不要使用"值"作为JSON的关键。键应该是有利的定义常量。因此,例如,上面我使用"name": "Ivan Bolgov""ss": "050-062-0769",服务器知道在哪里查找名为namess的密钥。在这里做任何你想做的事情,但你原来的JSON请求似乎把密钥(通常是预先知道的)和值(与这些密钥相关的值)混为一起。

  5. 如果您要进行x-www-form-urlencoded请求,则必须对所提供的值进行百分比编码,就像我上面所做的那样。值得注意的是,在这些类型的请求中不允许使用空格字符等字符,因此您必须对它们进行百分比编码。毋庸置疑,如果您做了正确的JSON请求,则不需要这种愚蠢。

    但请注意,在编码百分比时,不要试图使用默认的.urlQueryAllowed字符集,因为它会允许某些字符无法传递。所以我定义了一个.urlQueryValueAllowed,它删除了.urlQueryAllowed字符集中的某些字符(改编自Alamofire中使用的模式):

    extension CharacterSet {
    
        /// Returns the character set for characters allowed in the individual parameters within a query URL component.
        ///
        /// The query component of a URL is the component immediately following a question mark (?).
        /// For example, in the URL `http://www.example.com/index.php?key1=value1#jumpLink`, the query
        /// component is `key1=value1`. The individual parameters of that query would be the key `key1`
        /// and its associated value `value1`.
        ///
        /// According to RFC 3986, the set of unreserved characters includes
        ///
        /// `ALPHA / DIGIT / "-" / "." / "_" / "~"`
        ///
        /// In section 3.4 of the RFC, it further recommends adding `/` and `?` to the list of unescaped characters
        /// for the sake of compatibility with some erroneous implementations, so this routine also allows those
        /// to pass unescaped.
    
        static var urlQueryValueAllowed: CharacterSet = {
            let generalDelimitersToEncode = ":#[]@"    // does not include "?" or "/" due to RFC 3986 - Section 3.4
            let subDelimitersToEncode = "!$&'()*+,;="
    
            var allowed = CharacterSet.urlQueryAllowed
            allowed.remove(charactersIn: generalDelimitersToEncode + subDelimitersToEncode)
            return allowed
        }()
    
    }
    
  6. 我建议更改PHP以接受JSON请求,例如:

    <?php
    
        // read the raw post data
    
        $handle = fopen("php://input", "rb");
        $raw_post_data = '';
        while (!feof($handle)) {
            $raw_post_data .= fread($handle, 8192);
        }
        fclose($handle);
    
        // decode the JSON into an associative array
    
        $request = json_decode($raw_post_data, true);
    
        // you can now access the associative array how ever you want
    
        if ($request['foo'] == 'bar') {
            $response['success'] = true;
            $response['value']   = 'baz';
        } else {
            $response['success'] = false;
        }
    
        // I don't know what else you might want to do with `$request`, so I'll just throw
        // the whole request as a value in my response with the key of `request`:
    
        $raw_response = json_encode($response);
    
        // specify headers
    
        header("Content-Type: application/json");
        header("Content-Length: " . strlen($raw_response));
    
        // output response
    
        echo $raw_response;
    ?>
    

    然后,您可以简化请求的构建,从而无需使用x-www-form-urlencoded次请求执行的所有百分比编码:

    class PostForData {
        func performRequest(completion:  @escaping (String?, Error?) -> Void) {
            // Build the json body
    
            let dictionary = [
                "name": "Ivan Bolgov",
                "ss": "050-062-0769"
            ]
            let data = try! JSONEncoder().encode(dictionary)
    
            // build the request
    
            let url = URL(string: "http://odnakrov.info/MyWebService/api/test.php")!
            var request = URLRequest(url: url)
            request.httpMethod = "POST"
            request.httpBody = data
    
            // It's not required, but it's good practice to set `Content-Type` (to specify what you're sending)
            // and `Accept` (to specify what you're expecting) headers.
    
            request.setValue("application/json", forHTTPHeaderField: "Content-Type")
            request.setValue("application/json", forHTTPHeaderField: "Accept")
    
            // now perform the prepared request
    
            let task = URLSession.shared.dataTask(with: request) { data, _, error in
                guard let data = data, error == nil else {
                    completion(nil, error)
                    return
                }
                let responseString = String(data: data, encoding: .utf8)
                completion(responseString, nil)
            }
            task.resume()
        }
    }