Ошибка компилятора: метод с селектором Objective C конфликтует с предыдущим объявлением с тем же селектором Objective C


209

Я начинаю изучать Swift и слежу за очень хорошими лекциями Стэнфордского университета на YouTube. Вот ссылка, если вы заинтересованы или это помогает (хотя это не требуется, чтобы понять мою проблему):

Разработка приложений для iOS 8 с помощью Swift - 2. Больше Xcode и Swift, MVC

Следуя лекциям, я дошел до того, что (насколько я мог судить) мой код был идентичен коду в видео, но в моей системе я получил ошибку компилятора. После многих проб и ошибок мне удалось сократить мой код до двух примеров, один из которых генерирует ошибку, другой - нет, но я понятия не имею, что на самом деле является причиной ошибки или как ее устранить.

Код, который создает ошибку:

import UIKit

class BugViewController: UIViewController
{
    func perform(operation: (Double) -> Double) {
    }

    func perform(operation: (Double, Double) -> Double) {
    }
}

Это создает следующую ошибку компилятора:

Метод 'execute' с селектором Objective-C 'execute:' конфликтует с предыдущим объявлением с тем же селектором Objective-C

Просто удаляя подкласс UIViewController, код компилируется:

import UIKit

class BugViewController
{
    func perform(operation: (Double) -> Double) {
    }

    func perform(operation: (Double, Double) -> Double) {
    }
}

Некоторая другая информация, которая может иметь или не иметь отношение к делу:

  • Я недавно перешел на Йосемити.
  • Когда я установил Xcode, у меня появилась бета-версия (версия 6.3 (6D543q)), потому что (если я правильно помню) это была версия, которая мне была нужна для запуска в моей версии OS X.

Я наполовину надеюсь, что это ошибка в компиляторе, потому что иначе это не имеет никакого смысла для меня. Любая помощь с благодарностью получена!


3
Вы можете запустить Xcode 6.2 на Yosemite. Вы можете скачать его из магазина приложений, и он может работать в вашей системе с бета-версией. Я бы не рекомендовал использовать Xcode 6.3 для класса Стэнфорда на данный момент, потому что он бета и включает Swift 1.2, который отличается от предыдущей версии Swift, использованной в видео.
Vacawama

2
(В настоящее время принятый) ответ пользователя (feb) от 5 апреля больше не является лучшим. Вместо этого ответ (Джеймс Чжан) от 16 апреля является более конкретным и правильным.
флеботинум

Ответы:


144

Objective-C не поддерживает перегрузку методов, вы должны использовать другое имя метода. Когда вы унаследовали UIViewController, вы унаследовали NSObject и сделали класс совместимым с Obj-C. С другой стороны, Swift поддерживает перегрузку, поэтому он работает при удалении наследства.


2
Переопределение метода ПОДДЕРЖКИ Objective-C (с константой (подавляемых) предупреждений компилятора, уведомляющих вас о перегрузке чего-либо уже реализованного), Apple просто не хочет, чтобы вы делали так, чтобы их структуры не перегружались. Я использую такие перегрузки ИП на UIFontкаждый день.
Мичи

Ответ @ polarwar ниже - лучший ответ для Swift 2: stackoverflow.com/a/31500740/144088
Crashalot

237

Я сам тоже беру курс Standford, и я тоже застрял здесь на долгое время, но после некоторых поисков я нашел кое-что отсюда: заметки о выпуске XCode и упомянутое ниже:

Swift 1.2 строго следит за проверкой перегрузки на основе типов методов и инициализаторов @objc, что не поддерживается Objective-C.

// Has the Objective-C selector "performOperation:".
func performOperation(op: NSOperation) { /* do something */ }
// Also has the selector "performOperation:".
func performOperation(fn: () -> Void) {
    self.performOperation(NSBlockOperation(block: fn))
}

Этот код будет работать при вызове из Swift, но может легко потерпеть крах при вызове из Objective-C. Чтобы решить эту проблему, используйте тип, который не поддерживается Objective-C, чтобы компилятор Swift не выставлял член во время выполнения Objective C:

  • Если это имеет смысл, пометьте член как личный, чтобы отключить вывод @objc.
  • В противном случае используйте фиктивный параметр со значением по умолчанию, например: _ nonobjc: () = (). (19826275)

Переопределения методов, предоставляемых Objective-C в частных подклассах, не выводятся как @objc, что приводит к сбою компилятора Swift. Явно добавьте атрибут @objc к любым таким переопределяющим методам. (19935352)

Символы из SDK недоступны при использовании Open Quickly в проекте или рабочей области, где используется Swift. (20349540)

то, что я сделал, просто добавил «private» перед методом переопределения, например так:

    private func performOperation(operation: Double -> Double) {
    if operandStack.count >= 1 {
        displayValue = operation(operandStack.removeLast())
        enter()
    }
}

