Swift 5:什么是“转义闭包捕获变异的‘自我’参数”以及如何修复它

Ahm*_*Els 19 api struct closures swift

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

也许这不是在视图中循环返回数据的最佳方法,但是在我尝试获取返回的 Api 数据以在 SwiftUI 视图中循环它之后,我收到错误:

Escaping closure captures mutating 'self' parameter
Run Code Online (Sandbox Code Playgroud)

不知道我在这节课中遗漏了什么地方或什么

这是文件的图片

在此处输入图片说明

内容视图.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()
    }
}
Run Code Online (Sandbox Code Playgroud)

服务层类 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()
    }
}
Run Code Online (Sandbox Code Playgroud)

Rob*_*ier 23

问题在于它ContentView是一个结构体,这意味着它是一个值类型。你不能把它传递给一个闭包并改变它。如果你这样做了,什么都不会改变,因为闭包会有它自己独立的结构副本。

你的问题是你混合了你的视图和模型。一个给定的 View 可以有很多很多的副本(每次它传递给一个函数时,都会制作一个副本)。您不会希望这些副本中的每一个都发起请求。而是将这个请求逻辑移动到一个 Model 对象中,让 View 观察它。

  • 你能用上面的代码给出一个例子吗? (4认同)

gar*_*ing 13

只需将呼叫从内部某处移动init到内部onAppearbody

  • 这是一个被低估的评论,今天节省了我很多时间。谢谢加勒特! (3认同)

loc*_*wei 5

我今天遇到了同样的问题,找到了这个帖子,然后我按照@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())
Run Code Online (Sandbox Code Playgroud)

在点击按钮之前,ContentView看起来像这样:

敲击前

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

敲击后

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