Bagaimana saya bisa membuat referensi protokol yang lemah di Swift 'murni' (tanpa @objc)

561

weakreferensi tampaknya tidak berfungsi di Swift kecuali protocoldinyatakan sebagai @objc, yang saya tidak inginkan dalam aplikasi Swift murni.

Kode ini memberikan kesalahan kompilasi ( weaktidak dapat diterapkan ke tipe non-kelas MyClassDelegate):

class MyClass {
  weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate {
}

Saya perlu awalan protokol dengan @objc, lalu berfungsi.

Pertanyaan: Apa cara Swift 'murni' untuk mencapai a weak delegate?

hnh
sumber
perhatikan ... stackoverflow.com/a/60837041/294884
Fattie

Jawaban:

1038

Anda perlu mendeklarasikan jenis protokol sebagai AnyObject.

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

class SomeClass {
    weak var delegate: ProtocolNameDelegate?
}

Menggunakan AnyObjectAnda mengatakan bahwa hanya kelas yang dapat memenuhi protokol ini, sedangkan struct atau enum tidak bisa.

flainez
sumber
25
Masalah saya dengan solusi ini adalah bahwa memanggil delegasi menyebabkan crash - EXC_BAD_ACCESS (seperti yang dicatat oleh orang lain di tempat lain). Ini sepertinya bug. Satu-satunya solusi yang saya temukan adalah menggunakan @objc dan menghilangkan semua tipe data Swift dari protokol.
Jim T
12
Apa cara yang benar untuk melakukan delegasi yang lemah sekarang di Swift? Dokumentasi Apple tidak menunjukkan atau menyatakan delegasi sebagai lemah dalam kode contoh mereka: developer.apple.com/library/ios/documentation/swift/conceptual/…
C0D3
2
Ini tidak selalu aman - ingat bahwa Anda hanya perlu membuat delegasi lemah jika ia juga memegang referensi ke delegator & Anda perlu memutus siklus referensi yang kuat. Jika delegasi tidak memiliki referensi kepada delegator, delegasi dapat keluar dari ruang lingkup (karena lemah) dan Anda akan memiliki crash dan masalah lain: / sesuatu yang perlu diingat.
Trev14
5
BTW: Saya pikir "gaya baru" (Swift 5) harus dilakukan protocol ProtocolNameDelegate: AnyObject, tetapi tidak masalah.
hnh
1
Itu harus AnyObjectsejak classakan ditinggalkan pada beberapa titik.
José
283

Jawaban Tambahan

Saya selalu bingung tentang apakah delegasi harus lemah atau tidak. Baru-baru ini saya telah belajar lebih banyak tentang delegasi dan kapan harus menggunakan referensi yang lemah, jadi izinkan saya menambahkan beberapa poin tambahan di sini demi pemirsa di masa depan.

  • Tujuan menggunakan weakkata kunci adalah untuk menghindari siklus referensi yang kuat (mempertahankan siklus). Siklus referensi yang kuat terjadi ketika dua instance kelas memiliki referensi kuat satu sama lain. Jumlah referensi mereka tidak pernah menjadi nol sehingga mereka tidak pernah dibatalkan alokasi.

  • Anda hanya perlu menggunakan weakjika delegasi adalah kelas. Struct cepat dan enum adalah tipe nilai (nilainya disalin ketika instance baru dibuat), bukan tipe referensi, sehingga mereka tidak membuat siklus referensi yang kuat .

  • weakreferensi selalu opsional (jika tidak, Anda akan menggunakan unowned) dan selalu menggunakan var(tidak let) sehingga opsional dapat diatur ke nilsaat itu dialokasikan.

  • Kelas induk harus secara alami memiliki referensi yang kuat untuk kelas anaknya dan karenanya tidak menggunakan weakkata kunci. Ketika seorang anak ingin referensi ke orang tuanya, itu harus membuatnya menjadi referensi yang lemah dengan menggunakan weakkata kunci.

