Mengapa membuat "Opsional Unwrapped Optionals", karena itu menyiratkan Anda tahu ada nilai?


493

Mengapa Anda membuat "Opsional Unwrapped Optional" vs membuat hanya variabel biasa atau konstan? Jika Anda tahu bahwa itu dapat berhasil dibuka, mengapa harus membuat opsional? Misalnya, mengapa ini:

let someString: String! = "this is the string"

akan lebih bermanfaat daripada:

let someString: String = "this is the string"

Jika "opsional menunjukkan bahwa konstanta atau variabel diizinkan memiliki 'tidak ada nilai'", tetapi "kadang-kadang jelas dari struktur program bahwa opsional akan selalu memiliki nilai setelah nilai itu ditetapkan pertama kali", apa gunanya menjadikannya opsional di tempat pertama? Jika Anda tahu opsional selalu akan memiliki nilai, bukankah itu membuatnya tidak opsional?

Jawaban:


127

Pertimbangkan kasus objek yang mungkin memiliki properti nil saat sedang dibangun dan dikonfigurasikan, tetapi tidak dapat diubah dan tidak nihil setelahnya (NSImage sering diperlakukan dengan cara ini, meskipun dalam kasusnya kadang-kadang masih berguna untuk bermutasi). Opsinya yang tidak terbuka secara implisit akan membersihkan kodenya dengan baik, dengan kehilangan keselamatan yang relatif rendah (selama satu jaminan dipegang, itu akan aman).

(Sunting) Agar lebih jelas: opsional reguler hampir selalu lebih disukai.


459

Sebelum saya dapat menjelaskan kasus penggunaan untuk Opsional Unwrapped Secara Implisit, Anda harus sudah memahami apa Optional dan Oppt Unwrapped secara implisit di Swift. Jika tidak, saya sarankan Anda terlebih dahulu membaca artikel saya tentang opsional

Kapan Harus Menggunakan Pilihan yang Tidak Dibungkus Secara Opsional

Ada dua alasan utama bahwa seseorang akan membuat Opsional Unwrapped Opsional. Semua harus dilakukan dengan mendefinisikan variabel yang tidak akan pernah diakses ketika nilkarena jika tidak, kompiler Swift akan selalu memaksa Anda untuk secara eksplisit membuka Bungkusan Opsional.

1. Konstan Yang Tidak Dapat Didefinisikan Selama Inisialisasi

Setiap konstanta anggota harus memiliki nilai pada saat inisialisasi selesai. Kadang-kadang, konstanta tidak dapat diinisialisasi dengan nilai yang benar selama inisialisasi, tetapi masih dapat dijamin memiliki nilai sebelum diakses.

Menggunakan variabel Opsional mengatasi masalah ini karena Opsional secara otomatis diinisialisasi dengan nildan nilai yang nantinya akan berisi akan tetap tidak berubah. Namun, bisa jadi menyakitkan untuk terus membuka bungkus variabel yang Anda tahu pasti tidak nol. Opsional Unwrapped secara implisit mencapai manfaat yang sama dengan Opsional dengan manfaat tambahan yang tidak harus secara eksplisit dibuka di mana-mana.

Contoh yang bagus dari ini adalah ketika variabel anggota tidak dapat diinisialisasi dalam subkelas UIView hingga tampilan dimuat:

class MyView: UIView {
    @IBOutlet var button: UIButton!
    var buttonOriginalWidth: CGFloat!

    override func awakeFromNib() {
        self.buttonOriginalWidth = self.button.frame.size.width
    }
}

Di sini, Anda tidak dapat menghitung lebar asli tombol hingga tampilan dimuat, tetapi Anda tahu itu awakeFromNibakan dipanggil sebelum metode lain apa pun pada tampilan (selain inisialisasi). Alih-alih memaksa nilai untuk secara terbuka terbuka secara tidak ada gunanya di seluruh kelas Anda, Anda dapat mendeklarasikannya sebagai Pilihan yang Tidak Dibungkus Secara Implisit.

2. Ketika Aplikasi Anda Tidak Dapat Sembuh Dari Makhluk Bervariasi nil

