1. 程式人生 > >Swift中的可選型別?和隱式可選型別!的深入理解

Swift中的可選型別?和隱式可選型別!的深入理解

可選型別、隱式可選型別

Swift中,可選型別其根源是一個列舉型,裡面有None和Some兩種型別。其實所謂的nil就是Optional.None, 非nil就是Optional.Some, 然後會通過Some(T)包裝(wrap)原始值,這也是為什麼在使用Optional的時候要拆包(從enum裡取出來原始值)的原因, 也是PlayGround會把Optional值顯示為類似{Some "hello world"}的原因,這裡是enum Optional的定義:

  1. enum Optional<T> : LogicValue, Reflectable {  
  2.     case
     None  
  3.     case Some(T)  
  4.     init()  
  5.     init(_ some: T)  
  6.     /// Allow use in a Boolean context.
  7.     func getLogicValue() -> Bool  
  8.     /// Haskell's fmap, which was mis-named
  9.     func map<U>(f: (T) -> U) -> U?  
  10.     func getMirror() -> Mirror  
  11. }  


語法使用“?”操作符及"!"號操作符

如:“var optionalString: String? = 

"Hello"
optionalString == nil
 
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}”

大家把optionalName改為nil時看一下會有什麼結果?按照理解,應該是? = 後的為可選值,即當我們的的變數為nil時,這裡如果有?=操作,則會使用?=後的值作為預設值,而不會為nil. 這個等有環境時,驗證一下。

經驗證:

分別執行:

  1. var optional :String? = 
    "ok good";//注意?與=號之間有空格?號緊貼最後一個字母不能有空格
  2. println(optional)  

輸出為:

ok good

  1. var optional :String?//注意?與=號之間有空格?號緊貼最後一個字母不能有空格
  2. println(optional)  

輸出為:

nil


來看下!號,官方釋為隱式解包:主要用在一個變數/常量在定義瞬間完成之後值一定會存在的情況。這主要用在類的初始化過程中。

官風例子:

  1. let possibleString: String? = "An optional string."
  2. println(possibleString!) // requires an exclamation mark to access its value
  3. // prints "An optional string."
  4. let assumedString: String! = "An implicitly unwrapped optional string."
  5. println(assumedString) // no exclamation mark is needed to access its value

實說話,你照這個例子執行,還真看不出什麼,得不出什麼結論。因此我自己Z磨著,試著理解一個英文翻譯。再自己操刀練習。得出下面的一些結論。

  1. var optionVariables:String? //?相當於下面這種寫法的語法糖
  2. //var optionVariables : Optional<Int>
  3. let value = optionVariables?.hashValue  
  4. /* 
  5. optionVariables是可選型別的字串,如果optionVariables是nil,則hashValue也為nil 
  6. 如果optionVariables不為nil,hashValue就是strValue字串的雜湊值 
  7. 到這裡我們看到了?的兩種使用場景: 
  8. 1.宣告Optional值變數 
  9. 2.用在對Optional值操作中,用來判斷是否能響應後面的操作 
  10. */
  11. //對於可選型別變數,不能直接進行操作,否則會報錯
  12. //let hashval = optionVariables.hashValue //'String?' does not have a member named 'hashValue'
  13. //因此要訪問值就需要解包,解包有兩種
  14. //第一種:使用if let/var xxx = 
  15. if let hv = optionVariables  
  16. {  
  17.     //run ok;
  18. }  
  19. //第二種:使用!號
  20. let hv = optionVariables!.hashValue  
  21. //這裡的!表示“我確定這裡的的strValue一定是非nil的,盡情呼叫吧” ,比如這種情況:
  22. if optionVariables {  
  23.     let hashv = optionVariables!.hashValue  
  24. }  
  25. //{}裡的optionVariables一定是非nil的,所以就能直接加上!,強制拆包(unwrap)並執行後面的操作


