问题
Question
Apple\'s docs specify that:
willSet and didSet observers are not called when a property is first initialized. They are only called when the property’s value is set outside of an initialization context.
Is it possible to force these to be called during initialization?
Why?
Let\'s say I have this class
class SomeClass {
var someProperty: AnyObject {
didSet {
doStuff()
}
}
init(someProperty: AnyObject) {
self.someProperty = someProperty
doStuff()
}
func doStuff() {
// do stuff now that someProperty is set
}
}
I created the method doStuff
, to make the processing calls more concise, but I\'d rather just process the property within the didSet
function. Is there a way to force this to call during initialization?
Update
I decided to just remove the convenience intializer for my class and force you to set the property after initialization. This allows me to know didSet
will always be called. I haven\'t decided if this is better overall, but it suits my situation well.
回答1:
Create an own set-Method and use it within your init-Method:
class SomeClass {
var someProperty: AnyObject! {
didSet {
//do some Stuff
}
}
init(someProperty: AnyObject) {
setSomeProperty(someProperty)
}
func setSomeProperty(newValue:AnyObject) {
self.someProperty = newValue
}
}
By declaring
someProperty
as type:AnyObject!
(an implicitly unwrapped optional), you allow self to fully initialize withoutsomeProperty
being set. When you callsetSomeProperty(someProperty)
you're calling an equivalent ofself.setSomeProperty(someProperty)
. Normally you wouldn't be able to do this because self hasn't been fully initialized. SincesomeProperty
doesn't require initialization and you are calling a method dependent on self, Swift leaves the initialization context and didSet will run.
回答2:
If you use defer
inside of an initializer, for updating any optional properties or further updating non-optional properties that you've already initialized and after you've called any super.init()
methods, then your willSet
, didSet
, etc. will be called. I find this to be more convenient than implementing separate methods that you have to keep track of calling in the right places.
For example:
public class MyNewType: NSObject {
public var myRequiredField:Int
public var myOptionalField:Float? {
willSet {
if let newValue = newValue {
print("I'm going to change to \(newValue)")
}
}
didSet {
if let myOptionalField = self.myOptionalField {
print("Now I'm \(myOptionalField)")
}
}
}
override public init() {
self.myRequiredField = 1
super.init()
// Non-defered
self.myOptionalField = 6.28
// Defered
defer {
self.myOptionalField = 3.14
}
}
}
Will yield:
I'm going to change to 3.14
Now I'm 3.14
回答3:
As a variation of Oliver's answer, you could wrap the lines in a closure. Eg:
class Classy {
var foo: Int! { didSet { doStuff() } }
init( foo: Int ) {
// closure invokes didSet
({ self.foo = foo })()
}
}
Edit: Brian Westphal's answer is nicer imho. The nice thing about his is that it hints at the intent.
回答4:
I had the same problem and this works for me
class SomeClass {
var someProperty: AnyObject {
didSet {
doStuff()
}
}
init(someProperty: AnyObject) {
defer { self.someProperty = someProperty }
}
func doStuff() {
// do stuff now that someProperty is set
}
}
回答5:
This works if you do this in a subclass
class Base {
var someProperty: AnyObject {
didSet {
doStuff()
}
}
required init() {
someProperty = "hello"
}
func doStuff() {
print(someProperty)
}
}
class SomeClass: Base {
required init() {
super.init()
someProperty = "hello"
}
}
let a = Base()
let b = SomeClass()
In a
example, didSet
is not triggered. But in b
example, didSet
is triggered, because it is in the subclass. It has to do something with what initialization context
really means, in this case the superclass
did care about that
回答6:
While this isn't a solution, an alternative way of going about it would be using a class constructor:
class SomeClass {
var someProperty: AnyObject {
didSet {
// do stuff
}
}
class func createInstance(someProperty: AnyObject) -> SomeClass {
let instance = SomeClass()
instance.someProperty = someProperty
return instance
}
}
回答7:
In the particular case where you want to invoke willSet
or didSet
inside init
for a property available in your superclass, you can simply assign your super property directly:
override init(frame: CGRect) {
super.init(frame: frame)
// this will call `willSet` and `didSet`
someProperty = super.someProperty
}
Note that Charlesism solution with a closure would always work too in that case. So my solution is just an alternative.
回答8:
You can solve it in obj-с way:
class SomeClass {
private var _someProperty: AnyObject!
var someProperty: AnyObject{
get{
return _someProperty
}
set{
_someProperty = newValue
doStuff()
}
}
init(someProperty: AnyObject) {
self.someProperty = someProperty
doStuff()
}
func doStuff() {
// do stuff now that someProperty is set
}
}
来源:https://stackoverflow.com/questions/25230780/is-it-possible-to-allow-didset-to-be-called-during-initialization-in-swift