Ini seharusnya sangat jarang, tetapi jika aplikasi Anda tidak dapat terus berjalan jika suatu variabel nilsaat diakses, itu akan membuang-buang waktu untuk mengujinya nil. Biasanya jika Anda memiliki kondisi yang pasti benar untuk aplikasi Anda untuk terus berjalan, Anda akan menggunakan assert. Opsional Unwrapped Implisit memiliki pernyataan untuk nil dibangun langsung ke dalamnya. Meski begitu, sering kali baik untuk membuka bungkusan opsional dan menggunakan pernyataan yang lebih deskriptif jika nihil.

Ketika Tidak Menggunakan Opsional Unwrapped Secara implisit

1. Variabel Anggota Terhitung yang Dihitung

Kadang-kadang Anda memiliki variabel anggota yang tidak boleh nol, tetapi tidak dapat ditetapkan ke nilai yang benar selama inisialisasi. Salah satu solusinya adalah dengan menggunakan Opsional Unwrapped Opsional, tetapi cara yang lebih baik adalah dengan menggunakan variabel malas:

class FileSystemItem {
}

class Directory : FileSystemItem {
    lazy var contents : [FileSystemItem] = {
        var loadedContents = [FileSystemItem]()
        // load contents and append to loadedContents
        return loadedContents
    }()
}

Sekarang, variabel anggota contentstidak diinisialisasi sampai pertama kali diakses. Ini memberi kelas kesempatan untuk masuk ke keadaan yang benar sebelum menghitung nilai awal.

Catatan: Ini sepertinya bertentangan dengan # 1 dari atas. Namun, ada perbedaan penting yang harus dibuat. Di buttonOriginalWidthatas harus ditetapkan selama viewDidLoad untuk mencegah siapa pun mengubah lebar tombol sebelum properti diakses.

2. Di Tempat Lain

Untuk sebagian besar, Opsional Unwrapped Opsional harus dihindari karena jika digunakan secara salah, seluruh aplikasi Anda akan macet ketika diakses sementara nil. Jika Anda tidak pernah yakin tentang apakah suatu variabel dapat nol, selalu default untuk menggunakan Opsional normal. Membuka gulungan variabel yang tidak pernah niltentu tidak terlalu menyakitkan.


4
Jawaban ini harus diperbarui untuk beta 5. Anda tidak dapat lagi menggunakan if someOptional.
Santa Claus

2
@SantaClaus hasValuedidefinisikan tepat di Opsional. Saya lebih suka semantik hasValueuntuk yang dari != nil. Saya merasa itu jauh lebih dimengerti untuk programmer baru yang belum menggunakan nilbahasa lain. hasValuejauh lebih logis daripada nil.
drewag

2
Sepertinya hasValueditarik dari beta 6. Ash memasukkannya kembali ... github.com/AshFurrow/hasValue
Chris Wagner

1
@newacct Mengenai jenis pengembalian initializers Objc, itu lebih merupakan implisit Unwrapped Opsional. Perilaku yang Anda gambarkan untuk menggunakan "non-opsional" adalah persis apa yang akan dilakukan Optional Unwrapped Optional (tidak gagal sampai diakses). Mengenai membiarkan program gagal sebelumnya dengan membuka paksa, saya setuju bahwa menggunakan non-opsional lebih disukai, tetapi ini tidak selalu memungkinkan.
drewag

1
@ confile no. Tidak peduli apa, itu akan muncul di Objective-C sebagai pointer (jika itu opsional, secara implisit terbuka, atau non-opsional).
drewag

56

Opsinya yang tidak dibungkus secara implisit berguna untuk menghadirkan properti sebagai tidak-opsional ketika benar-benar harus opsional di bawah penutup. Ini sering diperlukan untuk "mengikat simpul" antara dua objek terkait yang masing-masing memerlukan referensi ke yang lain. Masuk akal bila tidak ada referensi yang benar - benar opsional, tetapi salah satunya harus nol saat pasangan diinisialisasi.

Sebagai contoh:

// These classes are buddies that never go anywhere without each other
class B {
    var name : String
    weak var myBuddyA : A!
    init(name : String) {
        self.name = name
    }
}

class A {
    var name : String
    var myBuddyB : B
    init(name : String) {
        self.name = name
        myBuddyB = B(name:"\(name)'s buddy B")
        myBuddyB.myBuddyA = self
    }
}

var a = A(name:"Big A")
println(a.myBuddyB.name)   // prints "Big A's buddy B"