凡在變數或常量後加上?的都是一個可選變數/可選常量
凡在變數或常量後加上!的都是隱式可選變數/常量,有點難理解,首先該變數或常量滿足可選型別,其主要是可被當生一般的變數/常量來使用,而不需要每次都驗證是否有值。

注:如果一個隱式解包的可選型別不包含一個實際值,那麼對它的訪問會丟擲一個執行時錯誤。在變數/常量名後面加!的情況也是一樣的。

  1. var possibleString: String? = "An optional string."
  2. //possibleString = nil
  3. println(possibleString) // possibleString 為可選變數,需要使用!來訪問的值
分析:首先 possibleString 因後面帶上了?說明這是一個可選的,同時前面加上var為變數,所以這是一個可選型別的變數。其可選值為 "An optional string." 再來看執行println後,可以看出輸出為 An optional string. 這點很明顯。再來看一下把println這句改一下改為 (即在可選變數後面加上一個!號。)
  1. <span style="font-size:18px;">println(possibleString!) // possibleString 為可選變數,需要使用!來訪問的值</span>
這裡結果與沒有加!號時是完全一樣的,輸出為An optional string. 

好,現在重點來了,這是很關鍵的一個測試。把possibleString = nil 這句註釋放開讓其動行,再分別來看一下println帶!和不帶!的情況:

情況一:不帶!號時,輸出為nil .

  1. <span style="font-size:18px;">        var possibleString: String? = "An optional string."
  2.         possibleString = nil  
  3.         println(possibleString) </span>  
情況二:再來看一下帶!號
  1. <span style="font-size:18px;">        var possibleString: String? = "An optional string."
  2.         possibleString = nil  
  3.         println(possibleString!) // possibleString 為可選變數,需要使用!來訪問的值</span>
這時執行到這句println就會crash了。會報

fatal error: Can't unwrap Optional.None

錯誤。 

在情況一時,為什麼不會報錯,是因為這是一個可選變數當變數為nil時,自動驗證是否有可選的值,有則使用可選值,在情況二,加上!訪問符來訪問possibleString 變數,但由於possibleString設為了nil (等價於var possibleString: String?) 其並沒有包含一個實際值,所以拋異常.同樣對於下面使用!號來宣告的也一樣道:

  1. <span style="font-size:18px;">        var assumedString: String! = "An implicitly unwrapped optional string."
  2.         assumedString = nil  
  3.         println(assumedString!)</span>  
同樣會報:fatal error: Can't unwrap Optional.None

如果你定義了一個可選型別並且沒有給予初始值的時候,會預設設定為nil
var surveyAnswer: String? // 初自動設定為nil
注: Swift 的nil不同於Object-C中的nil. Object-C中,nil是一個指標指向不存在的物件。Swift中,nil不是指標而是一個特定型別的空值。任何型別的可選變數都可以被設為nil,不光是指標。

在swift中作何變數/常量的宣告都必須帶有初始值,否則就要宣告為可選型。

即var btn:UIButton 這樣是編譯報錯的。因些必須改為帶初始化的如:

var btn2 :UIButton = UIButton()

或者使用? 和! 來約束。

因此常常宣告可選或隱式可選變數如:

var btn :UIButton?      // 預設btn = nil

var edt :UITextField!  // 預設edt = nil

至於什麼時候使用?什麼情況下使用!號來約束變數,我還沒有悟出真真原理。

