Jak mogę utworzyć słabe odniesienie do protokołu w „czystym” Swift (bez @objc)

561

weakodniesienia wydają się nie działać w Swift, chyba że a protocoljest zadeklarowane jako @objc, czego nie chcę w czystej aplikacji Swift.

Ten kod podaje błąd kompilacji ( weaknie można go zastosować do typu nieklasowego MyClassDelegate):

class MyClass {
  weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate {
}

Muszę prefiksować protokół @objc, a następnie działa.

Pytanie: Jaki jest „czysty” Szybki sposób na osiągnięcie weak delegate?

hnh
źródło

Odpowiedzi:

1038

Musisz zadeklarować typ protokołu jako AnyObject.

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

class SomeClass {
    weak var delegate: ProtocolNameDelegate?
}

Używając AnyObjectpowiesz, że tylko klasy mogą być zgodne z tym protokołem, podczas gdy struktury lub wyliczenia nie.

flainez
źródło
25
Mój problem z tymi rozwiązaniami polega na tym, że wywołanie delegata powoduje awarię - EXC_BAD_ACCESS (jak zauważają inni w innym miejscu). To wydaje się być błędem. Jedynym rozwiązaniem, jakie znalazłem, jest użycie @objc i wyeliminowanie wszystkich typów danych Swift z protokołu.
Jim T
12
Jaki jest właściwy sposób wykonywania słabych delegatów w Swift? Dokumentacja Apple nie pokazuje ani nie deklaruje delegata jako słabego w swoim przykładowym kodzie: developer.apple.com/library/ios/documentation/swift/conceptual/…
C0D3
2
Nie zawsze jest to bezpieczne - pamiętaj, że musisz osłabić delegata tylko wtedy, gdy zawiera on również odniesienie do delegata i musisz przerwać ten silny cykl odniesienia. Jeśli delegat nie ma odniesienia do delegata, może wyjść poza zakres (ponieważ jest słaby), a Ty będziesz mieć awarie i inne problemy: / coś, o czym należy pamiętać.
Trev14,
5
BTW: Myślę, że „nowy styl” (Swift 5) ma zrobić protocol ProtocolNameDelegate: AnyObject, ale nie ma znaczenia.
hnh 30.04.19
1
Powinno tak być, AnyObjectponieważ classw pewnym momencie będzie przestarzałe.
José
283

Dodatkowa odpowiedź

Zawsze byłem zdezorientowany, czy delegaci powinni być słabi, czy nie. Niedawno dowiedziałem się więcej o delegatach i kiedy używać słabych referencji, dlatego dodam tutaj kilka dodatkowych punktów ze względu na przyszłych widzów.

  • Celem użycia weaksłowa kluczowego jest uniknięcie silnych cykli odniesienia (zachowanie cykli). Silne cykle odniesienia mają miejsce, gdy dwie instancje klasy mają silne odniesienia do siebie. Ich liczba referencyjna nigdy nie spada do zera, więc nigdy nie zostają zwolnione.

  • Musisz użyć tylko, weakjeśli delegat jest klasą. Szybkie struktury i wyliczenia są typami wartości (ich wartości są kopiowane podczas tworzenia nowej instancji), a nie typami referencyjnymi, więc nie tworzą silnych cykli referencyjnych .

  • weakreferencje są zawsze opcjonalne (w przeciwnym razie byś użył unowned) i zawsze używają var(nie let), aby opcjonalne można było ustawić, nilgdy zostanie zwolnione.

  • Klasa nadrzędna powinna oczywiście mieć silne odniesienie do swoich klas podrzędnych, a zatem nie powinna używać weaksłowa kluczowego. Gdy dziecko chce mieć odniesienie do swojego rodzica, powinno uczynić je słabym odniesieniem za pomocą weaksłowa kluczowego.

  • weakpowinien być używany, gdy chcesz odwołać się do klasy, której nie jesteś właścicielem, nie tylko dla dziecka odwołującego się do jego rodzica. Gdy dwie niehierarchiczne klasy muszą się nawzajem odnosić, wybierz jedną, która ma być słaba. Ten, który wybierzesz, zależy od sytuacji. Więcej informacji na ten temat znajdziesz w odpowiedziach na to pytanie .

  • Zasadniczo delegaci powinni być oznaczeni jako,weak ponieważ większość delegatów odwołuje się do klas, których nie są właścicielami. Jest to z pewnością prawda, gdy dziecko używa delegata do komunikowania się z rodzicem. Dokumentacja zaleca stosowanie słabego odniesienia dla delegata . (Ale też to zobacz .)

