Как сделать слабую ссылку на протокол в «чистом» Swift (без @objc)

365

weak ссылки, похоже, не работают в Swift, если protocol не объявлен как @objc, чего я не хочу в чистом приложении Swift.

Этот код дает ошибку компиляции (weak не может применяться к типу некласса MyClassDelegate):

class MyClass {
  weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate {
}

Мне нужно префикс протокола @objc, затем он работает.

Вопрос: Что такое "чистый" быстрый способ выполнить weak delegate?

Теги:
delegates
swift-protocols

4 ответа

759
Лучший ответ

Вам нужно объявить тип протокола как class.

protocol ProtocolNameDelegate: class {
    // Protocol stuff goes here
}

class SomeClass {
    weak var delegate: ProtocolNameDelegate?
}

Я понимаю, что с помощью class вы гарантируете, что этот протокол будет использоваться только для классов и других вещей, таких как перечисления или структуры.

  • 23
    Моя проблема с этими решениями заключается в том, что вызов делегата вызывает сбой - EXC_BAD_ACCESS (как отмечалось другими в другом месте). Это похоже на ошибку. Единственное решение, которое я нашел, - это использование @objc и исключение всех типов данных Swift из протокола.
  • 9
    Как правильно делать слабых делегатов сейчас в Swift? Документация Apple не показывает и не объявляет делегата слабым в своем примере кода: developer.apple.com/library/ios/documentation/swift/conceptual/…
Показать ещё 5 комментариев
142

Дополнительный ответ

Меня всегда путали, должны ли делегаты быть слабыми или нет. Недавно я узнал больше о делегатах и ​​когда использовать слабые ссылки, поэтому позвольте мне добавить некоторые дополнительные пункты здесь для будущих зрителей.

  • Цель использования ключевого слова weak заключается в том, чтобы избежать сильных опорных циклов (сохранение циклов). Сильные циклы ссылок происходят, когда два экземпляра класса имеют сильные ссылки друг на друга. Их подсчеты ссылок никогда не обращаются к нулю, поэтому они никогда не освобождаются.

  • Вам нужно использовать weak, если делегат является классом. Swift-структуры и перечисления представляют собой типы значений (их значения копируются при создании нового экземпляра), а не ссылочные типы, поэтому они не создают сильных ссылочных циклов.

  • weak ссылки всегда являются необязательными (в противном случае вы использовали бы unowned) и всегда используете var (not let), чтобы опционально можно было установить nil при его освобождении.

  • Родительский класс должен, естественно, иметь сильную ссылку на его дочерние классы и, следовательно, не использовать ключевое слово weak. Однако, когда ребенок хочет ссылку на своего родителя, он должен сделать его слабым, используя ключевое слово weak.

  • weak следует использовать, если вам нужна ссылка на класс, который у вас нет, а не только на ссылку на родителя. Когда два неиерархических класса должны ссылаться друг на друга, выберите один, чтобы быть слабым. Вы выбираете, зависит от ситуации. Подробнее см. Ответы на этот вопрос.

  • Как правило, делегаты должны быть помечены как weak, потому что большинство делегатов ссылаются на классы, которым они не владеют. Это определенно верно, когда ребенок использует делегат для связи с родителем. Тем не менее, есть еще некоторые ситуации, где делегат может и должен использовать сильную ссылку.

  • Протоколы могут использоваться как для ссылочных типов (классов), так и для типов без ссылки (structs, enums). Поэтому в вероятном случае, когда вам нужно сделать делегат слабым, вам нужно добавить ключевое слово class в протокол, чтобы он знал, что он должен использоваться только со ссылочными типами.

    protocol MyClassDelegate: class {
        // ...
    }
    
    class SomeClass {
        weak var delegate: MyClassDelegate?
    }
    

Дальнейшее исследование

Чтение следующих статей помогло мне понять это намного лучше. Они также обсуждают связанные проблемы, такие как ключевое слово unowned и сильные ссылочные циклы, которые происходят с закрытием.

Связанные

  • 5
    Это все приятно и интересно, но на самом деле это не связано с моим первоначальным вопросом, который касается ни самого слабого / ARC, ни того, почему делегаты обычно являются слабыми. Мы уже знаем обо всем этом и просто удивляемся, как можно объявить слабую ссылку на протокол (отлично ответил @flainez).
  • 23
    Ты прав. У меня на самом деле был тот же вопрос, что и у вас ранее, но я скучал по этой основной информации. Я прочитал выше и сделал дополнительные заметки, чтобы помочь себе понять все вопросы, связанные с вашим вопросом. Теперь я думаю, что могу применить ваш принятый ответ и знать, почему я это делаю. Я надеюсь, что это поможет будущим зрителям.
Показать ещё 3 комментария
5

Update: Похоже, что руководство было обновлено, и пример, о котором я говорил, был удален. См. Ответ на комментарий @flainez выше.

Оригинал: Использование @objc - это правильный способ сделать это, даже если вы не взаимодействуете с Obj-C. Он гарантирует, что ваш протокол применяется к классу, а не к перечислению или структуре. См. "Проверка соответствия протокола" в руководстве.

  • 0
    Как уже упоминалось, это ИМО, а не ответ на вопрос. Простая программа Swift должна быть в состоянии стоять самостоятельно без привязки к NS'изму (это может означать, что больше не будет использоваться делегат, а есть какая-то другая конструктивная конструкция). Мой чистый Swift MyClass на самом деле не заботится, является ли пункт назначения структурой или объектом, и при этом мне не нужны дополнительные параметры. Может быть, они исправят это позже, это новый язык в конце концов. Возможно, что-то вроде «протокола класса XYZ», если требуется ссылочная семантика?
  • 4
    Я думаю, что стоит также отметить, что у \ @objc есть дополнительные побочные эффекты - предложение @SOXhausted для NSObjectProtocol немного лучше. С \ @objc - если делегат класса принимает аргумент объекта, такой как 'handleResult (r: MySwiftResultClass)', MySwiftResultClass теперь должен наследоваться от NSObject! И, вероятно, это больше не пространство имен и т. Д. Короче говоря: \ @objc - это функция моста, а не языковая.
Показать ещё 3 комментария
-5

Apple использует "NSObjectProtocol" вместо "class".

public protocol UIScrollViewDelegate : NSObjectProtocol {
   ...
}

Это также работает для меня и удаляет ошибки, которые я видел при попытке реализовать собственный шаблон делегата.

  • 5
    Этот вопрос не имеет отношения к этому вопросу, речь идет о создании чистого класса Swift (в частности, без NSObject), поддерживающего объект делегата. Речь идет не о реализации протоколов Objective C, что вы и делаете. Последний требует @objc или NSObjectProtocol.
  • 0
    Хорошо, но не рекомендуется.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню