Haruskah kita selalu menggunakan [tidak dimiliki diri] di dalam penutupan di Swift


467

Dalam WWDC 2014 sesi 403 Intermediate Swift dan transkrip , ada slide berikut

masukkan deskripsi gambar di sini

Pembicara mengatakan dalam kasus itu, jika kita tidak menggunakannya di [unowned self]sana, itu akan menjadi kebocoran memori. Apakah itu berarti kita harus selalu menggunakan [unowned self]penutupan di dalam?

Pada baris 64 dari ViewController.swift dari aplikasi Swift Weather , saya tidak menggunakan [unowned self]. Tapi saya memperbarui UI dengan menggunakan beberapa @IBOutlets seperti self.temperaturedan self.loadingIndicator. Mungkin OK karena semua @IBOutletyang saya definisikan adalah weak. Tetapi untuk keamanan, haruskah kita selalu menggunakan [unowned self]?

class TempNotifier {
  var onChange: (Int) -> Void = {_ in }
  var currentTemp = 72
  init() {
    onChange = { [unowned self] temp in
      self.currentTemp = temp
    }
  }
}

tautan gambar terputus
Daniel Gomez Rico

@ DanielG.R. Terima kasih, saya bisa melihatnya. i.stack.imgur.com/Jd9Co.png
Jake Lin

2
Kecuali saya salah, contoh yang diberikan dalam salindia salah — onChangeharusnya merupakan [weak self]penutupan, karena itu properti publik (internal, tetapi masih), sehingga objek lain bisa mendapatkan dan menyimpan penutupan, menjaga objek TempNotifier tetap ada (tanpa batas waktu jika objek menggunakan tidak melepaskan onChangepenutupan sampai ia melihat TempNotifierhilang, melalui referensi lemah sendiri ke TempNotifier) . Jika var onChange …dulu private var onChange …maka [unowned self]akan benar. Saya tidak 100% yakin akan hal ini; tolong koreksi seseorang jika saya salah.
Slipp D. Thompson

@Jake Lin `var onChange: (Int) -> Void = {}` apakah kurung kurawal mewakili penutupan kosong? sama seperti dalam mendefinisikan array kosong dengan []? Saya tidak dapat menemukan penjelasan di dokumen Apple.
bibscy

@bibscy ya, {}adalah closure kosong (instance dari closure) sebagai default (tidak melakukan apa-apa), (Int) -> Voidadalah definisi closure.
Jake Lin

Jawaban:


871

Tidak, pasti ada saat di mana Anda tidak ingin menggunakannya [unowned self]. Kadang-kadang Anda ingin penutup untuk menangkap diri untuk memastikan bahwa itu masih ada pada saat penutupan dipanggil.

Contoh: Membuat permintaan jaringan asinkron

Jika Anda membuat permintaan jaringan asynchronous Anda lakukan ingin penutupan untuk mempertahankan selfketika selesai permintaan. Objek itu mungkin telah dibatalkan alokasi tetapi Anda masih ingin dapat menangani penyelesaian permintaan.

Kapan harus menggunakan unowned selfatauweak self

Satu-satunya waktu di mana Anda benar-benar ingin menggunakan [unowned self]atau [weak self]ketika Anda akan membuat siklus referensi yang kuat . Siklus referensi yang kuat adalah ketika ada lingkaran kepemilikan di mana objek akhirnya memiliki satu sama lain (mungkin melalui pihak ketiga) dan karena itu mereka tidak akan pernah dapat dialokasikan kembali karena mereka berdua memastikan bahwa satu sama lain tetap ada.

Dalam kasus khusus penutupan, Anda hanya perlu menyadari bahwa variabel apa pun yang dirujuk di dalamnya, akan "dimiliki" oleh penutupan. Selama penutupannya ada, benda-benda itu dijamin ada. Satu-satunya cara untuk menghentikan kepemilikan itu, adalah dengan melakukan [unowned self]atau [weak self]. Jadi jika kelas memiliki penutupan, dan penutupan itu menangkap referensi yang kuat untuk kelas itu, maka Anda memiliki siklus referensi yang kuat antara penutupan dan kelas. Ini juga termasuk jika kelas memiliki sesuatu yang memiliki penutupan.