因此藉助於自己的幾次驗證來幫助大家理解。

  1. <span style="font-size:18px;">        var btn  :UIButton?      // 預設btn = nil
  2.         var btn2 :UIButton = UIButton()    // 預設例項化一個對物件
  3.         var btn3 :UIButton!     // 預設btn = nil
  4.         //var btn4 :UIButton    //編譯期報錯 要求進行初始化操作
  5.         //執行會報錯fatal error: Can't unwrap Optional.None 因btn = nil
  6.         btn!.tintColor = UIColor.blackColor()  
  7.         btn!.imageEdgeInsets = UIEdgeInsets(top:1,left:2,bottom:3,right:4)  
  8.         btn!.frame = CGRectMake(0,0,50,40)  
  9.         //執行正常
  10.         btn2.tintColor = UIColor.blackColor()  
  11.         btn2.imageEdgeInsets = UIEdgeInsets(top:1,left:2,bottom:3,right:4)  
  12.         btn2.frame = CGRectMake(0,0,50,40)  
  13.         //執行會報錯fatal error: Can't unwrap Optional.None 因btn3 = nil
  14.         btn3.tintColor = UIColor.blackColor()  
  15.         btn3.imageEdgeInsets = UIEdgeInsets(top:1,left:2,bottom:3,right:4)  
  16.         btn3.frame = CGRectMake(0,0,50,40)</span>  

因此為了執行期不crash可以改為如下:
  1. <span style="font-size:18px;">        var btn  :UIButton?      // 預設btn = nil
  2.         var btn2 :UIButton = UIButton()    // 預設例項化一個對物件
  3.         var btn3 :UIButton!     // 預設btn = nil
  4.         //執行會報錯fatal error: Can't unwrap Optional.None 因btn = nil
  5.         if var tmpbtn = btn  
  6.         {  
  7.             btn!.tintColor = UIColor.blackColor()  
  8.             btn!.imageEdgeInsets = UIEdgeInsets(top:1,left:2,bottom:3,right:4)  
  9.             btn!.frame = CGRectMake(0,0,50,40)  
  10.         }  
  11.         //執行正常
  12.         btn2.tintColor = UIColor.blackColor()  
  13.         btn2.imageEdgeInsets = UIEdgeInsets(top:1,left:2,bottom:3,right:4)  
  14.         btn2.frame = CGRectMake(0,0,50,40)  
  15.         //執行會報錯fatal error: Can't unwrap Optional.None 因btn3 = nil
  16.         if var tmpbtn = btn  
  17.         {  
  18.             btn3.tintColor = UIColor.blackColor()  
  19.             btn3.imageEdgeInsets = UIEdgeInsets(top:1,left:2,bottom:3,right:4)  
  20.             btn3.frame = CGRectMake(0,0,50,40)  
  21.         }</span>  
或者
  1. <span style="font-size:18px;">        var btn  :UIButton?      // 預設btn = nil
  2.         var btn2 :UIButton = UIButton()    // 預設例項化一個對物件
  3.         var btn3 :UIButton!     // 預設btn = nil
  4.         //執行會報錯fatal error: Can't unwrap Optional.None 因btn = nil
  5.         if btn  
  6.         {  
  7.             btn!.tintColor = UIColor.blackColor()  
  8.             btn!.imageEdgeInsets = UIEdgeInsets(top:1,left:2,bottom:3,right:4)  
  9.             btn!.frame = CGRectMake(0,0,50,40)  
  10.         }  
  11.         //執行正常
  12.         btn2.tintColor = UIColor.blackColor()  
  13.         btn2.imageEdgeInsets = UIEdgeInsets(top:1,left:2,bottom:3,right:4)  
  14.         btn2.frame = CGRectMake(0,0,50,40)  
  15.         //執行會報錯fatal error: Can't unwrap Optional.None 因btn3 = nil
  16.         if btn3  
  17.         {  
  18.             btn3.tintColor = UIColor.blackColor()  
  19.             btn3.imageEdgeInsets = UIEdgeInsets(top:1,left:2,bottom:3,right:4)  
  20.             btn3.frame = CGRectMake(0,0,50,40)  
  21.         }</span>  
注:如果一個可選型別存在沒有值的可能的話,不應該使用解包(隱式)可選型別。這種情況下,一定要使用正常的可選型別。這句話我個人是這樣理解的,如var view:UIView。當我的整個應用中或整個類中不可能存在view = nil的情況時可以設定為var view:UIView! 否則就可宣告為var view:UIView?