问题
How can you set a property's value in Swift, without calling its didSet()
function outside of an initialization context? The code below was a failed experiment to achieve this within the classes' noside()
function
class Test
{
var toggle : Bool = 0
var hoodwink : Int = 0 {
didSet(hoodwink)
{
toggle = !toggle
}
}
// failed attempt to set without a side effect
func noside(newValue : Int)
{
hoodwink = newValue
println("hoodwink: \(hoodwink) state: \(toggle)")
}
func withside(newValue : Int)
{
self.hoodwink = newValue
println("hoodwink: \(hoodwink) state: \(toggle)")
}
}
It is quite trivial to do in Objective-C with auto-synthesized properties:
With side effect (if present in setter):
self.hoodwink = newValue;
Without side effect:
_hoodwink = newValue;
回答1:
What you do in Objective-C to "avoid side effects" is accessing the backing store of the property - its instance variable, which is prefixed with underscore by default (you can change this using the @synthesize
directive).
However, it looks like Swift language designers took specific care to make it impossible to access the backing variables for properties: according to the book,
If you have experience with Objective-C, you may know that it provides two ways to store values and references as part of a class instance. In addition to properties, you can use instance variables as a backing store for the values stored in a property.
Swift unifies these concepts into a single property declaration. A Swift property does not have a corresponding instance variable, and the backing store for a property is not accessed directly. (emphasis is mine)
Of course this applies only to using the "regular language" means, as opposed to using reflection: it might provide a way around this restriction, at the expense of readability.
回答2:
A possible hack around this is to provide a setter which bypasses your didSet
var dontTriggerObservers:Bool = false
var selectedIndexPath:NSIndexPath? {
didSet {
if(dontTriggerObservers == false){
//blah blah things to do
}
}
}
var primitiveSetSelectedIndexPath:NSIndexPath? {
didSet(indexPath) {
dontTriggerObservers = true
selectedIndexPath = indexPath
dontTriggerObservers = false
}
}
Ugly but workable
回答3:
If you exactly know when you want to apply side effects just make it explicitly:
1 Solution:
func noside(newValue : Int) {
hoodwink = newValue
}
func withside(newValue : Int) {
self.hoodwink = newValue
toggle = !toggle
}
2 Solution:
var toggle : Bool = false
var hoodwink : Int = 0
var hoodwinkToggle: Int {
get { return hoodwink }
set(newValue) {
hoodwink = newValue
toggle = !toggle
}
}
- func setHoodwinkWithToggle(hoodwink: Int) {...}
- ....
I think these solutions will be more clear and readable, then using one variable which at some calls should have side effects and shouldn't at others.
回答4:
The issue I was trying to solve is I wanted to get a doCommonUpdate
call when one value changed, or one if both changed at the same time. Doing this created a recursion because if either value changed it would call didSet
each time. Example setup, mine was more involved:
class Person {
var first: String = "" {
didSet {
updateValues(first: first, last: last)
}
}
var last: String = "" {
didSet {
updateValues(first: first, last: last)
}
}
init() {}
init(first: String, last: String) {
self.first = first
self.last = last
}
// also want to set both at the same time.
func updateValues(first: String, last: String) {
// self.first = first // causes recursion.
// self.last = last
doCommonSetup(first: first, last: last)
}
func doCommonSetup(first: String, last: String) {
print(first, last)
}
}
let l = Person()
l.first = "Betty"
l.last = "Rubble"
_ = Person(first: "Wilma", last: "Flintstone")
> Betty
> Betty Rubble
Note Wilma does not print because of the commented out lines.
My solution was to move all those variables into a separate struct. The this approach solves the problem and has the side benefit of creating a another grouping which helps meaning. We still get the doCommonSetup
when either value changes independently and when we get both values changed at the same time.
class Person2 {
struct Name {
let first: String
let last: String
}
var name: Name
init() {
name = Name(first: "", last: "")
}
init(first: String, last: String) {
name = Name(first: first, last: last)
updateValues(name: name)
}
var first: String = "" {
didSet {
name = Name(first: first, last: self.last)
updateValues(name: name)
}
}
var last: String = "" {
didSet {
name = Name(first: self.first, last: last)
updateValues(name: name)
}
}
// also want to set both at the same time.
func updateValues(name: Name) {
self.name = name
doCommonSetup(name: name)
}
func doCommonSetup(name: Name) {
print(name.first, name.last)
}
}
let p = Person2()
p.first = "Barney"
p.last = "Rubble"
_ = Person2(first: "Fred", last: "Flintstone")
> Barney
> Barney Rubble
> Fred Flintstone
来源:https://stackoverflow.com/questions/25394194/swift-how-to-change-a-propertys-value-without-calling-its-didset-function