Swift SwiftUI 按钮操作关闭中的错误:"Cannot use mutating member on immutable value: 'self' is immutable"

Swift error in SwiftUI Button action closure: "Cannot use mutating member on immutable value: 'self' is immutable"

以下是我的 Swift 应用程序中 ContentView 的简化版本。错误 Cannot use mutating member on immutable value: 'self' is immutable 出现在我的 Button 操作闭包内的行 self.classifyImage(self.image) 上。如何将 image 设置为可变的?或者有更好的方法来完成我想要完成的事情吗?本质上,我想在我的 ContentView 中传递 UIImage var,以便我的 Vision CoreML 模型通过我这里的 classifyImage 函数进行处理。

struct ContentView: View {
    @State private var image = UIImage()

    private lazy var classificationRequest: VNCoreMLRequest = {
        do {
          let model = try VNCoreMLModel(for: SqueezeNet().model)
        
          let request = VNCoreMLRequest(model: model) { request, _ in
              if let classifications =
                request.results as? [VNClassificationObservation] {
                print("Classification results: \(classifications)")
              }
          }
          request.imageCropAndScaleOption = .centerCrop
          return request
        } catch {
          fatalError("Failed to load Vision ML model: \(error)")
        }
    }()

    private mutating func classifyImage(_ image: UIImage) {
        guard let orientation = CGImagePropertyOrientation(
          rawValue: UInt32(image.imageOrientation.rawValue)) else {
          return
        }

        guard let ciImage = CIImage(image: image) else {
          fatalError("Unable to create \(CIImage.self) from \(image).")
        }

        DispatchQueue.global(qos: .userInitiated).async {
          let handler =
            VNImageRequestHandler(ciImage: ciImage, orientation: orientation)
          do {
              try handler.perform([self.classificationRequest])
          } catch {
            print("Failed to perform classification.\n\(error.localizedDescription)")
          }
        }
    }

    var body: some View {
        Button(action: {
            self.classifyImage(self.image).   // <-- error
        }) {
            // Button text here
        }
        // blah blah
    }
}

问题是您正在处理结构。更改结构内部的值在语义上与为其分配新值相同。因此,当您的 ContentView 结构是通过 var contentView = ContentView() 定义时,该函数将起作用。如果你使用 let contentView = ContentView() 比你得到你的错误。不同之处在于,通过使用变异函数 swift 为图像分配新值会自动创建一个新的 Struct 并将其分配给 var contentView = ....

另一种更好的方法是使用这样的 ViewModel:

import Combine
import UIKit
....
class ContentViewModel: ObservableObject {
    var image: uiImage
    @Published var classification: String

    init(_ image: UIImage) {
       self.image = image
    }

    func classifyImage() {
       // classify your image and assign the result to the published var classification. This way your view will be automatically updated on a change
    }
}

然后您可以像这样在您的视图中使用该模型:

struct ContentView: View {
    @ObservedObject private var viewModel = ContentViewModel(UIImage())

    var body: some View {
        Button(action: {
            self.viewModel.classifyImage()
        }) {
            // Button text here
        }
        // blah blah
    }
}

这是一种更简洁的方法,因为您将逻辑封装到视图模型中并且不会用处理代码污染视图。

您不能从内部将 View 作为结构体进行变异(因此不能进行惰性创建、不能变异函数等)。如果你需要在某处更改image,那么直接分配给它作为状态。

这是固定的(可编译的)部分代码。测试 Xcode 12.

struct ContentView: View {
    @State private var image = UIImage()

    private let classificationRequest: VNCoreMLRequest = {
        do {
          let model = try VNCoreMLModel(for: SqueezeNet().model)

          let request = VNCoreMLRequest(model: model) { request, _ in
              if let classifications =
                request.results as? [VNClassificationObservation] {
                print("Classification results: \(classifications)")
              }
          }
          request.imageCropAndScaleOption = .centerCrop
          return request
        } catch {
          fatalError("Failed to load Vision ML model: \(error)")
        }
    }()

    private func classifyImage(_ image: UIImage) {
        guard let orientation = CGImagePropertyOrientation(
          rawValue: UInt32(image.imageOrientation.rawValue)) else {
          return
        }

        guard let ciImage = CIImage(image: image) else {
          fatalError("Unable to create \(CIImage.self) from \(image).")
        }

        DispatchQueue.global(qos: .userInitiated).async {
          let handler =
            VNImageRequestHandler(ciImage: ciImage, orientation: orientation)
          do {
              try handler.perform([self.classificationRequest])
          } catch {
            print("Failed to perform classification.\n\(error.localizedDescription)")
          }
        }
    }

    var body: some View {
        Button(action: {
            self.classifyImage(self.image)   // <-- error
        }) {
            // Button text here
        }
        // blah blah
    }
}