Khususnya dalam contoh dari video

Dalam contoh pada slide, TempNotifiermemiliki penutupan melalui onChangevariabel anggota. Jika mereka tidak menyatakan selfsebagai unowned, penutupan juga akan memiliki selfsiklus referensi yang kuat.

Perbedaan antara unowneddanweak

Perbedaan antara unowneddan weakadalah yang weakdinyatakan sebagai Opsional sementara unownedtidak. Dengan mendeklarasikannya weakAnda bisa menangani kasing yang mungkin ada di dalam penutupan di beberapa titik. Jika Anda mencoba mengakses unownedvariabel yang nihil, itu akan merusak seluruh program. Jadi hanya gunakan unownedketika Anda positif bahwa variabel akan selalu ada saat penutupan ada


1
Hai. Jawaban yang bagus Saya berjuang untuk memahami diri yang tidak dimiliki. Alasan untuk menggunakan lemahSelf hanya menjadi 'diri menjadi opsional', tidak cukup bagi saya. Mengapa saya secara khusus ingin menggunakan stackoverflow.com/questions/32936264/…

19
@robdashnash, Keuntungan menggunakan diri yang tidak dimiliki adalah Anda tidak perlu membuka opsi yang bisa menjadi kode yang tidak perlu jika Anda tahu pasti dengan desain, bahwa itu tidak akan pernah menjadi nol. Pada akhirnya, diri yang tidak dimiliki digunakan untuk singkatnya dan mungkin juga sebagai petunjuk bagi pengembang masa depan bahwa Anda tidak pernah mengharapkan nilai nol.
drewag

77
Kasus untuk digunakan [weak self]dalam permintaan jaringan yang tidak sinkron, ada di pengontrol tampilan tempat permintaan itu digunakan untuk mengisi tampilan. Jika pengguna mundur, kita tidak perlu lagi mengisi tampilan, kita juga tidak perlu referensi ke pengontrol tampilan.
David James

1
weakreferensi juga diatur ke nilsaat objek dialokasikan. unownedreferensi tidak.
BergQuester

1
Saya sedikit bingung. unowneddigunakan untuk non-Optionalsementara weakdigunakan untuk Optionaljadi kami selfadalah Optionalatau non-optional?
Muhammad Nayab

193

Perbarui 11/2016

Saya menulis artikel tentang memperluas jawaban ini (melihat ke SIL untuk memahami apa yang dilakukan ARC), periksa di sini .

Jawaban asli

Jawaban sebelumnya tidak benar-benar memberikan aturan langsung tentang kapan harus menggunakan satu di atas yang lain dan mengapa, jadi izinkan saya menambahkan beberapa hal.

Diskusi yang tidak dimiliki atau lemah bermuara pada pertanyaan tentang masa pakai variabel dan penutupan yang merujuknya.

cepat lemah vs tidak dimiliki

Skenario

Anda dapat memiliki dua skenario yang mungkin:

  1. Penutupan memiliki masa pakai variabel yang sama, sehingga penutupan hanya dapat dicapai sampai variabel tercapai . Variabel dan penutupan memiliki umur yang sama. Dalam hal ini Anda harus menyatakan referensi sebagai tidak dimiliki . Contoh umum adalah yang [unowned self]digunakan dalam banyak contoh penutupan kecil yang melakukan sesuatu dalam konteks orang tua mereka dan bahwa tidak dirujuk di tempat lain tidak hidup lebih lama dari orang tua mereka.

  2. Masa penutupan adalah independen dari salah satu variabel, penutupan masih bisa direferensikan ketika variabel tidak dapat dijangkau lagi. Dalam hal ini Anda harus mendeklarasikan referensi sebagai lemah dan memverifikasi itu tidak nol sebelum menggunakannya (jangan paksa membuka). Contoh umum dari ini adalah [weak delegate]Anda dapat melihat dalam beberapa contoh penutupan referensi objek delegasi (seumur hidup) yang sama sekali tidak terkait.

