讀古今文學網 > iOS編程基礎:Swift、Xcode和Cocoa入門指南 > 3.2 變量聲明 >

3.2 變量聲明

正如第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章將會深入介紹這一點。