Closed iccir closed 8 years ago
I propose the following new property attribute names:
willSet
, didSet
, willChange
, didChange
willSet
and didSet
are inspired by Swift's property observers. They will always be called, even if a property value is the same.
willChange
and didChange
are only called when a property value will/did change (Using ==
or ===
for primitive values and -[BaseObject isEqual:]
for objects).
Each property attribute will specify a method which will be called upon the object.
If the method signature has an argument, that argument will be the new value of the property for willSet
and willChange
and the old value of the property for didSet
and didChange
.
An extreme example:
@property (willSet=_willSetBar:,willChange=_willChangeBar,didChange=_didChangeBar:,didSet=_didSetBar) Number bar;
// Generated code
- (void) setBar:(Number)newBar
{
let oldBar = _bar;
[self _willSetBar:newBar];
if (oldBar != newBar) { // Or !== ?
[self _willChangeBar];
_bar = newBar;
[self _didChangeBar:oldBar];
}
[self _didSetBar];
}
Based on our usage, we should just use ==
or ===
for testing equality. Likely ===
, as that is what lodash uses for _.isEqual
.
A few things I don't like about this:
1) The use of camelCase when other property attributes use snake_case. snake_case would be more consistent, but I really don't want to type out did_change
each time.
2) Having four new attributes for [will/did][Set/Change] is overkill.
Perhaps an a simpler solution would be:
@property (observer=setNeedsDisplay) Number cornerRadius;
Another issue: @property
should be about the external interface to a property. It's weird to have it reference a private method. For example, we have a lot of cases where an -_updateLayer
method needs to be called in response to a property change.
@property (observer=_updateLayer) Number cornerRadius;
From an Obj-C perspective, @property
would be in the header file, and knowledge about _updateLayer shouldn't be present there.
It might make sense to use a separate @observe
directive, in the spirit of @synthesize
or @dynamic
.
The syntax would likely be:
@observe (ATTRIBUTES) PROPERTIES;
With ATTRIBUTES being something like:
change
- call observers when the value changes. Default.set
- always call observers. Mutually exclusive with change
.before=
- selector to call before change/setafter=
- selector to call after change/setabstract
- use ==
to determine equality. Likely overkillstrict
- use ===
to determine equality. Default.isEqual
? - use -isEqual:
to determine equality. Likely overkillA complex example might be:
@property Number foo;
@property Number bar;
@observe (change, before=_fooWillChange, after=_fooDidChange) foo;
@observe (set, before=_barWillSet, after=_barDidSet) bar;
@observe (change, after=setNeedsDisplay) bar, foo;
// A change to foo will call: _fooWillChange, _fooDidChange, setNeedsDisplay on self
// A change to bar will call setNeedsDisplay
// Any invocation to setBar: will also call _barWillSet and _barDidSet
While the most common scenario:
@property Number cornerRadius;
@observe (after=setNeedsDisplay) cornerRadius;
Really, only the change
and after
attributes would need to be implemented for the most common case.
For now, support set
, change
, before=
, and after=
attributes. Others could be added in the future as necessary. This is in the 2.0 branch and documentation has been updated.
A very common idiom in our source base (and Obj-C source bases) is redrawing a view in response to a property change. For example:
A lot of our code would be reduced if the compiler could generate this boilerplate. Perhaps something like: