Apa yang dimaksud dengan "Kesalahan fatal: Tidak terduga ditemukan nih saat membuka bungkusan nilai Opsional"?

415

Program Swift saya macet dengan EXC_BAD_INSTRUCTIONdan salah satu dari kesalahan serupa berikut. Apa artinya kesalahan ini, dan bagaimana cara memperbaikinya?

Kesalahan fatal: Tidak terduga ditemukan nil saat membuka bungkus nilai Opsional

atau

Kesalahan fatal: Tidak terduga ditemukan nihil sementara secara implisit membuka bungkusan nilai Opsional


Posting ini dimaksudkan untuk mengumpulkan jawaban atas masalah "yang tidak terduga ditemukan", sehingga tidak tersebar dan sulit ditemukan. Jangan ragu untuk menambahkan jawaban Anda sendiri atau mengedit jawaban wiki yang ada.

pkamb
sumber
8
@RobertColumbia, ini adalah pasangan Tanya Jawab Komunitas Wiki dengan dokumentasi masalah yang luas, saya tidak berpikir pertanyaan ini harus ditutup karena kurangnya MCVE.
JAL
Buat variabel seperti ini [var nameOfDaughter: String? ] dan gunakan variabel seperti ini [jika biarkan _ = nameOfDaughter {}] adalah pendekatan terbaik.
iOS

Jawaban:

673

Jawaban ini adalah wiki komunitas . Jika Anda merasa ini bisa dibuat lebih baik, silakan mengeditnya !

Latar Belakang: Apa itu Opsional?

Dalam Swift, Optionaladalah 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 -1digunakan 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 niltidak sama dengan nildi Objective-C. Di Objective-C, niladalah 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.

garis jatuh

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 anOptionalStringyang nildi 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 nilsaat 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 anOptionalIntmengandung nilai, itu akan dibuka dan ditugaskan ke numberkonstanta 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 whereklausa.

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 aadalah tipe opsional dan tipe byang sama a(walaupun biasanya non-opsional).

Ini pada dasarnya memungkinkan Anda mengatakan "Jika aberisi nilai, buka bungkusannya. Jika tidak maka kembalilah bsebagai gantinya ”. Misalnya, Anda dapat menggunakannya seperti ini:

let number = anOptionalInt ?? 0

Ini akan menentukan numberkonstanta Inttipe, yang akan mengandung nilai anOptionalInt, jika mengandung nilai, atau 0sebaliknya.

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 Fooinstance opsional .

var foo : Foo?

Jika kami ingin memanggil metode fooyang tidak mengembalikan apa pun, kami dapat melakukannya:

foo?.doSomethingInteresting()

Jika fooberisi 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 nildi 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 fooadalah 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 Voidpada 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 foobersifat 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 foosendiri sebelum mengakses metode atau metode panggilan yang mengembalikan nilai.

Juga, seperti namanya, Anda dapat 'rantai' pernyataan ini bersama-sama. Ini berarti bahwa jika foomemiliki properti opsional baz, yang memiliki properti qux- Anda dapat menulis yang berikut:

let optionalQux = foo?.baz?.qux

Sekali lagi, karena foodan bazbersifat opsional, nilai yang dikembalikan dari quxakan selalu menjadi opsional terlepas dari apakah quxitu sendiri opsional.

map dan flatMap

Fitur yang sering kurang digunakan dengan opsional adalah kemampuan untuk menggunakan mapdan flatMapfungsinya. 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 mapfungsi ke dalamnya - kita dapat menggunakan stringByAppendingStringfungsi untuk menggabungkannya ke string lain.

Karena stringByAppendingStringmengambil argumen string non-opsional, kami tidak dapat memasukkan string opsional kami secara langsung. Namun, dengan menggunakan map, kita dapat menggunakan izin stringByAppendingStringuntuk digunakan jika anOptionalStringmemiliki nilai.

Sebagai contoh:

var anOptionalString:String? = "bar"

anOptionalString = anOptionalString.map {unwrappedString in
    return "foo".stringByAppendingString(unwrappedString)
}

print(anOptionalString) // Optional("foobar")

Namun, jika anOptionalStringtidak memiliki nilai, mapakan kembali nil. Sebagai contoh:

var anOptionalString:String?

anOptionalString = anOptionalString.map {unwrappedString in
    return "foo".stringByAppendingString(unwrappedString)
}

print(anOptionalString) // nil

flatMapbekerja 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 catchblok.

