Why lazy property and didSet will end up with recursion call? - ios

The below code works just fine
class A {
var s: MyStruct! {
didSet {
print("didSet")
print(n)
}
}
lazy var n: Int = s.x + 1
func viewDidLoad() {
s = MyStruct()
}
}
struct MyStruct {
var x = 1
init() { print("MyStruct init") }
}
let a = A()
a.viewDidLoad()
with output :
MyStruct init
didSet
2
However, if we have lazy properties as follow
class A {
var s: MyStruct! {
didSet {
print("didSet")
print(n)
}
}
lazy var n: Int = s.x + 1
func viewDidLoad() {
s = MyStruct()
}
}
struct MyStruct {
lazy var x = 1
init() { print("MyStruct init") }
}
let a = A()
a.viewDidLoad()
It will end up with endless recursion call
MyStruct init
didSet
didSet
didSet
...
Why lazy property and didSet will end up with recursion call?

You are declaring a lazy stored property. When the struct is initialized with MyStruct(), there's no value stored in MyStruct.x.
It will only be populated when it is accessed first time. When a property is changed, a value type like struct MyStruct is considered to be changed as well - so it's didSet is invoked (again) upon first access of x.
Here's how it becomes infinite loop.
viewDidLoad() > A.s.setter > A.s.didset [Expected]
First access of A.n.getter for the print(n) part.
s.x is lazy stored and upon first value population (update), it triggers - A.s.modify > A.s.didset & we land again at A.n.getter.
It loops indefinitely between 2 & 3 after this.
See screenshot -

Related

Observing multiple published variable changes inside ObservableObject

I have an ObservableObject that contains multiple published variables to handle my app state.
Whenever one of those published variables change, I want to call a function inside my ObservableObject. What's the best way to do that?
class AppModelController: ObservableObject {
#Published var a: String = "R"
#Published var b: CGFloat = 0.0
#Published var c: CGFloat = 0.9
// Call this function whenever a, b or c change
func check() -> Bool {
}
}
You can use didSet, like this code:
class AppModelController: ObservableObject {
#Published var a: String = "R" { didSet(oldValue) { if (a != oldValue) { check() } } }
#Published var b: CGFloat = 0.0 { didSet(oldValue) { if (b != oldValue) { check() } } }
#Published var c: CGFloat = 0.9 { didSet(oldValue) { if (c != oldValue) { check() } } }
func check() {
// Some Work!
}
}
The simplest thing you can do is listen to objectWillChange. The catch is that it gets called before the object updates. You can use .receive(on: RunLoop.main) to get the updates on the next loop, which will reflect the changed values:
import Combine
class AppModelController: ObservableObject {
#Published var a: String = "R"
#Published var b: CGFloat = 0.0
#Published var c: CGFloat = 0.9
private var cancellable : AnyCancellable?
init() {
cancellable = self.objectWillChange
.receive(on: RunLoop.main)
.sink { newValue in
let _ = self.check()
}
}
// Call this function whenever a, b or c change
func check() -> Bool {
return true
}
}

DidSet not working in init function swift 3

I have already seen
Is it possible to allow didSet to be called during initialization in Swift?
for me it is not working..
I am working in project where I have created class below
protocol FileManagerHelper {
var fileName:String {get}
var fileCategory:FileCategory {get set}
var isFileExitsAtPath:Bool {get}
var filePath:String {get}
var fileType:FileTypes {get set}
}
class FileManager:FileManagerHelper {
// Other property
//STORED PROPERY INIT WHEN OBJECT WILL CREATED WITH FileCategory OBJECT
var fileCategory:FileCategory {
didSet {
switch fileCategory {
case .XYZ:
print("Test")
... other cases
}
}
required init(fileCategory:FileCategory,fileType:FileTypes = .Image) {
self.fileCategory = fileCategory
self.path = self.folderPath + self.fileName
}
}
did set method is not calling of fileCategory
NOTE: I don't want to give default value , I want to pass it runtime from init method
Tries
1) defer
use of self in method called $defer before all stored property are initialised
2) Create custom method that will assign that value and call it from init
private func setCategory(with category:FileCategory) {
self.fileCategory = category
}
Use of method call setCategory before stored property ...
I know that all stored property should be initialised before instance created. Till that instance will not been created so i won't call methods (using self) may be that why above solution not working
Please help me if any one have idea
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
One way to solve this is to extract the didSet logic into a separate method and call this method from both didSet and init:
class FileManager: FileManagerHelper {
var fileCategory:FileCategory {
didSet {
didSetFileCategory()
}
}
required init(fileCategory:FileCategory,fileType:FileTypes = .Image) {
self.fileCategory = fileCategory
self.path = self.folderPath + self.fileName
didSetFileCategory()
}
private func didSetFileCategory() {
switch fileCategory {
case .XYZ:
print("Test")
//... other cases
}
}
}

