"Ambiguous Use of 'Propertyname'" Error Given Overridden Property with Didset Observer

Ambiguous use of 'propertyName' error given overridden property with didSet observer

As @sgaw points out, this has been confirmed as a known bug by the Apple engineers (for Xcode 6 Beta - Version 6.0 (6A215l))

https://devforums.apple.com/thread/229668?tstart=0

What is the right way to override a property in a subclass in Swift?

I don't get that error in 6.1, but the underlying problem is you have an infinite loop here. What you meant to say is:

// This is wrong, but what you meant
override var someStoredProperty: Int? {
willSet {
super.someStoredProperty = newValue! + 10
}
}

Note the super. (Which is yet another reason I strongly recommend using self. on properties, to make it clear when these infinite loops exist.)

But this code is meaningless. Before setter, you set the value to x + 10. You then set the value to x. What you really meant was:

override var someStoredProperty: Int? {
didSet {
if let value = someStoredProperty {
super.someStoredProperty = value + 10
}
}
}

Why does a property observer run when a member of the existing value is changed?

Swift needs to treat the mutation of myValue.msgStr as having value semantics; meaning that a property observer on myValue needs to be triggered. This is because:

  1. myValue is a protocol-typed property (which also just happens to be optional). This protocol isn't class-bound, so conforming types could be both value and reference types.

  2. The myStr property requirement has an implicitly mutating setter because of both (1) and the fact that it hasn't been marked nonmutating. Therefore the protocol-typed value may well be mutated on mutating though its myStr requirement.

Consider that the protocol could have been adopted by a value type:

struct S : MyProtocol {
var msgStr: String?
}

In which case a mutation of msgStr is semantically equivalent to re-assigning an S value with the mutated value of msgStr back to myValue (see this Q&A for more info).

Or a default implementation could have re-assigned to self:

protocol MyProtocol {
init()
var msgStr: String? { get set }
}

extension MyProtocol {
var msgStr: String? {
get { return nil }
set { self = type(of: self).init() }
}
}

class MyClass : MyProtocol {
required init() {}
}

class MyWrapperClass {

// consider writing an initialiser rather than using an IUO as a workaround.
var myValue: MyProtocol! {
didSet {
print("In MyWrapperClass didSet")
}
}
}

In which case the mutation of myValue.myStr re-assigns a completely new instance to myValue.

If MyProtocol had been class-bound:

protocol MyProtocol : class {
var msgStr: String? { get set }
}

or if the msgStr requirement had specified that the setter must be non-mutating:

protocol MyProtocol {
var msgStr: String? { get nonmutating set }
}

then Swift would treat the mutation of myValue.msgStr as having reference semantics; that is, a property observer on myValue won't get triggered.

This is because Swift knows that the property value cannot change:

  1. In the first case, only classes can conform, and property setters on classes cannot mutate self (as this is an immutable reference to the instance).

  2. In the second case, the msgStr requirement can only either be satisfied by a property in a class (and such properties don't mutate the reference) or by a computed property in a value type where the setter is non-mutating (and must therefore have reference semantics).

Alternatively, if myValue had just been typed as MyClass!, you would also get reference semantics because Swift knows you're dealing with a class:

class MyClass {
var msgStr: String? {
didSet {
print("In MyClass didSet")
}
}
}

class MyWrapperClass {
var myValue: MyClass! {
didSet {
print("In MyWrapperClass didSet")
}
}
}

let wrapperObj = MyWrapperClass()
wrapperObj.myValue = MyClass() // Line1
wrapperObj.myValue.msgStr = "Some other string" // Line2

// In MyWrapperClass didSet
// In MyClass didSet

Overriding didSet

I have tried your code in a playground and got and error

Variable with getter/ setter cannot have and initial value.

So I just removed ="" from the orverriding variable. like below:

class Foo {
var name: String = "" { didSet { print("name has been set") } }
}

class Bar: Foo {
override var name: String {
didSet {
print("print this first")
// print the line set in the superclass
}
}
}

let bar = Bar()
bar.name = "name"

and that's what I got in consol:

name has been set
print this first


Related Topics



Leave a reply



Submit