草庐IT

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

coder 2024-01-28 原文

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

这是我的测试代码:

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")
}
}

最佳答案

这个闭包是@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? 以便 View Controller 可以知道是否发生了错误(如果它关心,它可以看到发生了什么样的错误)。

请注意,我将您的 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
        }()
    
    }
    

我建议更改您的 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()
    }
}

关于ios - 从 URLSession 返回数据并保存在属性变量中,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48890209/

有关ios - 从 URLSession 返回数据并保存在属性变量中的更多相关文章

  1. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  2. ruby - 为什么 4.1%2 使用 Ruby 返回 0.0999999999999996?但是 4.2%2==0.2 - 2

    为什么4.1%2返回0.0999999999999996?但是4.2%2==0.2。 最佳答案 参见此处:WhatEveryProgrammerShouldKnowAboutFloating-PointArithmetic实数是无限的。计算机使用的位数有限(今天是32位、64位)。因此计算机进行的浮点运算不能代表所有的实数。0.1是这些数字之一。请注意,这不是与Ruby相关的问题,而是与所有编程语言相关的问题,因为它来自计算机表示实数的方式。 关于ruby-为什么4.1%2使用Ruby返

  3. ruby-on-rails - 如果为空或不验证数值,则使属性默认为 0 - 2

    我希望我的UserPrice模型的属性在它们为空或不验证数值时默认为0。这些属性是tax_rate、shipping_cost和price。classCreateUserPrices8,:scale=>2t.decimal:tax_rate,:precision=>8,:scale=>2t.decimal:shipping_cost,:precision=>8,:scale=>2endendend起初,我将所有3列的:default=>0放在表格中,但我不想要这样,因为它已经填充了字段,我想使用占位符。这是我的UserPrice模型:classUserPrice回答before_val

  4. ruby-on-rails - 在混合/模块中覆盖模型的属性访问器 - 2

    我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah

  5. ruby-on-rails - 如何使用 instance_variable_set 正确设置实例变量? - 2

    我正在查看instance_variable_set的文档并看到给出的示例代码是这样做的:obj.instance_variable_set(:@instnc_var,"valuefortheinstancevariable")然后允许您在类的任何实例方法中以@instnc_var的形式访问该变量。我想知道为什么在@instnc_var之前需要一个冒号:。冒号有什么作用? 最佳答案 我的第一直觉是告诉你不要使用instance_variable_set除非你真的知道你用它做什么。它本质上是一种元编程工具或绕过实例变量可见性的黑客攻击

  6. ruby - 通过 ruby​​ 进程共享变量 - 2

    我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是

  7. ruby - 多个属性的 update_column 方法 - 2

    我有一个具有一些属性的模型:attr1、attr2和attr3。我需要在不执行回调和验证的情况下更新此属性。我找到了update_column方法,但我想同时更新三个属性。我需要这样的东西:update_columns({attr1:val1,attr2:val2,attr3:val3})代替update_column(attr1,val1)update_column(attr2,val2)update_column(attr3,val3) 最佳答案 您可以使用update_columns(attr1:val1,attr2:val2

  8. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  9. ruby - Nokogiri 剥离所有属性 - 2

    我有这个html标记:我想得到这个:我如何使用Nokogiri做到这一点? 最佳答案 require'nokogiri'doc=Nokogiri::HTML('')您可以通过xpath删除所有属性:doc.xpath('//@*').remove或者,如果您需要做一些更复杂的事情,有时使用以下方法遍历所有元素会更容易:doc.traversedo|node|node.keys.eachdo|attribute|node.deleteattributeendend 关于ruby-Nokog

  10. ruby - 检查字符串是否包含散列中的任何键并返回它包含的键的值 - 2

    我有一个包含多个键的散列和一个字符串,该字符串不包含散列中的任何键或包含一个键。h={"k1"=>"v1","k2"=>"v2","k3"=>"v3"}s="thisisanexamplestringthatmightoccurwithakeysomewhereinthestringk1(withspecialcharacterslike(^&*$#@!^&&*))"检查s是否包含h中的任何键的最佳方法是什么,如果包含,则返回它包含的键的值?例如,对于上面的h和s的例子,输出应该是v1。编辑:只有字符串是用户定义的。哈希将始终相同。 最佳答案

随机推荐