ios Swift ! and ?

來源:互聯網
上載者:User

標籤:style   class   blog   code   ext   color   

swift ?和!之間區別:

Swift 引入的最不一樣的可能就是 Optional Value 了。在聲明時,我們可以通過在類型後面加一個? 來將變數聲明為 Optional 的。如果不是 Optional 的變數,那麼它就必須有值。而如果沒有值的話,我們使用 Optional 並且將它設定為 nil 來表示沒有值。

//num 不是一個 Intvar num: Int?  //num 沒有值num = nil  //nil  //num 有值num = 3    //{Some 3}  

Apple 在 Session 上告訴我們,Optinal Value 其實就是一個盒子,你盒子裡可能裝著實際的值,也可能什麼都沒裝。

我們看到 Session 裡或者文檔裡天天說 Optional Optional,但是我們在代碼裡基本一個 Optional 都沒有看到,這是為什麼呢?而且,上面代碼中給 num 賦值為 3 的時候的那個輸出為什麼看起來有點奇怪?其實,在宣告類型時的這個 ? 僅僅只是 Apple 為了簡化寫法而提供的一個文法糖。實際上我們是有 Optional 類型的聲明,就這裡的 num 為例,最正規的寫法應該是這樣的:

//真 Optional 聲明和使用var num: Optional<Int>  num = Optional<Int>()  num = Optional<Int>(3)  

沒錯,num 不是 Int 類型,它是一個 Optional 類型。到底什麼是 Optional 呢,點進去看看:

