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.
Related
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 -
I'm trying to pass data between viewControllers, but something seems wrong.
The first viewController I want to set the "Bool" to the protocol function to be able to recover in the other screen. What am I doing wrong, I always used protocols but at this time I got in trouble.
That's how I'm doing that:
//
// ComboBoxNode.swift
//
import Foundation
import SWXMLHash
protocol ComboBoxNodeDelegate {
func getCustomOption(data:Bool)
}
class ComboBoxNode: FormControlNode, IFormControlDataSource {
var listType: String?
var dataSource: String?
var dataSourceValue: String?
var dataSourceText: String?
var hasCustomOption:Bool?
var customOptionText: String?
var ctrlDataSourceType: String?
var parameters = [ParameterNode]()
var staticList: FormControlStaticListNode?
var delegate:ComboBoxNodeDelegate?
override init(indexer: XMLIndexer) {
super.init(indexer: indexer)
guard let element = indexer.element else {
preconditionFailure("Error")
}
let isCustomOption = element.bool(by: .hasCustomOption) ?? hasCustomOption
if isCustomOption == true {
self.delegate?.getCustomOption(data: hasCustomOption!)
}
self.readFormControlDataSource(indexer: indexer)
}
override func accept<T, E: IViewVisitor>(visitor: E) -> T where E.T == T {
return visitor.visit(node: self)
}
}
That's how I'm trying to recover on next screen:
// FormPickerViewDelegate.swift
import Foundation
import ViewLib
import RxSwift
class FormPickerViewDelegate: NSObject {
var items = Variable([(value: AnyHashable, text: String)]()) {
didSet {
PickerNodeDelegate = self
self.setDefaultValues()
}
}
private var controlViewModel: FormControlViewModel
private var customText:Bool?
private var PickerNodeDelegate:ComboBoxNodeDelegate?
init(controlViewModel: FormControlViewModel) {
self.controlViewModel = controlViewModel
}
func getItemByValue(_ value: Any) -> (AnyHashable, String)? {
if value is AnyHashable {
let found = items.value.filter {$0.value == value as! AnyHashable}
if found.count >= 1 {
return found[0]
}
}
return nil
}
}
extension FormPickerViewDelegate:ComboBoxNodeDelegate {
func getCustomOption(data: Bool) {
customText = data
}
}
Instead of setting PickerNodeDelegate = self in didSet {} closure
var items = Variable([(value: AnyHashable, text: String)]()) {
didSet {
PickerNodeDelegate = self
self.setDefaultValues()
}
}
Assign it in your init() function instead
init(controlViewModel: FormControlViewModel) {
self.controlViewModel = controlViewModel
PickerNodeDelegate = self
}
Note, your should declare your delegate to be weak also, since it's a delegate, your protocol should conform to be a class type in order to be weakified.
protocol ComboBoxNodeDelegate: class
...
weak var delegate: ComboBoxNodeDelegate?
Here is an example, hope it helps!
protocol ComboBoxNodeDelegate {
func getCustomOption(data:Bool) -> String
}
class ViewOne:ComboBoxNodeDelegate {
var foo:Bool = false
var bar:String = "it works!"
/** Return: String */
func getCustomOption(data:Bool) -> String { //conform here to protocol
// do whatever you wanna do here ...example
self.foo = data // you can set
return bar // even return what you want
}
//initialize
func initalizeViewTwo() {
let v2 = ViewTwo()
v2.delegate = self //since `self` conforms to the ComboBoxNodeDelegate protcol you are allowed to set
}
}
class ViewTwo {
var delegate:ComboBoxNodeDelegate?
func getCustomOption_forV1() {
let view2_foo = delegate.getCustomOption(data:true)
print(view2_foo) // should print "it works!"
}
}
All parameters passed around in Swift are constants -- so you cannot change them.
If you want to change them in a function, you must declare your protocol to pass by reference with inout:
protocol ComboBoxNodeDelegate {
func getCustomOption(data: inout Bool)
}
Note: you cannot pass a constant (let) to this function. It must be a variable -- which I see you are doing!
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
}
}
}
I am using a framework to provide custom UI elements in my main project. There are class properties for the UI element classes within the framework. Each of these classes extend common iOS classes, and have their own .xib files.
For instance:
open class BaseTableViewCell : UITableViewCell {
#IBOutlet public var title: UILabel!
open func setContentDimmed(_ dimmed:Bool) {
if dimmed {
self.title.alpha = 0.3 // < crashes with nil object
} else {
self.title.alpha = 1.0 // < crashes with nil object
}
}
The problem is that when I instantiate a BaseTableViewCell object and call the setContentDimmed(true) func, it crashes. The only way I can make it work is if I change
#IBOutlet public var title: UILabel!
to an optional like:
#IBOutlet public var title: UILabel?
then I MUST unwrap it or its nil (even though its NOT declared as un unwrapped optional in the framework)
open class BaseTableViewCell : UITableViewCell {
#IBOutlet public var title: UILabel?
open func setContentDimmed(_ dimmed:Bool) {
if let titleLabel = self.title {
if dimmed {
titleLabel.alpha = 0.3 // < doesn't crash, works
} else {
titleLabel.alpha = 1.0 // < doesn't crash, works
}
}
}
This was working before moving BaseTableViewCell into my framework. Now its always an optional that MUST be unwrapped or it's nil. Any idea whats going on here?
I've tried everything - Im out of ideas.
Why not do something like this?
#IBOutlet public var title: UILabel!{
didSet{
guard dimmed != nil { title.alpha = 1.0; return }
title.alpha = dimmed! ? 0.3 : 1.0
}
}
var dimmed: Bool? {
didSet{
guard title != nil else { return }
title.alpha = dimmed! ? 0.3 : 1.0
}
}
You are most likely just calling the setContentDimmed before the property is even set.
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.
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
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.
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.
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
}
}
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
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
}
}
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.
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.
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
}
}