The errorkonstan Anda lihat dalam catchblok belum dinyatakan oleh kami - itu secara otomatis oleh catch.

Anda juga dapat mendeklarasikan errordiri 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 trycara 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

Hamish
sumber
87
Secara pribadi, saya ingin mengucapkan terima kasih karena telah berupaya untuk menulis semua ini. Saya merasa seperti ini tentu akan sangat membantu bagi pemula dan para ahli yang menggunakan swift.
Pranav Wadhwa
15
Saya senang Anda merasa terbantu. Jawaban ini adalah wiki komunitas, jadi ini adalah kolaborasi antara banyak orang (7, sejauh ini)!
jtbandes
1
Kesalahan ini terputus-putus dalam kasus saya. Ada saran? Kode dalam stackoverflow.com/questions/50933681/…
py_ios_dev
1
Mungkin inilah saatnya untuk memperbarui jawaban ini untuk digunakan compactMap()alih-alih flatMap().
Nicolas Miari
jadi saya kira solusi terbaik dan pendek adalah "Operator Penggabungan Nil", kan?
mehdigriche
65

TL; DR jawab

Dengan sedikit pengecualian , aturan ini berwarna emas:

Hindari penggunaan !

Deklarasikan variabel opsional ( ?), bukan opsional yang tidak terbuka (IUO) ( !)

Dengan kata lain, lebih baik gunakan:
var nameOfDaughter: String?

Dari pada:
var nameOfDaughter: String!

Buka bungkusan variabel opsional menggunakan if letatauguard let

Baik membuka bungkusan variabel 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 untuk ringkas, untuk pemahaman penuh membaca jawaban yang diterima


Sumber daya

Sajjon
sumber
40

Pertanyaan ini muncul SEPANJANG WAKTU di SO. Ini adalah salah satu hal pertama yang dihadapi oleh pengembang Swift baru.

Latar Belakang:

Swift menggunakan konsep "Opsional" untuk menangani nilai yang dapat mengandung nilai, atau tidak. Dalam bahasa lain seperti C, Anda dapat menyimpan nilai 0 dalam variabel untuk menunjukkan bahwa itu tidak mengandung nilai. Namun, bagaimana jika 0 adalah nilai yang valid? Maka Anda mungkin menggunakan -1. Bagaimana jika -1 adalah nilai yang valid? Dan seterusnya.

Opsional Swift memungkinkan Anda mengatur variabel jenis apa pun untuk mengandung nilai yang valid, atau tidak ada nilai.

Anda memberi tanda tanya setelah tipe ketika Anda mendeklarasikan variabel ke mean (tipe x, atau tidak ada nilai).

Opsional sebenarnya adalah sebuah wadah selain berisi variabel jenis tertentu, atau tidak sama sekali.

Pilihan harus "dibuka" untuk mengambil nilai di dalamnya.

"!" operator adalah operator "buka paksa". Itu mengatakan "percayalah padaku. Aku tahu apa yang aku lakukan. Aku menjamin bahwa ketika kode ini berjalan, variabel tidak akan mengandung nihil." Jika Anda salah, Anda mengalami crash.

Kecuali Anda benar - benar tahu apa yang Anda lakukan, hindari "!" memaksa membuka bungkusan operator. Ini mungkin sumber crash terbesar untuk memulai programmer Swift.

Cara berurusan dengan opsional:

Ada banyak cara lain untuk berurusan dengan opsional yang lebih aman. Berikut adalah beberapa (bukan daftar lengkap)

Anda dapat menggunakan "penjilidan opsional" atau "jika membiarkan" mengatakan "jika opsi ini mengandung nilai, simpan nilai itu ke variabel baru, non-opsional. Jika opsional tidak mengandung nilai, lewati badan pernyataan if ini ".

Berikut adalah contoh pengikatan opsional dengan fooopsional 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 tetapkan saat menggunakan biding opsional hanya ada (hanya "dalam cakupan") di badan pernyataan if.

Sebagai alternatif, Anda bisa menggunakan pernyataan penjaga, yang memungkinkan Anda keluar dari fungsi Anda jika variabelnya nihil:

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 dalam Swift 2. Guard memungkinkan Anda mempertahankan "jalur emas" melalui kode Anda, dan menghindari tingkat peningkatan bersarang yang kadang-kadang dihasilkan dari penggunaan "jika dibiarkan" pengikatan opsional.

