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

562

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

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

class MyClass {
  weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate {
}

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

Вопрос: Что такое «чистый» способ Свифта weak delegate?

HNH
источник
делать заметки ... stackoverflow.com/a/60837041/294884
Толстяк

Ответы:

1041

Вам необходимо объявить тип протокола как AnyObject.

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

class SomeClass {
    weak var delegate: ProtocolNameDelegate?
}

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

flainez
источник
25
Моя проблема с этими решениями состоит в том, что вызов делегата вызывает сбой - EXC_BAD_ACCESS (как отмечено другими в другом месте). Это похоже на ошибку. Единственное решение, которое я нашел, это использовать @objc и исключить из протокола все типы данных Swift.
Джим Т
12
Как правильно делать слабых делегатов сейчас в Swift? Документация Apple не показывает и не объявляет делегата слабым в своем примере кода: developer.apple.com/library/ios/documentation/swift/conceptual/…
C0D3
2
Это не всегда безопасно - помните, что вам нужно сделать делегата слабым, только если он также содержит ссылку на делегата, и вам нужно разорвать этот цикл сильных ссылок. Если делегат не имеет ссылки на делегат, он может выйти из области видимости (потому что он слабый), и у вас будут сбои и другие проблемы: / что-то, что нужно иметь в виду.
Trev14
5
Кстати, я думаю, что «новый стиль» (Swift 5) должен делать protocol ProtocolNameDelegate: AnyObject, но это не имеет значения.
HNH
1
Так и должно быть, AnyObjectпотому classчто в какой-то момент будет устаревшим.
Хосе
284

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

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

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

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

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

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

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

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

  • Протоколы могут использоваться как для ссылочных типов (классов), так и для типов значений (структуры, перечисления). Таким образом, в вероятном случае, когда вам нужно сделать делегата слабым, вы должны сделать его протоколом только для объекта. Способ сделать это - добавить AnyObjectв список наследования протокола. (В прошлом вы делали это с помощью classключевого слова, но AnyObjectсейчас это предпочтение .)

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

Дальнейшее изучение

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

связанные с

Suragch
источник
5
Это все приятно и интересно, но на самом деле это не связано с моим первоначальным вопросом, который касается ни самого слабого / ARC, ни того, почему делегаты обычно являются слабыми. Мы уже знаем обо всем этом и просто удивляемся, как можно объявить слабую ссылку на протокол (отлично ответил @flainez).
HNH
30
Вы правы. У меня на самом деле был тот же вопрос, что и у вас ранее, но я скучал по этой основной информации. Я прочитал выше и сделал дополнительные заметки, чтобы помочь себе понять все вопросы, связанные с вашим вопросом. Теперь я думаю, что могу применить ваш принятый ответ и знать, почему я это делаю. Надеюсь, возможно, это поможет и будущим зрителям.
Сурагч
5
Но могу ли я иметь слабый протокол, который не зависит от типа? Протокол сам по себе не заботится о том, какой объект соответствует самому себе. Таким образом, класс или структура могут соответствовать ему. Можно ли по-прежнему иметь преимущество от того, что оба способны соответствовать ему, но иметь только слабые типы классов, которые соответствуют?
FlowUI. SimpleUITesting.com
> Поскольку большинство делегатов ссылаются на классы, которые им не принадлежат, я бы переписал это как: большинство делегаторов. В противном случае не принадлежащий объекту становится владельцем
Виктор Jalencas
36

AnyObject это официальный способ использовать слабую ссылку в Swift.

class MyClass {
    weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate: AnyObject {
}

От Apple:

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

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Protocols.html#//apple_ref/doc/uid/TP40014097-CH25-ID276

Тим Чен
источник
7
Интересно. Не classрекомендуется в Swift 4.1?
HNH
@hnh Вы все еще можете создать «псевдопротокол», сделав его классом, но протокол: AnyObject делает именно то, что запрашивает OP, с меньшим количеством побочных эффектов, чем создание класса. (вы все еще не можете использовать такой протокол с типами значений, но объявление его классом тоже не решит)
Arru
8

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

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

Уильям Руст
источник
Как уже упоминалось, это ИМО, а не ответ на вопрос. Простая программа Swift должна быть в состоянии стоять сама по себе без привязки к NS'изму (это может означать, что больше не будет использоваться делегат, а будет какая-то другая конструкция). Мой чистый Swift MyClass на самом деле не заботится, является ли пункт назначения структурой или объектом, и при этом мне не нужны дополнительные параметры. Может быть, они исправят это позже, это новый язык в конце концов. Возможно, что-то вроде «протокола класса XYZ», если требуется ссылочная семантика?
hnh
4
Я думаю, что стоит также отметить, что у \ @objc есть дополнительные побочные эффекты - предложение @SOXhausted для NSObjectProtocol немного лучше. С \ @objc - если делегат класса принимает аргумент объекта, такой как 'handleResult (r: MySwiftResultClass)', MySwiftResultClass теперь должен наследоваться от NSObject! И, вероятно, это больше не пространство имен и т. Д. Короче говоря: \ @objc - это функция моста, а не языковая.
hnh
Я думаю, что они решили это. Вы сейчас пишете: протокол MyClassDelegate: класс {}
user3675131
Где документация по этому вопросу? Либо я слепой, либо делаю что-то не так, потому что не могу найти никакой информации об этом ... O_O
BastiBen
Я не уверен, отвечает ли он на вопрос ОП или нет, но это полезно, особенно если вы взаимодействуете с Objc-C;)
Дэн Розенстарк
-1

протокол должен быть подклассом AnyObject, класса

пример приведен ниже

    protocol NameOfProtocol: class {
   // member of protocol
    }
   class ClassName: UIViewController {
      weak var delegate: NameOfProtocol? 
    }
Ракеш Кунвар
источник
-9

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

public protocol UIScrollViewDelegate : NSObjectProtocol {
   ...
}

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

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