I just updated to Xcode 13.3 and I’m seeing several instances of a new warning that I’ve not seen with previous versions of Xcode. As an example, I have a simple table view cell named LabelAndSwitchTableViewCell that looks like this:
import UIKit
class LabelAndSwitchTableViewCell: UITableViewCell {
private let label: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private let _switch: UISwitch = {
let _switch = UISwitch()
_switch.translatesAutoresizingMaskIntoConstraints = false
_switch.addTarget(self, action: #selector(didToggleSwitch), for: .valueChanged)
return _switch
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentView.addSubview(label)
contentView.addSubview(_switch)
// layout constraints removed for brevity
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func didToggleSwitch() {
print("Switch was toggled...")
}
}
As you can see, I’m adding a target to the switch that I want to be called when the value of the switches changes:
_switch.addTarget(self, action: #selector(didToggleSwitch), for: .valueChanged)
After updating to Xcode 13.3, I’m now seeing a new warning on this line:
'self' refers to the method 'LabelAndSwitchTableViewCell.self', which may be unexpected
Xcode’s suggestion to silence this warning is to replace:
_switch.addTarget(self, action: #selector(didToggleSwitch), for: .valueChanged)
…with…
_switch.addTarget(LabelAndSwitchTableViewCell.self, action: #selector(didToggleSwitch), for: .valueChanged)
Making this change does silence the warning but it also causes the app to crash (unrecognized selector) when I toggle the switch. Here’s the dump from that crash:
[app_mockup.LabelAndSwitchTableViewCell didToggleSwitch]: unrecognized selector sent to class 0x1043d86e8
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[app_mockup.LabelAndSwitchTableViewCell didToggleSwitch]: unrecognized selector sent to class 0x1043d86e8'
Making the didToggleSwitch()
method static
will prevent the crash but I’m not sure why I’d want to do that. I can obviously revert the change (from LabelAndSwitchTableViewCell.self
back to just self
) but I’m wondering if there’s something else that I should be doing to address this?
3
Answers
You can fix by changing the lets to lazy var’s
The Xcode fix-it suggestion is just wrong.
The reason is
self
is not ready yet in phase 1 of object initialisation. Phase 1 is to set all stored properties, and only in phase 2, you can access toself
.To fix your code, you can use
lazy
property, where the initialisation phase 1 is completed.Here is the reference:
https://docs.swift.org/swift-book/LanguageGuide/Initialization.html
For this warning:
NSObject
,self
is an error when to use in a stored property directly.NSObject
,self
gets compiled to an auto closure(ClassXXX) -> () -> ClassXXX
. So in runtime, self will be used as current instance. But, since Swift 5.6 is warning us on that, I think the reference of self in stored property of a NSObject subclass may not be allowed in future swift versions.Example 1: error when there is no super class
Example 2: the compiled self in AST
For this code:
Here is part of the compiled AST:
LabelAndSwitchTableViewCell.self as suggested, will not work in most cases. Use nil and search the responder chain.
addTarget(_:action:for:)