Setiap Binstance harus selalu memiliki myBuddyAreferensi yang valid , jadi kami tidak ingin membuat pengguna memperlakukannya sebagai opsional, tetapi kami membutuhkannya menjadi opsional sehingga kami dapat membuat Bsebelum kita memiliki Auntuk merujuk.

NAMUN! Persyaratan referensi timbal balik semacam ini sering merupakan indikasi kopling ketat dan desain yang buruk. Jika Anda mendapati diri Anda bergantung pada opsi yang secara implisit tidak terbuka, Anda mungkin harus mempertimbangkan refactoring untuk menghilangkan ketergantungan-silang.


7
Saya pikir salah satu alasan mereka membuat fitur bahasa ini adalah@IBOutlet
Jiaaro

11
+1 untuk peringatan "NAMUN". Ini mungkin tidak benar setiap saat, tapi itu pasti sesuatu yang harus diwaspadai.
JMD

4
Anda masih memiliki siklus referensi yang kuat antara A dan B. Pilihan yang terbuka secara implisit JANGAN membuat referensi yang lemah. Anda masih harus mendeklarasikan myByddyA atau myBuddyB sebagai lemah (mungkin myBuddyA)
drewag

6
Untuk menjadi lebih jelas mengapa jawaban ini salah dan sangat menyesatkan: Secara implisit Unwrapped Opsional sama sekali tidak ada hubungannya dengan manajemen memori dan tidak mencegah siklus penyimpanan. Namun, Opsional Unwrapped Implisit masih berguna dalam keadaan yang dijelaskan untuk mengatur referensi dua arah. Jadi cukup menambahkan weakdeklarasi dan menghapus "tanpa menciptakan siklus mempertahankan kuat"
drewag

1
@rewag: Anda benar - Saya telah mengedit jawaban untuk menghapus siklus penyimpanan. Saya bermaksud membuat backreference lemah tapi saya kira itu terlintas di benak saya.
n8

37

Opsinya yang tidak terbuka secara implisit adalah kompromi pragmatis untuk membuat pekerjaan di lingkungan hybrid yang harus beroperasi dengan kerangka kerja Kakao yang ada dan konvensi mereka lebih menyenangkan, sementara juga memungkinkan migrasi bertahap ke dalam paradigma pemrograman yang lebih aman - tanpa penunjuk nol - ditegakkan oleh kompiler Swift.

Buku Swift, dalam bab Dasar-Dasar , bagian Opsional Unwrapped Implisit mengatakan:

Opsinya yang tidak dibungkus secara implisit berguna ketika nilai opsinya dipastikan ada segera setelah opsinya pertama kali ditentukan dan pasti dapat diasumsikan ada pada setiap titik sesudahnya. Penggunaan utama dari opsi-opsi yang tidak tersirat secara implisit di Swift adalah selama inisialisasi kelas, seperti yang dijelaskan dalam Referensi yang Tidak Dimiliki dan Properti Opsional yang Tidak Dibungkus Secara implisit .
...
Anda dapat menganggap opsional yang tidak dibungkus secara implisit sebagai memberikan izin untuk opsional yang akan dibuka secara otomatis setiap kali digunakan. Alih-alih menempatkan tanda seru setelah nama opsional setiap kali Anda menggunakannya, Anda menempatkan tanda seru setelah jenis opsional ketika Anda mendeklarasikannya.

Ini datang ke menggunakan kasus di mana non nil-ness properti didirikan melalui konvensi penggunaan, dan tidak bisa ditegakkan oleh compiler selama inisialisasi kelas. Misalnya, UIViewControllerproperti yang diinisialisasi dari NIB atau Storyboards, di mana inisialisasi dibagi menjadi fase terpisah, tetapi setelah viewDidLoad()Anda dapat mengasumsikan bahwa properti secara umum ada. Kalau tidak, untuk memenuhi kompiler, Anda harus menggunakan pembungkusan paksa , pengikatan opsional atau rantai opsional hanya untuk mengaburkan tujuan utama kode.

Di atas bagian dari buku Swift merujuk juga ke bab Penghitungan Referensi Otomatis :

