「致命錯誤:在解開可選值時意外發現nil」是什麼意思?

個人Swift程序因EXC_BAD_INSTRUCTION和如下錯誤而崩潰。 這是什麼意思,我該如何解決? html

致命錯誤:解開Optional值時意外發現nil ios


該帖子旨在收集「意外發現的零」問題的答案,以使它們不會分散且很難找到。 隨意添加您本身的答案或編輯現有的Wiki答案。 程序員


#1樓

這個答案是社區維基 若是您認爲它能夠作得更好,請隨時對其進行編輯 編程

背景:什麼是可選的?

在Swift中, Optional是一個泛型類型 ,能夠包含一個值(任何類型),或者根本不包含任何值。 swift

在許多其餘編程語言中,一般使用特定的「前哨」值來指示缺乏值 。 例如,在Objective-C中, nil空指針 )指示缺乏對象。 但這在處理原始類型時變得更加棘手-應該使用-1來指示缺乏整數,或者可能INT_MIN或其餘某個整數嗎? 若是選擇任何特定值表示「無整數」,則意味着它再也不能夠視爲有效值api

Swift是一種類型安全的語言,這意味着該語言可幫助您弄清代碼可使用的值的類型。 若是代碼的一部分須要一個字符串,則類型安全性可防止您誤將其傳遞給Int。 安全

在Swift中, 任何類型均可以設爲optional 。 可選的值能夠從原始類型,任何值特殊值nil閉包

可選用?定義? 類型的後綴: app

var anInt: Int = 42
var anOptionalInt: Int? = 42
var anotherOptionalInt: Int?    // `nil` is the default when no value is provided

nil表示可選項中缺乏值: 編程語言

anOptionalInt = nil

(請注意,此nil與Objective-C中的nil 。在Objective-C中, nil是缺乏有效的對象指針 ;在Swift中,Optionals不限於對象/引用類型。Optional的行爲相似於Haskell的行爲也許 。)


爲何會出現「 致命錯誤:在展開可選值時意外發現nil 」?

爲了訪問可選值(若是它有一個值),您須要將其拆開 。 能夠安全或強制打開可選值。 若是強行解開一個可選值,但它沒有值,則程序將崩潰,並顯示以上消息。

Xcode將經過突出顯示一行代碼來向您顯示崩潰。 在這條線上發生問題。

撞線

兩種不一樣類型的強制展開會發生此崩潰:

1.顯式展開力

這是用! 可選的運算符。 例如:

let anOptionalString: String?
print(anOptionalString!) // <- CRASH

因爲anOptionalString在此處nil ,所以您在強制展開它的行上會發生崩潰。

2.隱式展開的可選

這些用!定義! ,而不是? 類型以後。

var optionalDouble: Double!   // this value is implicitly unwrapped wherever it's used

假定這些可選參數包含一個值。 所以,每當您訪問隱式展開的可選內容時,它將自動爲您強制展開。 若是不包含值,它將崩潰。

print(optionalDouble) // <- CRASH

爲了找出致使崩潰的變量,您能夠在單擊以顯示定義時按住⌥ ,在這裏能夠找到可選類型。

特別是IBOutlet,一般是隱式解包的可選。 這是由於您的xib或情節提要初始化後將在運行時鏈接出口。 所以,您應該確保在裝入插座以前不要訪問插座。還應該檢查故事板/ xib文件中的鏈接是否正確,不然值在運行時將爲nil ,所以在隱式包含它們時會崩潰解開。 固定鏈接時,請嘗試刪除定義插座的代碼行,而後從新鏈接它們。


我何時應該強制解開Optional?

顯式展開力

一般,永遠不要用!強制強行打開可選項! 操做員。 在某些狀況下,可能會使用! 是能夠接受的,但只有在100%肯定可選值包含一個值的狀況下,才應使用它。

雖然可能有某種狀況下可使用武力展開,你也知道對於一個可選包含一個值一個事實 -沒有一個單一的地方,你不能安全地打開那可選替代。

隱式展開的可選

設計這些變量是爲了使您能夠將它們的分配推遲到之後的代碼中。 您有責任在訪問它們以前確保它們具備價值。 可是,因爲它們涉及強制展開,所以它們本質上仍然是不安全的,由於即便您指定nil有效,它們也會假定您的值不爲nil。

您應僅將隱式解包的可選內容用做最後的選擇 。 若是您可使用惰性變量或爲變量提供默認值 ,則應這樣作,而不要使用隱式展開的可選變量。

可是,在某些狀況下,隱式展開的可選內容是有好處的 ,而且您仍然可使用下面列出的各類方式來安全地展開它們-但始終應謹慎使用。


我如何安全地處理可選項目?

檢查可選變量是否包含值的最簡單方法是將其與nil比較。

if anOptionalInt != nil {
    print("Contains a value!")
} else {
    print("Doesn’t contain a value.")
}

可是,使用可選參數時,實際上99.9%的時間都須要訪問它包含的值(若是它包含一個值)。 爲此,您可使用Optional Binding

可選裝訂

可選綁定容許您檢查可選項是否包含值–並容許您將展開的值分配給新變量或常量。 if let x = anOptional {...}if var x = anOptional {...} ,則使用語法,具體取決於綁定後是否須要修改新變量的值。

例如:

if let number = anOptionalInt {
    print("Contains a value! It is \(number)!")
} else {
    print("Doesn’t contain a number")
}

首先,請檢查可選內容是否包含值。 若是 ,則將「 unwrapped」值分配給新變量( number )–而後您能夠自由使用它,就像它是非可選的同樣。 若是可選參數包含值,則將按照您的指望調用else子句。

關於可選綁定的巧妙之處在於,您能夠同時解開多個可選對象。 您能夠只用逗號分隔語句。 若是全部可選選項均未包裝,則該語句將成功。

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")
}

另外一個巧妙的技巧是,在展開值後,您還可使用逗號來檢查該值的特定條件。

if let number = anOptionalInt, number > 0 {
    print("anOptionalInt contains a value: \(number), and it’s greater than zero!")
}

在if語句中使用可選綁定的惟一陷阱是,您只能從該語句的範圍內訪問未包裝的值。 若是須要從語句範圍以外訪問該值,則可使用guard語句

保護性語句使您能夠定義成功的條件–只有知足該條件,當前做用域纔會繼續執行。 它們用語法guard condition else {...}

所以,要將它們與可選綁定一塊兒使用,能夠執行如下操做:

guard let number = anOptionalInt else {
    return
}

(請注意,在防禦體內, 必須使用控制傳遞語句之一才能退出當前正在執行的代碼的範圍)。

若是anOptionalInt包含一個值,則將其解開並分配給新的number常量。 保護的代碼將繼續執行。 若是它不包含值,則保護程序將執行方括號內的代碼,這將致使控制權的轉移,所以緊隨其後的代碼將不會執行。

關於保護語句的真正精妙之處在於,展開後的值如今能夠在該語句後的代碼中使用(由於咱們知道,未來的代碼只能在可選值具備值的狀況執行)。 這對於消除經過嵌套多個if語句而建立的「厄運金字塔」很是有用

例如:

guard let number = anOptionalInt else {
    return
}

print("anOptionalInt contains a value, and it’s: \(number)!")

警衛隊還支持if語句支持的相同技巧,例如同時展開多個可選對象並使用where子句。

是否使用if或guard語句徹底取決於未來的任何代碼是否要求可選變量包含值。

無合併運算符

Nil合併運算符三元條件運算符的精簡版本,主要用於將可選選項轉換爲非可選選項。 它具備語法a ?? b a ?? b ,其中a是一個可選的類型和b是相同的類型a (儘管一般非可選)。

從本質上講,您能夠說「若是a包含一個值,則將其取消包裝。 若是沒有,則返回b 」。 例如,您能夠這樣使用它:

let number = anOptionalInt ?? 0

這將定義一個Int類型的number常量,若是包含一個值,則將包含anOptionalInt的值,不然將包含0

它只是如下方面的簡寫:

let number = anOptionalInt != nil ? anOptionalInt! : 0

可選連接

您可使用可選連接來調用方法或訪問可選屬性。 只需在變量名後加上?便可完成此操做? 使用時。

例如,假設咱們有一個變量foo ,其類型爲可選的Foo實例。

var foo : Foo?

若是咱們想在foo上調用不返回任何內容的方法,則能夠簡單地執行如下操做:

foo?.doSomethingInteresting()

若是foo包含一個值,則將在其上調用此方法。 若是沒有,則不會發生任何很差的事情-代碼將繼續繼續執行。

(這相似於在Objective-C中將消息發送到nil行爲)

所以,這也能夠用於設置屬性以及調用方法。 例如:

foo?.bar = Bar()

一樣,若是foonil那麼這裏也不會發生任何很差的事情。 您的代碼將繼續執行。

可選連接容許您執行的另外一種巧妙技巧是檢查設置屬性或調用方法是否成功。 您能夠經過將返回值與nil進行比較來作到這一點。