'count' cannot be used on type 'ViewController'

I am having an issue with passing a value to a class in swift.I have the ViewControler.swift that creates the object and other UI sort of stuff. Here is the code:
class ViewController: UIViewController {
//creates the Intractive button objects
#IBOutlet var Bag1: UIButton!
#IBOutlet var Bag2: UIButton!
#IBOutlet var Bag3: UIButton!
#IBOutlet var lblTokenSlider: UILabel!
#IBOutlet var slider: UIStepper!
#IBOutlet var Go: UIButton!
#IBOutlet var counter: UILabel!
var count = 10
var noOfBags = 3
//gives acces to game AP!
var gameAPI = GameAPI(noOfBags: count, noOfTokens: noOfBags )
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
lblTokenSlider.hidden = true
slider.hidden = true
Go.hidden = true
counter.hidden = true
Bag3.hidden = false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Event Drivers
#IBAction func btnBag1(sender: AnyObject) {
Bag1.userInteractionEnabled = false
Bag2.userInteractionEnabled = false
Bag3.userInteractionEnabled = false
}
#IBAction func btnBag2(sender: AnyObject) {
}
#IBAction func btnBag3(sender: AnyObject) {
}
#IBAction func SliderAction(sender: AnyObject) {
}
#IBAction func RemoveTokens(sender: AnyObject) {
}
}
}
And here is my gameAPI:
import Foundation
private enum MoveError: ErrorType {
case Empty
}
class GameAPI {
var noOfBags: Int
var bagArray:[Bag] = []
init(noOfBags: Int, noOfTokens : Int){
self.noOfBags = noOfBags
for _ in 0..<noOfBags {
bagArray.append(Bag(counter: noOfTokens))
}
}
/* Returns the amount of counters are in a bag */
func getCounts(i :Int ) -> Int {
return bagArray[i].getCount()
}
func isBagEmpty(i: Int) -> Bool {
if (bagArray[i].getCount() <= 0){
return true
}
else {
return false
}
}
func removeCounter(bagToRemove: Int, counters: Int ) throws{
do {
try self.bagArray[bagToRemove].removeCount(counters)
}
catch{
throw MoveError.Empty
}
}
}
The Issue is where I declare GameAPI in the ViewController and I get
' Instance member 'count' cannot be used on type 'ViewController''
on line
var gameAPI = GameAPI(noOfBags: count, noOfTokens: noOfBags )
But if I was to switch the variables out and used fixed values like:
var gameAPI = GameAPI(noOfBags: 10, noOfTokens: 3 )
It works fine. I really dont understand why this is not working.
Thank you
The problem is that you have the initializer of an instance variable using another instance variable. In general, you cannot use an instance variable until all the instance variables are initialized. Therefore you have to break the dependency between instance variables.
You have several options:
Move your initialization to an ininitalizer. That's a bit difficult with UIViewController though because you need at least two initializers, leading to code duplication:
init(...) {
let count = 10
let noOfBags = 3
self.count = count
self.noOfBags = noOfBags
self.gameAPI = GameAPI(noOfBags: count, noOfTokens: noOfBags)
super.init(...)
}
Declare count and noOfBags as global constants:
static let count = 10
static let noOfBags = 3
Therefore your GameAPI initializer won't use self.
If you need count and noOfBags as variables, you can create global constants initialCount and initialNoOfBags and then
var count = initialCount
var noOfBags = initialNoOfBags
var gameAPI = GameAPI(noOfBags: initialCount, noOfTokens: initialNoOfBags)
You can initialize your gameAPI lazily:
lazy var gameAPI: GameAPI = GameAPI(noOfBags: self.count, noOfTokens: self.noOfBags)
It's because you can only declare variable outside any method in your ViewController class. but you can not assign any variable outside any method.
Suppose you are declaring one variable like:
let tempVar = 0
Which is outside of method but inside your view controller class like:
import UIKit
class ViewController: UIViewController {
let tempVar = 0
}
Now if you create another variable inside your class like:
import UIKit
class ViewController: UIViewController {
let tempVar = 0
let tempVar2 = tempVar
}
And you are giving a value to that variable tempVar2 is tempVar. Which will give you an error like:
Instance member 'tempVar' cannot be used on type 'ViewController'
You are getting this because you are assigning value to tempVar2 instance at wrong place. you can solve this error by assigning values into viewDidLoad method or in any other method this way:
import UIKit
class ViewController: UIViewController {
let tempVar = 0
var tempVar2 = 1
override func viewDidLoad() {
tempVar2 = tempVar
}
}
In Swift you cannot use instance properties within a property initializer because property initializers run before 'self' is available.
You can fix this by making your gameAPI variable a computed property instead.
So instead of:
var count = 10
var noOfBags = 3
var gameAPI = GameAPI(noOfBags: count, noOfTokens: noOfBags)
Try this:
var count = 10
var noOfBags = 3
var gameAPI: GameAPI {
return GameAPI(noOfBags: count, noOfTokens: noOfBags)
}