Namun, ada skenario ketiga, di mana kedua properti harus selalu memiliki nilai, dan tidak ada properti yang harus pernah nilbegitu inisialisasi selesai. Dalam skenario ini, akan berguna untuk menggabungkan properti yang tidak dimiliki pada satu kelas dengan properti opsional yang terbuka secara implisit di kelas lainnya.

Ini memungkinkan kedua properti diakses secara langsung (tanpa bungkusan opsional) setelah inisialisasi selesai, sambil tetap menghindari siklus referensi.

Ini datang ke keunikan karena tidak menjadi bahasa sampah yang dikumpulkan, oleh karena itu mematahkan siklus tetap ada pada Anda sebagai seorang programmer dan opsional yang secara terbuka membuka alat untuk menyembunyikan kekhasan ini.

Itu mencakup "Kapan harus menggunakan opsional yang secara terbuka tidak terbungkus dalam kode Anda?" pertanyaan. Sebagai pengembang aplikasi, sebagian besar Anda akan menemukannya dalam tanda tangan metode perpustakaan yang ditulis dalam Objective-C, yang tidak memiliki kemampuan untuk mengekspresikan jenis opsional.

Dari Menggunakan Swift dengan Cocoa dan Objective-C, bagian Bekerja dengan nihil :

Karena Objective-C tidak membuat jaminan bahwa suatu objek adalah nihil, Swift membuat semua kelas dalam tipe argumen dan tipe pengembalian opsional dalam API Objective-C yang diimpor. Sebelum Anda menggunakan objek Objective-C, Anda harus memeriksa untuk memastikan bahwa itu tidak hilang.

Dalam beberapa kasus, Anda mungkin benar - benar yakin bahwa metode atau properti Objective-C tidak pernah mengembalikan nilreferensi objek. Untuk membuat objek dalam skenario khusus ini lebih nyaman untuk dikerjakan, Swift mengimpor tipe objek sebagai opsional yang terbuka secara implisit . Tipe opsional yang tidak dibungkus secara implisit mencakup semua fitur keselamatan dari tipe opsional. Selain itu, Anda dapat mengakses nilai secara langsung tanpa memeriksanilatau membuka bungkusnya sendiri. Saat Anda mengakses nilai dalam jenis opsional ini tanpa membukanya dengan aman terlebih dahulu, opsional yang tidak terbuka secara implisit memeriksa apakah nilainya hilang. Jika nilainya hilang, kesalahan runtime terjadi. Sebagai akibatnya, Anda harus selalu memeriksa dan membuka sendiri pilihan yang secara implisit terbuka, kecuali jika Anda yakin bahwa nilainya tidak dapat hilang.

... dan di luar sini berbaring naga


Terima kasih atas jawaban menyeluruh ini. Bisakah Anda memikirkan daftar periksa cepat kapan harus menggunakan Opsional yang Tidak Dibungkus Secara Implisit dan kapan variabel standar akan mencukupi?
Hairgami_Master

@Hairgami_Master Saya menambahkan jawaban saya sendiri dengan daftar dan contoh nyata
drewag

18

Contoh sederhana satu baris (atau beberapa baris) tidak mencakup perilaku opsional dengan sangat baik - ya, jika Anda mendeklarasikan variabel dan segera memberikan nilai, tidak ada gunanya opsional.

Kasus terbaik yang pernah saya lihat sejauh ini adalah pengaturan yang terjadi setelah inisialisasi objek, diikuti dengan penggunaan yang "dijamin" untuk mengikuti pengaturan itu, misalnya dalam pengontrol tampilan:

class MyViewController: UIViewController {

    var screenSize: CGSize?

    override func viewDidLoad {
        super.viewDidLoad()
        screenSize = view.frame.size
    }

    @IBAction printSize(sender: UIButton) {
        println("Screen size: \(screenSize!)")
    }
}

Kami tahu printSizeakan dipanggil setelah tampilan dimuat - ini adalah metode tindakan yang terhubung ke kontrol di dalam tampilan itu, dan kami memastikan untuk tidak menyebutnya sebaliknya. Jadi kita bisa menghemat beberapa pemeriksaan-opsional / mengikat dengan !. Swift tidak dapat mengenali jaminan itu (setidaknya sampai Apple menyelesaikan masalah penghentian), jadi Anda memberi tahu kompilator bahwa itu ada.

