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
}
}
以下是我的 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
}
}