Предупреждение Xcode 13.3: ""self" относится к методу "{object}.self", что может быть неожиданным

Я только что обновился до Xcode 13.3 и вижу несколько экземпляров нового предупреждения, которого не видел в предыдущих версиях Xcode. В качестве примера у меня есть простая ячейка табличного представления с именем LabelAndSwitchTableViewCell, которая выглядит так:

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), но мне интересно, есть ли что-то еще, что я должен сделать, чтобы решить эту проблему?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
0
557
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

Вы можете исправить это, изменив let на lazy var.

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

Предложение по исправлению Xcode просто неверно.

Не пробовал делать мои реквизиты ленивыми варами, но вы абсолютно правы - это предотвращает предупреждение! Не уверен, что я полностью понимаю, почему ленивая инициализация свойства переключателя имеет значение в этом конкретном случае, но я, конечно, не могу придумать недостаток, поэтому я соответствующим образом обновлю свой код. Спасибо за предложение!

bmt22033 21.03.2022 17:48

Я не знаю, является ли это ошибкой в ​​​​13.3 или есть какая-то реальная проблема, связанная с тем, что self не гарантируется, что он находится рядом, инициируется и может быть захвачен в рамках автоматического оценивания замыкания.

Shadowrun 21.03.2022 18:06

Я думаю, что в некоторых случаях это может быть ошибкой кодирования, но до сих пор не было предупреждений. (и предупреждение и исправление на данный момент сбивают с толку) self определенно недоступен, когда цель добавляется в это замыкание, но в большинстве случаев это также не требуется, чтобы быть готовым. Может случиться так, что событие селектора, например, .allEvents, и какое-то событие произойдет до самоподготовки + вам нужно обработать это событие. Я считаю, что это очень маловероятный сценарий, когда вам нужно событие перед собой для элементов управления. ленивый просто сделайте так, чтобы этот var был инициализирован после себя.

Gábor Hertelendy 26.03.2022 13:46

Причина в том, что self еще не готов к фазе 1 инициализации объекта. Фаза 1 — установить все сохраненные свойства, и только на фазе 2 вы можете получить доступ к self.

Чтобы исправить свой код, вы можете использовать свойство lazy, где фаза инициализации 1 завершена.

Вот ссылка: https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

Для этого предупреждения:

  1. Если нет суперкласса или суперкласса нет NSObject, self является ошибкой при непосредственном использовании в хранимом свойстве.
  2. Если суперкласс NSObject, self компилируется в автозакрытие (ClassXXX) -> () -> ClassXXX. Таким образом, во время выполнения self будет использоваться как текущий экземпляр. Но, поскольку Swift 5.6 предупреждает нас об этом, я думаю, что ссылка на себя в сохраненном свойстве подкласса NSObject может быть запрещена в будущих версиях Swift.

Пример 1: ошибка при отсутствии суперкласса

Пример 2: скомпилированное self в AST

Для этого кода:

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

Вот часть скомпилированного AST:

Почему же тогда целевое действие работает во время выполнения и правильно нацеливается на сам экземпляр, а не на ClassXXX.self?

Shadowrun 22.03.2022 07:49

На данный момент, если класс является подклассом NSObject, компилятор Swift скомпилирует self в автоматическое замыкание: (ClassXXX) -> () -> ClassXXX (я проверил компилируемые языки AST и SIL, которые могут подтвердить автоматическое замыкание self). Таким образом, во время выполнения self будет использоваться как текущий экземпляр. Но, поскольку Swift 5.6 предупреждает нас об этом, я думаю, что ссылка на self в сохраненном свойстве подкласса NSObject может быть запрещена в будущих версиях Swift.

Simon Wang 22.03.2022 10:45

Ответ отредактирован, чтобы включить пример AST

Simon Wang 22.03.2022 11:05

Спасибо за дополнительное пояснение!

bmt22033 22.03.2022 12:07

LabelAndSwitchTableViewCell.self, как было предложено, в большинстве случаев не работает. Используйте nil и выполните поиск в цепочке респондентов.

addTarget(_:действие:для:)

Как сейчас написано, ваш ответ неясен. Пожалуйста, редактировать, чтобы добавить дополнительную информацию, которая поможет другим понять, как это относится к заданному вопросу. Дополнительную информацию о том, как писать хорошие ответы, можно найти в справочном центре.

Community 06.04.2022 20:53

Другие вопросы по теме