URLSession.shared.dataTask 代码块未运行

发布于 2025-01-13 01:36:38 字数 1264 浏览 5 评论 0原文

我正在尝试在 Swift 中进行相当简单的 API 调用,但由于某种原因,我的 dataTask 代码没有运行。我已经确保 .resume() 在那里。该代码过去一直有效,但是最近发生了一些变化,我不知道它是什么。我唯一能想到的就是网址。我已经更改了成分,但是当将 url 输入浏览器时,它会正常返回 JSON 数据。运行此函数时,我连续收到两条“Outside URLSession.shared.dataTask.....”消息,中间没有任何内容,表明 URLSession 代码块未运行。我对 API 有点陌生,因此,任何帮助将不胜感激。如果我可以提供更多信息,请告诉我。另外,我使用的是较旧的 MacBook,并且正在使用 Swift5(如果这有影响的话)。谢谢!

    let url: URL! = URL(string: "https://api.spoonacular.com/recipes/findByIngredients?ingredients=" + ingredientString + "&apiKey=aaabbbccc111222333")
    print("URL: " + url.absoluteString)
    
    let request = URLRequest(url: url)
    
    // Make the API call
    print("Outide URLSession.shared.dataTask.....")
    let session = URLSession.shared.dataTask(with: request) { data, response, error in
        print("Inside URLSession.shared.dataTask.....")
        DispatchQueue.main.async {
            print("Inside DispatchQueue.main.async....")
            if data == nil {
                print("No data recieved.")
            }
            print("data != nil.... Moving on to JSONDecoder....")
            self.model = try! JSONDecoder().decode([RecipeSearchElement].self, from: data!)
        }
    }
    session.resume()
    
    print("Outside URLSession.shared.dataTask.....")

I'm trying to make a fairly simple API call in Swift but, for some reason, my dataTask code is not running. I've made sure that the .resume() is there. This code has worked in the past but, something has changed recently and I don't know what it is. The only thing I can think of is the url. I've changed the ingredients but, when putting the url into a browser, it returns JSON data normally. When running this function, I get two "Outside URLSession.shared.dataTask....." messages in a row with nothing in between, indicating that the URLSession block of code isn't running. I'm a little new to APIs so, any help would be greatly appreciated. Please let me know if there's any more information I can provide. Also, I'm on an older MacBook and am using Swift5 if that makes a difference. Thanks!

    let url: URL! = URL(string: "https://api.spoonacular.com/recipes/findByIngredients?ingredients=" + ingredientString + "&apiKey=aaabbbccc111222333")
    print("URL: " + url.absoluteString)
    
    let request = URLRequest(url: url)
    
    // Make the API call
    print("Outide URLSession.shared.dataTask.....")
    let session = URLSession.shared.dataTask(with: request) { data, response, error in
        print("Inside URLSession.shared.dataTask.....")
        DispatchQueue.main.async {
            print("Inside DispatchQueue.main.async....")
            if data == nil {
                print("No data recieved.")
            }
            print("data != nil.... Moving on to JSONDecoder....")
            self.model = try! JSONDecoder().decode([RecipeSearchElement].self, from: data!)
        }
    }
    session.resume()
    
    print("Outside URLSession.shared.dataTask.....")

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

烟若柳尘 2025-01-20 01:36:38

与您眼前的问题无关(我在其他地方回答了这个问题),我建议对例程进行一些更改:

  • 不应通过字符串插值来构建 URL。使用URLComponents。例如,如果查询参数包含 URL 中不允许的空格或其他字符,URLComponents 将为您对其进行百分比编码。如果没有正确对其进行百分比编码,URL 的构建将会失败。

  • 我会避免 try!,如果服务器响应不符合您的预期,这将使应用程序崩溃。人们应该在 do-catch 块中使用 try,这样它就能优雅地处理错误,并在失败时告诉您出了什么问题。

  • 我建议将 URLSessionDataTask 重命名为 task 或类似名称,以避免将“会话”与在该会话上运行的“任务”混淆。

  • 我不建议从 URLSession 的后台队列更新模型。在后台队列中获取并解析响应并更新主队列上的模型。

因此:

var components = URLComponents(string: "https://api.spoonacular.com/recipes/findByIngredients")
components?.queryItems = [
    URLQueryItem(name: "ingredients", value: ingredientString),
    URLQueryItem(name: "apiKey", value: "aaabbbccc111222333")
]

guard let url = components?.url else {
    print("Unable to build URL")
    return
}

// Make the API call
let task = URLSession.shared.dataTask(with: url) { data, _, error in
    DispatchQueue.main.async {
        guard error == nil, let data = data else {
            print("No data received:", error ?? URLError(.badServerResponse))
            return
        }

        do {
            let model = try JSONDecoder().decode([RecipeSearchElement].self, from: data)
            DispatchQueue.main.async {
                self.model = model
            }
        } catch let parseError {
            print("Parsing error:", parseError, String(describing: String(data: data, encoding: .utf8)))
        }
    }
}
task.resume()

在更高级的观察中,我永远不会让网络调用直接更新模型。我会把它留给来电者。例如,您可以使用完成处理程序模式:

@discardableResult
func fetchIngredients(
    _ ingredientString: String,
    completion: @escaping (Result<[RecipeSearchElement], Error>) -> Void
) -> URLSessionTask? {
    var components = URLComponents(string: "https://api.spoonacular.com/recipes/findByIngredients")
    components?.queryItems = [
        URLQueryItem(name: "ingredients", value: ingredientString),
        URLQueryItem(name: "apiKey", value: "aaabbbccc111222333")
    ]

    guard let url = components?.url else {
        completion(.failure(URLError(.badURL)))
        return nil
    }

    // Make the API call
    let task = URLSession.shared.dataTask(with: url) { data, _, error in
        print("Inside URLSession.shared.dataTask.....")
        DispatchQueue.main.async {
            guard error == nil, let data = data else {
                DispatchQueue.main.async {
                    completion(.failure(error ?? URLError(.badServerResponse)))
                }
                return
            }

            do {
                let model = try JSONDecoder().decode([RecipeSearchElement].self, from: data)
                DispatchQueue.main.async {
                    completion(.success(model))
                }
            } catch let parseError {
                DispatchQueue.main.async {
                    completion(.failure(parseError))
                }
            }
        }
    }
    task.resume()

    return task
}

然后调用者可以执行以下操作:

fetchIngredients(ingredientString) { [weak self] result in
    switch result {
    case .failure(let error): print(error)
    case .success(let elements): self?.model = elements
    }
}

这有两个好处:

  • 调用者现在知道模型何时更新,因此您可以在适当的时间点(如果您愿意)。

  • 它保持了更好的职责分离,从架构上避免了网络层与视图或视图模型(或呈现器或控制器)层的紧密耦合。

请注意,我还会返回 URLSessionTask 对象,以防调用者稍后想要取消它,但我将其设为 @discardableResult,这样您就不必如果您此时不处理取消事宜,请担心这一点。

Unrelated to your immediate question at hand (which I answered elsewhere), I would advise a few changes to the routine:

  • One should not build a URL through string interpolation. Use URLComponents. If, for example, the query parameter included a space or other character not permitted in a URL, URLComponents will percent-encode it for you. If do not percent-encode it properly, the building of the URL will fail.

  • I would avoid try!, which will crash the app if the server response was not what you expected. One should use try within a do-catch block, so it handles errors gracefully and will tell you what is wrong if it failed.

  • I would recommend renaming the URLSessionDataTask to be task, or something like that, to avoid conflating “sessions” with the “tasks” running on that session.

  • I would not advise updating the model from the background queue of the URLSession. Fetch and parse the response in the background queue and update the model on the main queue.

Thus:

var components = URLComponents(string: "https://api.spoonacular.com/recipes/findByIngredients")
components?.queryItems = [
    URLQueryItem(name: "ingredients", value: ingredientString),
    URLQueryItem(name: "apiKey", value: "aaabbbccc111222333")
]

guard let url = components?.url else {
    print("Unable to build URL")
    return
}

// Make the API call
let task = URLSession.shared.dataTask(with: url) { data, _, error in
    DispatchQueue.main.async {
        guard error == nil, let data = data else {
            print("No data received:", error ?? URLError(.badServerResponse))
            return
        }

        do {
            let model = try JSONDecoder().decode([RecipeSearchElement].self, from: data)
            DispatchQueue.main.async {
                self.model = model
            }
        } catch let parseError {
            print("Parsing error:", parseError, String(describing: String(data: data, encoding: .utf8)))
        }
    }
}
task.resume()

