Should property observers fired in initializers?

Currently if we assign a new value to a property in a convenience initializer, the didSet property observer won't fire. But if I wrap the property assignment in a helper method, the didSet will fire if I call the helper method in the convenience initializer. I'm not sure if it is the intended behavior.

updated:
How property observers can be fired in initializers (whether is convenience initializer or designated initializer) can be concluded as these:

  1. Property observers won't fire in property-initialization statement (This behavior is obvious and I have no doubt about it).
  2. After all properties are initialized, assigning to a property still won't fire observers. But if I wrap this kind of assignment in a method, and call this method instead of directly assign to the property in the initializer, the observer will fire.

So basically the correct behavior I think Swift should have is that after all properties are initialized in a initializer, all following property assignments will fire the observers (whether wrapped in methods or not). The behavior I proposed is safe, and is not confusing. What do you think?

3 Likes

I guess this got lost! The current behavior comes from a few rules chaining together:

  1. It's not safe to run arbitrary methods before all properties have been initialized (including any in superclasses).
  2. Property observers are basically arbitrary methods.
  3. It would be confusing if assigning to a property in an initializer sometimes ran observers (or went through overrides) and sometimes didn't.
  4. Therefore, all property accesses in an initializer are direct.
  5. Convenience initializers could safely be exempt from this rule, but the code that implements it doesn't check whether the initializer is convenience or not. There's just one rule for all kinds of initializers.

So, we could change this behavior, but it would definitely be a language change that needs discussion.

Thanks for explaining!

After I post the thread, I have run more tests and find this behavior apply to all initializers. How property observers can be fired in initializers (whether is convenience initializer or designated initializer) can be concluded as these:

  1. Property observers won't fire in property-initialization statement (This behavior is obvious and I have no doubt about it).
  2. After all properties are initialized, assigning to a property still won't fire observers. But if I wrap this kind of assignment in a method, and call this method instead of directly assign to the property in the initializer, the observer will fire.

So basically the correct behavior I think Swift should have is that after all properties are initialized in a initializer, all following property assignments will fire the observers (whether wrapped in methods or not). The behavior I proposed is safe, and is not confusing. What do you think?

1 Like

It definitely has the potential to be confusing, because it turns a simpler rule (property observers don't fire in initialisers) into a more complex and subtle rule that requires people to keep track of when all properties have been initialised. Simple code rearrangement in initialisers could then result in observers now firing when they didn't before, or vice versa. It is possible that the benefits could outweigh the potential confusion, though.

1 Like

I may have not made my points clear, but now property observers do fire in initializers if you wrap the assignment statements into a method.

I don't think "vice versa" will happen. And if the property observers fire when they didn't before, all properties of the object are guaranteed to have been initialized.

I wouldn't object to changing the behavior myself, but how would this not be a behavior-breaking change? There's not even anything the compiler could do to warn.

The change I proposed will only change one situation: observers will fire when properties are assigned new values directly after all properties are initialized in initializer. I'm not familiar with the compiler though, so I'm not sure if the change can be implemented.

I understand what the change would be, but the change in behavior would affect rebuilt code without it being obvious. I think it is quite likely that code exists which assumes that willSet/didSet do not fire during initialization.

1 Like

I understood this, but this is uniform behaviour within the method, which you can't call until all properties are initialised anyway, so it's still relatively easy to understand.

Either one can happen, it has to be symmetric because you can “change it back”:

// Firing when they didn't before - type with two properties, a and b
self.a = 1
self.a = 2 // doesn't fire
self.b = 3
// =>
self.a = 1
self.b = 3
self.a = 2 // fires

then just reverse the two examples for firing → not firing. If someone is relying on the property observers to either fire or not fire then seemingly trivial rearrangements can cause problems here.

In the current swift implementation,

self.a = 1 
self.b = 3 
self.a = 2

This won't fire the property observer in the initializer. So I still don't understand how it can happen when observers in current swift will fire and after rearrangement won't fire in my proposal. Can you give the complete code sample?

To clarify, I'm talking about what would happen when you rearrange code in an initialiser if your proposal is implemented. I'm not talking about what would happen in the transition between the current implementation and your proposal. If your proposal was implemented, then seemingly trivial code rearrangement can lead to changes in observer firing, as demonstrated in my example.

Now I understand you point. But in current swift implementation, another seemingly trivial code rearrangement can also lead to changes in observer firing:

If my colleague delete the wrapper method I wrote and move the statements into the initializer directly, the observer won't fire (it fires in method calls in initializer).

I think the behavior is much more implict and hard to test. That's why I think the current swift implementation is a bit confusing.