[Swift基礎語法入門] Initialization詳解

開篇

聲明一個類、結構體或枚舉,就像繪制了一副草圖,即使描繪得再栩栩如生,也僅躍于紙上罷了。

舉例來說,描述一輛汽車:車型、車身顏色、出廠日期等等。為此聲明一個 Car 的類:

class Car{
  var name:String    /**> 車輛型號 */
  var color:String   /**> 車身顏色 */
  var date:NSDate    /**> 出廠日期 */
  // 當然還有其他屬性
}

此時手上只有Car的設計圖,這并不意味著你已經擁有一輛保時捷或法拉利可以去兜風了;所以嘍,還是將設計稿圖交給工廠,由他們按照設計圖制造一輛貨真價實的小汽車交付給你。ps:制造出來的車在編程中叫實例(顧名思義:實際的例子)。

等等,工廠拒絕了!拒絕理由:車型沒有指明,噴什么顏色呢?

修改如下:

class Car{
  var name:String = "保時捷911"   /**> 車輛型號 */
  var color:String = "紅色"       /**> 車身顏色 */
  var date:NSDate = NSDate()     /**> 出廠日期 */
  // 當然還有其他屬性
}

ok!默認車型“保時捷”、車身顏色“紅色”,出廠日期為加工當天。

獨樂樂不如眾樂樂,于是大手一揮,再加工一打!

廠家:還是全紅色的保時捷911?

當初想得過于簡單,設計稿圖參數全給了默認值,失策失策!
修改如下:

class Car{
  var name:String        /**> 車輛型號 */
  var color:String       /**> 車身顏色 */
  var date:NSDate        /**> 出廠日期 */
  // 當然還有其他屬性
  
  init(name:String,color:String){
    self.name = name
    self.color = color
    date = NSDate()
  }
}

至此,告訴廠家:第一輛要藍色的保時捷911,第二輛要紅色的保時捷Cayenne,第三輛...

幾種類型的Initialization

枚舉:

enum Direction{
    case East, South, West, North
    
    init(symbol:Character){
        switch symbol{
            case "E":
                self = .East
            case "S":
                self = .South
            case "W":
                self = .West
            case "N":
                self = .North
            default:
                self = .East
        }
    }
}

let dir = Direction(symbol: "S") // .South

結構體1:

struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
let temp = Fahrenheit()

結構體2:

struct User {
    var username:String
    var password:String
    
}
// 盡管沒有聲明初始化方法 但是對于結構體默認是有memberwise initialization
let user = User(username: "test", password: "123456")

類:

// 類初始化
class Car{
    var name:String
    var color:String
    var age:Int
    
    init(name:String,color:String,age:Int){
        self.name = name
        self.color = color
        self.age = age
    }
}

何為Initialization

根據設計稿圖制造出車輛實例,在車輛出廠使用前,必須確認車輛車型和為車身噴漆,這是一個 Initialization 過程,保證車輛實例使用前完成所有的準備工作。

官方文檔對此的描述是:

This process involves setting an initial value for each stored property on that instance and performing any other setup or initialization that is required before the new instance is ready for use.

關鍵詞:stored property存儲屬性,換句話說對于computed property計算屬性是排除在外的。

再來說說 Swift 中 Initialization 的聲明形式,使用init修飾符即可:

init() {
    // perform some initialization here
}

默認值

倘若你偏愛紅色的保時捷,那么你可以一開始就為實例屬性color設定默認屬性為“紅色”

class Car{
  // ...
  var color:String = "紅色"       /**> 車身顏色 */
  // ...
}

自定義

倘若你天馬星空,那么你就應該聲明一個自定義的構造方法,傳入配色和車型來實例化車輛。

class Car{
  var name:String        /**> 車輛型號 */
  var color:String       /**> 車身顏色 */
  var date:NSDate        /**> 出廠日期 */
  // 當然還有其他屬性
  
  init(name:String,color:String){
    self.name = name
    self.color = color
    date = NSDate()
  }
}

局部和外部參數名

當然就像函數(function)和方法(method)一樣,構造方法也是可以擁有local name 和 external name。

Swift 自動為每一個參數提供 external parameter 名字,換句話說你丫別提供了??!