Ini memecah keamanan jenis sampai tingkat tertentu. Di mana pun Anda memiliki opsi yang secara terbuka tidak tersirat adalah tempat aplikasi Anda bisa mogok jika "jaminan" Anda tidak selalu berlaku, jadi ini adalah fitur yang harus digunakan dengan hemat. Selain itu, menggunakan !sepanjang waktu membuatnya terdengar seperti Anda berteriak, dan tidak ada yang suka itu.


Mengapa tidak hanya menginisialisasi screenSize ke CGSize (tinggi: 0, lebar: 0) dan menyimpan kesulitan karena harus berteriak variabel setiap kali Anda mengaksesnya?
Martin Gordon

Ukuran mungkin bukan contoh terbaik, karena CGSizeZerobisa menjadi nilai sentinel yang baik dalam penggunaan di dunia nyata. Tetapi bagaimana jika Anda memiliki ukuran yang dimuat dari nib yang mungkin sebenarnya nol? Kemudian menggunakan CGSizeZerosebagai penjaga tidak membantu Anda membedakan antara nilai yang tidak disetel dan disetel ke nol. Selain itu, ini berlaku sama baiknya untuk jenis lain yang dimuat dari nib (atau di mana pun setelahnya init): string, referensi untuk subview, dll.
rickster

2
Bagian dari titik Opsional dalam bahasa fungsional adalah tidak memiliki nilai sentinel. Anda memiliki nilai atau tidak. Anda seharusnya tidak memiliki kasus di mana Anda memiliki nilai yang menunjukkan nilai yang hilang.
Wayne Tanner

4
Saya pikir Anda telah salah memahami pertanyaan OP. OP tidak bertanya tentang kasus umum untuk opsional, melainkan secara khusus tentang kebutuhan / penggunaan opsional yang tidak dibungkus secara implisit (yaitu tidak let foo? = 42, bukan let foo! = 42). Ini tidak membahas itu. (Ini mungkin jawaban yang relevan tentang opsional, ingatlah, tetapi bukan tentang opsional yang terbuka secara implisit yang merupakan hewan yang berbeda / terkait.)
JMD

15

Apple memberikan contoh yang bagus dalam Bahasa Pemrograman Swift -> Penghitungan Referensi Otomatis -> Menyelesaikan Siklus Referensi yang Kuat Antara Instance Kelas -> Referensi Tidak Dimiliki dan Properti Opsional yang Tidak Dibungkus Secara Tersirat

class Country {
    let name: String
    var capitalCity: City! // Apple finally correct this line until 2.0 Prerelease (let -> var)
    init(name: String, capitalName: String) {
        self.name = name
        self.capitalCity = City(name: capitalName, country: self)
    }
}

class City {
    let name: String
    unowned let country: Country
    init(name: String, country: Country) {
        self.name = name
        self.country = country
    }
}

Penginisialisasi untuk Citydipanggil dari dalam penginisialisasi untuk Country. Namun, penginisialisasi untuk Countrytidak dapat lolos selfke Citypenginisialisasi sampai Countryinstance baru sepenuhnya diinisialisasi, seperti yang dijelaskan dalam Inisialisasi Dua Fase .

Untuk mengatasi persyaratan ini, Anda mendeklarasikan capitalCityproperti Countrysebagai properti opsional yang terbuka secara implisit.


Tutorial yang disebutkan dalam jawaban ini ada di sini .
Franklin Yu

3

Dasar pemikiran dari pilihan implisit lebih mudah untuk dijelaskan dengan terlebih dahulu melihat alasan untuk membuka paksa.

Membuka paksa paksa opsional (implisit atau tidak), menggunakan! operator, berarti Anda yakin bahwa kode Anda tidak memiliki bug dan opsional sudah memiliki nilai di mana itu sedang dibuka. Tanpa ! operator, Anda mungkin hanya akan menegaskan dengan penjilidan opsional:

 if let value = optionalWhichTotallyHasAValue {
     println("\(value)")
 } else {
     assert(false)
 }

yang tidak sebaik

println("\(value!)")

Sekarang, opsional implisit memungkinkan Anda menyatakan memiliki opsional yang Anda harapkan akan selalu memiliki nilai ketika dibuka, dalam semua kemungkinan aliran. Jadi itu hanya selangkah lebih maju dalam membantu Anda - dengan melonggarkan persyaratan penulisan! untuk membuka setiap kali, dan memastikan bahwa runtime masih akan kesalahan jika asumsi Anda tentang aliran salah.


