正如第1章所述,變量是通過let或var聲明的:
·let聲明的變量是常量,其值在首次賦值(初始化)後就不會再變化了。
·var聲明的變量才是真正的變量,其值可以被後續的賦值所改變。
不過,變量的類型是絕對不會改變的。使用var聲明的變量可以被賦予不同的值,但該值必須要符合變量的類型。這樣,在聲明變量時,需要為其指定好類型,然後變量就會一直具有該類型。你可以顯式或隱式地指定變量的類型:
顯式變量類型聲明
在聲明中的變量名後面,添加一個冒號和類型名:
var x : Int
通過初始化創建隱式變量類型
如果將變量初始化作為聲明的一部分,並且沒有提供顯式的類型,那麼Swift就會根據初始化值推斷其類型:
var x = 1 // and now x is an Int
完全可以顯式聲明變量的類型,然後為其賦予一個初始值,並將這些工作在一步中完成:
var x : Int = 1
在該示例中,顯式類型聲明是多餘的,因為類型(Int)可以通過初始值推斷出來。但有時,提供顯式類型的同時又賦予一個初始值並不多餘。比如,下面列出的各種情況:
Swift的推斷可能是錯誤的
我遇到的一個常見情況就是當我提供初始值作為數值字面值時,Swift會將其推斷為Int或Double,這取決於字面值是否包含了小數點。不過還有很多其他的數值類型!如果遇到這種情況,我會顯式提供類型,如下代碼所示:
let separator : CGFloat = 2.0
Swift無法推斷出類型
在這種情況下,顯式變量類型可以讓Swift推斷出初始值的類型。選項集合就是一種非常常見的情況(第4章將會介紹)。如下代碼無法編譯通過:
var opts = [.Autoreverse, .Repeat] // compile error
問題在於名字.Autoreverse與.Repeat分別是UIViewAnimationOptions.Autoreverse與UIViewAnimationOptions.Repeat的簡寫,不過除非我們告訴Swift,否則它是不知道這一點的:
let opts : UIViewAnimationOptions = [.Autoreverse, .Repeat]
程序員無法推斷出類型
我常常會加上多餘的顯式類型聲明來提醒我自己。如下示例來自於我所編寫的代碼:
let duration : CMTime = track.timeRange.duration
在上述代碼中,track的類型是AVAssetTrack。Swift非常清楚AVAssetTrack的timeRange屬性的duration屬性是個CMTime。不過,我不知道!為了提醒自己,我顯式添加了類型。
由於可以使用顯式變量類型,因此變量在聲明時無需初始化。下面這樣寫是合法的:
let x : Int
現在,x是個空盒子,一個沒有初始值的Int變量。不過,如果可以避免,我強烈建議你不要對局部變量採取這種做法。這麼做並非災難,因為Swift編譯器會阻止你使用從未賦過值的變量,只不過不是一個好習慣而已。
能夠證明該規則的一個例外情況是條件初始化。有時,我們只有在執行了某些條件測試後才知道某個變量的初始值是什麼。不過,變量本身只能聲明一次,因此它必須要提前聲明,然後根據條件進行初始化。下面這麼做是合理的(不過還有更好的寫法):
let timed : Bool if val == 1 { timed = true } else { timed = false }
在將變量的地址作為實參傳遞給函數時,變量必須要提前聲明並初始化,即便初始值是假的亦如此。回憶一下第2章的示例:
var arrow = CGRectZero var body = CGRectZero CGRectDivide(rect, &arrow, &body, Arrow.ARHEIGHT, .MinYEdge)
代碼運行後,兩個CGRectZero值將被替換掉;它們僅僅是佔位符而已,為了滿足編譯器的要求。
有時,你希望調用的Cocoa方法會立刻返回一個值,然後在傳遞給相同方法的函數中使用該值。比如,Cocoa有一個UIApplication實例方法,其聲明如下所示:
func beginBackgroundTaskWithExpirationHandler(handler: ( -> Void)?) -> UIBackgroundTaskIdentifier
該函數會返回一個數字(UIBackgroundTaskIdentifier就是個Int),然後再調用傳遞給它的函數(handler),該函數會使用一開始返回的數字。Swift的安全規則不允許你使用一行代碼聲明持有該數字的變量,然後在匿名函數中使用它:
let bti = UIApplication.sharedApplication .beginBackgroundTaskWithExpirationHandler({ UIApplication.sharedApplication.endBackgroundTask(bti) }) // error: variable used within its own initial value
因此,你需要提前聲明好變量;不過,Swift還會提示另一個錯誤:
var bti : UIBackgroundTaskIdentifier bti = UIApplication.sharedApplication .beginBackgroundTaskWithExpirationHandler({ UIApplication.sharedApplication.endBackgroundTask(bti) }) // error: variable captured by a closure before being initialized
解決辦法就是提前聲明好變量,然後為其賦予一個假的初始值作為佔位符:
var bti : UIBackgroundTaskIdentifier = 0 bti = UIApplication.sharedApplication .beginBackgroundTaskWithExpirationHandler({ UIApplication.sharedApplication.endBackgroundTask(bti) })
對象的實例屬性(在枚舉、結構體或類聲明的頂層)可以在對象的初始化器函數中進行初始化,而不必在聲明中賦值。對於常量實例屬性(let)與變量實例屬性(var),具有顯式類型而不直接賦予初始值是合法且常見的。第4章將會深入介紹這一點。