How to observe change of value and show it? In Swift

I have a variable that takes the number of objects in the array in a different class, how can I keep track of the change of this variable in the current class? I did a lot of different attempts but failed.
var digitIndex: Int! {
set {
self.digitIndex = newValue
}
get {
return firstClass.indexesOfSelectedNodes().count
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if context == &digitIndex {
if let newValue = change?[NSKeyValueChangeNewKey] {
print("newValue")
infoLabel.text = "\(newValue)"
}
}
}
1) in your code
var digitIndex: Int! {
set {
self.digitIndex = newValue
}
get {
return firstClass.indexesOfSelectedNodes().count
}
}
digitIndex is computed property! You are not able to set self.digitIndex within its own setter!
this code, even though compiled, will run forever :-)
var i: Int {
get {
return 10
}
set {
i = newValue
}
}
i = 100
print(i)
2) How to use willSet and didSet (for STORED properties)?
class C1 {}
class C2 {}
class C {
var objects: [AnyObject] = [C1(),C1(),C2()] {
willSet {
print(objects, objects.count, newValue)
}
didSet {
print(objects, objects.count)
}
}
func add(object: AnyObject) {
objects.append(object)
}
}
let c = C()
c.add(C1())
/*
[C1, C1, C2] 3 [C1, C1, C2, C1]
[C1, C1, C2, C1] 4
*/
var i: Int = 0 {
willSet {
print(newValue, "will replace", i)
}
didSet {
print(oldValue, "was replaced by", i)
}
}
i = 100
/*
100 will replace 0
0 was replaced by 100
*/
you could combine the computed and stored properties for your advantage
// 'private' storage
var _j:Int = 0
var j: Int {
get {
return _j
}
set {
print(newValue)
if newValue < 300 {
_j = newValue
} else {
print(newValue, "refused")
}
}
}
print(j) // prints 0
j = 200 // prints 200
print(j) // prints 200
j = 500 // prints 500 refused
print(j) // prints 200
Try this :
var digitIndex: Int! {
set {
self.digitIndex = newValue
}
get {
return firstClass.indexesOfSelectedNodes().count
}
didSet {
//you will get new value here
}
}
No need to add observer for our class properties, you just required to add observer for the properties provided by super class.
You can make use of a delegate handling the communication between your two classes.
An example follows below, using a custom delegate MyDelegate (protocol). A delegate instance is initialized in MyOtherClass (e.g., a view controller class), which makes a delegate callback whenever the array myArr is updated in this class. This, in turn, updates the value of digitIndex in MyCurrentClass (e.g., some custom control), which conforms to MyDelegate by implementing the blueprinted arrUpdated(..) method. Finally, the didSet property observer on digitIndex in MyCurrentClass tells us via console print-out that its value has been updated.
protocol MyDelegate: class {
func arrUpdated(arr: [Int])
}
class MyDifferentClass {
private var myArr : [Int] = [] {
didSet {
// Call delegate.
delegate?.arrUpdated(myArr)
}
}
weak var delegate: MyDelegate?
}
class MyCurrentClass: MyDelegate {
var myDifferentClass : MyDifferentClass
var digitIndex: Int = 0 {
didSet {
print("digitIndex updated: \(digitIndex)")
}
}
init(a: MyDifferentClass) {
myDifferentClass = a
myDifferentClass.delegate = self
}
// MyDelegate
func arrUpdated(arr: [Int]) {
digitIndex = arr.count
}
}
Test:
var a = MyDifferentClass()
var b = MyCurrentClass(a: a)
a.myArr.append(1) // prints 'digitIndex updated: 1'