Ada juga konstruksi yang disebut "operator penggabungan nil". Dibutuhkan bentuk "optional_var ?? replacement_val". Ini mengembalikan variabel non-opsional dengan tipe yang sama dengan data yang terkandung dalam opsional. Jika opsional berisi nil, itu 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 bisa menggunakan try / catch atau guard error handling, tetapi umumnya salah satu teknik lain di atas lebih bersih.

EDIT:

Satu lagi, gotcha yang sedikit lebih halus dengan opsional adalah "opsional yang tidak terbuka secara implisit. Ketika kita mendeklarasikan foo, kita dapat mengatakan:

var foo: String!

Dalam hal ini foo masih merupakan opsional, tetapi Anda tidak perlu membukanya untuk referensi. Itu berarti setiap kali Anda mencoba referensi foo, Anda gagal jika tidak ada.

Jadi kode ini:

var foo: String!


let upperFoo = foo.capitalizedString

Akan macet karena referensi ke properti Kapitalisasi foo meskipun kami tidak membuka paksa foo. hasil cetak terlihat bagus, tetapi tidak.

Dengan demikian, Anda ingin benar-benar berhati-hati dengan pilihan yang secara implisit tidak terbuka. (dan mungkin bahkan menghindari mereka sepenuhnya sampai Anda memiliki pemahaman yang kuat tentang opsional.)

Intinya: Ketika Anda pertama kali belajar Swift, berpura-pura "!" karakter bukan bagian dari bahasa. Mungkin membuat Anda kesulitan.

Duncan C
sumber
7
Anda harus mempertimbangkan menjadikan ini wiki publik.
vacawama
3
Edit jawaban Anda, dan di bawah kotak edit di sebelah kanan adalah kotak centang wiki komunitas . Anda tidak akan lagi mendapatkan perwakilan untuk jawabannya, tetapi saya tahu ini bukan perwakilan yang terang-terangan.
vacawama
6
Mengingat bahwa pertanyaan ini ditanyakan sejuta kali di situs, jika saya akan meluangkan waktu untuk memposting pertanyaan yang dijawab sendiri sebagai jawaban kanonik untuk pertanyaan tersebut, saya berharap jawabannya jauh lebih lengkap daripada ini. Tidak ada tentang guardklausa. Tidak ada tentang penggunaan if varyang sama validnya dengan konstruk if let. Tidak ada apa-apa tentang whereklausa yang saya rasa layak disebutkan ketika kita berbicara tentang if letmengikat (ini menghilangkan seluruh lapisan sarang dalam banyak kasus). Tidak ada apa pun tentang rantai opsional.
nhgrif
6
Dan ketika Anda menyebutkan opsional yang tidak terbungkus secara implisit, Anda bahkan tidak menyebutkan bahwa Anda dapat menggunakan semua trik opsional yang disebutkan di atas untuk secara aman membuka opsi yang tidak terbuka secara implisit. Kami juga tidak membahas membuka beberapa opsi dalam klausa yang sama.
nhgrif
1
@ nhgrif, saya memang mengatakan "Anda juga bisa menggunakan try / catch atau guard error handling, tetapi umumnya salah satu teknik lain di atas lebih bersih." Anda tentu memiliki beberapa poin bagus. Ini adalah topik yang cukup besar. Bagaimana dengan menyumbangkan jawaban Anda sendiri yang mencakup hal-hal yang saya tidak suka membuat komentar sniping? Dengan begitu pertanyaan dan jawabannya menjadi aset yang lebih baik ke situs.
Duncan C
13

Karena jawaban di atas dengan jelas menjelaskan cara bermain dengan aman dengan Opsional. Saya akan mencoba menjelaskan apa yang benar-benar opsional dalam cepat.

Cara lain untuk mendeklarasikan variabel opsional adalah

var i : Optional<Int>

Dan jenis opsional hanyalah enumerasi dengan dua kasus, yaitu

 enum Optional<Wrapped> : ExpressibleByNilLiteral {
    case none 
    case some(Wrapped)
    .
    .
    .
}

Jadi untuk menetapkan nol ke variabel kami 'i'. Kita dapat melakukan var i = Optional<Int>.none atau untuk menetapkan suatu nilai, kita akan memberikan beberapa nilai var i = Optional<Int>.some(28)

