Swift 5:什么是“转义转义捕获突变的'自我'参数”以及如何解决

时间:2019-10-10 16:04:43

标签: swift api struct closures

大家好,我正在尝试制作一个简单且可重复使用的Swift网络层

也许这不是在视图中循环返回数据的最佳方法,但是在我尝试将返回的Api数据传递到SwiftUI视图中进行循环后,我得到了错误提示:

Escaping closure captures mutating 'self' parameter

不知道我在本课中错过的地方或什么

这是文件的图片

enter image description here

ContentView.swift

struct ContentView: View {

    var emptyDataArr: [CollectionItem] = []

    init() {
        ServiceLayer.request(router: Router.getSources) { (result: Result<[String : [CollectionItem]], Error>) in
            switch result {
            case .success(let data):
                print(data)
                self.emptyDataArr = data["custom_collections"]!

            case .failure:
                print(result)
            }
        }
    }

    var body: some View {
        VStack (alignment: .leading) {
            Text("No thing yet")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

ServiceLayer类ServiceLayer.swift

class ServiceLayer {
    // 1.
    class func request<T: Codable>(router: Router, completion: @escaping (Result<[String: [T]], Error>) -> ()) {
        // 2.
        var components = URLComponents()
        components.scheme = router.scheme
        components.host = router.host
        components.path = router.path
        components.queryItems = router.parameters
        // 3.
        guard let url = components.url else { return }
        var urlRequest = URLRequest(url: url)
        urlRequest.httpMethod = router.method
        // 4.
        let session = URLSession(configuration: .default)
        let dataTask = session.dataTask(with: urlRequest) { data, response, error in
            // 5.
            guard error == nil else {
                completion(.failure(error!))
                print(error?.localizedDescription)
                return
            }
            guard response != nil else {
                return
            }
            guard let data = data else {
                return
            }
            print(data)
            // 6.
            let responseObject = try! JSONDecoder().decode([String: [T]].self, from: data)
            // 7.
            DispatchQueue.main.async {
                // 8.
                completion(.success(responseObject))
            }
        }
        dataTask.resume()
    }
}

3 个答案:

答案 0 :(得分:6)

问题在于ContentView是一个结构,这意味着它是一个值类型。您不能将其传递给闭包并对其进行突变。如果这样做的话,什么都不会改变,因为闭包将拥有其自己的struct的独立副本。

您的问题是您将视图和模型混合在一起。一个给定的View可以有很多很多副本(每次将其传递给函数时,都会创建一个副本)。您不希望所有这些副本都发起一个请求。而是将请求逻辑移到Model对象中,然后让View对其进行观察。

答案 1 :(得分:0)

我今天遇到了同样的问题,找到了这篇文章,然后我按照@Rob Napier的建议,最后做了一个可行的例子。

希望以下代码可以提供帮助(注意:它不能直接回答您的问题,但我认为它将作为示例):

import Combine
import SwiftUI
import PlaygroundSupport

let url = URL(string: "https://source.unsplash.com/random")!

// performs a network request to fetch a random image from Unsplash’s public API
func imagePub() -> AnyPublisher<Image?, Never> {
    URLSession.shared
        .dataTaskPublisher(for: url)
        .map { data, _ in Image(uiImage: UIImage(data: data)!)}
        .print("image")
        .replaceError(with: nil)
        .eraseToAnyPublisher()
}

class ViewModel: ObservableObject {
    
    // model
    @Published var image: Image?
    
    // simulate user taps on a button
    let taps = PassthroughSubject<Void, Never>()
    
    var subscriptions = Set<AnyCancellable>()
    
    init() {
        taps
            // ⭐️ map the tap to a new network request
            .map { _ in imagePub() }
            // ⭐️ accept only the latest tap
            .switchToLatest()
            .assign(to: \.image, on: self)
            .store(in: &subscriptions)
    }
    
    func getImage() {
        taps.send()
    }
    
}

struct ContentView: View {
    
    // view model
    @ObservedObject var viewModel = ViewModel()
    
    var body: some View {
        VStack {
            viewModel.image?
                .resizable().scaledToFit().frame(height: 400).border(Color.black)
            Button(action: {
                self.viewModel.getImage()
            }, label: {
                Text("Tap")
                    .padding().foregroundColor(.white)
                    .background(Color.pink).cornerRadius(12)
            })
        }.padding().background(Color.gray)
    }
}

PlaygroundPage.current.setLiveView(ContentView())

在点击按钮之前,ContentView如下所示:

before tapping

点击按钮后(等待几秒钟),看起来像这样:

after tapping

所以我知道它正在工作^ _ ^

答案 2 :(得分:0)

只需将调用从 init 内部移动到 onAppear 中某个位置的 body