Know when a weak var becomes nil in Swift?

Let's say I have a weak var view: UIView? in my class Button {}. Is there any way to know when view loses its reference and becomes nil?
I tried using weak var view: UIView? {} (aka a computed property) in order to override set {}, but that didn't work because now it's a computed property and can't store a weak reference (how annoying!).
Edit:
#fqdn's answer didn't work with this code... Try it in an Xcode Playground
import UIKit
class Test {
weak var target: UIView? {
willSet {
if !newValue { println("target set to nil") }
else { println("target set to view") }
}
}
}
class Button {
var view: UIView? = UIView()
}
var t = Test()
var b = Button()
t.target = b.view
b.view = nil // t.target's willSet should be fired here
Your output console should display:
target set to view
target set to nil
My console displays
target set to view
b.view is the strong reference for the UIView instance. t.target is the weak reference. Therefore, if b.view is set to nil, the UIView instance is deallocated and t.target will be equal to nil.
If your button is holding a reference to another view, it should either be an owner of that view (i.e., it should hold a strong reference) or it should not care when that view goes away (i.e., its weak reference to it becomes nil.) There is no notification when weak references become nil, and that is by design.
In particular, Swift property observers are not called when weak references become nil, as the following code demonstrates:
class A : CustomStringConvertible {
var s: String?
init(s: String) {
self.s = s;
print("\(self) init")
}
deinit {
print("\(self) deinit")
}
var description: String {
get { return "[A s:\(s ?? "nil")]" }
}
}
class B : CustomStringConvertible {
weak var a:A? {
willSet {
print("\(self) willSet a")
}
didSet {
print("\(self) didSet a")
}
}
init(a: A?) {
self.a = a
print("\(self) init")
}
deinit {
print("\(self) deinit")
}
var description: String {
get { return "[B a:\(a == nil ? "nil" : String(describing: a!))]" }
}
}
func work() {
var a: A? = A(s: "Hello")
var b = B(a: a)
print("\(b)")
a = nil
print("\(b)")
b.a = A(s: "Goodbye")
}
work()
When work() is called, the console gives the following output:
[A s:Hello] init
[B a:[A s:Hello]] init
[B a:[A s:Hello]]
[A s:Hello] deinit
[B a:nil]
[A s:Goodbye] init
[B a:nil] willSet a
[B a:[A s:Goodbye]] didSet a
[A s:Goodbye] deinit
[B a:nil] deinit
Notice that in neither case of the instance of A deallocating and its weak reference in the instance of B becoming nil are the property observers called. Only in the direct case of assignment to B.a are they called.

Resources