Search code examples
iosswiftgetter-setteriboutlet

There is difference in behaviour when an instance variable is computed instead of being stored


I have a custom UIView class that is initiated from the xib file. It has instance property called title of type String?. Whenever, the title property is set, the text of a UITextField gets changed to the value of the title property.

If the title property is a stored property, the program works as expected.

If the title property is a computed property, then the program crashes with EXC_BAD_ACCESS error which I assume is because an IBOutlet had not yet been initialized.

Can anyone explain why if title is a stored property, it works but if it is an computed property it fails?

Following is the source code- The NibView is a subclass of UIView and handles the loading of xib file

class NibView: UIView {
    override init(frame: CGRect) {
        super.init(frame: frame)
        loadNib()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        loadNib()
    }
}

The implementation of loadNib method is inside an extension

extension UIView {
    func loadNib() {
        guard let view = nib.instantiate(withOwner: self, options: nil).first as? UIView else { return }

        view.frame = bounds
        addSubview(view)
    }
}

The definition of nib property on UIView is in another extension

extension UIView {
    static var nib: UINib {
        return UINib(nibName: String(describing: self), bundle: nil)
    }

    var nib: UINib {
        return type(of: self).nib
    }
}

The following class is the class which has the title property.

class ProgressView: NibView {
    var title: String? {
        didSet {
            titleLabel.text = title
        }
    }

    @IBOutlet private weak var titleLabel: UILabel!
}

The above class is used as follows-

let view = ProgressView()
addSubview(view)
view.title = "Loading"

Running the above code works as expected. However if the implementation of ProgressView is changed to use a computed property as below, then it fails

class ProgressView: NibView {
    var title: String? {
        get {
            return titleLabel.text
        }
        set {
            titleLabel.text = newValue
        }
    }

    @IBOutlet private weak var titleLabel: UILabel!
}

Can anyone point out why where is difference in behaviour when the title property is computed instead of being stored?

Edit - The main thread crashes with "Thread 1: EXC_BAD_ACCESS (code=EXC_I386_GPFLT)"

The method on top of the call stack is "ProgressView.title.modify".

Edit 2- I am not sure what I have done but I am unable to reproduce the issue after restarting xcode. Even if computed property is used, it works as expected.


Solution

  • Your description is far from explanatory, but I'm guessing that there is a ProgressView nib in which the File's Owner is a ProgressView, and there is an titleLabel outlet from the File's owner to a label inside the nib. (I assume this because otherwise I can't explain your use of withOwner: self.)

    On that assumption I can't reproduce any problem. Both your ways of expressing title work just fine for me. I put print statements to make sure the right one was being called, and it is; no matter whether this is a didSet or the setter of a computed property, we load just fine and I see the "Loading" text.

    My code is in a view controller's viewDidLoad, if that makes a difference.

    (By the way, I regard your use of ProgressView() with suspicion. This results in a zero-size view. It might not seem to make any difference, but it's a bad idea. The label is a subview of the zero-size view. If the zero-size view clipped its subviews, the label would be invisible. Even if the zero-size view does not clip its subviews, if the label were a button, the button would not work. Zero-size views are a bad idea. You should give your ProgressView a real frame.)