Lets say I have a singleton Manager
class Manager {
static let sharedInstance = Manager()
var text: String {
didSet (value) { print("didSet \(value)") }
}
init () {
self.text = "hello"
}
}
If I do
Manager.sharedInstance.text = "world"
text is still 'hello'
but if I do it twice, the second time it is world
It is working fine.
The behaviour your experienced is explained by 2 facts
Fact 1
As Apple says didSet (and willSet as well) is not called during the init.
The willSet and didSet observers provide a way to observe (and to
respond appropriately) when the value of a variable or property is
being set. The observers are not called when the variable or property
is first initialized. Instead, they are called only when the value is
set outside of an initialization context.
Fact 2
The parameter of didSet does refer to the old value, so you should
rename value as oldValue.
use text in the print
So from this
didSet (value) { print("didSet \(value)") }
to this
didSet (oldValue) { print("didSet \(text)") }
Instead in your code you are printing the old value (the one that has been overwritten).
Related
Any idea how I can return a UIView from within didSet ?
I have a method that returns a UIView.
I need to observe an Int and return a UIView as the Int changes. I have a didSet observer set, however, I get an error when trying to return the UIView.
Appreciate any help! Thanks.
func newUIView() -> UIView {
var newUIView = UIView()
return newUIView
}
var observingValue: Int = someOtherValue {
didSet {
//Xcode complains whether I use return or not
return func newUIView()
}
}
You say in a comment:
I guess my struggle is how to observe that value and react (update UI) to it accordingly
An observer is a perfectly good way to do that. But you don't return something; you call something. This is a very, very common pattern in Swift iOS Cocoa programming:
var myProperty : MyType {
didSet {
self.updateUI()
}
}
func updateUI() {
// update the UI based on the properties
}
At the time that the didSet code runs, myProperty has already been changed, so the method updateUI can fetch it and use it to update the interface.
What you are asking doesn't make any sense.
A didSet is a block of code you add to an instance variable that gets invoked when anybody changes the value of that variable. There is no place to return anything.
If you need code that changes an instance variable and returns a view, you need to write a function:
func updateObservingValue(newValue: Int) -> UIView {
observingValue = newValue
return newUIView()
}
With a Combine Publisher, I can use the following to call a closure whenever a value changes:
let cancellable = x.sink { value in … }
How can I achieve the same behaviour with a variable marked #State or #Binding?
Update
The below answer doesn't seem to work anymore, instead one can use .onChange(of:) instead on the property
.onChange(of: someProperty) { value in
//use new value for someProperty
}
You can use willSet and didSet as with any normal property
#State private var someProperty: String {
didSet {
}
willSet {
}
}
To do KVO in Swift, you make a NSObject-based #objc class, and can observe properties that are stored and marked #objc and dynamic.
Can you observe on computed properties (that are #objc and dynamic)?
I just glanced at the Objective-C guide, and it showed you could mark KVO dependencies for a computed property. And (I think) observe on an array property changing at the element level. Can either of those be done in Swift?
You asked:
Can you observe on computed properties (that are #objc and dynamic)?
Yes. Just mark your computed property #objc and dynamic and when the setter for the computed property is called, then the observer will be called.
For example, consider a synchronizer class, where you want to observe the computed property, value :
class Synchronized: NSObject {
private var _value: Int
private let queue = DispatchQueue(label: "synchronizer", attributes: .concurrent)
init(value: Int) {
_value = value
super.init()
}
#objc dynamic var value: Int {
get { queue.sync { _value } }
set { queue.async(flags: .barrier) { self._value = newValue} }
}
}
Here, the value is a computed variable, which you can set and all the usual KVO can take place:
token = foo.observe(\.value, options: .new) { _, change in
print(change.newValue ?? "Unknown")
}
This observer is triggered when the value setter is called.
The only trick is going to be if you want the KVO triggered notification if the the value returned by the computed property changes via something other than the computed property’s setter. In that case, you’d have to manually issue the KVO notifications.
I have an swift class
class ApplicationManager {
var fanMode: FanMode
init()
{
self.applyDefaultSettings()
}
func applyDefaultSettings()
{
if let unwrappedFanMode = userDefaults.valueForKey(Consts.kStoredFanMode) as? FanMode {
self.fanMode = unwrappedFanMode
}
}
}
The code above throws this issue:
Use of 'self' in method call 'applyDefaultSettings' before all stored properties are initialized
What should I do here? So as message say I need to initialize all stored properties before I call any other method of class. So it means in init method I should initialize at least fanMode property. But I want to have method that apply kind of default settings for my properties to provide simple readability and clean code architecture. But maybe it's ok to use initializer of class to init all needed fields.
You also can do it by using this code:
var fanMode: FanMode = {
if let unwrappedFanMode = userDefaults.valueForKey(Consts.kStoredFanMode) as? FanMode {
return unwrappedFanMode
} else {
return FanMode()//some default implementation
}
}()
It is readable as You want.
As per Apple documentation, Swift does not allow you to left uninitialised variables or constants. If you want to set some default settings then assign your variables with initial values that will act as your default settings and later you can change them.
All instance properties must be initialized in the init method. You can either move the initialization to the init (defaultMode would be your default value if userDefaults is nil):
init() {
fanMode = (userDefaults?.valueForKey(Consts.kStoredFanMode) as? FanMode) ?? defaultMode
}
Set a default value for that property, for example:
var fanMode: FanMode = defaultMode
Or you can make your fanMode nullable:
var fanMode: FanMode? = nil
You can use an implicity unwrapped optional. Just add a ! to the variable declaration.
class ApplicationManager {
var fanMode: FanMode! //Implicitly unwrapped optional.
init()
{
self.applyDefaultSettings()
}
func applyDefaultSettings()
{
if let unwrappedFanMode = userDefaults.valueForKey(Consts.kStoredFanMode) as? FanMode {
self.fanMode = unwrappedFanMode
}
}
}
Basically it tricks xCode into telling it "Hey this variable is initialized and value will never be nil". But you want to be careful using these as if it does turn out to be nil your program will crash. But in your case it should be fine since you initialize it in the init method so it will never be nil before using it.
Why this code will trigger didSet when init
final public class TestDidSet {
static var _shared: TestDidSet! = TestDidSet()
func testA() { }
private var test = true {
didSet {
print("didSet test when initing!!!!!:\(test)")
}
}
private var _value: Bool! {
didSet {
print("didSet when initing!!!!!:\(_value)")
}
}
private init() {
testSet()
_value = false
test = false
}
private func testSet() {
_value = true
}
}
TestDidSet._shared.testA()
any idea?
should it not trigger didSet?
someone help!
update:
My point of view is this,
testSet() and _value = false is doing the same thing, but testSet() is outside init(), so testSet() will trigger didSet while _value = false not. Why?!
It's not optional type or other reason, that cause 'didSet', I suppose.
When you declare a property with an implicitly unwrapped optional type (Bool! in your case), it gets implicitly assigned a default value of nil. Then afterwards if you assign it with some other value in your initializer then the didSet observer gets triggered because it's already a second assignment. didSet is supposed to not be triggered only on a first one.
The didSet{} closure is called every time you assign a new value to your properties (even if you assign it at the declaration (inline) or at the initialisation).