3

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.

17
  • 1
    Your code compiles for me in my tests (with adding the required init etc..). What system are you using and targeting? Is the code inside some other class or struct, or just as you present it separate from any other? On MacOS 15.6, Xcode 26.0, target iOS 18.6. Commented Jun 19 at 23:13
  • @workingdogsupportUkraine I'm not up to Xcode 26 yet (and all the stuff associated with it). Do you also have a way to try this with Xcode 16.4? If so, and if you see the problem there, you could give an answer that the "solution" is to wait for Xcode 26, where the problem is evidently fixed. Commented Jun 19 at 23:25
  • tried this using Xcode 16.4, and it works very well for me. Commented Jun 19 at 23:31
  • I'll try throwing away DerivedData. Commented Jun 19 at 23:59
  • 1
    For the sake of future readers, in Swift 6.2, you can isolate the CustomStringConvertible to the main actor. See stackoverflow.com/q/79674589/1271826 for info on idiosyncrasies with backward support for print for macOS/iOS/etc prior to 26. Commented Jun 26 at 16:45

2 Answers 2

2

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 @MainActors 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.

Sign up to request clarification or add additional context in comments.

2 Comments

nonisolated(unsafe) is what I was trying to get away from. MainActor.assumeIsolated is good.
"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" Whoa, good point. In my view, this casts doubt on the integrity of the entire Swift concurrency project with regards to how it interfaces with UIKit. I keep banging up against issues in this area.
1

Solution is to not use Objective-C's description

Create new MainActored property or protocol for your needs.

4 Comments

I'm not using Objective-C anything. I'm using Swift CustomStringConvertible, for the purpose for which it was designed. developer.apple.com/documentation/swift/customstringconvertible
@matt yet it's just overriding NSObject's description,
No, a protocol can't override anything. It's just a template. I'm overriding NSObject's description, so I can conform to CustomStringConvertible. That is the basis of the question. That is what you have to grapple with in order to answer the question: you are not answering the question, you are denying the question.
@matt I am about override word in you Piece.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.