enum Optional<T> : LogicValue, Reflectable {      case None    case Some(T)    init()    init(_ some: T)    /// Allow use in a Boolean context.    func getLogicValue() -> Bool    /// Haskell‘s fmap, which was mis-named    func map<U>(f: (T) -> U) -> U?    func getMirror() -> Mirror}

你也許會大吃一驚。我們每天和 Swift 打交道用的 Optional 居然是一個泛型枚舉 enum,而其實我們在使用這個枚舉時,如果沒有值,我們就規定這個枚舉的是 .None,如果有,那麼它就是Some(value)(帶值枚舉這裡不展開了,有不明白的話請看文檔吧)。而這個枚舉又恰好實現了LogicValue 介面,這也就是為什麼我們能使用 if 來對一個 Optinal 的值進行判斷並進一步進行 unwrap 的依據。

var num: Optional<Int> = 3  if num {       //因為有 LogicValue,                 //.None 時 getLogicValue() 返回 false               //.Some 時返回 true   var realInt = num!   realInt     //3}

既然 var num: Int? = nil 其實給 num 賦的值是一個枚舉的話,那這個 nil 到底又是什嗎?它被賦值到哪裡去了?一直注意的是,Swift 裡的 nil 和 objc 裡的 nil 完全不是一回事兒。objc 的 nil 是一個實實在在的指標,它指向一個空的對象。而這裡的 nil 雖然代表空,但它只是一個語意上的概念,確是有實際的類型的,看看 Swift 的 nil 到底是什麼吧:

/// A null sentinel value.var nil: NilType { get }  

nil 其實只是 NilType 的一個變數,而且這個變數是一個 getter。Swift 給了我們一個文檔注釋,告訴我們 nil 其實只是一個 null 的標記值。實際上我們在聲明或者賦值一個 Optional 的變數時,? 文法糖做的事情就是聲明一個 Optional<T>,然後查看等號右邊是不是 nil 這個標記值。如果不是,則使用init(_ some: T) 用等號右邊的類型 T 的值產生一個 .Some 枚舉並賦值給這個 Optional 變數;如果是 nil,將其賦為 None 枚舉。

所以說,Optional背後的故事,其實被這個小小的 ? 隱藏了。

我想,Optional 討論到這裡就差不多了,還有三個小問題需要說明。

首先,NilType 這個類型非常特殊,它似乎是個 built in 的類型,我現在沒有拿到關於它的任何資料。我本身逆向是個小白,現在看起來 Swift 的逆向難度也比較大,所以關於 NilType 的一些行為還是只能猜測。而關於 nil 這一 NilType 的類型的變數來說,猜測的話,它可能是 Optional.None 的一種類似多型表現,因為首先它確實是指向 0x0 的,並且與 Optional.None 的 content 的內容指向一致。但是具體細節還要等待挖掘或者公布了。

其次,Apple 推薦我們在 unwrap 的時候使用一種所謂的隱式方法,即下面這種方式來 unwrap:

var num: Int? = 3  if let n = num {      //have a num} else {    //no num}

最後,這樣隱式調用足夠安全,效能上似乎應該也做最佳化(有點忘了..似乎說過),推薦在 unwrap 的時候儘可能寫這樣的推斷,而減少直接進行 unwrap 這種行為。

最後一個問題是 Optional 的變數也可以是 Optinal。因為 Optional 就相當於一個黑盒子,可以知道盒子裡有沒有東西 (通過 LogicValue),也可以開啟這個盒子 (unwrap) 來拿到裡面的東西 (你要的類型的變數或者代表沒有東西的 nil)。請注意,這裡沒有任何規則限制一個 Optional 的量不能再次被 Optional,比如下面這種情況是完全 OK 的:

var str: String? = "Hi"         //{Some "Hi"}  var anotherStr: String?? = str  //{{Some "Hi"}}  

這其實是沒有多少疑問的,很完美的兩層 Optional,使用的時候也一層層解開就好。但是如果是 nil 的話,在這裡就有點尷尬...

var str: String? = nil  var anotherStr: String?? = nil  

 

? 那是什麼??,! 原來如此!!

問號和歎號現在的用法都是原來 objc 中沒有的概念。說起來簡單也簡單,但是背後也還是不少玄機。原來就已經存在的用法就不說了,這裡把新用法從淺入深逐個總結一下吧。

首先是 ?

  • ? 放在類型後面作為 Optional 類型的標記

這個用法上面已經說過,其實就是一個 Optional<T> 的文法糖,自動將等號後面的內容 wrap 成 Optional。給個用例,不再多說:

var num: Int? = nil        //聲明一個 Int 的 Optional,並將其設為啥都沒有  var str: String? = "Hello" //聲明一個 String 的 Optional,並給它一個字串  
  • ? 放在某個 Optional 變數後面,表示對這個變數進行判斷,並且隱式地 unwrap。比如說
foo?.somemethod()  

相比起一般的先判斷再調用,類似這樣的判斷的好處是一旦判斷為 nil 或者說是 false,語句便不再繼續執行,而是直接返回一個 nil。上面的寫法等價於

if let maybeFoo = foo {      maybeFoo.somemethod()}

這種寫法更存在價值的地方在於可以鏈式調用,也就是所謂的 Optional Chaining,這樣可以避免一大堆的條件分支,而使代碼變得易讀簡潔。比如:

 

if let upper = john.residence?.address?.buildingIdentifier()?.uppercaseString {      println("John‘s uppercase building identifier is \(upper).")}

 

注意最後 buildingIdentifier 後面的問號是在 () 之後的,這代表了這個 Optional 的判斷對象是buildingIdentifier() 的傳回值。

  • ? 放在某個 optional 的 protocol 方法的括弧前面,以表示詢問是否可以對該方法調用

這中用法相當於以前 objc 中的 -respondsToSelector: 的判斷,如果對象響應這個方法的話,則進行調用。例子:

delegate?.questionViewControllerDidGetResult?(self, result)  

 

中的第二個問號。注意和上面在 () 後的問號不一樣,這裡是在 () 之前的,表示對方法的詢問。

其實在 Swift 中,預設的 potocol 類型是沒有 optional 的方法的,因為基於這個前提,可以對型別安全進行確保。但是 Cocoa 架構中的 protocol 還是有很多 optional 的方法,對於這些可選的介面方法,或者你想要聲明一個帶有可選方法的介面時,必須要在聲明 protocol 時再其前面加上 @objc 關鍵字,並在可選方法前面加上 @optional

@objc protocol CounterDataSource {    @optional func optionalMethod() -> Int    func requiredMethod() -> Int    @optional var optionalGetter: Int { get }}

 

然後是 ! 新用法的總結

  • ! 放在 Optional 變數的後面,表示強制的 unwrap 轉換:
foo!.somemethod()  

 

這將會使一個 Optional<T> 的量被轉換為 T。但是需要特別注意,如果這個 Optional 的量是 nil 的話,這種轉換會在運行時讓程式崩潰。所以在直接寫 ! 轉換的時候一定要非常注意,只有在有必死決心和十足把握時才做 ! 強轉。如果待轉換量有可能是 nil 的話,我們最好使用 if let 的文法來做一個判斷和隱式轉換,保證安全。

  • ! 放在類型後面,表示強制的隱式轉換。

這種情況下和 ? 放在類型後面的行為比較類似,都是一個型別宣告的文法糖。? 聲明的是 Optional,而 ! 其實聲明的是一個 ImplicitlyUnwrappedOptional 類型。首先需要明確的是,這個類型是一個struct,其中關鍵區段是一個 Optional<T> 的 value,和一組從這個 value 裡取值的 getter 和 方法:

struct ImplicitlyUnwrappedOptional<T> : LogicValue, Reflectable {      var value: T?    //...    static var None: T! { get }    static func Some(value: T) -> T!    //...}

 

從外界來看,其實這和 Optional 的變數是類似的,有 Some 有 None。其實從本質上來說,ImplicitlyUnwrappedOptional 就是一個儲存了 Optional,實現了 Optional 對外的方法特性的一個類型,唯一不同的是,Optional 需要我們手動進行進行 unwrap (不管是使用 var! 還是 let if 賦值,總要我們做點什麼),而 ImplicitlyUnwrappedOptional 則會在使用的時候自動地去 unwrap,並對繼續之後的操作調用,而不必去增加一次手動的顯示/隱式操作。

為什麼要這麼設計呢?主要是基於 objc 的 Cocoa 架構的兩點考慮和妥協。

首先是 objc 中是有指向Null 物件的指標的,就是我們所習慣的 nil。在 Swift 中,為了處理和 objc 的 nil 的相容,我們需要一個可為空白的量。而因為 Swift 的目的就是打造一個完全型別安全的語言,因此不僅對於 class,對於其他的類型結構我們也需要型別安全。於是很自然地,我們可以使用 Optional 的空來對 objc 做等效。因為 Cocoa 架構有大量的 API 都會返回 nil,因此我們在用 Swift 表達它們的時候,也需要換成對應的既可以表示存在,也可以表示不存在的 Optional

那這樣的話,不是直接用 Optional 就好了嗎?為什麼要弄出一個 ImplicitlyUnwrappedOptional 呢?因為易用性。如果全部用 Optional 封裝的話,在調用很多 API 時我們就都需要轉來轉去,十分麻煩。而對於 ImplicitlyUnwrappedOptional 因為編譯器為我們進行了很多處理,使得我們在確信傳回值或者要傳遞的值不是空的時候,可以很方便的不需要做任何轉換,直接使用。但是對於那些 Cocoa 有可能返回 nil,我們本來就需要檢查的方法,我們還是應該寫 if 來進行轉換和檢查。

比如說,以下的寫法就會在運行時導致一個 EXC_BAD_INSTRUCTION

let formatter = NSDateFormatter()  let now = formatter.dateFromString("not_valid")  let soon = now.dateByAddingTimeInterval(5.0) // EXC_BAD_INSTRUCTION  

 

因為 dateFromString 返回的是一個 NSDate!,而我們的輸入在原來會導致一個 nil 的返回,這裡我們在使用 now 之前需要進行檢查:

let formatter = NSDateFormatter()  let now = formatter.dateFromString("not_valid")  if let realNow = now {      realNow.dateByAddingTimeInterval(5.0)} else {    println("Bad Date")}

 

這和以前在 objc 時代做的事情差不多,或者,用更 Swift 的方式做

let formatter = NSDateFormatter()  let now = formatter.dateFromString("not_valid")  let soon = now?.dateByAddingTimeInterval(5.0)  

 

 

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.