Penggunaan Aktual

Jadi, mana yang akan / harus Anda benar-benar gunakan sebagian besar waktu?

Mengutip Joe Groff dari twitter :

Tidak dimiliki lebih cepat dan memungkinkan untuk kekekalan dan non-opsional.

Jika Anda tidak perlu lemah, jangan gunakan itu.

Anda akan menemukan lebih banyak tentang *pekerjaan batin yang tidak dimiliki di sini .

* Biasanya juga disebut sebagai tidak dimiliki (aman) untuk menunjukkan bahwa pemeriksaan runtime (yang menyebabkan crash untuk referensi yang tidak valid) dilakukan sebelum mengakses referensi yang tidak dimiliki.


26
Saya lelah mendengar penjelasan burung beo "gunakan minggu jika diri bisa nol, gunakan tidak dimiliki ketika tidak pernah bisa nol". Oke, kami mengerti - dengar jutaan kali! Jawaban ini sebenarnya menggali lebih dalam tentang kapan diri dapat nihil dalam bahasa Inggris biasa, yang langsung menjawab pertanyaan OP. Terima kasih atas penjelasan yang luar biasa ini !!
TruMan1

Terima kasih @ TruMan1, saya sebenarnya menulis posting tentang ini yang akan segera berakhir di blog saya, akan memperbarui jawabannya dengan tautan.
Umberto Raimondi

1
Jawaban yang bagus, sangat praktis. Saya terinspirasi untuk mengalihkan beberapa vars lemah sensitif kinerja saya ke yang tidak dimiliki sekarang.
original_username

"Masa penutupan adalah independen dari salah satu variabel" Apakah Anda memiliki kesalahan ketik di sini?
Sayang

1
Jika penutupan selalu memiliki masa hidup yang sama dengan objek induk, bukankah jumlah referensi akan diatasi ketika objek dihancurkan? Mengapa Anda tidak bisa hanya menggunakan 'diri' dalam situasi ini alih-alih mengganggu yang tidak dimiliki atau lemah?
LegendLength

105

Saya pikir saya akan menambahkan beberapa contoh nyata khusus untuk pengontrol tampilan. Banyak penjelasan, tidak hanya di sini di Stack Overflow, benar-benar bagus, tapi saya bekerja lebih baik dengan contoh dunia nyata (@drewag memiliki awal yang baik tentang ini):

  • Jika Anda memiliki penutupan untuk menangani respons dari permintaan penggunaan jaringan weak, karena mereka berumur panjang. Pengontrol tampilan bisa menutup sebelum permintaan selesai sehingga selftidak lagi menunjuk ke objek yang valid ketika penutupan dipanggil.
  • Jika Anda memiliki penutupan yang menangani acara pada tombol. Ini bisa terjadi unownedkarena begitu pengontrol tampilan hilang, tombol dan item lain yang selfdirujuknya hilang pada saat yang sama. Blok penutup juga akan hilang pada saat yang sama.

    class MyViewController: UIViewController {
          @IBOutlet weak var myButton: UIButton!
          let networkManager = NetworkManager()
          let buttonPressClosure: () -> Void // closure must be held in this class. 
    
          override func viewDidLoad() {
              // use unowned here
              buttonPressClosure = { [unowned self] in
                  self.changeDisplayViewMode() // won't happen after vc closes. 
              }
              // use weak here
              networkManager.fetch(query: query) { [weak self] (results, error) in
                  self?.updateUI() // could be called any time after vc closes
              }
          }
          @IBAction func buttonPress(self: Any) {
             buttonPressClosure()
          }
    
          // rest of class below.
     }

