Xcode 13.3 警告:“'self' 引用方法'{object}.self',这可能是意外的

Xcode 13.3 warning: "'self' refers to the method '{object}.self', which may be unexpected

我刚刚更新到 Xcode 13.3,我看到了几个新警告实例,这是我在以前的 Xcode 版本中没有看到的。例如,我有一个名为 LabelAndSwitchTableViewCell 的简单 table 视图单元格,如下所示:

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...")
    }
}

如您所见,我正在向开关添加一个目标,当开关的值发生变化时我希望调用该目标:

_switch.addTarget(self, action: #selector(didToggleSwitch), for: .valueChanged)

更新到 Xcode 13.3 后,我现在在这一行看到一个新警告:

'self' refers to the method 'LabelAndSwitchTableViewCell.self', which may be unexpected

Xcode 消除此警告的建议是替换:

_switch.addTarget(self, action: #selector(didToggleSwitch), for: .valueChanged)

...与...

_switch.addTarget(LabelAndSwitchTableViewCell.self, action: #selector(didToggleSwitch), for: .valueChanged)

进行此更改会使警告静音,但它也会导致应用程序在我切换开关时崩溃(无法识别的选择器)。这是崩溃的转储:

[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'

制作 didToggleSwitch() 方法 static 将防止崩溃,但我不确定为什么要这样做。我显然可以恢复更改(从 LabelAndSwitchTableViewCell.self 恢复到 self),但我想知道我是否应该做其他事情来解决这个问题?

您可以通过将 lets 更改为惰性变量来修复

private lazy var _switch2: UISwitch = {
    let _switch = UISwitch()
    _switch.translatesAutoresizingMaskIntoConstraints = false
    _switch.addTarget(self, action: #selector(didToggleSwitch), for: .valueChanged)
    return _switch
}()

Xcodefix-it的建议是错误的。

原因是 self 在对象初始化的第 1 阶段尚未准备好。第一阶段是设置所有存储的属性,只有第二阶段才能访问self.

要修复您的代码,您可以使用 lazy 属性,其中初始化阶段 1 已完成。

参考资料如下: https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

对于此警告:

  1. 如果没有superclass,或者superclass不是NSObjectself在存储的[=59=中使用时是错误的]直接。
  2. 如果超级 class 是 NSObjectself 被编译为自动关闭 (ClassXXX) -> () -> ClassXXX。所以在运行时,self 将被用作当前实例。但是,由于 Swift 5.6 就此警告我们,我认为在未来的 swift 版本中可能不允许在 NSObject subclass 的存储 属性 中引用 self 。

例1:没有super时报错class

示例2:AST中编译的self

对于此代码:

import Foundation
class MyTest: NSObject  {
    var myself = self
}

这是编译后的 AST 的一部分:

LabelAndSwitchTableViewCell.self 如建议的那样,在大多数情况下不起作用。使用 nil 并搜索响应链。

addTarget(_:action:for:)