Menurut swift, 'nil' adalah tidak adanya nilai. Dan untuk membuat instance yang diinisialisasi dengan nilKita harus menyesuaikan diri dengan protokol yang disebut ExpressibleByNilLiteraldan hebat jika Anda dapat menebaknya, hanya Optionalsmematuhi ExpressibleByNilLiteraldan menyesuaikan dengan tipe lain tidak disarankan.

ExpressibleByNilLiteralmemiliki metode tunggal yang disebut init(nilLiteral:)yang menginisialisasi instace dengan nil. Anda biasanya tidak akan memanggil metode ini dan menurut dokumentasi cepat, tidak disarankan untuk memanggil inisialisasi ini secara langsung ketika kompiler memanggilnya setiap kali Anda menginisialisasi tipe opsional dengan nilliteral.

Bahkan saya sendiri harus membungkus (tidak bermaksud kata-kata) kepalaku di sekitar Opsional: D Happy Swfting All .

Tharzeez
sumber
12

Pertama, Anda harus tahu apa nilai Opsional. Anda dapat melangkah ke Bahasa Pemrograman Swift untuk detail.

Kedua, Anda harus tahu nilai opsional memiliki dua status. Satu adalah nilai penuh, dan yang lainnya adalah nilai nol. Jadi sebelum Anda menerapkan nilai opsional, Anda harus memeriksa negara mana itu.

Anda bisa menggunakan if let ...atau guard let ... elsedan sebagainya.

Salah satu cara lain, jika Anda tidak ingin memeriksa status variabel sebelum implementasi Anda, Anda juga dapat menggunakannya var buildingName = buildingName ?? "buildingName".

QiunCheng
sumber
8

Saya memiliki kesalahan ini satu kali ketika saya mencoba untuk menetapkan 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 mengetahui bahwa saya tidak dapat menetapkan nilai outlet controller tujuan karena controller belum dimuat atau diinisialisasi.

Jadi saya memecahkannya 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)
        }
    }
}

Pengendali 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 seperti saya menemukan jawaban yang ditandai adalah sumber yang bagus untuk memahami pilihan dan bagaimana mereka bekerja tetapi belum membahas masalah itu sendiri secara langsung.

Wissa
sumber
Hanya saja, jangan lupa menyebutkan tempat untuk memanggil updateViewpengontrol tujuan;)
Ahmad F
@AhmadF poin bagus. Namun memanggil updateViewkontroler tujuan tidak diperlukan dalam kasus ini karena saya menggunakan namevariabel untuk mengatur nameLabel.textdi viewDidLoad. Tetapi jika kita melakukan banyak pengaturan, itu pasti akan lebih baik membuat fungsi lain dan memanggilnya viewDidLoad.
Wissa
7

Pada dasarnya Anda mencoba menggunakan nilai nihil di tempat-tempat di mana Swift hanya memperbolehkan yang tidak nihil, dengan memberi tahu kompiler untuk mempercayai Anda bahwa tidak akan pernah ada nilai nihil di sana, sehingga memungkinkan aplikasi Anda untuk dikompilasi.

