Apa yang dimaksud dengan "Kesalahan fatal: Nil yang tidak terduga ditemukan saat membuka bungkus nilai Opsional"?
Program Swift saya mengalami crash EXC_BAD_INSTRUCTION
dan salah satu kesalahan serupa berikut ini. Apa arti kesalahan ini, dan bagaimana cara memperbaikinya?
Kesalahan fatal: Tanpa diduga ditemukan nol saat membuka bungkus nilai Opsional
atau
Kesalahan fatal: Tidak terduga ditemukan nol saat secara implisit membuka bungkus nilai Opsional
Postingan ini dimaksudkan untuk mengumpulkan jawaban atas masalah yang "tak terduga nihil", agar tidak tersebar dan sulit ditemukan. Silakan menambahkan jawaban Anda sendiri atau mengedit jawaban wiki yang ada.
Jawaban
Jawaban ini adalah wiki komunitas . Jika Anda merasa ini bisa diperbaiki, silakan edit !
Latar Belakang: Apa itu Opsional?
Di Swift, Optional<Wrapped>
adalah tipe opsi : ini dapat berisi nilai apa pun dari tipe asli ("Dibungkus"), atau tanpa nilai sama sekali (nilai khusus nil
). Nilai opsional harus dibuka sebelum dapat digunakan.
Opsional adalah tipe generik , yang berarti Optional<Int>
dan Optional<String>
merupakan tipe yang berbeda - tipe di dalamnya <>
disebut tipe Wrapped. Di bawah kap, Opsional adalah enum dengan dua casing: .some(Wrapped)
dan .none
, di mana .none
setara dengan nil
.
Opsional dapat dideklarasikan menggunakan tipe bernama Optional<T>
, atau (paling umum) sebagai singkatan dengan ?
sufiks.
var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int? // `nil` is the default when no value is provided
var aVerboseOptionalInt: Optional<Int> // equivalent to `Int?`
anOptionalInt = nil // now this variable contains nil instead of an integer
Opsional adalah alat sederhana namun ampuh untuk mengekspresikan asumsi Anda saat menulis kode. Kompilator dapat menggunakan informasi ini untuk mencegah Anda melakukan kesalahan. Dari Bahasa Pemrograman Swift :
Swift adalah bahasa yang aman untuk tipe , yang berarti bahasanya membantu Anda memperjelas tentang jenis nilai yang dapat digunakan kode Anda. Jika bagian dari kode Anda memerlukan a
String
, type safety mencegah Anda meneruskannya secara tidakInt
sengaja. Demikian pula, jenis keamanan mencegah Anda secara tidak sengaja meneruskan opsionalString
ke bagian kode yang memerlukan non-opsionalString
. Keamanan jenis membantu Anda menangkap dan memperbaiki kesalahan sedini mungkin dalam proses pengembangan.
Beberapa bahasa pemrograman lain juga memiliki tipe opsi umum : misalnya, Maybe di Haskell, opsi di Rust, dan opsional di C ++ 17.
Dalam bahasa pemrograman tanpa jenis opsi, nilai "sentinel" tertentu sering digunakan untuk menunjukkan tidak adanya nilai yang valid. Di Objective-C, misalnya, nil
( pointer nol ) mewakili ketiadaan suatu objek. Untuk tipe primitif seperti int
, pointer null tidak dapat digunakan, jadi Anda memerlukan variabel terpisah (seperti value: Int
dan isValid: Bool
) atau nilai sentinel yang ditentukan (seperti -1
atau INT_MIN
). Pendekatan ini rawan kesalahan karena mudah lupa untuk memeriksa isValid
atau memeriksa nilai sentinel. Selain itu, jika nilai tertentu dipilih sebagai sentinel, itu berarti nilai tersebut tidak dapat lagi diperlakukan sebagai nilai yang valid .
Tipe opsi seperti Swift Optional
memecahkan masalah ini dengan memperkenalkan nilai khusus yang terpisah nil
(sehingga Anda tidak perlu menentukan nilai sentinel), dan dengan memanfaatkan sistem tipe kuat sehingga kompiler dapat membantu Anda mengingat untuk memeriksa nil bila perlu.
Mengapa saya mendapatkan " kesalahan fatal: tidak terduga ditemukan nol saat membuka bungkus 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 dengan paksa. Jika Anda membuka paksa sebuah opsional, dan tidak memiliki nilai, program Anda akan macet dengan pesan di atas.
Xcode akan menunjukkan kerusakan dengan menyorot sebaris kode. Masalahnya terjadi di baris ini.
Kerusakan ini dapat terjadi dengan dua jenis buka paksa:
1. Pembongkaran Paksa Eksplisit
Ini dilakukan dengan !
operator sebagai opsional. Sebagai contoh:
let anOptionalString: String?
print(anOptionalString!) // <- CRASH
Kesalahan fatal: Tanpa diduga ditemukan nol 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 yang Tidak Dibungkus Secara Implisit
Ini didefinisikan dengan a !
, bukan ?
setelah tipe.
var optionalDouble: Double! // this value is implicitly unwrapped wherever it's used
Opsional ini diasumsikan berisi nilai. Oleh karena itu, setiap kali Anda mengakses opsional yang tidak terbungkus secara implisit, secara otomatis akan dibuka paksa untuk Anda. Jika tidak mengandung nilai, itu akan macet.
print(optionalDouble) // <- CRASH
Kesalahan fatal: Tidak terduga ditemukan nol saat secara implisit membuka bungkus nilai Opsional
Untuk mengetahui variabel mana yang menyebabkan crash, Anda dapat menahan ⌥sambil mengklik untuk menampilkan definisi, di mana Anda mungkin menemukan tipe opsional.
IBOutlets, khususnya, biasanya merupakan opsional yang tidak terbungkus secara implisit. Ini karena xib atau storyboard Anda akan menghubungkan outlet pada waktu proses, setelah inisialisasi. Oleh karena itu, Anda harus memastikan bahwa Anda tidak mengakses outlet sebelum dimuat. Anda juga harus memeriksa apakah koneksi sudah benar di file storyboard / xib Anda, jika tidak, nilainya akan berada nil
pada waktu proses, dan karena itu akan macet saat dibuka bungkusnya secara implisit . Saat memperbaiki koneksi, coba hapus baris kode yang menentukan outlet Anda, lalu sambungkan kembali.
Kapan saya harus membuka paksa Opsional?
Pembongkaran Paksa Eksplisit
Sebagai aturan umum, Anda tidak boleh secara eksplisit memaksa membuka bungkus opsional dengan !
operator. Mungkin ada kasus di mana penggunaan !
dapat diterima - tetapi Anda hanya boleh menggunakannya jika Anda 100% yakin bahwa opsional berisi nilai.
Meskipun mungkin ada saat di mana Anda dapat menggunakan paksa membuka bungkus, seperti yang Anda ketahui fakta bahwa opsional berisi nilai - tidak ada satu tempat pun di mana Anda tidak dapat dengan aman membuka bungkus opsional itu.
Opsional yang Tidak Dibungkus Secara Implisit
Variabel ini dirancang agar Anda dapat menunda tugasnya hingga nanti dalam kode Anda. Merupakan tanggung jawab Anda untuk memastikan bahwa mereka memiliki nilai sebelum Anda mengaksesnya. Namun, karena melibatkan force unrapping, mereka masih secara inheren tidak aman - karena mereka menganggap nilai Anda bukan nil, meskipun menetapkan nil valid.
Anda sebaiknya hanya menggunakan opsi yang tidak terbungkus secara implisit sebagai upaya terakhir . Jika Anda dapat menggunakan variabel malas , atau memberikan nilai default untuk variabel - Anda harus melakukannya daripada menggunakan opsional yang tidak terbungkus secara implisit.
Namun, ada beberapa skenario di mana opsional yang tidak terbungkus secara implisit 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 dapat menangani Opsional dengan aman?
Cara termudah untuk memeriksa apakah suatu opsional berisi nilai, adalah membandingkannya dengan nil
.
if anOptionalInt != nil {
print("Contains a value!")
} else {
print("Doesn’t contain a value.")
}
Namun, 99,9% dari waktu saat bekerja dengan opsional, Anda sebenarnya ingin mengakses nilai yang dikandungnya, jika mengandung satu sama sekali. Untuk melakukan ini, Anda dapat menggunakan Penjilidan Opsional .
Binding opsional
Pengikatan Opsional memungkinkan Anda untuk memeriksa apakah sebuah opsional berisi nilai - dan memungkinkan Anda untuk menetapkan nilai yang tidak dibungkus ke variabel atau konstanta baru. Ini menggunakan sintaks if let x = anOptional {...}
atau if var x = anOptional {...}
, tergantung apakah 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")
}
Apa yang dilakukan ini adalah pertama-tama memeriksa apakah opsional 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 berisi nilai, klausa else akan dipanggil, seperti yang Anda harapkan.
Apa yang menarik tentang penjilidan opsional, adalah Anda dapat membuka beberapa opsional pada saat yang bersamaan. Anda bisa memisahkan pernyataan dengan koma. Pernyataan itu akan berhasil jika semua opsional telah 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 rapi lainnya adalah Anda juga dapat menggunakan koma untuk memeriksa kondisi tertentu pada nilai, setelah membukanya.
if let number = anOptionalInt, number > 0 {
print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}
Satu-satunya tangkapan dengan menggunakan pengikatan opsional dalam pernyataan if, adalah bahwa Anda hanya dapat mengakses nilai yang tidak dibungkus dari dalam cakupan pernyataan. Jika Anda membutuhkan akses ke nilai dari luar cakupan 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 pengikatan opsional, Anda dapat melakukan ini:
guard let number = anOptionalInt else {
return
}
(Perhatikan bahwa di dalam badan penjaga, Anda harus menggunakan salah satu pernyataan pengalihan kontrol untuk keluar dari cakupan kode yang saat ini dijalankan).
Jika anOptionalInt
berisi nilai, itu akan dibuka dan ditetapkan ke number
konstanta baru . Kode setelah penjaga kemudian akan terus dijalankan. Jika tidak mengandung nilai - penjaga akan mengeksekusi kode di dalam tanda kurung, yang akan menyebabkan transfer kontrol, sehingga kode segera setelahnya tidak akan dieksekusi.
Hal yang benar-benar rapi tentang pernyataan penjaga adalah nilai yang tidak dibungkus sekarang tersedia untuk digunakan dalam kode yang mengikuti pernyataan tersebut (seperti yang kita tahu bahwa kode masa depan hanya dapat dijalankan jika opsional memiliki nilai). Ini bagus untuk menghilangkan 'pyramids of doom' yang dibuat dengan membuat beberapa pernyataan if.
Sebagai contoh:
guard let number = anOptionalInt else {
return
}
print("anOptionalInt contains a value, and it’s: \(number)!")
Penjaga juga mendukung trik rapi yang sama dengan yang didukung pernyataan if, seperti membuka bungkusan beberapa opsi secara bersamaan dan menggunakan where
klausa.
Apakah Anda menggunakan pernyataan if atau guard sepenuhnya bergantung pada apakah kode di masa mendatang memerlukan opsional untuk memuat nilai.
Operator Penggabungan Nihil
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 dengan a
(meskipun biasanya non-opsional).
Ini pada dasarnya memungkinkan Anda mengatakan "Jika a
berisi nilai, buka bungkusnya. Jika tidak, maka kembalikan b
sebagai gantinya ”. Misalnya, Anda bisa menggunakannya seperti ini:
let number = anOptionalInt ?? 0
Ini akan menentukan tipe number
konstan Int
, yang akan berisi nilai anOptionalInt
, jika mengandung nilai, atau 0
sebaliknya.
Ini hanya singkatan dari:
let number = anOptionalInt != nil ? anOptionalInt! : 0
Rantai Opsional
Anda dapat menggunakan Rangkaian Opsional untuk memanggil metode atau mengakses properti secara opsional. Ini hanya dilakukan dengan mencadangkan nama variabel dengan a ?
saat menggunakannya.
Misalnya, kita memiliki variabel foo
, dengan tipe Foo
instance opsional .
var foo : Foo?
Jika kami ingin memanggil metode foo
yang tidak mengembalikan apa pun, kami cukup melakukan:
foo?.doSomethingInteresting()
Jika foo
berisi nilai, metode ini akan dipanggil di atasnya. Jika tidak, tidak ada hal buruk yang akan terjadi - kode akan terus berjalan.
(Ini adalah perilaku yang mirip dengan mengirim pesan ke nil
di Objective-C)
Oleh karena itu, ini juga dapat digunakan untuk menyetel 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 berjalan.
Trik rapi lainnya yang memungkinkan Anda lakukan oleh rangkaian opsional adalah memeriksa apakah menyetel properti atau memanggil metode berhasil. Anda dapat melakukan ini dengan membandingkan nilai pengembalian dengan nil
.
(Ini karena nilai opsional akan dikembalikan, Void?
bukan 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 saat mencoba mengakses properti atau memanggil metode yang mengembalikan nilai. Karena foo
bersifat opsional, apa pun yang dikembalikan darinya juga akan menjadi opsional. Untuk mengatasinya, Anda bisa membuka bungkus opsional yang dikembalikan menggunakan salah satu metode di atas - atau membuka bungkusan foo
itu sendiri sebelum mengakses metode atau memanggil metode yang mengembalikan nilai.
Juga, seperti namanya, Anda bisa 'merangkai' pernyataan ini menjadi satu. Ini berarti jika foo
memiliki properti opsional baz
, yang memiliki properti qux
- Anda dapat menulis yang berikut ini:
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 nilai qux
itu sendiri opsional.
map
dan flatMap
Fitur yang sering kurang digunakan dengan pilihan adalah kemampuan untuk menggunakan fungsi map
dan flatMap
. Ini memungkinkan Anda untuk menerapkan transformasi non-opsional ke variabel opsional. Jika sebuah opsional memiliki nilai, Anda dapat menerapkan transformasi yang diberikan padanya. Jika tidak memiliki nilai, ia akan tetap ada nil
.
Misalnya, Anda memiliki string opsional:
let anOptionalString:String?
Dengan menerapkan map
fungsi padanya - kita dapat menggunakan stringByAppendingString
fungsi tersebut 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 allow 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 sama dengan map
, kecuali ini memungkinkan Anda untuk mengembalikan opsional lain dari dalam badan closure. Ini berarti Anda dapat memasukkan opsional ke dalam proses yang memerlukan input non-opsional, tetapi dapat mengeluarkan opsional itu sendiri.
try!
Sistem penanganan kesalahan Swift dapat digunakan dengan aman dengan Do-Try-Catch :
do {
let result = try someThrowingFunc()
} catch {
print(error)
}
Jika someThrowingFunc()
melontarkan kesalahan, kesalahan tersebut 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 menyatakan error
diri Anda sendiri, memiliki keuntungan karena dapat mentransmisikannya ke format yang berguna, 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 "mencoba paksa" dengan try!
:
let result = try! someThrowingFunc()
Konsep yang dijelaskan dalam posting ini juga berlaku di sini: jika terjadi kesalahan, aplikasi akan macet.
Anda hanya boleh menggunakan 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 lengkap - dan yang opsional try?
,, dalam kasus yang jarang terjadi di mana penanganan kesalahan tidak penting.
Sumber daya
TL; DR jawaban
Dengan sedikit pengecualian , aturan ini emas:
Hindari penggunaan !
Deklarasikan variabel opsional ( ?
), bukan opsional yang dibuka secara implisit (IUO) ( !
)
Dengan kata lain, lebih baik gunakan:
var nameOfDaughter: String?
Dari pada:
var nameOfDaughter: String!
Buka bungkus variabel opsional menggunakan if let
atauguard let
Salah satu variabel yang dibuka seperti ini:
if let nameOfDaughter = nameOfDaughter {
print("My daughters name is: \(nameOfDaughter)")
}
Atau seperti ini:
guard let nameOfDaughter = nameOfDaughter else { return }
print("My daughters name is: \(nameOfDaughter)")
Jawaban ini dimaksudkan agar ringkas, untuk pemahaman penuh membaca jawaban yang diterima
Sumber daya
Pertanyaan ini muncul SEPANJANG WAKTU . Ini adalah salah satu hal pertama yang dihadapi oleh pengembang Swift baru.
Latar Belakang:
Swift menggunakan konsep "Opsional" untuk menangani nilai yang bisa berisi nilai, atau tidak. Dalam bahasa lain seperti C, Anda mungkin menyimpan nilai 0 dalam variabel untuk menunjukkan bahwa itu tidak mengandung nilai. Namun, bagaimana jika 0 adalah nilai yang valid? Kemudian Anda bisa menggunakan -1. Bagaimana jika -1 adalah nilai yang valid? Dan seterusnya.
Opsional Swift memungkinkan Anda menyiapkan variabel jenis apa pun agar berisi nilai yang valid, atau tanpa nilai.
Anda memberi tanda tanya setelah tipe ketika Anda mendeklarasikan variabel menjadi berarti (tipe x, atau tanpa nilai).
Opsional sebenarnya adalah wadah yang berisi variabel dari jenis tertentu, atau tidak sama sekali.
Opsional harus "dibuka" untuk mengambil nilai di dalamnya.
The "!" operator adalah operator "paksa membuka". Ia mengatakan "percayalah. Saya tahu apa yang saya lakukan. Saya jamin bahwa ketika kode ini berjalan, variabel tidak akan berisi nol." Jika Anda salah, Anda crash.
Kecuali jika Anda benar - benar tahu apa yang Anda lakukan, hindari tanda "!" paksa operator membuka bungkus. Ini mungkin sumber error terbesar untuk pemrogram Swift pemula.
Bagaimana menangani pilihan:
Ada banyak cara lain untuk menangani pilihan yang lebih aman. Berikut beberapa (bukan daftar lengkap)
Anda dapat menggunakan "pengikatan opsional" atau "jika biarkan" mengatakan "jika opsional ini berisi nilai, simpan nilai itu ke dalam variabel non-opsional baru. Jika opsional tidak berisi nilai, lewati isi pernyataan if ini ".
Berikut adalah contoh penjilidan opsional dengan foo
opsional kami :
if let newFoo = foo //If let is called optional binding. {
print("foo is not nil")
} else {
print("foo is nil")
}
Perhatikan bahwa variabel yang Anda tentukan saat Anda menggunakan penawaran opsional hanya ada (hanya "dalam cakupan") di badan pernyataan if.
Alternatifnya, Anda dapat menggunakan pernyataan penjaga, yang memungkinkan Anda keluar dari fungsi jika variabelnya nil:
func aFunc(foo: Int?) {
guard let newFoo = input else { return }
//For the rest of the function newFoo is a non-optional var
}
Pernyataan penjaga ditambahkan di Swift 2. Penjaga memungkinkan Anda mempertahankan "jalur emas" melalui kode Anda, dan menghindari tingkat if bersarang yang terus meningkat yang terkadang dihasilkan dari penggunaan pengikatan opsional "jika biarkan".
Ada juga konstruksi yang disebut "operator penggabungan nihil". Ini mengambil bentuk "optional_var ?? replacement_val". Ini mengembalikan variabel non-opsional dengan jenis yang sama seperti data yang terdapat dalam opsional. Jika opsional berisi nil, ia mengembalikan nilai ekspresi setelah "??" simbol.
Jadi Anda bisa menggunakan kode seperti ini:
let newFoo = foo ?? "nil" // "??" is the nil coalescing operator
print("foo = \(newFoo)")
Anda juga dapat menggunakan try / catch atau guard error handling, tetapi umumnya salah satu teknik di atas lebih bersih.
EDIT:
Gotcha lain yang sedikit lebih halus dengan pilihan adalah "pilihan yang tidak terbungkus secara implisit. Saat mendeklarasikan foo, kita dapat mengatakan:
var foo: String!
Dalam hal ini foo masih opsional, tetapi Anda tidak perlu membukanya untuk mereferensikannya. Itu berarti setiap kali Anda mencoba mereferensikan foo, Anda macet jika nihil.
Jadi kode ini:
var foo: String!
let upperFoo = foo.capitalizedString
Akan macet saat merujuk ke properti capitalizedString foo meskipun kami tidak membuka paksa foo. cetakannya terlihat bagus, tapi sebenarnya tidak.
Jadi, Anda ingin sangat berhati-hati dengan opsi yang tidak terbungkus secara implisit. (dan mungkin bahkan menghindari mereka sepenuhnya sampai Anda memiliki pemahaman yang kuat tentang pilihan.)
Intinya: Saat Anda pertama kali mempelajari Swift, anggaplah "!" karakter bukan bagian dari bahasa. Kemungkinan akan membuat Anda mendapat masalah.
Karena jawaban di atas dengan jelas menjelaskan cara bermain aman dengan Opsional. Saya akan mencoba menjelaskan apa itu Opsional sebenarnya dengan cepat.
Cara lain untuk mendeklarasikan variabel opsional adalah
var i : Optional<Int>
Dan tipe Opsional tidak lain adalah pencacahan dengan dua kasus, yaitu
enum Optional<Wrapped> : ExpressibleByNilLiteral {
case none
case some(Wrapped)
.
.
.
}
Jadi untuk menetapkan nil ke variabel 'i' kami. Kita dapat melakukan
var i = Optional<Int>.none
atau memberikan nilai, kita akan memberikan beberapa nilai
var i = Optional<Int>.some(28)
Menurut swift, 'nihil' adalah ketiadaan nilai. Dan untuk membuat instance yang diinisialisasi dengan nil
Kami harus mematuhi protokol yang disebut ExpressibleByNilLiteral
dan hebat jika Anda dapat menebaknya, hanya Optionals
menyesuaikan ExpressibleByNilLiteral
dan menyesuaikan dengan jenis lain tidak disarankan.
ExpressibleByNilLiteral
memiliki satu metode yang disebut init(nilLiteral:)
yang menginisialisasi sebuah instace dengan nil. Anda biasanya tidak akan memanggil metode ini dan menurut dokumentasi swift tidak disarankan untuk memanggil penginisialisasi ini secara langsung karena kompilator memanggilnya setiap kali Anda menginisialisasi tipe Opsional dengan nil
literal.
Bahkan diriku sendiri harus membungkus (tidak ada permainan kata-kata) kepalaku di sekitar Opsional: D Selamat Bertukar Semua .
Pertama, Anda harus tahu apa itu nilai Opsional. Anda dapat membuka Bahasa Pemrograman Swift untuk detailnya.
Kedua, Anda harus tahu bahwa nilai opsional memiliki dua status. Satu adalah nilai penuh, dan yang lainnya adalah nilai nihil. Jadi sebelum Anda menerapkan nilai opsional, Anda harus memeriksa statusnya.
Anda bisa menggunakan if let ...
atau guard let ... else
dan sebagainya.
Dengan satu cara lain, jika Anda tidak ingin memeriksa status variabel sebelum menerapkan, Anda juga dapat menggunakan var buildingName = buildingName ?? "buildingName"
.
Saya mengalami kesalahan ini sekali ketika saya mencoba untuk mengatur nilai Outlet saya dari metode persiapan untuk segue sebagai berikut:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? DestinationVC{
if let item = sender as? DataItem{
// This line pops up the error
destination.nameLabel.text = item.name
}
}
}
Kemudian saya menemukan bahwa saya tidak dapat mengatur nilai-nilai outlet pengontrol tujuan karena pengontrol belum dimuat atau diinisialisasi.
Jadi saya menyelesaikannya dengan cara ini:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? DestinationVC{
if let item = sender as? DataItem{
// Created this method in the destination Controller to update its outlets after it's being initialized and loaded
destination.updateView(itemData: item)
}
}
}
Pengontrol Tujuan:
// This variable to hold the data received to update the Label text after the VIEW DID LOAD
var name = ""
// Outlets
@IBOutlet weak var nameLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
nameLabel.text = name
}
func updateView(itemDate: ObjectModel) {
name = itemDate.name
}
Saya harap jawaban ini membantu siapa pun di luar sana dengan masalah yang sama karena saya menemukan jawaban yang ditandai adalah sumber daya yang bagus untuk memahami pilihan dan cara kerjanya tetapi belum mengatasi masalah itu sendiri secara langsung.
Pada dasarnya Anda mencoba menggunakan nilai nil di tempat di mana Swift hanya mengizinkan nilai non-nil, dengan memberi tahu compiler untuk mempercayai Anda bahwa tidak akan pernah ada nilai nil di sana, sehingga memungkinkan aplikasi Anda untuk dikompilasi.
Ada beberapa skenario yang menyebabkan jenis kesalahan fatal ini:
membuka paksa:
let user = someVariable!
Jika
someVariable
nihil, maka Anda akan mengalami crash. Dengan melakukan force unwrap, Anda memindahkan tanggung jawab pemeriksaan nil dari kompilator ke Anda, pada dasarnya dengan melakukan buka paksa Anda menjamin kepada kompilator bahwa Anda tidak akan pernah memiliki nilai nil di sana. Dan coba tebak apa yang terjadi jika entah bagaimana nilai nihil diakhirisomeVariable
?Larutan? Gunakan pengikatan opsional (alias if-let), lakukan pemrosesan variabel di sana:
if user = someVariable { // do your stuff }
gips paksa (turun):
let myRectangle = someShape as! Rectangle
Di sini dengan casting paksa Anda memberi tahu kompiler untuk tidak lagi khawatir, karena Anda akan selalu memiliki
Rectangle
instance di sana. Dan selama itu berlaku, Anda tidak perlu khawatir. Masalah dimulai saat Anda atau kolega Anda dari proyek mulai mengedarkan nilai non-persegi panjang.Larutan? Gunakan pengikatan opsional (alias if-let), lakukan pemrosesan variabel di sana:
if let myRectangle = someShape as? Rectangle { // yay, I have a rectangle }
Opsional yang terbuka secara implisit. Anggaplah Anda memiliki definisi kelas berikut:
class User { var name: String! init() { name = "(unnamed)" } func nicerName() { return "Mr/Ms " + name } }
Sekarang, jika tidak ada yang mengacaukan
name
properti dengan menyetelnyanil
, maka itu berfungsi seperti yang diharapkan, namun jikaUser
diinisialisasi dari JSON yang tidak memilikiname
kunci, maka Anda mendapatkan kesalahan fatal saat mencoba menggunakan properti.Larutan? Jangan gunakan :) Kecuali Anda 102% yakin bahwa properti akan selalu memiliki nilai non-nil pada saat perlu digunakan. Dalam kebanyakan kasus, mengonversi menjadi opsional atau non-opsional akan berfungsi. Menjadikannya non-opsional juga akan menghasilkan kompiler yang membantu Anda dengan memberi tahu jalur kode yang Anda lewatkan memberikan nilai ke properti itu
Outlet yang tidak terhubung atau belum terhubung. Ini adalah kasus tertentu dari skenario # 3. Pada dasarnya Anda memiliki beberapa kelas yang memuat XIB yang ingin Anda gunakan.
class SignInViewController: UIViewController { @IBOutlet var emailTextField: UITextField! }
Sekarang jika Anda melewatkan menghubungkan outlet dari editor XIB, maka aplikasi akan macet segera setelah Anda ingin menggunakan outlet. Larutan? Pastikan semua outlet terhubung. Atau gunakan
?
operator pada mereka:emailTextField?.text = "[email protected]"
. Atau nyatakan outlet sebagai opsional, meskipun dalam kasus ini kompilator akan memaksa Anda untuk membukanya di seluruh kode.Nilai yang berasal dari Objective-C, dan tidak memiliki anotasi nullability. Mari kita asumsikan kita memiliki kelas Objective-C berikut:
@interface MyUser: NSObject @property NSString *name; @end
Sekarang jika tidak ada anotasi nullability yang ditentukan (baik secara eksplisit atau melalui
NS_ASSUME_NONNULL_BEGIN
/NS_ASSUME_NONNULL_END
),name
properti tersebut akan diimpor di Swift sebagaiString!
(IUO - opsional yang dibuka secara implisit). Segera setelah beberapa kode swift ingin menggunakan nilainya, itu akan macet jikaname
nihil.Larutan? Tambahkan anotasi nullability ke kode Objective-C Anda. Berhati-hatilah, compiler Objective-C agak permisif dalam hal nullability, Anda mungkin akan mendapatkan nilai nil, bahkan jika Anda secara eksplisit menandainya sebagai
nonnull
.
Ini lebih merupakan komentar yang penting dan itulah mengapa opsional yang tidak terbungkus secara implisit dapat menipu ketika menyangkut nil
nilai debug .
Pikirkan kode berikut: Ini dikompilasi tanpa kesalahan / peringatan:
c1.address.city = c3.address.city
Namun pada saat runtime memberikan kesalahan berikut: Kesalahan fatal: Tidak terduga ditemukan nihil saat membuka bungkus nilai Opsional
Bisakah Anda memberi tahu saya benda apa itu nil
?
Tidak boleh!
Kode lengkapnya adalah:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var c1 = NormalContact()
let c3 = BadContact()
c1.address.city = c3.address.city // compiler hides the truth from you and then you sudden get a crash
}
}
struct NormalContact {
var address : Address = Address(city: "defaultCity")
}
struct BadContact {
var address : Address!
}
struct Address {
var city : String
}
Singkat cerita dengan menggunakan var address : Address!
Anda menyembunyikan kemungkinan bahwa variabel dapat berasal nil
dari pembaca lain. Dan ketika itu crash Anda seperti "apa-apaan ini ?! saya address
bukan opsional, jadi kenapa saya crash?!.
Karenanya lebih baik menulis seperti itu:
c1.address.city = c2.address!.city // ERROR: Fatal error: Unexpectedly found nil while unwrapping an Optional value
Bisakah Anda sekarang memberi tahu saya benda apa itu nil
?
Kali ini kode telah dibuat lebih jelas untuk Anda. Anda dapat merasionalisasi dan berpikir bahwa kemungkinan besar itu adalah address
parameter yang dibuka secara paksa.
Kode lengkapnya adalah:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var c1 = NormalContact()
let c2 = GoodContact()
c1.address.city = c2.address!.city
c1.address.city = c2.address?.city // not compile-able. No deceiving by the compiler
c1.address.city = c2.address.city // not compile-able. No deceiving by the compiler
if let city = c2.address?.city { // safest approach. But that's not what I'm talking about here.
c1.address.city = city
}
}
}
struct NormalContact {
var address : Address = Address(city: "defaultCity")
}
struct GoodContact {
var address : Address?
}
struct Address {
var city : String
}
Kesalahan EXC_BAD_INSTRUCTION
dan fatal error: unexpectedly found nil while implicitly unwrapping an Optional value
paling banyak muncul saat Anda menyatakan @IBOutlet
, tetapi tidak terhubung ke papan cerita .
Anda juga harus mempelajari tentang cara kerja Opsional , yang disebutkan dalam jawaban lain, tetapi ini adalah satu-satunya waktu yang paling terlihat oleh saya.
Jika Anda mendapatkan kesalahan ini di CollectionView, coba buat file CustomCell dan Custom xib juga.
tambahkan kode ini di ViewDidLoad () di mainVC.
let nib = UINib(nibName: "CustomnibName", bundle: nil)
self.collectionView.register(nib, forCellWithReuseIdentifier: "cell")
Saya menemukan kesalahan ini saat membuat segue dari pengontrol tampilan tabel ke pengontrol tampilan karena saya lupa menentukan nama kelas khusus untuk pengontrol tampilan di papan cerita utama.
Sesuatu yang sederhana yang patut diperiksa jika semuanya terlihat baik-baik saja