(這是由於可選值將返回Void?而不是不返回任何內容的方法上的Void

例如:

if (foo?.bar = Bar()) != nil {
    print("bar was set successfully")
} else {
    print("bar wasn’t set successfully")
}

可是,在嘗試訪問屬性或調用返回值的方法時,事情變得有些棘手。 由於foo是可選的,因此從它返回的任何內容也將是可選的。 爲了解決這個問題,您能夠解開使用上述方法之一返回的可選選項–或在訪問返回值的方法或調用返回值的方法以前解開foo自己。

一樣,顧名思義,您能夠將這些語句「連接」在一塊兒。 這意味着,若是foo具備可選屬性baz ,而該屬性具備屬性qux ,則能夠編寫如下代碼:

let optionalQux = foo?.baz?.qux

一樣,因爲foobaz是可選的,所以不管qux自己是否可選,從qux返回的值將始終是可選的。

mapflatMap

可選功能常用不足的功能是可使用mapflatMap函數。 這些容許您將非可選轉換應用於可選變量。 若是可選值具備值,則能夠對其應用給定的轉換。 若是沒有值,它將保持nil

例如,假設您有一個可選的字符串:

let anOptionalString:String?

經過對其應用map函數–咱們可使用stringByAppendingString函數將其鏈接到另外一個字符串。

因爲stringByAppendingString採用非可選字符串參數,所以咱們沒法直接輸入可選字符串。 可是,經過使用map ,若是anOptionalString具備值,咱們可使用allow stringByAppendingString

例如:

var anOptionalString:String? = "bar"

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

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

可是,若是anOptionalString沒有值,則map將返回nil 。 例如:

var anOptionalString:String?

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

print(anOptionalString) // nil

flatMap工做方式與map類似,不一樣之處在於,它容許您從閉包體內返回另外一個可選內容。 這意味着您能夠將可選項輸入到須要非可選輸入的過程當中,但能夠輸出可選項自己。

try!

Swift的錯誤處理系統能夠與Do-Try-Catch一塊兒安全使用:

do {
    let result = try someThrowingFunc() 
} catch {
    print(error)
}

若是someThrowingFunc()引起錯誤,該錯誤將被安全地捕獲在catch塊中。

您沒有在catch塊中看到的error常量已經由咱們聲明,它是catch自動生成的。

您也能夠本身聲明error ,它具備將其轉換爲有用格式的優勢,例如:

do {
    let result = try someThrowingFunc()    
} catch let error as NSError {
    print(error.debugDescription)
}

以這種方式使用try是嘗試,捕獲和處理來自throwing函數的錯誤的正確方法。

還有try? 吸取錯誤:

if let result = try? someThrowingFunc() {
    // cool
} else {
    // handle the failure, but there's no error information available
}

可是Swift的錯誤處理系統還提供了一種經過try來「強制嘗試」的方法try!

let result = try! someThrowingFunc()

這篇文章中解釋的概念也適用於此:若是引起錯誤,則應用程序將崩潰。

您只能使用try! 若是您能夠證實其結果在您的狀況下永遠不會失敗-這是很是罕見的。

大多數時候,您將使用完整的Do-Try-Catch系統-可選系統,請try? ,在極少數狀況下,處理錯誤並不重要。


資源資源


#2樓

這個問題出如今SO 全部的時間 。 這是新的Swift開發人員面臨的第一件事。

背景:

Swift使用「可選」的概念來處理可能包含值或不包含值的值。 在其餘語言(例如C)中,您可能會在變量中存儲值0,以指示該變量不包含任何值。 可是,若是0是有效值怎麼辦? 而後,您可使用-1。 若是-1是有效值怎麼辦? 等等。

使用Swift可選控件,您能夠設置任何類型的變量以包含有效值或不包含任何值。

當您聲明要表示的變量(類型x,或沒有值)時,您會在類型以後加上問號。

可選其實是一個容器,它包含給定類型的變量或不包含任何變量。

一個可選的須要被「解開」,以獲取內部的值。

「!」 運算符是「強制展開」運算符。 它說:「相信我。我知道我在作什麼。我保證在這段代碼運行時,變量將不包含nil。」 若是輸入錯誤,則會崩潰。

除非你真的知道你在作什麼,避免「!」 強制解開操做符。 對於新手Swift程序員來講,這多是最大的崩潰源。

如何處理可選項:

還有不少其餘處理可選選項更安全的方法。 這裏有一些(不是詳盡的清單)

您可使用「可選綁定」或「 if let」來表示「若是此可選包含值,則將該值保存到一個新的非可選變量中。若是可選不包含值,請跳過此if語句的主體」。

這是使用foo可選的可選綁定的示例:

if let newFoo = foo //If let is called optional binding. {
  print("foo is not nil")
} else {
  print("foo is nil")
}

