Jawaban ini adalah wiki komunitas . Jika Anda merasa ini bisa dibuat lebih baik, silakan mengeditnya !
Latar Belakang: Apa itu Opsional?
Dalam Swift, Optional
adalah tipe generik yang dapat berisi nilai (jenis apa pun), atau tidak ada nilai sama sekali.
Dalam banyak bahasa pemrograman lain, nilai "sentinel" tertentu sering digunakan untuk menunjukkan kurangnya nilai . Dalam Objective-C, misalnya, nil
( pointer nol ) menunjukkan tidak adanya objek. Tetapi ini menjadi lebih rumit ketika bekerja dengan tipe primitif - harus -1
digunakan untuk menunjukkan tidak adanya bilangan bulat, atau mungkin INT_MIN
, atau bilangan bulat lainnya? Jika nilai tertentu apa pun yang dipilih berarti "tidak ada bilangan bulat", itu berarti tidak dapat lagi diperlakukan sebagai nilai yang valid .
Swift adalah bahasa jenis-aman, yang berarti bahasa tersebut membantu Anda untuk lebih jelas tentang jenis nilai yang dapat digunakan oleh kode Anda. Jika bagian dari kode Anda mengharapkan sebuah String, ketik safety mencegah Anda dari melewatkannya sebuah Int secara tidak sengaja.
Di Swift, semua jenis dapat dibuat opsional . Nilai opsional dapat mengambil nilai apa pun dari jenis aslinya, atau nilai khusus nil
.
Opsional didefinisikan dengan ?
akhiran pada tipe:
var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int? // `nil` is the default when no value is provided
Kurangnya nilai dalam opsional ditunjukkan oleh nil
:
anOptionalInt = nil
(Perhatikan bahwa ini nil
tidak sama dengan nil
di Objective-C. Di Objective-C, nil
adalah tidak adanya penunjuk objek yang valid ; di Swift, Opsional tidak terbatas pada objek / jenis referensi. Opsional berperilaku mirip dengan Haskell's Maybe .)
Mengapa saya mendapatkan " kesalahan fatal: tiba-tiba ditemukan nol saat membuka nilai opsional "?
Dalam rangka untuk mengakses nilai opsional ini (jika memiliki satu sama sekali), Anda perlu membuka bungkusan itu. Nilai opsional dapat dibuka dengan aman atau paksa. Jika Anda membuka paksa opsi, dan tidak memiliki nilai, program Anda akan macet dengan pesan di atas.
Xcode akan menunjukkan kepada Anda crash dengan menyorot sebaris kode. Masalahnya terjadi pada baris ini.
Kecelakaan ini dapat terjadi dengan dua jenis force-unwrap:
1. Kekuatan Eksplisit Tidak Membuka
Ini dilakukan dengan !
operator pada opsional. Sebagai contoh:
let anOptionalString: String?
print(anOptionalString!) // <- CRASH
Kesalahan fatal: Tidak terduga ditemukan nil saat membuka bungkus nilai Opsional
Seperti anOptionalString
yang nil
di sini, Anda akan mendapatkan kecelakaan pada baris di mana Anda memaksa unwrap itu.
2. Opsional Unwrapped secara implisit
Ini didefinisikan dengan !
, bukan tipe ?
setelah.
var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
Opsional ini diasumsikan mengandung nilai. Oleh karena itu, setiap kali Anda mengakses opsi yang secara terbuka tidak tersirat, itu akan secara otomatis dibuka untuk Anda. Jika tidak mengandung nilai, itu akan macet.
print(optionalDouble) // <- CRASH
Kesalahan fatal: Tidak terduga ditemukan nihil sementara secara implisit membuka bungkusan nilai Opsional
Untuk mengetahui variabel yang menyebabkan kerusakan, Anda dapat menahan ⌥sambil mengeklik untuk menampilkan definisi, tempat Anda dapat menemukan jenis opsional.
IBOutlet, khususnya, biasanya merupakan opsi yang secara implisit tidak terbuka. Ini karena xib atau storyboard Anda akan menautkan outlet pada saat runtime, setelah inisialisasi. Karena itu Anda harus memastikan bahwa Anda tidak mengakses outlet sebelum mereka masuk. Anda juga harus memeriksa bahwa koneksi sudah benar dalam file storyboard / xib Anda, jika tidak, nilainya akan nil
saat runtime, dan karena itu crash ketika mereka secara implisit membuka bungkusan. . Saat memperbaiki koneksi, coba hapus baris kode yang mendefinisikan outlet Anda, lalu sambungkan kembali.
Kapan saya harus memaksa membuka bungkusan Opsional?
Angkatan Tidak Dibongkar Secara Eksplisit
Sebagai aturan umum, Anda tidak boleh memaksakan secara terbuka membuka opsi dengan !
operator. Mungkin ada kasus di mana penggunaan !
dapat diterima - tetapi Anda hanya boleh menggunakannya jika Anda yakin 100% bahwa opsional mengandung nilai.
Meskipun mungkin ada kesempatan di mana Anda bisa menggunakan pembungkusan paksa, karena Anda tahu fakta bahwa opsional mengandung nilai - tidak ada satu tempat pun di mana Anda tidak bisa membuka bungkusan opsional itu dengan aman.
Opsional Unwrapped secara implisit
Variabel-variabel ini dirancang sehingga Anda dapat menunda tugas mereka sampai nanti dalam kode Anda. Adalah tanggung jawab Anda untuk memastikan mereka memiliki nilai sebelum Anda mengaksesnya. Namun, karena mereka melibatkan pembongkaran paksa, mereka secara inheren tidak aman - karena mereka menganggap nilai Anda tidak-nol, meskipun menetapkan nihil valid.
Anda seharusnya hanya menggunakan opsional yang tidak dibungkus secara implisit sebagai pilihan terakhir . Jika Anda dapat menggunakan variabel lazy , atau memberikan nilai default untuk sebuah variabel - Anda harus melakukannya daripada menggunakan opsional yang terbuka secara implisit.
Namun, ada beberapa skenario di mana opsional tersirat secara terbuka bermanfaat , dan Anda masih dapat menggunakan berbagai cara untuk membuka bungkusnya dengan aman seperti yang tercantum di bawah ini - tetapi Anda harus selalu menggunakannya dengan hati-hati.
Bagaimana saya bisa dengan aman berurusan dengan Opsional?
Cara paling sederhana untuk memeriksa apakah suatu opsional mengandung suatu nilai, adalah dengan membandingkannya nil
.
if anOptionalInt != nil {
print("Contains a value!")
} else {
print("Doesn’t contain a value.")
}
Namun, 99,9% dari waktu ketika bekerja dengan opsional, Anda sebenarnya ingin mengakses nilai yang dikandungnya, jika mengandungnya sama sekali. Untuk melakukan ini, Anda dapat menggunakan Binding Opsional .
Penjilidan Opsional
Penjilidan Opsional memungkinkan Anda untuk memeriksa apakah suatu opsi mengandung nilai - dan memungkinkan Anda untuk menetapkan nilai yang tidak terbungkus ke variabel atau konstanta baru. Ini menggunakan sintaks if let x = anOptional {...}
atau if var x = anOptional {...}
, tergantung jika Anda perlu mengubah nilai variabel baru setelah mengikatnya.
Sebagai contoh:
if let number = anOptionalInt {
print("Contains a value! It is \(number)!")
} else {
print("Doesn’t contain a number")
}
Yang dilakukan adalah pertama-tama memeriksa apakah opsi berisi nilai. Jika tidak , maka 'terbuka' nilai ditugaskan untuk variabel baru ( number
) - yang kemudian dapat dengan bebas menggunakan seolah-olah itu non-opsional. Jika opsional tidak mengandung nilai, maka klausa lain akan dipanggil, seperti yang Anda harapkan.
Apa yang rapi tentang pengikatan opsional, adalah Anda dapat membuka beberapa opsi secara bersamaan. Anda bisa memisahkan pernyataan dengan koma. Pernyataan ini akan berhasil jika semua opsi tidak dibuka.
var anOptionalInt : Int?
var anOptionalString : String?
if let number = anOptionalInt, let text = anOptionalString {
print("anOptionalInt contains a value: \(number). And so does anOptionalString, it’s: \(text)")
} else {
print("One or more of the optionals don’t contain a value")
}
Trik lain yang rapi adalah Anda juga dapat menggunakan koma untuk memeriksa kondisi tertentu pada nilai, setelah membuka bungkusnya.
if let number = anOptionalInt, number > 0 {
print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}
Satu-satunya tangkapan dengan menggunakan penjilidan opsional dalam pernyataan if, adalah bahwa Anda hanya dapat mengakses nilai yang belum dibuka dari dalam lingkup pernyataan tersebut. Jika Anda membutuhkan akses ke nilai dari luar ruang lingkup pernyataan, Anda dapat menggunakan pernyataan penjaga .
Sebuah pernyataan penjaga memungkinkan Anda untuk menentukan kondisi untuk sukses - dan ruang lingkup saat hanya akan terus mengeksekusi jika kondisi yang terpenuhi. Mereka didefinisikan dengan sintaks guard condition else {...}
.
Jadi, untuk menggunakannya dengan penjilidan opsional, Anda dapat melakukan ini:
guard let number = anOptionalInt else {
return
}
(Perhatikan bahwa di dalam tubuh penjaga, Anda harus menggunakan salah satu pernyataan transfer kontrol untuk keluar dari lingkup kode yang saat ini mengeksekusi).
Jika anOptionalInt
mengandung nilai, itu akan dibuka dan ditugaskan ke number
konstanta baru . Kode setelah penjaga kemudian akan melanjutkan eksekusi. Jika tidak mengandung nilai - penjaga akan mengeksekusi kode di dalam tanda kurung, yang akan menyebabkan transfer kontrol, sehingga kode segera setelah itu tidak akan dieksekusi.
Hal yang benar-benar rapi tentang pernyataan penjaga adalah nilai yang belum dibuka sekarang tersedia untuk digunakan dalam kode yang mengikuti pernyataan (seperti yang kita tahu bahwa kode di masa depan hanya dapat dieksekusi jika opsional memiliki nilai). Ini bagus untuk menghilangkan 'piramida kehancuran' yang dibuat dengan membuat pernyataan if ganda.
Sebagai contoh:
guard let number = anOptionalInt else {
return
}
print("anOptionalInt contains a value, and it’s: \(number)!")
Penjaga juga mendukung trik rapi yang sama yang didukung oleh pernyataan if, seperti membuka beberapa opsi secara bersamaan dan menggunakan where
klausa.
Apakah Anda menggunakan pernyataan if atau guard sepenuhnya tergantung pada apakah kode di masa depan mengharuskan opsional untuk mengandung nilai.
Operator Penggabungan Nil
The Nil penggabungan Operator adalah versi singkat bagus dari operator kondisional terner , terutama dirancang untuk mengkonversi optionals untuk non-optionals. Ini memiliki sintaks a ?? b
, di mana a
adalah tipe opsional dan tipe b
yang sama a
(walaupun biasanya non-opsional).
Ini pada dasarnya memungkinkan Anda mengatakan "Jika a
berisi nilai, buka bungkusannya. Jika tidak maka kembalilah b
sebagai gantinya ”. Misalnya, Anda dapat menggunakannya seperti ini:
let number = anOptionalInt ?? 0
Ini akan menentukan number
konstanta Int
tipe, yang akan mengandung nilai anOptionalInt
, jika mengandung nilai, atau 0
sebaliknya.
Ini hanya singkatan untuk:
let number = anOptionalInt != nil ? anOptionalInt! : 0
Rantai opsional
Anda dapat menggunakan Chaining Opsional untuk memanggil metode atau mengakses properti pada opsional. Ini hanya dilakukan dengan suffixing nama variabel dengan ?
ketika menggunakannya.
Sebagai contoh, katakanlah kita memiliki variabel foo
, ketik Foo
instance opsional .
var foo : Foo?
Jika kami ingin memanggil metode foo
yang tidak mengembalikan apa pun, kami dapat melakukannya:
foo?.doSomethingInteresting()
Jika foo
berisi nilai, metode ini akan dipanggil. Jika tidak, tidak ada hal buruk yang akan terjadi - kode akan terus dijalankan.
(Ini adalah perilaku yang mirip dengan mengirim pesan ke nil
di Objective-C)
Ini karena itu juga dapat digunakan untuk mengatur properti serta metode panggilan. Sebagai contoh:
foo?.bar = Bar()
Sekali lagi, tidak ada yang buruk akan terjadi di sini jika foo
adalah nil
. Kode Anda akan terus dijalankan.
Trik lain yang rapi yang memungkinkan Anda melakukan perangkaian opsional adalah memeriksa apakah menyetel properti atau memanggil metode berhasil. Anda dapat melakukan ini dengan membandingkan nilai kembali ke nil
.
(Ini karena nilai opsional akan kembali Void?
daripada Void
pada metode yang tidak mengembalikan apa pun)
Sebagai contoh:
if (foo?.bar = Bar()) != nil {
print("bar was set successfully")
} else {
print("bar wasn’t set successfully")
}
Namun, hal-hal menjadi sedikit lebih rumit ketika mencoba mengakses properti atau memanggil metode yang mengembalikan nilai. Karena foo
bersifat opsional, apa pun yang dikembalikan darinya juga akan opsional. Untuk mengatasinya, Anda dapat membuka bungkusan opsional yang dikembalikan menggunakan salah satu metode di atas - atau membuka bungkusannya foo
sendiri sebelum mengakses metode atau metode panggilan yang mengembalikan nilai.
Juga, seperti namanya, Anda dapat 'rantai' pernyataan ini bersama-sama. Ini berarti bahwa jika foo
memiliki properti opsional baz
, yang memiliki properti qux
- Anda dapat menulis yang berikut:
let optionalQux = foo?.baz?.qux
Sekali lagi, karena foo
dan baz
bersifat opsional, nilai yang dikembalikan dari qux
akan selalu menjadi opsional terlepas dari apakah qux
itu sendiri opsional.
map
dan flatMap
Fitur yang sering kurang digunakan dengan opsional adalah kemampuan untuk menggunakan map
dan flatMap
fungsinya. Ini memungkinkan Anda untuk menerapkan transformasi non-opsional ke variabel opsional. Jika opsional memiliki nilai, Anda dapat menerapkan transformasi yang diberikan padanya. Jika tidak memiliki nilai, itu akan tetap nil
.
Misalnya, katakanlah Anda memiliki string opsional:
let anOptionalString:String?
Dengan menerapkan map
fungsi ke dalamnya - kita dapat menggunakan stringByAppendingString
fungsi untuk menggabungkannya ke string lain.
Karena stringByAppendingString
mengambil argumen string non-opsional, kami tidak dapat memasukkan string opsional kami secara langsung. Namun, dengan menggunakan map
, kita dapat menggunakan izin stringByAppendingString
untuk digunakan jika anOptionalString
memiliki nilai.
Sebagai contoh:
var anOptionalString:String? = "bar"
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // Optional("foobar")
Namun, jika anOptionalString
tidak memiliki nilai, map
akan kembali nil
. Sebagai contoh:
var anOptionalString:String?
anOptionalString = anOptionalString.map {unwrappedString in
return "foo".stringByAppendingString(unwrappedString)
}
print(anOptionalString) // nil
flatMap
bekerja serupa dengan map
, kecuali itu memungkinkan Anda untuk mengembalikan opsi lain dari dalam tubuh penutupan. Ini berarti Anda dapat memasukkan opsi ke dalam proses yang membutuhkan input non-opsional, tetapi dapat menampilkan sendiri opsi itu.
try!
Sistem penanganan kesalahan Swift dapat digunakan dengan aman dengan Do-Try-Catch :
do {
let result = try someThrowingFunc()
} catch {
print(error)
}
Jika someThrowingFunc()
melempar kesalahan, kesalahan akan ditangkap dengan aman di catch
blok.
The error
konstan Anda lihat dalam catch
blok belum dinyatakan oleh kami - itu secara otomatis oleh catch
.
Anda juga dapat mendeklarasikan error
diri Anda sendiri, itu memiliki keuntungan karena dapat melemparkannya ke format yang bermanfaat, misalnya:
do {
let result = try someThrowingFunc()
} catch let error as NSError {
print(error.debugDescription)
}
Menggunakan try
cara ini adalah cara yang tepat untuk mencoba, menangkap dan menangani kesalahan yang berasal dari fungsi melempar.
Ada juga try?
yang menyerap kesalahan:
if let result = try? someThrowingFunc() {
// cool
} else {
// handle the failure, but there's no error information available
}
Tetapi sistem penanganan kesalahan Swift juga menyediakan cara untuk "memaksa mencoba" dengan try!
:
let result = try! someThrowingFunc()
Konsep-konsep yang dijelaskan dalam posting ini juga berlaku di sini: jika ada kesalahan, aplikasi akan macet.
Anda hanya boleh menggunakannya try!
jika Anda dapat membuktikan bahwa hasilnya tidak akan pernah gagal dalam konteks Anda - dan ini sangat jarang.
Sebagian besar waktu Anda akan menggunakan sistem Do-Try-Catch yang lengkap - dan yang opsional try?
,, dalam kasus yang jarang terjadi di mana penanganan kesalahan tidak penting.
Sumber daya