获取 JSON,附加到数组:转义闭包捕获变异的“自我”参数

Posted

技术标签:

【中文标题】获取 JSON,附加到数组:转义闭包捕获变异的“自我”参数【英文标题】:Fetching JSON, appending to array: Escaping closure captures mutating 'self' parameter 【发布时间】:2021-04-30 17:50:46 【问题描述】:

我已经准备好a simple test project at Github 来演示我的问题:

我有一个 SwiftUI 列表,我尝试在其中显示 var items:[String]

当我只有一个像下面这样的硬编码数组时 - 它可以正常工作并在 iPhone 中显示:

items = (1...200).map  number in "Item \(number)" 

但是当我尝试获取 JSON 网页并将结果附加到 items 时,我得到了错误:

转义闭包捕获变异的“自我”参数

我知道items.append(str) 行从 dataTask 闭包中修改了父 ContentView 对象,这出于某种原因并不好......但是如何修复我的代码呢?

import SwiftUI

struct TopResponse: Codable 
    let data: [Top]


struct Top: Codable 
    let uid: Int
    let elo: Int
    let given: String
    let photo: String?
    let motto: String?
    let avg_score: Double?
    let avg_time: String?


struct ContentView: View 
    var items:[String];

    init() 
        items = (1...200).map  number in "Item \(number)" 
        
        let url = URL(string: "https://slova.de/ws/top")!
        let task = URLSession.shared.dataTask(with: url) 
            data, response, error in
            
            let decoder = JSONDecoder()
            guard let data = data else  return 
            do 
                let tops = try decoder.decode(TopResponse.self, from: data)
                for (index, top) in tops.data.enumerated() 
                    let str = "\(index + 1): \(top.given)"
                    items.append(str) // this results in compile error!
                
             catch 
                print("Error while parsing: \(error)")
            
        
        task.resume()
    

    var body: some View 
        List(items, id: \.self)  item in
            Text(item)
        
    

我应该将items 移出视图吗?

我的最终目标是在 Core Data 中包含 JSON 数据,然后从中更新/通知列表。

我在 android 中有一个这样的应用程序(结构为 MVVM),现在我正在尝试将它移植到 SwiftUI,作为一个 Swift 新手。

更新:

我已按照 achu 的建议添加了 a view model file(谢谢!),它确实有效,但只有当我拖动列表时,列表才会更新为新项目。并且有一个警告

[SwiftUI] 不允许从后台线程发布更改;确保在模型更新时从主线程发布值(通过接收(on:) 等运算符)。

【问题讨论】:

修改数组时实际上是在修改结构体,但结构体是一个值对象,因此您可以这样做。您应该有一个管理数据的类,并且在您的视图结构中没有该代码。 这个 SO question + answer 将有助于理解问题。 不要滥用 SwiftUI 视图作为 控制器。在额外的ObservableObject 类和publish 中加载数据 这是我在 SwiftUI 中如何做到这一点的教程:youtube.com/watch?v=h42OHc5CRBQ .. 虽然我建议学习使用组合方法:youtube.com/watch?v=fdxFp5vU6MQ&t=0s。这是 MVVM 上的另一个内容,可帮助您找到更好的方向 youtube.com/watch?v=wEf1YS4vyW8 【参考方案1】:

我会将items 移至 ViewModel 并最终将服务调用移至 APIManager 类

编辑:UI 更新应该在主线程中。添加了对ViewModelinit()的服务调用。

struct TestView: View 
    @ObservedObject var viewModel = TestViewModel()
    var body: some View 
        List(viewModel.items, id: \.self)  item in
            Text(item)
        
    


class TestViewModel: ObservableObject 
    @Published var items: [String] = []

    init() 
        self.fetchData()
       
    
    
    func fetchData() 
        let url = URL(string: "https://slova.de/ws/top")!
        let task = URLSession.shared.dataTask(with: url) 
            data, response, error in
            
            let decoder = JSONDecoder()
            guard let data = data else  return 
            do 
                let tops = try decoder.decode(TopResponse.self, from: data)
                for (index, top) in tops.data.enumerated() 
                    let str = "\(index + 1): \(top.given)"
                    self.updateItems(str)
                
             catch 
                print("Error while parsing: \(error)")
            
        
        task.resume()
    

    func updateItems(_ str: String) 
        DispatchQueue.main.async 
            self.items.append(str)
        
    

【讨论】:

谢谢,它几乎可以工作 - 请查看我更新的问题 更新了代码。您必须在 init 上调用 webservice 方法,并且 UI 更改应该在主线程中 我删除了items = (1...200).map number in "Item \(number)" 否则它将附加到这些项目中。如果你想在你的 items 数组中添加它,你可以添加它

以上是关于获取 JSON,附加到数组:转义闭包捕获变异的“自我”参数的主要内容,如果未能解决你的问题,请参考以下文章

错误:转义闭包捕获变异的“自我”参数

转义闭包捕获 Swift 中的变异“self”参数错误

SwiftUI 转义闭包捕获变异的“自我”参数

在 Swift 3.0 中的转义闭包中改变自我(结构/枚举)

Swift 3.0 错误:转义闭包只能按值显式捕获 inout 参数

Swift 4:转义闭包只能通过值显式捕获 inout 参数