17
Yang ini membutuhkan lebih banyak upvotes. Dua contoh solid yang menunjukkan bagaimana penutupan tombol tidak akan ada di luar umur pengontrol tampilan, dan karenanya dapat menggunakan yang tidak dimiliki, tetapi sebagian besar panggilan jaringan yang memperbarui UI harus lemah.
Tim Fuqua

2
Jadi hanya untuk memperjelas, apakah kita selalu menggunakan yang tidak dimiliki atau lemah ketika memanggil diri di blok penutup? Atau adakah saat di mana kita tidak akan menyebut lemah / tidak dimiliki? Jika demikian, dapatkah Anda memberikan contoh untuk itu juga?
Lukas

Terima kasih banyak.
Shawn Baek

1
Ini memberi saya pemahaman yang lebih dalam tentang [diri lemah] dan [diri tidak dimiliki] Terima kasih banyak @ pos!
Tommy

Ini bagus. bagaimana jika saya memiliki animasi yang didasarkan pada interaksi pengguna, tetapi perlu waktu untuk menyelesaikannya. Dan kemudian pengguna pindah ke viewController lain. Saya kira dalam hal ini saya masih harus menggunakan weakdaripada unownedbenar?
Sayang


50

Berikut ini adalah kutipan brilian dari Forum Pengembang Apple yang menjelaskan detail lezat:

unownedvs unowned(safe)vsunowned(unsafe)

unowned(safe)adalah referensi tidak memiliki yang menegaskan akses bahwa objek tersebut masih hidup. Ini semacam referensi opsional yang lemah yang secara implisit terbuka dengan x!setiap kali diakses. unowned(unsafe)seperti __unsafe_unretaineddi ARC — ini adalah referensi yang tidak memiliki, tetapi tidak ada pemeriksaan runtime bahwa objek tersebut masih hidup pada akses, sehingga referensi yang menggantung akan mencapai ke memori sampah. unownedselalu merupakan sinonim untuk unowned(safe)saat ini, tetapi tujuannya adalah bahwa itu akan dioptimalkan untuk unowned(unsafe)dalam -Ofast membangun ketika pemeriksaan runtime dinonaktifkan.

unowned vs. weak

unownedsebenarnya menggunakan implementasi yang jauh lebih sederhana daripada weak. Asli Swift benda membawa dua tuduhan referensi, dan unowned referensi benjolan unowned jumlah referensi bukan yang kuat jumlah referensi . Objek dideinisialisasi ketika jumlah referensi yang kuat mencapai nol, tetapi sebenarnya tidak deallocated sampai jumlah referensi yang tidak dimiliki juga mencapai nol. Hal ini menyebabkan memori dipegang sedikit lebih lama ketika ada referensi yang tidak dimiliki, tetapi itu biasanya tidak menjadi masalah ketikaunowned digunakan karena objek terkait harus memiliki masa hidup yang hampir sama, dan itu jauh lebih sederhana dan overhead yang lebih rendah daripada implementasi berbasis tabel-sisi yang digunakan untuk membidik referensi yang lemah.

Pembaruan: Dalam Swift modern secara weakinternal menggunakan mekanisme yang sama seperti unownedhalnya . Jadi perbandingan ini tidak benar karena membandingkan Objective-C weakdengan Swift unonwed.

Alasan

Apa tujuan menjaga memori tetap hidup setelah memiliki referensi mencapai 0? Apa yang terjadi jika kode mencoba melakukan sesuatu dengan objek menggunakan referensi yang tidak dimiliki setelah dideinisialisasi?

Memori tetap hidup sehingga jumlah tetapnya tetap tersedia. Dengan cara ini, ketika seseorang mencoba untuk mempertahankan referensi kuat ke objek yang tidak dimiliki, runtime dapat memeriksa bahwa jumlah referensi yang kuat lebih besar dari nol untuk memastikan bahwa itu aman untuk mempertahankan objek.

Apa yang terjadi dengan memiliki atau tidak memiliki referensi yang dimiliki oleh objek? Apakah seumur hidup mereka dipisahkan dari objek ketika itu diinisialisasi atau ingatan mereka juga dipertahankan sampai objek tersebut dialokasikan setelah referensi terakhir yang tidak dimiliki dilepaskan?