  • Protokoły mogą być używane zarówno dla typów referencyjnych (klas), jak i typów wartości (struktur, wyliczeń). Dlatego w prawdopodobnym przypadku, gdy musisz osłabić delegata, musisz uczynić go protokołem tylko obiektowym. Można to zrobić poprzez dodanie AnyObjectdo listy dziedziczenia protokołu. (W przeszłości robiłeś to za pomocą classsłowa kluczowego, ale AnyObjectteraz jest to preferowane ).

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

Dalsze badanie

Czytanie poniższych artykułów pomogło mi zrozumieć to znacznie lepiej. Omawiają również powiązane kwestie, takie jak unownedsłowo kluczowe i silne cykle odniesienia, które mają miejsce przy zamknięciach.

Związane z

Suragch
źródło
5
To wszystko jest miłe i interesujące, ale tak naprawdę nie ma związku z moim pierwotnym pytaniem - które nie dotyczy ani słabego / samego ARC, ani tego, dlaczego delegaci są zwykle słabi. Wiemy już o tym wszystkim i po prostu zastanawialiśmy się, jak zadeklarować słabe odwołanie do protokołu (odpowiedź doskonale odpowiedziała @flainez).
hnh
30
Masz rację. Właściwie miałem to samo pytanie, co wcześniej, ale brakowało mi wielu tych podstawowych informacji. Zrobiłem powyższe czytanie i sporządziłem dodatkowe notatki, aby pomóc sobie zrozumieć wszystkie problemy związane z twoim pytaniem. Teraz myślę, że mogę zastosować zaakceptowaną odpowiedź i wiedzieć, dlaczego to robię. Mam nadzieję, że może to również pomoże przyszłym widzom.
Suragch
5
Ale czy mogę mieć słaby protokół, który NIE zależy od typu? Sam protokół nie dba o to, jaki obiekt jest zgodny z samym sobą. Zatem zarówno klasa, jak i struktura mogą się z nią dostosować. Czy nadal można czerpać korzyści z tego, że oba są w stanie się z nią dostosować, ale tylko typy klas, które są zgodne, są słabe?
FlowUI. SimpleUITesting.com,
> ponieważ większość delegatów odwołuje się do klas, których nie mają, przepisałbym to jako: większość delegatów. W przeciwnym razie właścicielem nie będzie obiekt
Victor Jalencas,
36

AnyObject jest oficjalnym sposobem na użycie słabej referencji w Swift.

class MyClass {
    weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate: AnyObject {
}

Z Apple:

Aby uniknąć silnych cykli referencyjnych, delegaci powinni zostać zadeklarowani jako słabe referencje. Aby uzyskać więcej informacji o słabych referencjach, zobacz Silne cykle referencyjne między instancjami klas. Oznaczenie protokołu jako tylko dla klasy pozwoli później zadeklarować, że delegat musi użyć słabego odwołania. Protokół oznaczysz jako będący tylko klasą, dziedzicząc po AnyObject , jak omówiono w protokołach tylko dla klas.

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

Tim Chen
źródło
7
Ciekawy. Jest classprzestarzałe w Swift 4.1?
hnh
@hnh Nadal możesz stworzyć „pseudo-protokół”, czyniąc go klasą, ale protokół: AnyObject robi dokładnie to, o co prosi OP, z mniejszymi efektami ubocznymi niż uczynienie go klasą. (nadal nie możesz używać takiego protokołu z typami wartości, ale zadeklarowanie go jako klasy również tego nie rozwiąże)
Arru
8

Aktualizacja: wygląda na to, że instrukcja została zaktualizowana, a przykład, o którym mówiłem, został usunięty. Zobacz zmianę w odpowiedzi na @ flainez powyżej.

Oryginał: Korzystanie z @objc to właściwy sposób, nawet jeśli nie współpracujesz z Obj-C. Zapewnia, że ​​twój protokół jest stosowany do klasy, a nie do wyliczenia lub struktury. Zobacz „Sprawdzanie zgodności protokołu” w podręczniku.

William Rust
źródło
Jak wspomniano, IMO nie jest odpowiedzią na pytanie. Prosty program Swift powinien być w stanie oprzeć się bez powiązania z NS'ismem (może to oznaczać, że nie będzie już używał delegata, ale jakiś inny projekt). Moja czysta Swift MyClass nie obchodzi, czy miejscem docelowym jest struktura czy obiekt, ani też nie potrzebuję opcji. Może uda im się to naprawić później, w końcu to nowy język. Być może coś w rodzaju „protokołu klasowego XYZ”, jeśli wymagana jest semantyka odniesienia?
hnh
4
Myślę, że warto również zauważyć, że \ @objc ma dodatkowe skutki uboczne - sugestia NSObjectProtocol @eXhausted jest nieco lepsza. Z \ @objc - jeśli delegat klasy pobiera argument obiektowy, taki jak „handleResult (r: MySwiftResultClass)”, MySwiftResultClass musi teraz dziedziczyć po NSObject! I prawdopodobnie nie jest to już przestrzeń nazw itp. Krótko mówiąc: \ @objc jest funkcją pomostową, a nie językową.
hnh
Myślę, że rozwiązali to. Teraz piszesz: protokół MyClassDelegate: class {}
user3675131
Gdzie jest dokumentacja na ten temat? Albo jestem ślepy, albo robię coś złego, ponieważ nie mogę znaleźć żadnych informacji na ten temat ... O_O
BastiBen
Nie jestem pewien, czy odpowiada na pytanie PO, czy nie, ale jest to pomocne, zwłaszcza jeśli współpracujesz z Objc-C;)
Dan Rosenstark,
-1

protokół musi być podklasą AnyObject, klasa

przykład podany poniżej

    protocol NameOfProtocol: class {
   // member of protocol
    }
   class ClassName: UIViewController {
      weak var delegate: NameOfProtocol? 
    }
Rakesh Kunwar
źródło
-9

Apple używa „NSObjectProtocol” zamiast „class”.

public protocol UIScrollViewDelegate : NSObjectProtocol {
   ...
}

Działa to również dla mnie i usunąłem błędy, które widziałem podczas próby wdrożenia własnego wzorca delegowania.

Michael Rose
źródło
5
Nie dotyczy pytania, to pytanie dotyczy zbudowania czystej klasy Swift (konkretnie bez obiektu NSObject) obsługującego obiekt delegowany. Nie chodzi o wdrażanie protokołów Objective-C, co robisz. Ten ostatni wymaga @objc aka NSObjectProtocol.
hnh 19.04.16
OK, ale nie polecam.
DawnSong,