Adding weak Subview is causing crash for iOS14

671 Views Asked by At

I get crash logs for users with iOS14. Can I do anything about it, or should I wait till iOS14 is officially there?

Here is myCustomTableViewCell with a textField:

class TextTVCell: UITableViewCell, UITextFieldDelegate {
weak var delegate: TextTVCellDelegate?

weak var textField: UITextField? = {         // I have to make it optional because of weak reference
    let textField = UITextField()
    textField.translatesAutoresizingMaskIntoConstraints = false
    return textField
}()

override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
    super.init(style: style, reuseIdentifier: reuseIdentifier)
    setupSubviews()
}
required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

private func setupSubviews() {
    contentView.addSubview(textField!)
    ......... // constraints, ...
} 

Force unwrap causes the crash on any Device with iOS14. Below it works just fine.

Here is my crash log:

Incident Identifier: AB559313-AD92-4B3A-AA90-97AE47315DEF
Hardware Model:      iPhone9,4
Process:             FormaleBriefe [6654]
Path:                /private/var/containers/Bundle/Application/798B5135-DEB8-451D-858F-AE9FA47F6C7B/FormaleBriefe.app/FormaleBriefe
Identifier:          frugalResolution.Briefe
Version:             5 (2.2.2)
AppStoreTools:       11E707
AppVariant:          1:iPhone9,4:13
Code Type:           ARM-64 (Native)
Role:                Foreground
Parent Process:      launchd [1]
Coalition:           frugalResolution.Briefe [1134]


Date/Time:           2020-08-02 22:52:50.5708 +0200
Launch Time:         2020-08-02 22:52:48.3752 +0200
OS Version:          iPhone OS 14.0 (18A5332f)
Release Type:        Beta
Baseband Version:    4.50.04
Report Version:      104

Exception Type:  EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x0000000104fac2ac
Termination Signal: Trace/BPT trap: 5
Termination Reason: Namespace SIGNAL, Code 0x5
Terminating Process: exc handler [6654]
Triggered by Thread:  0

Thread 0 name:
Thread 0 Crashed:
0   FormaleBriefe                   0x0000000104fac2ac TextTVCell.setupSubviews() + 3800 (TextTVCell.swift:41)
1   FormaleBriefe                   0x0000000104fab458 TextTVCell.setupSubviews() + 132 (TextTVCell.swift:0)
2   FormaleBriefe                   0x0000000104facb98 specialized TextTVCell.init(style:reuseIdentifier:) + 296 (TextTVCell.swift:32)
3   FormaleBriefe                   0x0000000104fab374 @objc TextTVCell.init(style:reuseIdentifier:) + 72 (<compiler-generated>:0)
4   UIKitCore                       0x000000019b63f448 -[UITableView _dequeueReusableViewOfType:withIdentifier:] + 532 (UITableView.m:8843)
5   FormaleBriefe                   0x0000000104f8ca34 LetterWriterVC.tableView(_:cellForRowAt:) + 1504 (LetterWriterVC.swift:555)
6   FormaleBriefe                   0x0000000104f8d6bc @objc
1

There are 1 best solutions below

1
On

Your code was always an antipattern (aka wrong). A simple test will display the problem with pattern. I'll use a view controller:

weak var textField: UITextField? = { 
    let textField = UITextField()
    return textField
}()

override func viewDidLoad() {
    super.viewDidLoad()
    let tf = self.textField
    print(tf as Any) // nil
}

We run the app, and the console prints nil. And you can easily see why. At some point, the textField instance property is initialized, so the closure runs and a text field is returned and assigned to the textField. But that reference is weak! That means: do not hold on to me. So the instance property doesn't hold on to the text field; it drops it, the text field goes out of existence, and the reference is replaced by nil.

Simple solution: don't do that! It's an antipattern, as I said. Either delete the word weak or, if you are intent on maintaining a weak reference to the text field, use a different pattern, such as this:

weak var textField: UITextField?
private func createTextField() -> UITextField  {
    let textField = UITextField()
    self.textField = textField
    return textField
}
override func viewDidLoad() {
    super.viewDidLoad()
    print(tf as Any) // text field
    print(self.textField as Any) // text field
    // and now put it into the interface immediately
    // or it will go out of existence _again!_
}

Personally, what I do is not that. I do this:

weak var textField: UITextField?
private func createTextField() -> UITextField  {
    let textField = UITextField()
    return textField
}
override func viewDidLoad() {
    super.viewDidLoad()
    let tf = self.createTextField()
    self.textField = tf
    // and now put it into the interface immediately or it will go out of existence
}

In real life, that code would be in the setup routine. In other words, I make it the setup routine's job to make the views and assign them to their weak references while at the same time putting them into the interface.

Still another possibility, of course, is just to drop weak and do nothing else. Make textField a strong reference! There is absolutely nothing wrong with having a strong reference to a subview — unless, of course, that subview also has a strong reference to you, but that is unlikely and can be prevented if it arises.