3
Это решение является наиболее жизнеспособным, которое я считаю imho, так как в этом есть смысл установить этот метод закрытым
demental

38
Обратите внимание, что теперь есть также атрибут @nonobjc, который можно использовать для исключения метода из среды выполнения Objective-C.
Эрик J

2
Я второй комментарий @ ErikJ и ответ polarwar ниже. Похоже, что это лучший ответ для Swift 2 и xcode 7. Если вы еще не обновились, я настоятельно рекомендую это сделать.
Остин

Ответ @ polarwar ниже - лучший ответ для Swift 2: stackoverflow.com/a/31500740/144088
Crashalot

111

Как уже было сказано, ObjC не поддерживает перегрузку методов (два метода с одинаковыми именами), и в swift 2 под Xcode 7 есть два варианта решения таких проблем. Одним из вариантов является переименование метода с использованием атрибута:@objc(newNameMethod:)

func methodOne(par1, par2) {...}

@objc(methodTwo:)
func methodOne(par1) {...}

Другой вариант решения этой проблемы в Xcode 7+ - применение @nonobjcатрибута к любому методу, индексу или инициализатору.

func methodOne() {...}

@nonobjc
func methodOne() {...}

6
это решает проблему для быстрого 2 (и выше). должен быть обновлен как наиболее правильный ответ. ти.
Максим Векслер

2
Для тех, кто использует Swift 2 и Xcode 7 +, это правильный ответ, я согласен с polarwar
TerNovi

17

Проблема UIViewControllerв том, @objcкласс. При наследовании от UIViewController, BugViewControllerэто тоже @objcкласс.

Это означает, что он должен соответствовать правилам селекторов Objective-C (название метода). Методы func perform(operation: (Double) -> Double)и func perform(operation: (Double, Double) -> Double)оба имеют один и тот же селектор @selector(perform:). Это не разрешено

Чтобы решить эту проблему, используйте разные имена: like func perform1(operation: (Double) -> Double)и func perform2(operation: (Double, Double) -> Double).


Я думаю, что лучший способ справиться с этим - дать вашим perform()методам более описательные имена. Что делают эти методы? Как они меняют состояние контроллера вида? Посмотрите на другие UIViewControllerметоды, чтобы почувствовать стиль именования методов, или прочитайте, что имена методов должны быть выразительными и уникальными внутри класса


Спасибо - это отлично отвечает на мой вопрос, и, поскольку вы были первым, я отмечу это как правильное.
Auspice

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

2
@Auspice Возможно, не возникли ошибки с версией Xcode, которую они использовали для видео, но это все еще оставалось проблемой. Только в Xcode 6.3 компилятор смог обнаружить это предупреждение.
Мик МакКаллум

3
Пол Хегарти хочет продемонстрировать здесь «перегрузку» функции (2 функции с одним и тем же именем, но с разным набором аргументов), поэтому он намеренно использует одно и то же имя метода! Перегрузка разрешена только в Swift, но не в Objective-C. Вот почему решение состоит в том, чтобы либо удалить форму наследования UIViewController (которая является классом Objective-C), либо объявить метод закрытым. Оба решения подробно объясняются в других ответах здесь.
Ронни Веберс

На самом деле я использовал частное ключевое слово перед функцией. как, например, private func executeOperation (операция: Double -> Double) {} и приватный func executeOperation (операция: (Double, Double) -> Double) {} Здесь я добился перегрузки метода с помощью PRIVATE. потому что я использовал их оба в ViewController.Swift только. Почему компилятор не говорит ни одной ошибки?
iTag

5

Из https://developer.apple.com/library/ios/releasenotes/DeveloperTools/RN-Xcode/Chapters/xc6_release_notes.html в разделе «Примечания к выпуску Xcode 6.3» -> «Swift Language Changes» вы найдете

Swift теперь обнаруживает расхождения между перегрузкой и переопределением в системе типов Swift и эффективным поведением, наблюдаемым в среде выполнения Objective C.


2

Я получил ту же ошибку из-за наличия двух методов с одинаковой сигнатурой Obj-C:

static func prepareForUpSyncing(obj : NSManagedObject!) -> Bool
static func prepareForUpSyncing(objs : [NSManagedObject]!) -> Bool

Я не хотел отмечать одну из них как @nonobjc из-за возможности непредвиденных последствий во время выполнения. (Кто-то может исправить меня, если нет возможности)

Решил его, используя функцию внешнего имени параметра Swift (я сделал внешнее имя таким же, как локальное имя) для второго метода, который эффективно изменяет сигнатуру метода Obj-c:

static func prepareForUpSyncing(objs objs : [NSManagedObject]!) -> Bool {
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.