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.