請注意,您在使用可選出價時定義的變量僅在if語句的主體中存在(僅在「範圍內」)。

或者,您可使用保護語句,若是變量爲nil,則能夠退出函數:

func aFunc(foo: Int?) {
  guard let newFoo = input else { return }
  //For the rest of the function newFoo is a non-optional var
}

在Swift 2中添加了Guard語句。Guard使您能夠保留代碼中的「黃金路徑」,並避免嵌套的ifs級別不斷提升,而嵌套的ifs有時是因爲使用「 if let」可選綁定而致使的。

還有一個稱爲「零合併運算符」的構造。 它採用「 optional_var ?? replace_val」的形式。 它返回一個非可選變量,其類型與可選變量中包含的數據相同。 若是可選包含nil,則返回「 ??」以後的表達式的值 符號。

所以,您可使用以下代碼:

let newFoo = foo ?? "nil" // "??" is the nil coalescing operator
print("foo = \(newFoo)")

您也可使用try / catch或guard錯誤處理,可是一般,上述其餘技術之一更乾淨。

編輯:

另外一個帶有可選內容的微妙陷阱是「隱式展開的可選內容。當咱們聲明foo時,咱們能夠說:

var foo: String!

在這種狀況下,foo仍然是可選的,可是您沒必要解開它便可引用它。 這意味着您每次嘗試引用foo時,若是nil爲零,就會崩潰。

因此這段代碼:

var foo: String!


let upperFoo = foo.capitalizedString

即便咱們不強制展開foo,引用foo的capitalizedString屬性也會崩潰。 打印看起來不錯,但事實並不是如此。

所以,您要很是謹慎地使用隱式展開的可選內容。 (甚至可能徹底避免使用它們,直到您對可選項有了深入的瞭解。)

底線:當您第一次學習Swift時,請僞裝「!」 字符不是語言的一部分。 可能會惹上您的麻煩。


#3樓

首先,您應該知道什麼是可選值。 您能夠進入The Swift Programming Launage

有關詳細信息。

其次,您應該知道可選值具備兩個狀態。 一個是全值,另外一個是零值。 所以,在實現可選值以前,應檢查它的狀態。

您可使用if let ...guard let ... else等等。

另外一種方式,若是您不想在實現以前檢查其狀態,則還可使用var buildingName = buildingName ?? "buildingName" 改成使用var buildingName = buildingName ?? "buildingName"


#4樓

TL; DR答案

除了極少數例外 ,此規則是黃金:

避免使用!

聲明變量optional( ? ),而不是隱式解包的optional(IUO)( !

換句話說,請使用:
var nameOfDaughter: String?

代替:
var nameOfDaughter: String!

使用if letguard let解開可選變量

像這樣解開變量:

if let nameOfDaughter = nameOfDaughter {
    print("My daughters name is: \(nameOfDaughter)")
}

或像這樣:

guard let nameOfDaughter = nameOfDaughter else { return }
print("My daughters name is: \(nameOfDaughter)")

該答案旨在簡明扼要, 以便全面理解已接受的答案


#5樓

因爲以上答案清楚地說明了如何使用Optionals安全演奏。 我將盡速解釋什麼是Optional。

聲明可選變量的另外一種方法是

var i : Optional<Int>

可選類型不過是帶有兩種狀況的枚舉,即

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

所以,給變量「 i」分配零。 咱們能夠作var i = Optional<Int>.none或分配一個值,咱們將傳遞一些值var i = Optional<Int>.some(28)

斯威夫特認爲,「零」是價值的缺失。 並建立一個以nil初始化的實例,咱們必須遵照一個稱爲ExpressibleByNilLiteral的協議,若是您猜對了,那就太好了,只有Optionals符合ExpressibleByNilLiteral而且不鼓勵與其餘類型兼容。

ExpressibleByNilLiteral具備一個稱爲init(nilLiteral:)的單一方法,該方法用nil初始化一個實例。 一般,您不會調用此方法,而且根據swift文檔,建議您每當使用nil文字量初始化Optional類型時,就不建議直接調用此初始化方法,由於編譯器會調用它。

即便本身有包(沒有雙關語意)個人頭周圍選配:d 快樂Swfting全部

相關文章
相關標籤/搜索