來自官方文檔的例子:

struct Color {
    let red, green, blue: Double
    init(red: Double, green: Double, blue: Double) {
        self.red   = red //這里的red green blue 是局部參數名
        self.green = green
        self.blue  = blue
    }
    init(white: Double) {
        red   = white
        green = white
        blue  = white
    }
}
// 這里的red green blue 是外部參數名字
// 這個是很有必要的,否則調用者怎么知道我傳入的1.0 0.0 1.0 到底賦值給哪個參數了呢?。?!
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)

注意到redbluegreen 即使local parameters,能夠在init中使用;又可以在構造方法調用時作為external parameters 呈現

總有些“搗蛋鬼”就是想反其道而行:不要外部標簽可以嗎?? 好吧,答案自然是ok。使用** _ **下劃線占據external parameter name的位置,告知編譯器在調用構造方法時忽略外部參數名:

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
    init(_ celsius: Double) {
        temperatureInCelsius = celsius
    }
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius is 37.0

實例變量是可選參數類型

說說車輛的牌照,車輛剛制造出來時,對于車牌沒有強制要求,一個月后上牌就OK。所以嘍,我們可以為Car設定一個可選類型的車牌實例變量:

class Car{
  var licensePlate:String? /**> 因為車牌一開始可以是沒有的
  var name:String        /**> 車輛型號 */
  var color:String       /**> 車身顏色 */
  var date:NSDate        /**> 出廠日期 */
  // 當然還有其他屬性
  
  init(name:String,color:String){
    self.name = name
    self.color = color
    date = NSDate()
  }
}
let car = Car(name:"保時捷911",color:"紅色")
// 搖到號了 
car.licensePlate = "浙A123456"

常量屬性

讓我們在“計較”點,車輛車型選定后,之后是絕無修改的可能,要知道改裝車輛是違法的,所以嘍,將name 聲明為常量是一個明智的選擇。

class Car{
  var licensePlate:String? /**> 因為車牌一開始可以是沒有的
  let name:String        /**> 車輛型號 */
  var color:String       /**> 車身顏色 */
  var date:NSDate        /**> 出廠日期 */
  // 當然還有其他屬性
  
  init(name:String,color:String){
    self.name = name
    self.color = color
    date = NSDate()
  }
}

默認的 Initializers

Swift 為以下對象提供默認構造方法:

  • Structure(結構體)
  • 所有實例屬性均設置了默認值的Class(類)

以官方文檔為例:

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

對于類來說,我們并未寫一個init方法,而是給定每一個實例屬性默認值。
結構體有點特殊,無須為實例屬性設定默認值,先看看結構體的聲明:

struct StructTest{
  // memberwise 逐個成員初始化唄
  var para1:String
  var para2:String
  var para3:String
  var para4:String
}

注意我們并未給任何一個參數設定初始值,但實際上結構體已經就自定 memberwise initializer了(memberwise:逐個成員的意思),即生成了init(para1:,para2:,para3:,para4:)構造方法了!

值類型中的 Initializer Delegation

對于值類型(結構體和枚舉)來說,是沒有繼承的,這意味著只允許構造方法之間的互相調用,即使用self.init,而沒有super.init一說。

再次提醒,構造方法的作用就是確保實例在使用前,其所有實例屬性都設定了初始值!

舉例:

struct Size {
    var width = 0.0, height = 0.0
}
struct Point {
    var x = 0.0, y = 0.0
}

struct Rect {
    var origin = Point()
    var size = Size()
    // 1
    init() {}
    // 2
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    // 3
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        
        // 至于這里調用self.init() 無非就是不想在寫self.origin = origin和self.size = size 
        // 由于這里初始化代碼簡單,所以作用感覺不出,但是某些情況下確實可以節省代碼量
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}

1、2 和 3 都是構造方法,其中1中,由于我們為結構體中的所有成員都給定了默認值,因為init(){}方法中什么都沒寫,但是倘若我們未給結構體成員默認值,那么init(){}報錯Return from initializer without all stored properties 很好理解,構造方法的職責就是為所有實例變量在使用前設定初始值!假若你沒有那么做,自然報錯嘍。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容