Ada beberapa skenario yang menyebabkan kesalahan fatal seperti ini:

  1. membuka paksa:

    let user = someVariable!

    Jika someVariable nihil, maka Anda akan mengalami kerusakan. Dengan melakukan pembungkusan paksa, Anda memindahkan tanggung jawab nil check dari compiler ke Anda, pada dasarnya dengan melakukan bongkar paksa Anda menjamin ke kompiler bahwa Anda tidak akan pernah memiliki nilai nil di sana. Dan coba tebak apa yang terjadi jika entah bagaimana nilai nol berakhir di someVariable?

    Larutan? Gunakan penjilidan opsional (alias jika-biarkan), lakukan pemrosesan variabel di sana:

    if user = someVariable {
        // do your stuff
    }
  2. gips paksa (bawah):

    let myRectangle = someShape as! Rectangle

    Di sini dengan paksa casting Anda memberitahu kompiler untuk tidak lagi khawatir, karena Anda akan selalu memiliki Rectangleinstance di sana. Dan selama itu berlaku, Anda tidak perlu khawatir. Masalah dimulai ketika Anda atau kolega Anda dari proyek mulai mengedarkan nilai-nilai non-persegi panjang.

    Larutan? Gunakan penjilidan opsional (alias jika-biarkan), lakukan pemrosesan variabel di sana:

    if let myRectangle = someShape as? Rectangle {
        // yay, I have a rectangle
    }
  3. Opsinya terbuka secara implisit. Mari kita asumsikan 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 nameproperti dengan mengaturnya nil, maka ia berfungsi seperti yang diharapkan, namun jikaUser diinisialisasi dari JSON yang tidak memiliki namekunci, maka Anda mendapatkan kesalahan fatal ketika mencoba menggunakan properti.

    Larutan? Jangan menggunakannya :) Kecuali Anda 102% yakin bahwa properti akan selalu memiliki nilai nihil pada saat itu perlu digunakan. Dalam kebanyakan kasus, mengonversi ke opsional atau non-opsional akan berfungsi. Menjadikannya non-opsional juga akan menghasilkan kompiler membantu Anda dengan memberi tahu jalur kode yang Anda lewatkan memberikan nilai ke properti itu

  4. Outlet yang tidak terhubung, atau belum terhubung. Ini adalah kasus khusus dari skenario # 3. Pada dasarnya Anda memiliki beberapa kelas 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 menyatakan outlet sebagai opsional, meskipun dalam hal ini kompiler akan memaksa Anda untuk membuka seluruh kode.

  5. Nilai yang berasal dari Objective-C, dan yang tidak memiliki penjelasan nullability. Mari kita asumsikan kita memiliki kelas Objective-C berikut:

    @interface MyUser: NSObject
    @property NSString *name;
    @end

    Sekarang jika tidak ada penjelasan nullability ditentukan (baik secara eksplisit atau melalui NS_ASSUME_NONNULL_BEGIN/ NS_ASSUME_NONNULL_END), maka nameproperti akan diimpor di Swift sebagai String!(IUO - opsional yang secara implisit tidak terbuka). Segera setelah beberapa kode cepat ingin menggunakan nilainya, kode tersebut akan mogok jika nametidak ada.

    Larutan? Tambahkan anotasi nullability ke kode Objective-C Anda. Namun berhati-hatilah, kompiler Objective-C sedikit permisif ketika datang ke nullability, Anda mungkin berakhir dengan nilai nil, bahkan jika Anda secara eksplisit menandainya sebagai nonnull.

Cristik
sumber
2

Ini lebih dari komentar yang penting dan mengapa opsional opsional yang terbuka dapat menipu ketika datang ke nilnilai debugging .

Pikirkan kode berikut: Ini dikompilasi tanpa kesalahan / peringatan:

c1.address.city = c3.address.city

Namun saat runtime ia memberikan kesalahan berikut: Kesalahan fatal: Tidak terduga ditemukan nihil saat membuka bungkusan nilai opsional

Bisakah Anda memberi tahu saya objek yang mana nil?

Kamu tidak bisa!

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
}

Cerpen pendek dengan menggunakan var address : Address!Anda bersembunyi kemungkinan bahwa variabel dapat nildari pembaca lain. Dan ketika crash kamu seperti "apa sih ?!address bukan opsional, jadi mengapa saya menabrak?!.

Karena itu lebih baik menulis seperti itu:

c1.address.city = c2.address!.city  // ERROR:  Fatal error: Unexpectedly found nil while unwrapping an Optional value 

Dapatkah Anda sekarang memberi tahu saya objek mana yang dulu nil ?

Kali ini kode telah dibuat lebih jelas bagi Anda. Anda dapat merasionalisasi dan berpikir bahwa itu mungkinaddress parameter yang dibuka 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
}
Sayang
sumber
2

Kesalahan EXC_BAD_INSTRUCTIONdan fatal error: unexpectedly found nil while implicitly unwrapping an Optional valuemuncul paling banyak ketika Anda telah menyatakan @IBOutlet, tetapi tidak terhubung ke storyboard .

Anda juga harus belajar tentang cara kerja Opsional , yang disebutkan dalam jawaban lain, tetapi ini adalah satu-satunya waktu yang paling banyak muncul bagi saya.

Ale Mohamad
sumber
bukankah seharusnya @IBOutletpenyebab kesalahan ini memiliki kesalahan Fatal: Tidak terduga ditemukan nihil sementara secara implisit membuka bungkusan versi nilai opsional kesalahan?
pkamb
1
Itu benar. Mungkin ketika saya mengirim jawaban itulah yang saya maksud, dan saya salin menempel pesan kesalahan fatal pertama. Jawaban dari Hamish terlihat sangat lengkap tentang ini.
Ale Mohamad
2

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")
ShigaSuresh
sumber
0

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 storyboard utama.

Sesuatu yang sederhana yang perlu diperiksa jika semuanya terlihat ok

Mike Volmar
sumber