How to Allow Didset to Be Called During Initialization in Swift

Is it possible to allow didSet to be called during initialization in Swift?

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.

Why is didSet called when set inside an initializer?

The rule is that setter observers are not called during initialization. But by the time you set this property, initialization is over! The property was already given its initial value, namely nil. Therefore even though you are in an init method, you are not “during initialization” and the setter observer runs.

DidSet not working in init function swift 3

For me, using the defer is better readable.

import Foundation

class A {
var b: String {
didSet {
print("didSet called with value: \(b)")
}
}

init(x: String) {
self.b = x
defer { self.b = x }
}
}

let a = A(x: "It's Working!") // didSet called with value: It's Working!
print(a.b) // It's Working

swift - didSet not called on UIImageView

didSet will only be called when you set the imageView itself. For example:

imageView = UIImageView()

This doesn't really make sense, since you already have an image view and it's working completely fine. What you're trying to do is to monitor changes in one of your image view's properties.

To do this, try making a subclass of UIImageView, then observing changes in its image. Don't forget to set the custom class of your image view back in the storyboard, then reconnecting the outlet.

class CustomImageView: UIImageView {
override var image {
didSet {
/// do stuff
}
}
}

If you need to execute some code back in your view controller, try using a closure.

class CustomImageView: UIImageView {
var imageSet: (() -> Void)?
override var image {
didSet {
imageSet?()
}
}
}

class ViewController: UIViewController {
@IBOutlet weak var imageView: CustomImageView!

override func viewDidLoad() {
super.viewDidLoad()
imageView.imageSet = {
/// do stuff
}
}
}

Alternatively, you can use KVO.

Swift variable observers not called before super.init called

They are talking about following scenario:

class A {
var p: Bool {
didSet {
print(">>> didSet p to \(p)")
}
}

init() {
p = false // here didSet won't be called
}
}

class B: A {

override init() {
// here you could set B's properties, but not those inherited, only after super.init()
super.init()
p = true // here didSet will be called
}
}

B()

It will print following:

>>> didSet p to true

While to you it might seems natural, the documentation has to explicitly document this behavior.

How is didSet called again when setting inside through a function?

After checking with Swift github and asking questions about this problem, I find out that this problem is more complex as it seems. But there is a specific rule about this problem:

didSet observer will not trigger only if access to property within
its own didSet observer can be done through direct memory access.

Problem is that it is a little ambiguous when access to property will be direct(unless probably if you are developer of Swift). An important feature that has an effect on my question is this:

Class instance method never access class properties directly.

This quote shows problem with my code, even though I can argue that when an instance member should be able to access property directly whenever you call it in didSet observe. When I have a code like this:

class B {
var i = 0 {
didSet {
print("called")
doit()
}
}

func doit() {
self.i += 1
}
}

doit() function cannot access i directly which triggers didSet again causing infinite loop.

Now what is the workaround?

You can use inout for passing properties from its own didSet to a instance function without triggering didSet. Something like this:

class B {
var i = 0 {
didSet {
print("called")
doit(&i)
}
}

func doit(_ i: inout Int) {
i += 1
}
}

And one last thing. Starting Swift 5, conditions for selecting direct memory access for properties within its own didSet will become more restricted. Based on github, only conditions that will use direct memory access is are the following:

 Within a variable's own didSet/willSet specifier, access its storage
directly if either:
1) It's a 'plain variable' (i.e a variable that's not a member).
2) It's an access to the member on the implicit 'self' declaration.
If it's a member access on some other base, we want to call the setter
as we might be accessing the member on a *different* instance.

This means codes like following will trigger infinite loop while it does not right now:

class B {
var i = 0 {
didSet {
print("called")
var s = self
s.i += 1
}
}
}


Related Topics



Leave a reply



Submit