In a more advanced observation, I would never have a network call update the model directly. I would leave that to the caller. For example, you could use a completion handler pattern:

@discardableResult
func fetchIngredients(
    _ ingredientString: String,
    completion: @escaping (Result<[RecipeSearchElement], Error>) -> Void
) -> URLSessionTask? {
    var components = URLComponents(string: "https://api.spoonacular.com/recipes/findByIngredients")
    components?.queryItems = [
        URLQueryItem(name: "ingredients", value: ingredientString),
        URLQueryItem(name: "apiKey", value: "aaabbbccc111222333")
    ]

    guard let url = components?.url else {
        completion(.failure(URLError(.badURL)))
        return nil
    }

    // Make the API call
    let task = URLSession.shared.dataTask(with: url) { data, _, error in
        print("Inside URLSession.shared.dataTask.....")
        DispatchQueue.main.async {
            guard error == nil, let data = data else {
                DispatchQueue.main.async {
                    completion(.failure(error ?? URLError(.badServerResponse)))
                }
                return
            }

            do {
                let model = try JSONDecoder().decode([RecipeSearchElement].self, from: data)
                DispatchQueue.main.async {
                    completion(.success(model))
                }
            } catch let parseError {
                DispatchQueue.main.async {
                    completion(.failure(parseError))
                }
            }
        }
    }
    task.resume()

    return task
}

And then the caller could do:

fetchIngredients(ingredientString) { [weak self] result in
    switch result {
    case .failure(let error): print(error)
    case .success(let elements): self?.model = elements
    }
}

This has two benefits:

  • The caller now knows when the model is updated, so you can update your UI at the appropriate point in time (if you want).

  • It maintains a better separation of responsibilities, architecturally avoiding the tight coupling of the network layer with that of the view or view model (or presenter or controller) layers.

Note, I am also returning the URLSessionTask object in case the caller would like to cancel it at a later time, but I made it an @discardableResult so that you do not have to worry about that if you are not tackling cancelation at this point.

马蹄踏│碎落叶 2025-01-20 01:36:38

如果您 (a) 正在到达“外部”消息,但没有看到“内部”消息; (b) 绝对肯定您正在到达 resume 语句,这是以下几种可能性之一:

  1. 应用程序可能会在异步请求有时间完成之前终止。例如,如果这是一个命令行应用程序,并且您允许该应用程序在异步请求有机会完成之前退出,则可能会发生这种情况。如果您希望命令行应用等待网络请求完成,您可以运行一个在网络请求完成之前不会退出的 RunLoop

    如果您使用 Playground 并忽略设置 needsIndefiniteExecution,也可能会发生这种情况:

    导入 PlaygroundSupport
    PlaygroundPage.current.needsIndefiniteExecution = true
    

为了完整起见,还有一些其他不太常见的可能性:

  1. 您有一些其他网络请求,其完成处理程序被阻止/死锁,从而阻止任何其他内容在 URLSession 专用串行队列上运行。

  2. 您的代码中其他地方发生了线程爆炸,耗尽了有限的工作线程池,从而阻止其他任务/操作获得可用的工作线程。

If you (a) are reaching the “outside” message, but not seeing the “inside” message; and (b) are absolutely positive that you are reaching the resume statement, it is one of a few possibilities:

  1. The app may be terminating before the asynchronous request has time to finish. This can happen, for example, if this is a command-line app and you are allowing the app to quit before the asynchronous request has a chance to finish. If you want a command-line app to wait for a network request to finish, you might run a RunLoop that does not exit until the network request is done.

    It can also happen if you use a playground and neglect to set needsIndefiniteExecution:

    import PlaygroundSupport
    PlaygroundPage.current.needsIndefiniteExecution = true
    

For the sake of completeness, there are a few other, less common, possibilities:

  1. You have some other network request whose completion handler is blocked/deadlocked, thereby preventing anything else from running on the URLSession dedicated, serial, queue.

  2. You have thread explosion somewhere else in your code, exhausting the limited pool of worker threads, preventing other tasks/operations from being able to get an available worker thread.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文