Is it possible to allow didSet to be called during initialization in Swift?
IosSwiftDidsetIos Problem Overview
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.
Ios Solutions
Solution 1 - Ios
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
Solution 2 - Ios
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 without
> someProperty
being set. When you call
> setSomeProperty(someProperty)
you're calling an equivalent of
> self.setSomeProperty(someProperty)
. Normally you wouldn't be able to
> do this because self hasn't been fully initialized. Since
> someProperty
doesn't require initialization and you are calling a
> method dependent on self, Swift leaves the initialization context and
> didSet will run.
Solution 3 - Ios
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.
Solution 4 - Ios
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
}
}
Solution 5 - Ios
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
Solution 6 - Ios
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
}
}
Solution 7 - Ios
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.
Solution 8 - Ios
unfortunately, didSet observers aren't called when a root class is initialized.
If your class isn't a subclass, you have to use getters and setters to achieve the functionality you want:
class SomeClass {
private var _test: Int = 0
var test: Int {
get { _test }
set { _test = newValue }
}
init(test: Int) { self.test = test }
}
alternatively, if your class is a subclass, you can use didSet and do:
override init(test: int) {
super.init()
self.test = test
}
The didSet SHOULD get called after super.init() is called.
One thing I have not tried but MIGHT also work:
init(test: int) {
defer { self.test = test }
}
NOTE: you will need to make your properties nullable, or set a default value for them, or unwrap the class properties.
Solution 9 - Ios
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
}
}