標籤: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)