您的位置:首页 > 移动开发 > Swift

《swift2.0 官方教程中文版》 第2章-14构造过程

2015-12-01 17:08 453 查看
import Foundation

//构造过程是为了使用某个类、结构体或枚举类型的实例而进行的准备过程。这个过程包含了为实例中的每个存储型属性设置初始值和为其执行必要的准备和初始化任务。
//构造过程是通过定义构造器( Initializers )来实现的,这些构造器可以看做是用来创建特定类型实例的特殊方法。与 Objective-C
中的构造器不同,Swift 的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
//类的实例也可以通过定义析构器( deinitializer )在实例释放之前执行特定的清除工作

/*存储型属性的初始赋值**************************************************/
//注意:
当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观测器
//构造器在创建某特定类型的新实例时调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字 init
命名。
//init() {
//    // 在此处执行构造过程
//}

//下面例子中定义了一个用来保存华氏温度的结构体 Fahrenheit ,它拥有一个 Double
类型的存储型属性 temperature :
struct Fahrenheit {
    var temperature:
Double
    init() {
        temperature =
32.0
    }
}
var f = Fahrenheit()
print("The default temperature is
\(f.temperature)° Fahrenheit")
// 输出 "The default temperature is 32.0° Fahrenheit”

//注意:如果一个属性总是使用同一个初始值,可以为其设置一个默认值。无论定义默认值还是在构造器中赋值,最终它们实现的效果是一样的,只不过默认值将属性的初始化和属性的声明结合的更紧密。使用默认值能让你的构造器更简洁、更清晰,且能通过默认值自动推导出属性的类型;同时,它也能让你充分利用默认构造器、构造器继承(后续章节将讲到)等特性。
struct Fahrenheit2 {
    var temperature =
32.0
}

/*自定义构造过程**************************************************/
struct Celsius {
    var temperatureInCelsius:
Double = 0.0
    init(fromFahrenheit fahrenheit:
Double) {
        temperatureInCelsius = (fahrenheit -
32.0) / 1.8
    }
    init(fromKelvin kelvin:
Double) {
        temperatureInCelsius = kelvin -
273.15
    }
}
let boilingPointOfWater =
Celsius(fromFahrenheit: 212.0)
print(boilingPointOfWater.temperatureInCelsius)
// boilingPointOfWater.temperatureInCelsius
是 100.0
let freezingPointOfWater =
Celsius(fromKelvin: 273.15)
print(freezingPointOfWater.temperatureInCelsius)
// freezingPointOfWater.temperatureInCelsius
是 0.0

//第一个构造器拥有一个构造参数,其外部名字为 fromFahrenheit ,内部名字为 fahrenheit ;第二个构造器也拥有一个构造参数,其外部名字为
fromKelvin ,内部名字为 kelvin
。这两个构造器都将唯一的参数值转换成摄氏温度值,并保存在属性 temperatureInCelsius
中。

//以下例子中定义了一个结构体 Color ,它包含了三个常量: red
、 green 和 blue
。这些属性可以存储0.0到1.0
之间的值,用来指示颜色中红、绿、蓝成分的含量。

//Color 提供了一个构造器,其中包含三个 Double
类型的构造参数。 Color 也可以提供第二个构造器,它只包含 Double
类型名叫 white 的参数,它被用于给上述三个构造参数赋予同样的值。
struct Color{
    let red,green,blue:
Double
    init(red:Double,green:Double,blue:Double){
        self.red = red
        self.green = green
        self.blue = blue
    }
    init(white:Double){
        self.red = white
        self.green = white
        self.blue = white
    }
}
let magenta = Color(red:
1.0, green: 0.0, blue:
1.0)
let halfGray = Color(white:
0.5)

//let veryGreen = Color(0.0, 1.0, 0.0)
// 报编译时错误,需要外部名称

//如果你不希望为构造器的某个参数提供外部名字,你可以使用下划线(_)来显示描述它的外部名,以此重写上面所
说的默认行为。
struct Celsius2 {
    var temperatureInCelsius:
Double = 0.0
    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 =
Celsius2(37.0)
// bodyTemperature.temperatureInCelsius 为 37.0
//调用这种不需要外部参数名称的 Celsius(37.0)
构造器看起来十分简明的。因此适当使用这种 init(_ celsius: Double)
构造器可以提供 Double 类型的参数值而不需要加上外部名。

//如果你定制的类型包含一个逻辑上允许取值为空的存储型属性--不管是因为它无法在初始化时赋值,还是因为它
可以在之后某个时间点可以赋值为空--你都需要将它定义为可选类型 optional type
。可选类型的属性将自动初始化为空 nil ,表示这个属性是故意在初始化时设置为空的。
class SurveyQuestion {
    var text:
String
    var response:String?
    init(text:
String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}
let cheeseQuestion =
SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// 输出 "Do you like cheese?"
cheeseQuestion.response =
"Yes, I do like cheese."
//调查问题在问题提出之后,我们才能得到回答。所以我们将属性回答 response
声明为 String? 类型,或者说是
可选字符串类型 optional String 。当 SurveyQuestion
实例化时,它将自动赋值为空 nil ,表明暂时还不存在此
字符串。

//你可以修改上面的 SurveyQuestion
示例,用常量属性替代变量属性 text ,指明问题内容 text
在其创建之后不 会再被修改。尽管 text
属性现在是常量,我们仍然可以在其类的构造器中设置它的值:
class SurveyQuestion2 {
    let text:
String
    var response:
String?
    init(text:
String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}
let beetsQuestion =
SurveyQuestion2(text: "How about beets?")
beetsQuestion.ask()
// 输出 "How about beets?"
beetsQuestion.response =
"I also like beets. (But not with cheese.)"

/*默认构造器**************************************************/
//Swift 将为所有属性已提供默认值的且自身没有定义任何构造器的结构体或基类,提供一个默认的构造器。这个默认构造器将简单的创建一个所有属性值都设置为默认值的实例。
//下面例子中创建了一个类 ShoppingListItem ,它封装了购物清单中的某一项的属性:名字( name )、数
量( quantity )和购买状态 purchase state

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

struct Size {
    var width =
0.0, height = 0.0
}
let twoByTwo = Size(width:
2.0, height: 2.0)

/*值类型的构造器代理**************************************************/
//构造器可以通过调用其它构造器来完成实例的部分构造过程。这一过程称为构造器代理,它能减少多个构造器间的代码重复。
//注意:
假如你想通过默认构造器、逐一对象构造器以及你自己定制的构造器为值类型创建实例,我们建议你将自己定制
的构造器写到扩展( extension )中,而不是跟值类型定义混在一起。

//下面例子将定义一个结构体 Rect ,用来代表几何矩形。这个例子需要两个辅助的结构体 Size
和 Point ,它们
各自为其所有的属性提供了初始值 0.0 。
struct Size2 {
    var width =
0.0, height = 0.0
}
struct Point2 {
    var x =
0.0, y = 0.0
}
struct Rect {
        var origin =
Point2()
        var size =
Size2()
        init() {}
        init(origin:
Point2, size: Size2) {
            self.origin = origin
            self.size = size
        }
        init(center:
Point2, size: Size2) {
            let originX = center.x - (size.width /
2)
            let originY = center.y - (size.height /
2)
            self.init(origin:
Point2(x: originX, y: originY), size:size)
        }
}
//第一个 Rect
构造器 init() ,在功能上跟没有自定义构造器时自动获得的默认构造器是一样的。这个构造器是一个
空函数,使用一对大括号 {}
来描述,它没有执行任何定制的构造过程。调用这个构造器将返回一个 Rect
实 例,它的 origin
和 size 属性都使用定义时的默认值 Point(x: 0.0, y: 0.0)
和 Size(width: 0.0, height: 0.0) :
let basicRect = Rect()
// basicRect 的原点是 (0.0, 0.0),尺寸是 (0.0, 0.0)
let originRect = Rect(origin:
Point2(x: 2.0, y:
2.0),
                        size: Size2(width:
5.0, height: 5.0))
// originRect 的原点是 (2.0, 2.0),尺寸是 (5.0, 5.0)

let centerRect = Rect(center:
Point2(x: 4.0, y:
4.0), size: Size2(width:
3.0, height: 3.0))
// centerRect 的原点是 (2.5, 2.5),尺寸是 (3.0, 3.0)

/*类的继承和构造过程**************************************************/
//类的指定构造器的写法跟值类型简单构造器一样:
//init(parameters) {
//    statements
//}
//便利构造器也采用相同样式的写法,但需要在 init
关键字之前放置 convenience 关键字,并使用空格将它们俩分开:
//convenience init(parameters) {
//    statements
//}

//为了简化指定构造器和便利构造器之间的调用关系,Swift
采用以下三条规则来限制构造器之间的代理调用:
//规则1
指定构造器必须调用其直接父类的的指定构造器。
//规则2
便利构造器必须调用同一类中定义的其它构造器。
//规则3
便利构造器必须最终以调用一个指定构造器结束。
//一个更方便记忆的方法是:
//? 指定构造器必须总是向上代理
//? 便利构造器必须总是横向代理

//Swift 中类的构造过程包含两个阶段。第一个阶段,每个存储型属性通过引入它们的类的构造器来设置初始值。当
每一个存储型属性值被确定后,第二阶段开始,它给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。
//两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止属性值在初始化之前被访问;也可以防止属性被另外一个构造器意外地赋予不同的值。
//注意:Swift的两段式构造过程跟 Objective-C
中的构造过程类似。最主要的区别在于阶段 1,Objective-C
给每一 个属性赋值 0
或空值(比如说 0
或 nil )。Swift
的构造流程则更加灵活,它允许你设置定制的初始值,并自
如应对某些属性不能以 0 或 nil
作为合法默认值的情况。
class Vehicle {
    var numberOfWheels =
0
    var description:
String {
        return
"\(numberOfWheels) wheel(s)"
    }
}

let vehicle = Vehicle()
print("Vehicle:
\(vehicle.description)")

class Bicycle: Vehicle {
    override
init() {
        super.init()
        numberOfWheels =
2
    }
}

let bicycle = Bicycle()
print("Bicycle:
\(bicycle.description)")

// 食物
class Food {
    var name:
String
    init(name:
String) {
        self.name = name
    }
    convenience
init() {
        self.init(name:
"[Unnamed]")
    }
}
let namedMeat = Food(name:
"Bacon")
// namedMeat 的名字是 "Bacon”

let mysteryMeat = Food()
// mysteryMeat 的名字是 [Unnamed]

//一款调味料
class RecipeIngredient:
Food {
    var quantity:
Int
    init(name:
String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    override
convenience init(name:
String) {
        self.init(name: name, quantity:
1)
    }
}

let oneMysteryItem =
RecipeIngredient()
let oneBacon = RecipeIngredient(name:
"Bacon")
let sixEggs = RecipeIngredient(name:
"Eggs", quantity: 6)

class ShoppingListItem2:
RecipeIngredient {
    var purchased =
false
    var description:
String {
        var output =
"\(quantity) x
\(name.lowercaseString)"
        output += purchased ?
" ?" : " ?"
        return output
    }
}

//let breakfastList = [
//    ShoppingListItem2(),
//    ShoppingListItem2(name: "Bacon"),
//    ShoppingListItem2(name: "Eggs", quantity: 6),
//]
//breakfastList[0].name = "Orange juice"
//breakfastList[0].purchased = true
//for item in breakfastList {
//    print(item.description)
//}
// 1 x orange juice ?
// 1 x bacon ?
// 6 x eggs ?

/*可失败构造器**************************************************/
//如果一个类,结构体或枚举类型的对象,在构造自身的过程中有可能失败,则为其定义一个可失败构造器,是非常有必要的。这里所指的“失败”是指,如给构造器传入无效的参数值,或缺少某种所需的外部资源,又或是不满足某种必要的条件等。
struct Animal {
    let species:
String
    init?(species:
String) {
        if species.isEmpty {
return nil }
        self.species = species
    }
}

let someCreature =
Animal(species: "Giraffe")
// someCreature 的类型是 Animal?
而不是 Animal
if let giraffe =
someCreature {
    print("An animal was initialized with a species of
\(giraffe.species)")
}
// 打印 "An animal was initialized with a species of Giraffe"

let anonymousCreature =
Animal(species: "")
// anonymousCreature 的类型是 Animal?,
而不是 Animal
if anonymousCreature ==
nil {
    print("The anonymous creature could not be initialized")
}
// 打印 "The anonymous creature could not be initialized"

//注意:
空字符串( "" )和一个值为 nil
的可选类型的字符串是两个完全不同的概念。上例中的空字符串( "" )其实是一个有效的,非可选类型的字符串。这里我们只所以让 Animal
的可失败构造器,构建对象失
败,只是因为对于 Animal
这个类的 species 属性来说,它更适合有一个具体的值,而不是空字符串。
enum TemperatureUnit {
    case Kelvin, Celsius, Fahrenheit
    init?(symbol:
Character) {
        switch symbol {
            case
"K":
                self = .Kelvin
            case
"C":
                self = .Celsius
            case
"F":
                self = .Fahrenheit
            default:
                return
nil
        }
    }
}

let fahrenheitUnit =
TemperatureUnit(symbol: "F")
if fahrenheitUnit !=
nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}
// 打印 "This is a defined temperature unit, so initialization succeeded."

let unknownUnit = TemperatureUnit(symbol:
"X")
if unknownUnit ==
nil {
    print("This is not a defined temperature unit, so initialization failed.")
}
// 打印 "This is not a defined temperature unit, so initialization failed."

enum TemperatureUnit2:
Character {
    case Kelvin =
"K", Celsius = "C", Fahrenheit =
"F"
}
let fahrenheitUnit2 =
TemperatureUnit2(rawValue: "F")
if fahrenheitUnit2 !=
nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}
// prints "This is a defined temperature unit, so initialization succeeded."

let unknownUnit2 =
TemperatureUnit2(rawValue: "X")
if unknownUnit2 ==
nil {
    print("This is not a defined temperature unit, so initialization failed.")
}
// prints "This is not a defined temperature unit, so initialization failed."

class Product {
    let name:
String!
    init?(name:
String) {
        self.name = name
        if name.isEmpty {
return nil }
    }
}
//因为 name
属性是一个常量,所以一旦 Product
类构造成功, name 属性肯定有一个非 nil
的值。因此完全可 以放心大胆的直接访问 Product
类的 name 属性,而不用考虑去检查 name
属性是否有值
if let bowTie =
Product(name: "bow tie") {
    // 不需要检查 bowTie.name == nil
    print("The product's name is
\(bowTie.name)")
}
// 打印 "The product's name is bow tie"

//下面这个例子,定义了一个名为 CartItem
的 Product 类的子类。这个类建立了一个在线购物车中的物品的模
型,它有一个名为 quantity
的常量参数,用来表示该物品的数量至少为1:
//class CartItem: Product {
//    let quantity: Int!
//    init?(name: String, quantity: Int) {
//        super.init(name: name)
//        if quantity < 1 { return nil }
//        self.quantity = quantity
//    }
//}

//下例定义了一个名为 Document
的类,这个类中的 name
属性允许为 nil 和一个非空字符串,但不能是一个空字
符串:
class Document {
    var name:
String?
    // 该构造器构建了一个name属性值为nil的document对象
    init() {}
    // 该构造器构建了一个name属性值为非空字符串的document对象 
    init?(name:
String) {
        if name.isEmpty {
return nil }
        self.name = name
    }
}

class AutomaticallyNamedDocument:
Document {
        override
init() {
            super.init()
            self.name =
"[Untitled]"
        }
        override
init(name: String) {
            super.init()
            if name.isEmpty {
                self.name =
"[Untitled]"
            } else {
                self.name = name
            }
        }
}
//AutomaticallyNamedDocument 用一个非可失败构造器 init(name:) ,重写了基类的可失败构造器 init?(name:)
。因为子类用不同的方法处理了 name 属性的值为一个空字符串的这种情况。所以子类将不再需要一个可失
败的构造器。

/*必要构造器**************************************************/
//在类的构造器前添加 required
修饰符表明所有该类的子类都必须实现该构造器:
class SomeClass {
    required
init() {
    // 在这里添加该必要构造器的实现代码 
    }
}

//当子类重写基类的必要构造器时,必须在子类的构造器前同样添加 required
修饰符以确保当其它类继承该子类 时,该构造器同为必要构造器。在重写基类的必要构造器时,不需要添加 override
修饰符:
class SomeSubclass:
SomeClass {
    required
init() {
    // 在这里添加子类必要构造器的实现代码 
    }
}

/*通过闭包和函数来设置属性的默认值**************************************************/
//如果某个存储型属性的默认值需要特别的定制或准备,你就可以使用闭包或全局函数来为其属性提供定制的默认值。每当某个属性所属的新类型实例创建时,对应的闭包或函数会被调用,而它们的返回值会当做默认值赋值给这个属性。
//class SomeClass2 {
//    let someProperty: SomeType = {
//    // 在这个闭包中给 someProperty
创建一个默认值 // someValue 必须和 SomeType
类型相同 
//        return someValue
//    }()
//}
//注意闭包结尾的大括号后面接了一对空的小括号。这是用来告诉 Swift
需要立刻执行此闭包。如果你忽略了这对 括号,相当于是将闭包本身作为值赋值给了属性,而不是将闭包的返回值赋值给属性。
//注意:
如果你使用闭包来初始化属性的值,请记住在闭包执行时,实例的其它部分都还没有初始化。这意味着你不能够
在闭包里访问其它的属性,就算这个属性有默认值也不允许。同样,你也不能使用隐式的 self
属性,或者调用
其它的实例方法。

//西洋跳棋游戏在一副黑白格交替的 10x10
的棋盘中进行。为了呈现这副游戏棋盘, Checkerboard
结构体定义了 一个属性 boardColors ,它是一个包含 100
个布尔值的数组。数组中的某元素布尔值为 true 表示对应的是一个
黑格,布尔值为 false
表示对应的是一个白格。数组中第一个元素代表棋盘上左上角的格子,最后一个元素代表
棋盘上右下角的格子。
struct Checkerboard {
    let boardColors: [Bool] = {
        var temporaryBoard = [Bool]()
        var isBlack =
false
        for i
in 1...10 {
            for j
in 1...10 {
                temporaryBoard.append(isBlack)
                isBlack = !isBlack
            }
            isBlack = !isBlack
        }
    return temporaryBoard
    }()
    func squareIsBlackAtRow(row:
Int, column: Int) ->
Bool {
                return
boardColors[(row * 10) + column]
    }
}
//每当一个新的 Checkerboard
实例创建时,对应的赋值闭包会执行,一系列颜色值会被计算出来作为默认值赋值
给 boardColors 。上面例子中描述的闭包将计算出棋盘中每个格子合适的颜色,将这些颜色值保存到一个临时数
组 temporaryBoard 中,并在构建完成时将此数组作为闭包返回值返回。这个返回的值将保存到 boardColors
中,并可以通 squareIsBlackAtRow
这个工具函数来查询。
let board = Checkerboard()
print(board.squareIsBlackAtRow(0, column:
1))
// 输出 "true"
print(board.squareIsBlackAtRow(0, column:
2))
// 输出 "false"
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  iOS