Let the code speak for itself:
final class Piece: UIView {
var picName: String
var column: Int
var row: Int
override var description: String {
return "picname: \(picName); column: \(column); row: \(row)" // error
}
init(picName: String, column: Int, row: Int) {
self.picName = picName
self.column = column
self.row = row
super.init(frame: .zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This code, which has worked since Swift 1, is now illegal in Swift 6, evidently because description
, believe it or not, is not main actor-isolated:
Main actor-isolated property 'picName' can not be referenced from a nonisolated context (and so on)
How am I supposed to work around this?
EDIT: One workaround is to declare picName
, column
, and row
as constants, with let
instead of var
. But that doesn't necessarily do me any good, and in any case I still don't understand why that would make a difference, so this discovery just makes the mystery deeper.
Swift thinks this is unsafe because description
can be accessed from outside the main actor. It is a non-isolated requirement of the CustomStringConvertible
protocol, so implementations of it must be non-isolated too.
For example, something like this is possible:
let view = Piece(picName: "", column: 0, row: 0)
Task.detached {
print(view.description)
}
view.picName = "Something else"
While the detached task is running, the access of picName
in view.description
races with the write to picName
outside the task.
That said, it is very unlikely in practice that you would be accessing the description
of a UIView
outside of the main actor. Even the default implementation of UIView.description
accesses its frame
and layer
properties. Both of these accesses could cause data races if the access happens outside the main actor.
Option 1: I would just assume that accesses to description
are always on the main actor.
nonisolated override var description: String {
MainActor.assumeIsolated {
return "picname: \(picName); column: \(column); row: \(row)"
}
}
This will crash if description
is accessed from some other actor/no actor at all.
Option 2: make the properties you want to access nonisolated(unsafe)
.
nonisolated(unsafe) var picName: String
nonisolated(unsafe) var column: Int
nonisolated(unsafe) var row: Int
This is basically "accepting" the data races that could happen.
Option 3: make the class nonisolated
, and explicitly add @MainActor
to the members of the class that needs it.
nonisolated final class Piece: UIView
This is the "safest" option, but you might need to add a lot more @MainActor
s to things in the class. Also, you won't be able to send Piece
across isolation boundaries, but you are unlikely to do that with a UIView
anyway.