Semua sumber daya yang dimiliki oleh objek dilepaskan segera setelah referensi kuat terakhir objek dilepaskan, dan deinitnya dijalankan. Referensi yang tidak dimiliki hanya membuat memori tetap hidup — selain dari header dengan jumlah referensi, isinya sampah.

Gembira, ya?


38

Ada beberapa jawaban bagus di sini. Tetapi perubahan terbaru tentang bagaimana Swift mengimplementasikan referensi yang lemah harus mengubah keputusan penggunaan diri yang lemah setiap orang vs tidak dimiliki sendiri. Sebelumnya, jika Anda membutuhkan kinerja terbaik menggunakan diri yang tidak dimiliki lebih baik daripada diri yang lemah, selama Anda dapat memastikan bahwa diri tidak akan pernah nol, karena mengakses diri yang tidak dimiliki jauh lebih cepat daripada mengakses diri yang lemah.

Tetapi Mike Ash telah mendokumentasikan bagaimana Swift telah memperbarui implementasi vars lemah untuk menggunakan tabel sisi dan bagaimana ini secara substansial meningkatkan kinerja diri yang lemah.

https://mikeash.com/pyblog/friday-qa-2017-09-22-swift-4-weak-references.html

Sekarang karena tidak ada penalti kinerja yang signifikan untuk diri yang lemah, saya percaya kita harus default untuk menggunakannya di masa depan. Keuntungan dari diri yang lemah adalah bahwa ini adalah opsional, yang membuatnya jauh lebih mudah untuk menulis kode yang lebih benar, pada dasarnya alasan Swift adalah bahasa yang hebat. Anda mungkin berpikir Anda tahu situasi mana yang aman untuk penggunaan diri yang tidak dimiliki, tetapi pengalaman saya meninjau banyak kode pengembang lain adalah, sebagian besar tidak. Saya telah memperbaiki banyak crash ketika self yang tidak dimiliki dialokasikan, biasanya dalam situasi di mana utas latar selesai setelah pengontrol dialokasikan.

Bug dan crash adalah bagian pemrograman yang paling memakan waktu, menyakitkan, dan mahal. Lakukan yang terbaik untuk menulis kode yang benar dan hindari. Saya sarankan menjadikannya sebuah peraturan untuk tidak pernah memaksakan pilihan yang terbuka dan tidak pernah menggunakan diri yang tidak dimiliki, bukannya diri yang lemah. Anda tidak akan kehilangan sesuatu yang hilang saat paksa membuka dan diri yang tidak dimiliki sebenarnya aman. Tetapi Anda akan mendapatkan banyak dari menghilangkan sulit untuk menemukan dan men-debug crash dan bug.


Terima kasih atas pembaruan dan Amin pada paragraf terakhir.
moto

1
Jadi setelah perubahan baru Apakah pernah ada waktu di mana weaktidak dapat digunakan sebagai pengganti unowned?
Sayang

4

Menurut Apple-doc

  • Referensi yang lemah selalu dari tipe opsional, dan secara otomatis menjadi nihil ketika turunannya dideallocated.

  • Jika referensi yang ditangkap tidak akan pernah menjadi nol, itu harus selalu ditangkap sebagai referensi yang tidak dimiliki, dan bukan referensi yang lemah

Contoh -

    // if my response can nil use  [weak self]
      resource.request().onComplete { [weak self] response in
      guard let strongSelf = self else {
        return
      }
      let model = strongSelf.updateModel(response)
      strongSelf.updateUI(model)
     }

    // Only use [unowned self] unowned if guarantees that response never nil  
      resource.request().onComplete { [unowned self] response in
      let model = self.updateModel(response)
      self.updateUI(model)
     }

0

Jika tidak ada satu pun di atas yang masuk akal:

tl; dr