  • weakharus digunakan ketika Anda ingin referensi ke kelas yang tidak Anda miliki, bukan hanya untuk anak yang mereferensikan orang tuanya. Ketika dua kelas non-hirarkis perlu saling referensi, pilih satu untuk menjadi lemah. Yang Anda pilih tergantung pada situasinya. Lihat jawaban untuk pertanyaan ini untuk lebih lanjut tentang ini.

  • Sebagai aturan umum, delegasi harus ditandaiweak karena kebanyakan delegasi merujuk kelas yang tidak mereka miliki. Ini benar ketika seorang anak menggunakan delegasi untuk berkomunikasi dengan orang tua. Menggunakan referensi yang lemah untuk delegasi adalah apa yang direkomendasikan oleh dokumentasi . (Tapi lihat ini juga.)

  • Protokol dapat digunakan untuk tipe referensi (kelas) dan tipe nilai (struct, enums). Jadi, jika Anda perlu membuat delegasi lemah, Anda harus menjadikannya hanya protokol objek. Cara untuk melakukannya adalah dengan menambahkan AnyObjectke daftar warisan protokol. (Di masa lalu Anda melakukan ini menggunakan classkata kunci, tetapi AnyObjectlebih disukai sekarang .)

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

Pelajaran lanjutan

Membaca artikel-artikel berikut adalah yang membantu saya untuk memahami hal ini dengan lebih baik. Mereka juga membahas masalah terkait seperti unownedkata kunci dan siklus referensi kuat yang terjadi dengan penutupan.

Terkait

Suragch
sumber
5
Ini semua bagus dan menarik, tetapi tidak benar-benar terkait dengan pertanyaan awal saya - yang bukan tentang lemah / ARC itu sendiri atau tentang mengapa delegasi biasanya lemah. Kami sudah tahu tentang semua itu dan hanya ingin tahu bagaimana Anda dapat mendeklarasikan referensi protokol yang lemah (dijawab dengan sangat baik oleh @ flainez).
hnh
30
Kamu benar. Saya sebenarnya memiliki pertanyaan yang sama dengan Anda sebelumnya, tetapi saya kehilangan banyak informasi latar belakang ini. Saya melakukan bacaan di atas dan membuat catatan tambahan untuk membantu diri saya memahami semua masalah yang terkait dengan pertanyaan Anda. Sekarang saya pikir saya bisa menerapkan jawaban yang Anda terima dan tahu mengapa saya melakukannya. Saya berharap mungkin ini akan membantu pemirsa di masa depan juga.
Suragch
5
Tetapi bisakah saya memiliki protokol yang lemah yang TIDAK tergantung pada tipenya? Protokol sendiri tidak peduli objek apa yang sesuai dengan dirinya sendiri. Jadi baik kelas, atau struct dapat menyesuaikannya. Apakah mungkin untuk tetap memiliki manfaat karena keduanya dapat menyesuaikan diri, tetapi hanya memiliki tipe kelas yang menyesuaikan lemah?
FlowUI. SimpleUITesting.com
> karena sebagian besar delegasi merujuk kelas yang tidak mereka miliki, saya akan menulis ulang ini sebagai: kebanyakan delegasi. Kalau tidak, objek yang tidak dimiliki menjadi pemilik
Victor Jalencas
36

AnyObject adalah cara resmi untuk menggunakan referensi yang lemah di Swift.

class MyClass {
    weak var delegate: MyClassDelegate?
}

protocol MyClassDelegate: AnyObject {
}

Dari Apple:

Untuk mencegah siklus referensi yang kuat, delegasi harus dinyatakan sebagai referensi yang lemah. Untuk informasi lebih lanjut tentang referensi lemah, lihat Siklus Referensi Kuat Antara Instance Kelas. Menandai protokol sebagai kelas saja nantinya akan memungkinkan Anda untuk menyatakan bahwa delegasi harus menggunakan referensi yang lemah. Anda menandai protokol sebagai kelas saja dengan mewarisi dari AnyObject , seperti yang dibahas dalam Protokol Kelas Saja.

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

Tim Chen
sumber
7
Menarik. Apakah classusang dalam Swift 4.1?
hnh
@ hnh Anda masih dapat membuat "pseudo-protocol" dengan menjadikannya kelas, tetapi protokol: AnyObject melakukan persis apa yang diminta OP dengan efek samping yang lebih sedikit daripada menjadikannya kelas. (Anda masih tidak dapat menggunakan protokol semacam itu dengan tipe nilai, tetapi mendeklarasikannya sebagai kelas tidak akan menyelesaikannya juga)
Arru
8

Pembaruan: Sepertinya manual telah diperbarui dan contoh yang saya maksud telah dihapus. Lihat jawaban edit ke @ flainez di atas.

Asli: Menggunakan @objc adalah cara yang tepat untuk melakukannya bahkan jika Anda tidak beroperasi dengan Obj-C. Ini memastikan bahwa protokol Anda diterapkan ke kelas dan bukan enum atau struct. Lihat "Memeriksa Kesesuaian Protokol" dalam manual.

William Rust
sumber
Seperti disebutkan ini IMO bukan jawaban untuk pertanyaan. Program Swift yang sederhana harus dapat berdiri sendiri tanpa terikat dengan NS'ism (ini mungkin menyiratkan tidak menggunakan delegasi lagi tetapi beberapa konstruksi desain lainnya). MyClass Swift murni saya sebenarnya tidak peduli apakah tujuannya adalah struct atau objek, saya juga tidak perlu opsional. Mungkin mereka bisa memperbaikinya nanti, itu bahasa baru. Mungkin sesuatu seperti 'protokol kelas XYZ' jika semantik referensi diperlukan?
hnh
4
Saya pikir patut juga dicatat bahwa \ @objc memiliki efek samping tambahan - saran NSObjectProtocol dari @eXhausted sedikit lebih baik. Dengan \ @objc - jika delegasi kelas mengambil argumen objek, seperti 'handleResult (r: MySwiftResultClass)', MySwiftResultClass sekarang perlu diwarisi dari NSObject! Dan mungkin itu bukan namespace lagi, dll. Singkatnya: \ @objc adalah fitur penghubung, bukan bahasa.
hnh
Saya pikir mereka telah menyelesaikan ini. Anda sekarang menulis: protokol MyClassDelegate: class {}
user3675131
Di mana dokumentasi tentang ini? Entah saya buta atau melakukan sesuatu yang salah, karena saya tidak dapat menemukan informasi tentang ini ... O_O
BastiBen
Saya tidak yakin apakah itu menjawab pertanyaan OP atau tidak, tetapi ini sangat membantu terutama jika Anda beroperasi dengan Objc-C;)
Dan Rosenstark
-1

protokol harus subkelas dari AnyObject, kelas

contoh yang diberikan di bawah ini

    protocol NameOfProtocol: class {
   // member of protocol
    }
   class ClassName: UIViewController {
      weak var delegate: NameOfProtocol? 
    }
Rakesh Kunwar
sumber
-9

Apple menggunakan "NSObjectProtocol" alih-alih "kelas".

public protocol UIScrollViewDelegate : NSObjectProtocol {
   ...
}

Ini juga berfungsi untuk saya dan menghapus kesalahan yang saya lihat ketika mencoba menerapkan pola delegasi saya sendiri.

Michael Rose
sumber
5
Tidak relevan untuk pertanyaan, pertanyaan ini adalah tentang membangun kelas Swift murni (khususnya tanpa NSObject) yang mendukung objek delegasi. Ini bukan tentang mengimplementasikan protokol Objective-C, yang Anda lakukan. Yang terakhir membutuhkan @objc alias NSObjectProtocol.
hnh
OK, tapi tidak direkomendasikan.
DawnSong