Swift trigger didSet when init - swift

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).

Related

Is it possible to point class instance variables to `inout` parameters in Swift?

Is it possible for multiple class methods to access and modify a single inout parameter that is set in the class constructor? For example something like this:
class X {
var mySwitch: Bool
init(mySwitch: inout Bool) {
self.mySwitch = mySwitch
}
func updateSwitch() {
self.mySwitch.toggle() // this should toggle the external Boolean value that was originally passed into the init
}
}
// usage
var myBool: Bool = false
let x = X(mySwitch: &myBool)
x.updateSwitch()
print(myBool) // this should read 'true'
Short Answer
No.
Long Answer
There are other approaches that can satisfy this.
Binding Variables
In SwiftUI we use Binding Variables to do stuff like this. When the Binding variable updates, it also updates the bound variable. I'm not sure if it will work in Sprite Kit.
class X {
var mySwitch: Binding<Bool>
init(_ someSwitch: Binding<Bool>) {
self.mySwitch = someSwitch
}
func toggle() { mySwitch.wrappedValue.toggle() }
}
struct Y {
#State var mySwitch: Bool = false
lazy var switchHandler = X($mySwitch)
}
Callbacks
We can add a callback to X and call it on didSet of the boolean.
class X {
var mySwitch: Bool {
didSet { self.callback(mySwitch) } // hands the new value back to the call site in Y
}
let callback: (Bool) -> Void
init(_ someSwitch: Bool, _ callback: #escaping (Bool) -> Void) {
self.mySwitch = someSwitch
self.callback = callback
}
func toggle() { mySwitch = !mySwitch } // explicitly set to trigger didSet
}
class Y {
var mySwitch: Bool = false
lazy var switchHandler = X(mySwitch) {
self.mySwitch = $0 // this is where we update the local value
}
}
Short answer: NO.
Your question does not make sense. An inout parameter allows you read/write access to the parameter during the call to function. It has no meaning once the function returns. You sem to think it creates some persistent link between the parameter that is passed as inout and some other variable. It DOES NOT.
You can't "link" a variable that way because inout only works in the scope it's declared.
If what you want is to have some kind of global state, you can wrap it in a class.
class Wrapper {
var value = false
init() {}
}
class Modifier {
let wrapper: Wrapper
init(wrapper: Wrapper) {
self.wrapper = wrapper
}
func updateSwitch() {
wrapper.value.toggle()
}
}
let wrapper = Wrapper()
let modifier = Modifier(wrapper: wrapper)
modifier.updateSwitch()
print(wrapper.value) // this will read 'true'

How can I Observe a var in a Class?

Here is my code for a simple class, My goal is that observeValueOfModel() function automatically put changes of valueOfModel under control and print the correct message out!
I can manually use this func for getting the Answer, but the goal is this class be able understand and react to value change of valueOfModel. Thanks for help
class Model: ObservableObject {
var valueOfModel: Bool = Bool()
private func observeValueOfModel() {
if valueOfModel {
print("valueOfModel is True!")
}
else {
print("valueOfModel is False!")
}
}
}
The didSet fits in this case
class Model: ObservableObject {
var valueOfModel: Bool = Bool() {
didSet {
observeValueOfModel()
}
}
// ... other code
Combine will help you. Define your var as #Published to be able to subscribe to it
#Published var valueOfModel: Bool = true
You can subscribe to changes in the init or viewDidLoad for example. We store the subscription in the cancelable. Put it in the VC or as class property to keep the subscription alive.
let cancelable: AnyCancellable?
cancelable = valueOfModel.sink { [weak self] value
// this will get called as soon as valueOfModel gets updated
// do smth with value here
}

How to attach subscriber to State or Binding in SwiftUI/Combine?

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 {
}
}

How do I verify incoming data for properties?

In my model, I have a singleton class which will contain some global properties and methods. I think I've set up the class correctly but I need a way to verify incoming data for the properties. I'm trying to use get and set but these seem to need to return void. I can't use init because it's a singleton.
Am I missing something?
final class Globals {
private init(){}
static let sharedInstance = Globals()
//MARK: Properties
private var _peopleCount: Int!
var peopleCount: Int! {
get {
return _peopleCount
}
set(newPeopleCount) {
guard newPeopleCount > 0 else {
return nil // can't return nil here
}
}
}
}
You shouldn't define your variables as implicitly unwrapped optionals unless you have a very good reason to do so.
Your immediate error is that you cannot return a value in a setter, you need to assign the value to the variable there. If you want to mark an invalid value by peopleCount being nil, define peopleCount as Int? and assign to it nil when the check fails.
final class Globals {
private init(){}
static let sharedInstance = Globals()
//MARK: Properties
private var _peopleCount: Int?
var peopleCount: Int? {
get {
return _peopleCount
}
set(newPeopleCount) {
if let newValue = newPeopleCount, newValue > 0 {
_peopleCount = newValue
}
}
}
}
For most use cases, there is no need for the private backing variable, you can just use didSet to check the value before assigning it. Thanks for #LeoDabus for the idea in comments.
var peopleCount: Int? {
didSet {
if let newValue = peopleCount, newValue > 0 {
peopleCount = newValue
} else {
peopleCount = oldValue
}
}
}

Accessing a computed property outside of a method

I have a generic function inside a class in which a computed property is declared:
class CalculatorBrain {
var internalProgram = [AnyObject]()
var accumulator: Double = 0.0
var variableValues: Dictionary<String,Double> = [:]
func setOperand<T> (operand: T) {
if operand is Double {
accumulator = operand as! Double
internalProgram.append(operand as AnyObject)
}
else if variableName == operand as? String {
var dictionaryValue: Double? {
get {
return variableValues[variableName!]
}
set {
accumulator = newValue!
internalProgram.append(newValue! as AnyObject)
}
}
}
}
I want to set dictionaryValue to the value shown in the display from the view controller:
private var brain = CalculatorBrain()
#IBAction func setVariableValue(_ sender: UIButton) {
brain.dictionaryValue = displayValue
}
Obviously I can't, because dictionaryValue is locally defined and "Value of type CalculatorBrain has no memeber dictionaryValue" Now the question is, how can I make a computed property global, and make changes to it from inside a class method? Or, how can I access a computed property defined inside a class method from outside the function?
The problem is dictionaryValue is not a computed property of your class, it is just a variable declared in the setOperand function, so it is not accessible from outside the function.
You should declare it as a stored property of your class and change it when setOperand is called.