Як я можу зробити слабку посилання на протокол у "чистому" Swift (без @objc)


561

weakпосилання, схоже, не працює в Swift, якщо а protocolне оголошено як @objc, чого я не хочу в чистому додатку Swift.

Цей код дає помилку компіляції ( weakне може бути застосований до некласового типу MyClassDelegate):

class MyClass {
  weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate {
}

Мені потрібно профіксувати протокол @objc, потім він працює.

Запитання: Який "чистий" спосіб швидкого досягнення weak delegate?


Зверніть увагу ... stackoverflow.com/a/60837041/294884
Fattie

Відповіді:


1038

Вам потрібно оголосити тип протоколу як AnyObject.

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

class SomeClass {
    weak var delegate: ProtocolNameDelegate?
}

Використовуючи, AnyObjectви говорите, що цьому класу можуть відповідати лише класи, тоді як структури або перерахунки не можуть.


25
Моя проблема з цим рішенням полягає в тому, що виклик делегата викликає збій - EXC_BAD_ACCESS (як зазначають інші в інших місцях). Це, здається, помилка. Єдине знайдене нами рішення - використовувати @objc та усунути всі протоколи даних Swift з протоколу.
Джим Т

12
Який правильний спосіб робити слабких делегатів зараз у Свіфті? Документація Apple не показує або оголошує делегата слабким у своєму прикладі коду: developer.apple.com/library/ios/documentation/swift/conceptual/…
C0D3,

2
Це не завжди безпечно - пам’ятайте, що вам потрібно зробити делегата слабким лише тоді, коли він також містить посилання на делегатора і вам потрібно перервати цей сильний еталонний цикл. Якщо делегат не має посилання на делегатора, делегат може вийти за межі сфери (оскільки він слабкий), і у вас виникнуть збої та інші проблеми: / що потрібно пам’ятати.
Трев14,

5
BTW: Я думаю, що "новий стиль" (Swift 5) має робити protocol ProtocolNameDelegate: AnyObject, але це не має значення.
hnh

1
Це має бути, AnyObjectоскільки classв якийсь момент буде застарілим.
Жозе

283

Додатковий відповідь

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

  • Метою використання 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ключове слово, і чіткі еталонні цикли, що відбуваються із закриттям.

Пов'язані


5
Це все приємно і цікаво, але насправді не пов'язане з моїм початковим запитанням - яке не стосується ні слабких / ARC, ні того, чому делегати зазвичай слабкі. Ми вже про все це знаємо і просто цікавились, як ви можете оголосити слабку посилання на протокол (чудово відповів @flainez).
hnh

30
Ти правий. У мене насправді було те саме запитання, що і у вас раніше, але мені не вистачало багато цієї довідкової інформації. Я прочитав вищезгадане читання та зробив додаткові записки, щоб допомогти зрозуміти всі питання, пов’язані з вашим запитанням. Тепер я думаю, що можу застосувати вашу прийняту відповідь і знати, чому я це роблю. Сподіваюся, можливо, це допоможе і майбутнім глядачам.
Сурагч

5
Але чи можу я мати слабкий протокол, який НЕ залежить від типу? Протокол сам по собі не хвилює, який об'єкт відповідає собі. Тож і клас, або структура може йому відповідати. Чи можливо все-таки мати вигоду від того, щоб вони могли відповідати їй, але лише типи класів, які відповідають, бути слабкими?
FlowUI. SimpleUITesting.com

> оскільки більшість делегатів посилаються на класи, якими вони не володіють, я б переписав це як: більшість делегаторів. Інакше власник об'єкта стає власником
Віктор Яленкас

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 робить саме те, про що вимагає ОП, з меншими побічними ефектами, ніж робить його класом. (ви все одно не можете використовувати такий протокол із типами значень, але оголосити його класом це теж не вирішить)
Arru

8

Оновлення: схоже, керівництво оновлено, і приклад, про який я посилався, був видалений. Дивіться відповідь правки до @ flainez вище.

Оригінал: Використання @objc - це правильний спосіб зробити це, навіть якщо ви не взаємодієте з Obj-C. Це гарантує, що ваш протокол застосовується до класу, а не до enum або структура. Див. "Перевірка відповідності протоколу" в посібнику.


Як уже згадувалося, ІМО не є відповіддю на питання. Проста програма Swift повинна мати можливість стояти на власній прив'язці до NS'ism (це може означати, що більше не використовується делегат, а інша конструкція дизайну). Мій чистий Swift MyClass насправді не байдуже, призначення є структурою чи об’єктом, а також мені не потрібні додаткові опції. Можливо, вони зможуть виправити це пізніше, це все-таки нова мова. Можливо, щось на кшталт "клас протоколу XYZ", якщо потрібна довідкова семантика?
hnh

4
Я думаю, що також варто відзначити, що \ @objc має додаткові побічні ефекти - пропозиція NSObjectProtocol від @eXhausted трохи краще. З \ @objc - якщо делегат класу бере аргумент об'єкта, наприклад, 'handleResult (r: MySwiftResultClass)', MySwiftResultClass тепер повинен успадкувати від NSObject! І, мабуть, це вже не простір імен, і т. Д. Коротше кажучи: \ @objc є мостовою функцією, а не мовою.
hnh

Я думаю, що вони це вирішили. Ви зараз пишете: протокол MyClassDelegate: class {}
user3675131

Де документація на це? Або я сліпий, або роблю щось не так, тому що не можу знайти про це жодної інформації ...
O_O

Я не впевнений, чи відповідає він на запитання ОП, чи ні, але це корисно, особливо якщо ви співпрацюєте з Objc-C;)
Dan Rosenstark

-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 aka NSObjectProtocol.
hnh

Гаразд, але не рекомендується.
Світанок
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.