Sama seperti implicitly unwrapped optional, Jika Anda dapat menjamin bahwa referensi tidak akan nol pada saat digunakan, gunakan yang tidak dimiliki. Jika tidak, maka Anda harus menggunakan yang lemah.

Penjelasan:

Saya mengambil yang berikut di bawah ini di: tautan tidak dimiliki lemah . Dari apa yang saya kumpulkan, diri yang tidak dimiliki tidak mungkin nol, tetapi diri yang lemah bisa, dan diri yang tidak memiliki kepemilikan dapat menyebabkan petunjuk yang menggantung ... sesuatu yang terkenal di Objective-C. Semoga ini bisa membantu

"UNOWNED Lemah dan referensi yang tidak dimiliki memiliki perilaku yang sama tetapi BUKAN sama."

Referensi yang tidak dimiliki , seperti referensi yang lemah, jangan menambah jumlah retensi objek yang dirujuk. Namun, di Swift, referensi yang tidak dimiliki memiliki manfaat tambahan karena tidak menjadi Opsional . Ini membuat mereka lebih mudah dikelola daripada menggunakan pengikatan opsional. Ini tidak seperti Opsional yang Tidak Dibungkus Secara implisit. Selain itu, referensi yang tidak dimiliki adalah non-zeroing . Ini berarti bahwa ketika objek dideallocated, itu tidak nol pointer. Ini berarti bahwa penggunaan referensi yang tidak dimiliki dapat, dalam beberapa kasus, mengarah pada petunjuk menggantung. Untuk Anda para kutu buku di luar sana yang mengingat hari Objective-C seperti yang saya lakukan, referensi yang tidak dimiliki memetakan ke referensi yang tidak aman.

Di sinilah agak membingungkan.

Referensi yang lemah dan tidak dimiliki keduanya tidak meningkatkan jumlah penyimpanan.

Keduanya dapat digunakan untuk memutus siklus penahan. Jadi kapan kita menggunakannya ?!

Menurut dokumen Apple :

“Gunakan referensi yang lemah kapan pun valid untuk referensi itu menjadi nol di beberapa titik selama masa pakainya. Sebaliknya, gunakan referensi yang tidak dimiliki ketika Anda tahu bahwa referensi tidak akan pernah menjadi nol setelah ditetapkan selama inisialisasi. "


0
import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        let controller = storyboard.instantiateViewController(withIdentifier: "AnotherViewController")
        self.navigationController?.pushViewController(controller, animated: true)

    }

}



import UIKit
class AnotherViewController: UIViewController {

    var name : String!

    deinit {
        print("Deint AnotherViewController")
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        print(CFGetRetainCount(self))

        /*
            When you test please comment out or vice versa

         */

//        // Should not use unowned here. Because unowned is used where not deallocated. or gurranted object alive. If you immediate click back button app will crash here. Though there will no retain cycles
//        clouser(string: "") { [unowned self] (boolValue)  in
//            self.name = "some"
//        }
//


//
//        // There will be a retain cycle. because viewcontroller has a strong refference to this clouser and as well as clouser (self.name) has a strong refferennce to the viewcontroller. Deint AnotherViewController will not print
//        clouser(string: "") { (boolValue)  in
//            self.name = "some"
//        }
//
//


//        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser (self.name) has a weak refferennce to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)
//
//        clouser(string: "") { [weak self] (boolValue)  in
//            self?.name = "some"
//        }


        // no retain cycle here. because viewcontroller has a strong refference to this clouser. But clouser nos refference to the viewcontroller. Deint AnotherViewController will  print. As we forcefully made viewcontroller weak so its now optional type. migh be nil. and we added a ? (self?)

        clouser(string: "") {  (boolValue)  in
            print("some")
            print(CFGetRetainCount(self))

        }

    }


    func clouser(string: String, completion: @escaping (Bool) -> ()) {
        // some heavy task
        DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
            completion(true)
        }

    }

}

Jika Anda tidak yakin tentang [unowned self] itu gunakan [weak self]

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.