Как справиться с устареванием @objc с помощью #selector () в Swift 4?


131

Я пытаюсь преобразовать исходный код своего проекта из Swift 3 в Swift 4. Одно предупреждение, которое дает мне Xcode, касается моих селекторов.

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

button.addTarget(self, action: #selector(self.myAction), for: .touchUpInside)

Это предупреждение, которое он показывает:

Аргумент '#selector' относится к методу экземпляра 'myAction ()' в 'ViewController', который зависит от вывода атрибута '@objc', устаревшего в Swift 4

Добавьте '@objc', чтобы предоставить этот метод экземпляра Objective-C

Теперь нажатие Fixна сообщение об ошибке делает это с моей функцией:

// before
func myAction() { /* ... */ }

// after
@objc func myAction() { /* ... */ }

Я действительно не хочу переименовывать все свои функции, чтобы включить @objcметку, и предполагаю, что в этом нет необходимости.

Как мне переписать селектор, чтобы справиться с устареванием?


Связанный вопрос:


3
Нет, пометив их , как @objc это теперь необходимо для того , чтобы подвергнуть их Obj-C, и , следовательно , использовать с селекторов.
Хэмиш

3
Итак, устаревшая часть выводит функции общего доступа как @objc? Это немного раздражает, но я обычно делаю эти функции закрытыми, требуя от меня пометить их как в @objcлюбом случае.
Коннор

2
См. SE-0160 для получения дополнительной информации об изменении. Другой альтернативой является пометка вашего данного класса как @objcMembersдля того, чтобы предоставить Obj-C все совместимые с Obj-C члены, но я бы не советовал этого делать, если вам действительно не нужно, чтобы весь ваш класс был открыт.
Хэмиш

3
@LinusGeffarth Как сказано в предложении, это, вероятно, излишне увеличит размер вашего двоичного файла, а динамическое связывание займет больше времени. Я действительно не думаю, что это слишком хлопотно из-за дополнительной ясности, которую вы специально имеете в виду для конкретной вещи, которая будет использоваться из Obj-C.
Hamish

1
Пробовал, не работает.
LinusGeffarth

Ответы:


156

Исправление - это правильно - в селекторе нет ничего, что вы могли бы изменить, чтобы сделать метод, к которому он относится, доступным для Objective-C.

Вся причина этого предупреждения в первую очередь - результат SE-0160 . До Swift 4 internalили выше Objective-C совместимые члены NSObjectнаследующих классов предполагались @objcи, следовательно, были доступны для Objective-C, что позволяло их вызывать с помощью селекторов (поскольку для поиска метода требуется среда выполнения Obj-C реализация для данного селектора).

Однако в Swift 4 это уже не так. Теперь предполагается, что только очень конкретные объявления могут быть @objc, например, переопределениями @objcметодов, реализациями @objcтребований протокола и объявлениями с атрибутами, которые подразумевают @objc, например @IBOutlet.

Мотивация, стоящая за этим, как подробно описано в приведенном выше связанном предложении , состоит во-первых, чтобы предотвратить конфликты перегрузок методов в NSObjectнаследующих классах друг с другом из-за наличия идентичных селекторов. Во-вторых, это помогает уменьшить размер двоичного файла, не создавая преобразователей для членов, которые не должны быть открыты для Obj-C, и, в-третьих, повышает скорость динамического связывания.

Если вы хотите предоставить доступ к члену Obj-C, вам нужно пометить его @objc, например, как:

class ViewController: UIViewController {

    @IBOutlet weak var button: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        button.addTarget(self, action: #selector(foo), for: .touchUpInside)
    }

    @objc func foo() {
       // ... 
    }
}

(мигратор должен делать это автоматически с помощью селекторов при работе с выбранной опцией «минимизировать вывод»)

Чтобы предоставить группу участников Obj-C, вы можете использовать @objc extension:

@objc extension ViewController {

    // both exposed to Obj-C
    func foo() {}
    func bar() {}
}

Это откроет все элементы, определенные в нем, для Obj-C и выдаст ошибку для любых членов, которые не могут быть представлены Obj-C (если явно не отмечены как @nonobjc).

Если у вас есть класс, в котором все совместимые с Obj-C члены должны быть доступны для Obj-C, вы можете пометить класс как @objcMembers:

@objcMembers
class ViewController: UIViewController {
   // ...
}

Теперь все члены, которых можно предположить, @objcбудут. Однако я бы не советовал делать это, если вам действительно не нужны все члены, открытые для Obj-C, с учетом вышеупомянутых недостатков необоснованного раскрытия членов.


2
Почему Xcode не делает этого автоматически при преобразовании кода в последний синтаксис?
LinusGeffarth

1
Интересно, @IBActionтоже автоматически @objc? До сих пор этого не было, но это было бы логично. EDIT: nvm, в предложении четко указано, что этого @IBActionдостаточно для существования метода @objc.
Sulthan 06

8
Я ничего из этого не понимаю. У меня вообще нет Objective-C-кода. Нет ли чистого быстрого способа добавления целей к кнопке? Или использовать селекторы? Я не хочу, чтобы мой код был пронизан этим атрибутом. Это потому, что UIButton является производным от некоторой NSObject / Obj-c-вещи?
Sti

11
@Sti Итак, чтобы прямо ответить: « Нет ли чистого Swift-способа добавления целей к кнопке? Или использовать селекторы? » - нет, нет «чистого Swift» способа использования селекторов для отправки в методы. Они полагаются на среду выполнения Obj-C для поиска реализации метода для вызова определенного селектора - среда выполнения Swift не имеет такой возможности.
Хэмиш

12
Селекторы мужчин - беспорядок. Кажется, что с каждым другим быстрым обновлением они возятся. Почему мы не можем просто использовать быстрый селектор автозаполнения для методов. Делает код таким уродливым.
Джон Риселвато

16

Как официальная документация Apple . вам нужно использовать @objc для вызова вашего метода выбора.

В Objective-C селектор - это тип, который ссылается на имя метода Objective-C. В Swift селекторы Objective-C представлены Selectorструктурой и могут быть созданы с использованием #selector выражения. Чтобы создать селектор для метода, который можно вызывать из Objective-C, передайте имя метода, например #selector(MyViewController.tappedButton(sender:)). Чтобы создать селектор для метода получения или установки свойства Objective-C, передайте имя свойства с префиксом getter:или setter:меткой, например #selector(getter: MyViewController.myButton).


все, что я понимаю из этого, это то, что в некоторых случаях мне нужно добавить @objc в начало функции независимо от того, буду ли я когда-либо вызывать ее из Objective-C. Я только изучаю Swift, годами использую Obj-C
SundialSoft

10

Насколько я понимаю, Swift 4.2, все, что вам нужно сделать, это назначить @IBAction вашему методу, и вы можете избежать этой глупой аннотации @objc

`` `

let tap  =  UITapGestureRecognizer(target: self, action: #selector(self.cancel))


@IBAction func cancel()
{
    self.dismiss(animated: true, completion: nil)
}

1
Это также сообщит разработчику интерфейса, что эта функция может быть подключена, что может быть не тем, что вам нужно. Он также делает то же самое, что и @objc.
Джош Парадроид

2

Как уже упоминалось в других ответах, нет возможности избежать @objcаннотации для селекторов.

Но предупреждение, упомянутое в OP, можно отключить, выполнив следующие действия:

  1. Зайдите в настройки сборки
  2. Искать по ключевому слову @objc
  3. Установите значение интерфейса Swift 3 @objc наOff

ниже приведен снимок экрана, который иллюстрирует вышеупомянутые шаги:

Отключение предупреждения "Swift 3 @objc interface"

Надеюсь это поможет


Как это повлияет на ваш проект в целом?
Zonily Jame

1
Как насчет размера * .ipa или * .xarchive и времени сборки?
Зонили Джейм

@ZonilyJame я не заметил время сборки, но это не повлияло на размер IPA
S1LENT WARRIOR

Это не рекомендуемое значение, которое вы можете использовать, если используете версию swift 4.0+ в соответствии с рекомендациями Apple. Проверьте это help.apple.com/xcode/mac/current/#/deve838b19a1
Bhavin_m

1
Вы можете обновить этот ответ, установив значение по умолчанию в Xcode 11. Важно установить значение как для проекта, так и для ваших целей, чтобы полностью удалить предупреждение.
Tommie C.

2

Если вам нужны члены объекта c в вашем контроллере представления, просто добавьте @objcMembers в верхней части контроллера представления. А этого можно избежать, добавив в код IBAction.

@IBAction func buttonAction() {

}

Обязательно подключите эту розетку в раскадровке.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.