1
@newacct: non-nil dalam semua kemungkinan aliran melalui kode Anda tidak sama dengan non-nil dari inisialisasi induk (kelas / struct) melalui deallokasi. Interface Builder adalah contoh klasik (tetapi ada banyak pola inisialisasi tertunda lainnya): jika kelas hanya pernah digunakan dari nib, maka variabel outlet tidak akan ditetapkan init(yang mungkin bahkan tidak Anda terapkan), tetapi mereka ' kembali dijamin akan ditetapkan setelah awakeFromNib/ viewDidLoad.
rickster

3

Jika Anda tahu pasti, nilai kembali dari opsional, bukan nil, Secara opsional Unwrapped Optional digunakan untuk secara langsung menangkap nilai-nilai dari opsional dan non opsional tidak bisa.

//Optional string with a value
let optionalString: String? = "This is an optional String"

//Declaration of an Implicitly Unwrapped Optional String
let implicitlyUnwrappedOptionalString: String!

//Declaration of a non Optional String
let nonOptionalString: String

//Here you can catch the value of an optional
implicitlyUnwrappedOptionalString = optionalString

//Here you can't catch the value of an optional and this will cause an error
nonOptionalString = optionalString

Jadi ini adalah perbedaan antara penggunaan

let someString : String! dan let someString : String


1
Ini tidak menjawab pertanyaan OP. OP tahu apa yang Opsional Unwrapped Opsional itu.
Franklin Yu

0

Saya pikir Optionalitu nama yang buruk untuk konstruksi ini yang membingungkan banyak pemula.

Bahasa lain (Kotlin dan C # misalnya) menggunakan istilah ini Nullable, dan itu membuatnya jauh lebih mudah untuk memahami ini.

Nullableberarti Anda dapat menetapkan nilai nol ke variabel jenis ini. Jadi jika itu Nullable<SomeClassType>, Anda dapat menetapkan nol untuk itu, jika itu hanya SomeClassType, Anda tidak bisa. Itulah cara kerja Swift.

Mengapa menggunakannya? Nah, kadang-kadang Anda harus memiliki nol, itu sebabnya. Misalnya, ketika Anda tahu bahwa Anda ingin memiliki bidang dalam suatu kelas, tetapi Anda tidak dapat menetapkannya untuk apa pun ketika Anda membuat turunan dari kelas itu, tetapi Anda nanti akan melakukannya. Saya tidak akan memberikan contoh, karena orang-orang sudah menyediakannya di sini. Saya hanya menulis ini untuk memberikan 2 sen saya.

Btw, saya sarankan Anda melihat bagaimana ini bekerja dalam bahasa lain, seperti Kotlin dan C #.

Inilah tautan yang menjelaskan fitur ini di Kotlin: https://kotlinlang.org/docs/reference/null-safety.html

Bahasa lain, seperti Java dan Scala memiliki Optionals, tetapi mereka bekerja secara berbeda dari Optionals di Swift, karena tipe Java dan Scala semuanya dapat dibatalkan secara default.

Semua dalam semua, saya pikir fitur ini seharusnya dinamai Nullabledalam Swift, dan tidak Optional...


0

Implicitly Unwrapped Optionaladalah gula sintaksis untuk Optionalitu tidak memaksa programmer untuk membuka bungkus variabel. Ini dapat digunakan untuk variabel yang tidak dapat diinisialisasi selama two-phase initialization processdan menyiratkan non-nil. Variabel ini berperilaku sebagai non-nil tetapi sebenarnya adalah variabel opsional . Contoh yang bagus adalah - outlet Interface Builder

Optional biasanya lebih disukai

var nonNil: String = ""
var optional: String?
var implicitlyUnwrappedOptional: String!

func foo() {
    //get a value
    nonNil.count
    optional?.count

    //Danderour - makes a force unwrapping which can throw a runtime error
    implicitlyUnwrappedOptional.count

    //assign to nil
//        nonNil = nil //Compile error - 'nil' cannot be assigned to type 'String'
    optional = nil
    implicitlyUnwrappedOptional = nil
}
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.