Home > Net >  Why is conformance to an object required for read/write extension properties to work on let variable
Why is conformance to an object required for read/write extension properties to work on let variable

Time:05-01

I know the title may be confusing, but this should clear it up.

Say I define the following extension on UIView...

extension UIView {

    var isVisible:Bool{
        get { return !isHidden }
        set { isHidden = !newValue }
    }
}

In code, I can do this without issue...

let myView = UIView()
myView.isVisible = true

But if I try pulling out the extension into a reusable protocol (so I can apply it to both UIView and NSView without having to duplicate the code) like so...

public protocol ExtendedView {
    var isHidden: Bool { get set }
}

public extension ExtendedView {

    var isVisible: Bool{
        get{ return !isHidden }
        set{ isHidden = !newValue }
    }
}

extension UIView: ExtendedView {}
extension NSView: ExtendedView {}

...then while I can read it like so...

let myView = UIView()

if myView.isVisible {
    ....
}

...This line will not compile!

myView.isVisible = true

It gives the following compile-time error...

cannot assign to property: 'myView' is a 'let' constant

To fix it, I have to either change the variable to a var (not what I want to do), or conform the protocol to AnyObject, like so...

public protocol ExtendedView : AnyObject {
    var isHidden: Bool { get set }
}

My question is why? I mean the compiler knows at compile time the type of item the extension is being applied to so why does the protocol have to conform to AnyObject? (Yes, I do acknowledge that extending UIView (or NSView) implies an object, but still... doesn't the call site know it's not a value type?)

CodePudding user response:

doesn't the call site know it's not a value type?

That doesn't matter. Protocol members allows for mutation of self. For example, if you don't constrain the protocol to AnyObject, this will always compile:

set { self = newValue as? Self ?? self }

I.e. protocols provide the only way to be able to change a reference internally. Even though you're not actually doing that in your code, the possibility of the reference mutation is there.


I have to either change the variable to a var (not what I want to do), or conform the protocol to AnyObject

Although it's not apparent why you shouldn't be constraining to AnyObject or something more restrictive, you can just use

nonmutating set

But why are you not just constraining as much as possible? E.g.

public protocol ExtendedView: NSObject {

or a per-platform typealias?

CodePudding user response:

I mean the compiler knows at compile time the type of item the extension is being applied to

I know, it looks like the compiler knows that, especially when you write the lines next to each other like that:

let myView = UIView()
myView.isVisible = true

But command-click on isVisible in that code, and where do you end up? In the protocol ExtendedView. In other words, isVisible is not ultimately a property declared by UIView; it's a property declared by ExtendedView.

And nothing about the protocol itself guarantees that the adopting object will be a reference type — unless you guarantee it by qualifying the protocol, either directly or in an extension of the protocol, by saying what kind of object can adopt it.


I would just like to add that the situation you've posited is extremely specialized: the issue only arises in exactly the situation you've created, where a protocol extension injects a computed property implementation into its adopters. That's not a common thing to do.

  • Related