31 October 2014

基础部分

常量和变量

let来声明常量,用var来声明变量

类型推断

初始化常量或者变量的时候如果所赋的值类型已知,就可以触发类型推断。

类型标注

如果声明时不初始化,使用类型标注:需要在常量或者变量名后面加上一个冒号和空格,然后加上类型名称。

var welcomeMessage: String

字符串插值

字符串插值:将常量或变量名放入圆括号中,并在开括号前使用反斜杠将其转义。

println("The current value of friendlyWelcome is \(friendlyWelcome)")
// 输出 "The current value of friendlyWelcome is Bonjour!

嵌套注释

通过运用嵌套多行注释,你可以快速方便的注释掉一大段代码,即使这段代码之中已经含有了多行注释块。

/* 这是第一个多行注释的开头
/* 这是第二个被嵌套的多行注释 */
这是第一个多行注释的结尾 */

类型安全

swift是类型安全的,会在编译你的代码时进行类型检查。

元祖

高阶数据类型比如元组,可以让你创建或者传递一组数据,比如作为函数的返回值时,你可以用一个元组可以返回多个值。

元组在函数返回值时很有用,但是并不适合创建复杂的数据结构。如果你的数据结构并不是临时使用,请使用类或者结构体而不是元组。

可选类型

可选类型,用于处理值缺失的情况。可选表示“那儿有一个值,并且它等于 x ”或者“那儿没有值”。

可选值的强制解析:当你确定可选类型确实包含值之后,你可以在可选的名字后面加一个感叹号!来获取值。

类型别名

类型别名就是给现有类型定义另一个名字。你可以使用typealias关键字来定义类型别名。

可选绑定

if let actualNumber = possibleNumber.toInt() {
    println("\(possibleNumber) has an integer value of \(actualNumber)")
} else {
    println("\(possibleNumber) could not be converted to an integer")
}
// 输出 "123 has an integer value of 123"

“如果possibleNumber.toInt返回的可选Int包含一个值,创建一个叫做actualNumber的新常量并将可选包含的值赋给它。” 如果转换成功,actualNumber常量可以在if语句的第一个分支中使用。它已经被可选类型包含的值初始化过,所以不需要再使用 !后缀来获取它的值。在这个例子中,actualNumber只被用来输出转换结果。 你可以在可选绑定中使用常量和变量。如果你想在if语句的第一个分支中操作actualNumber的值,你可以改成if var actualNumber,这样可选类型包含的值就会被赋给一个变量而非常量。

nil

var serverResponseCode: Int? = 404
// serverResponseCode 包含一个可选的 Int 值 404
serverResponseCode = nil
// serverResponseCode 现在不包含值

nil不能用于非可选的常量和变量。如果你的代码中有常量或者变量需要处理值缺失的情况,请把它们声明成对应的可选类型。

断言

assert函数传入一个结果为true或者false的表达式以及一条信息,当表达式为false的时候这条信息会被显示:

let age = -3
assert(age >= 0, "A person's age cannot be less than zero")
// 因为 age < 0,所以断言会触发

在这个例子中,只有age >= 0为true的时候,即age的值非负的时候,代码运行才会继续。如果age的值是负数,就像代码中那样,age >= 0为false,断言被触发,结束应用。

断言的适用情景:

  1. 整数类型的下标索引被传入一个自定义下标脚本实现,但是下标索引值可能太小或者太大。

  2. 需要给函数传入一个值,但是非法的值可能导致函数不能正常执行。

  3. 一个可选值现在是nil,但是后面的代码运行需要一个非nil值。

基本运算符

赋值

赋值操作并不返回任何值,这个特性使你无法把==错写成=

算术运算

默认情况下不允许在数值运算中出现溢出情况。但是你可以使用 Swift 的溢出运算符来实现溢出运算(如a &+ b

空合运算符

a ?? b将对可选类型a进行空判断,如果a包含一个值就进行解封,否则就返回一个默认值b

相当于a != nil ? a! : b

区间运算符

a...b:从a到b(包括a和b)

a..<b:从a到b但不包括b

字符串和字符

Swift的String类型与NSString类进行了无缝桥接。

在 Objective-C 和 Cocoa 中,您通过选择两个不同的类(NSStringNSMutableString)来指定该字符串是否可以被修改,Swift 中的字符串是否可以修改仅通过定义的是变量还是常量来决定,实现了多种类型可变性操作的统一。

值类型

如果您创建了一个新的字符串,那么当其进行常量、变量赋值操作或在函数/方法中传递时,会进行值拷贝。 任何情况下,都会对已有字符串值创建新副本,并对该新副本进行传递或赋值操作。

Swift 默认字符串拷贝的方式保证了在函数/方法中传递的是字符串的值。 很明显无论该值来自于哪里,都是您独自拥有的。 您可以放心您传递的字符串本身不会被更改。

swift这个特性和java是一样的。

与 Cocoa 中的NSString不同,当您在 Cocoa 中创建了一个NSString实例,并将其传递给一个函数/方法,或者赋值给一个变量,您传递或赋值的是该NSString实例的一个引用,除非您特别要求进行值拷贝,否则字符串不会生成新的副本来进行赋值操作。

字符串比较

==可以来比较两string内容是否一样。

集合类型

数组

Swift 数组特定于它所存储元素的类型。这与 Objective-C 的 NSArrayNSMutableArray 不同,这两个类可以存储任意类型的对象,并且不提供所返回对象的任何特别信息。在 Swift 中,数据值在被存储进入某个数组之前类型必须明确,方法是通过显式的类型标注或类型推断,而且不是必须是class类型。例如: 如果我们创建了一个Int值类型的数组,我们不能往其中插入任何不是Int类型的数据。 Swift 中的数组是类型安全的,并且它们中包含的类型必须明确。

var shoppingList: [String] = [“Eggs”, “Milk”] var shoppingList = [“Eggs”, “Milk”] //类型推断 // shoppingList 已经被构造并且拥有两个初始项。

Shoppinglist数组被声明为变量(var关键字创建)而不是常量(let创建)是因为以后可能会有更多的数据项被插入其中。

可以通过+=在数组尾部添加数据项。

使用for-in循环来遍历所有数组中的数据项:

for item in shoppingList {
    println(item)
}

enumerate返回一个由每一个数据项索引值和数据值组成的元组:

for (index, value) in enumerate(shoppingList) {
    println("Item \(index + 1): \(value)")
}

字典

Swift 的字典使用时需要具体规定可以存储键和值类型。不同于 Objective-C 的NSDictionary和NSMutableDictionary 类可以使用任何类型的对象来作键和值并且不提供任何关于这些对象的本质信息。在 Swift 中,在某个特定字典中可以存储的键和值必须提前定义清楚,方法是通过显性类型标注或者类型推断。

Swift 的字典使用Dictionary<KeyType, ValueType>定义,其中KeyType是字典中键的数据类型,ValueType是字典中对应于这些键所存储值的数据类型。

KeyType的唯一限制就是可哈希的,这样可以保证它是独一无二的,所有的 Swift 基本类型(例如String,Int, Double和Bool)都是默认可哈希的,并且所有这些类型都可以在字典中当做键使用。

更新字典:

if let oldValue = airports.updateValue("Dublin Internation", forKey: "DUB") {
    println("The old value for DUB was \(oldValue).")
}
// 输出 "The old value for DUB was Dublin."(DUB原值是dublin)

使用下标语法来在字典中检索特定键对应的值。由于使用一个没有值的键这种情况是有可能发生的,可选类型返回这个键存在的相关值,否则就返回nil:

if let airportName = airports["DUB"] {
    println("The name of the airport is \(airportName).")
} else {
    println("That airport is not in the airports dictionary.")
}
// 打印 "The name of the airport is Dublin Internation."(机场的名字是都柏林国际)

遍历字典,返回元祖:

for (airportCode, airportName) in airports {
    println("\(airportCode): \(airportName)")
}

字典元素的遍历顺序和插入顺序可能不同,字典的内容在内部是无序的,所以遍历元素时不能保证顺序。

控制流

switch的 case 语句中匹配的值可以是由 case 体内部临时的常量或者变量决定,也可以由where分句描述更复杂的匹配条件。另外不会发生贯穿的情况,如果想贯穿需要使用fallthrough

let someCharacter: Character = "e"
switch someCharacter {
case "a", "e", "i", "o", "u":
    println("\(someCharacter) is a vowel")
case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
"n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
    println("\(someCharacter) is a consonant")
default:
    println("\(someCharacter) is not a vowel or a consonant")
}

case也可以区间匹配。

case (let x, 0)将匹配一个纵坐标为0的点,并把这个点的横坐标赋给临时的常量x。

where子句用于case中:

let yetAnotherPoint = (1, -1)
switch yetAnotherPoint {
case let (x, y) where x == y:
    println("(\(x), \(y)) is on the line x == y")
case let (x, y) where x == -y:
    println("(\(x), \(y)) is on the line x == -y")
case let (x, y):
    println("(\(x), \(y)) is just some arbitrary point")
}
// 输出 "(1, -1) is on the line x == -y"

函数

返回元祖:

func count(string: String) -> (vowels: Int, consonants: Int, others: Int) {
    var vowels = 0, consonants = 0, others = 0
    for character in string {
        switch String(character).lowercaseString {
        case "a", "e", "i", "o", "u":
            ++vowels
        case "b", "c", "d", "f", "g", "h", "j", "k", "l", "m",
          "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z":
            ++consonants
        default:
            ++others
        }
    }
    return (vowels, consonants, others)
}

调用的时候:

let total = count("some arbitrary string!")
println("\(total.vowels) vowels and \(total.consonants) consonants")
// prints "6 vowels and 13 consonants"

外部参数

有时候,调用函数时,给每个参数命名是非常有用的,因为这些参数名可以指出各个实参的用途是什么。

如果你希望函数的使用者在调用函数时提供参数名字,那就需要给每个参数除了局部参数名外再定义一个外部参数名。外部参数名写在局部参数名之前,用空格分隔。

func someFunction(externalParameterName localParameterName: Int) {
    // function body goes here, and can use localParameterName
    // to refer to the argument value for that parameter
}

注意: 如果你提供了外部参数名,那么函数在被调用时,必须使用外部参数名。

以下是个例子,这个函数使用一个结合者(joiner)把两个字符串联在一起:

func join(s1: String, s2: String, joiner: String) -> String {
    return s1 + joiner + s2
}

当你调用这个函数时,这三个字符串的用途是不清楚的:

join("hello", "world", ", ")
// returns "hello, world"

为了让这些字符串的用途更为明显,我们为 join 函数添加外部参数名:

func join(string s1: String, toString s2: String, withJoiner joiner: String) -> String {
    return s1 + joiner + s2
}

在这个版本的 join 函数中,第一个参数有一个叫 string 的外部参数名和 s1 的局部参数名,第二个参数有一个叫 toString 的外部参数名和 s2 的局部参数名,第三个参数有一个叫 withJoiner 的外部参数名和 joiner 的局部参数名。

现在,你可以使用这些外部参数名以一种清晰地方式来调用函数了:

join(string: "hello", toString: "world", withJoiner: ", ")
// returns "hello, world"

使用外部参数名让第二个版本的 join 函数的调用更为有表现力,更为通顺,同时还保持了函数体是可读的和有明确意图的。

常量参数和变量参数

函数参数默认是常量。试图在函数体中更改参数值将会导致编译错误。

但是,有时候,如果函数中有传入参数的变量值副本将是很有用的。你可以通过指定一个或多个参数为变量参数,从而避免自己在函数中定义新的变量。变量参数不是常量,你可以在函数中把它当做新的可修改副本来使用。

通过在参数名前加关键字 var 来定义变量参数:

func alignRight(var string: String, count: Int, pad: Character) -> String {
    let amountToPad = count - countElements(string)
    if amountToPad < 1 {
        return string
    }
    let padString = String(pad)
    for _ in 1...amountToPad {
        string = padString + string
    }
    return string
}
let originalString = "hello"
let paddedString = alignRight(originalString, 10, "-")
// paddedString is equal to "-----hello"
// originalString is still equal to "hello"

alignRight 函数将参数 string 定义为变量参数。这意味着 string 现在可以作为一个局部变量,用传入的字符串值初始化,并且可以在函数体中进行操作。

注意: 对变量参数所进行的修改在函数调用结束后便消失了,并且对于函数体外是不可见的。变量参数仅仅存在于函数调用的生命周期中。

输入输出参数

变量参数,正如上面所述,仅仅能在函数体内被更改。如果你想要一个函数可以修改参数的值,并且想要在这些修改在函数调用结束后仍然存在,那么就应该把这个参数定义为输入输出参数。

定义一个输入输出参数时,在参数定义前加 inout 关键字。一个输入输出参数有传入函数的值,这个值被函数修改,然后被传出函数,替换原来的值。

你只能将变量作为输入输出参数。你不能传入常量或者字面量,因为这些量是不能被修改的。当传入的参数作为输入输出参数时,需要在参数前加&符,表示这个值可以被函数修改。

注意: 输入输出参数不能有默认值。如果你用 inout 标记一个参数,这个参数不能被 var 或者 let 标记。 下面是例子,swapTwoInts 函数,有两个分别叫做 a 和 b 的输入输出参数:

func swapTwoInts(inout a: Int, inout b: Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

这个 swapTwoInts 函数仅仅交换 a 与 b 的值。该函数先将 a 的值存到一个暂时常量 temporaryA 中,然后将 b 的值赋给 a,最后将 temporaryA 幅值给 b。

你可以用两个 Int 型的变量来调用 swapTwoInts。需要注意的是,someInt 和 anotherInt 在传入 swapTwoInts 函数前,都加了 & 的前缀:

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
println("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// prints "someInt is now 107, and anotherInt is now 3”

从上面这个例子中,我们可以看到 someInt 和 anotherInt 的原始值在 swapTwoInts 函数中被修改,尽管它们的定义在函数体外。

注意: 输入输出参数和返回值是不一样的。上面的 swapTwoInts 函数并没有定义任何返回值,但仍然修改了 someInt 和 anotherInt 的值。输入输出参数是函数对函数体外产生影响的另一种方式。

函数类型

var mathFunction: (Int, Int) -> Int = addTwoInts

这个可以读作: “定义一个叫做 mathFunction 的变量,类型是‘一个有两个 Int 型的参数并返回一个 Int 型的值的函数’,并让这个新变量指向 addTwoInts 函数”。

闭包

闭包是自包含的函数代码块,可以在代码中被传递和使用。 Swift 中的闭包与 C 和 Objective-C 中的代码块(blocks)以及其他一些编程语言中的 lambdas 函数比较相似。

下面结合一个具体的例子,还看如何使用闭包:

sorted函数需要传入两个参数:

  1. 已知类型的数组:

    let names = [“Chris”, “Alex”, “Ewa”, “Barry”, “Daniella”]

  2. 闭包函数,该闭包函数需要传入与数组类型相同的两个值,并返回一个布尔类型值来告诉sorted函数当排序结束后传入的第一个参数排在第二个参数前面还是后面。此例为:(String, String) -> Bool。

基本方式

提供排序闭包函数的一种方式是撰写一个符合其类型要求的普通函数,并将其作为sort函数的第二个参数传入:

func backwards(s1: String, s2: String) -> Bool {
    return s1 > s2
}
var reversed = sorted(names, backwards)
// reversed 为 ["Ewa", "Daniella", "Chris", "Barry", "Alex"]

闭包表达式

reversed = sorted(names, { (s1: String, s2: String) -> Bool in
    return s1 > s2
})

可见其实就是上面backwards方法搬过来了,注意闭包的函数体部分由关键字in引入。

推断参数和返回类型

可以简化为:

reversed = sorted(names, { s1, s2 in return s1 > s2 } )

单表达式隐式返回

reversed = sorted(names, { s1, s2 in s1 > s2 } )

参数缩写

如果您在闭包表达式中使用参数名称缩写,您可以在闭包参数列表中省略对其的定义,并且对应参数名称缩写的类型会通过函数类型进行推断。 in关键字也同样可以被省略,因为此时闭包表达式完全由闭包函数体构成:

reversed = sorted(names, { $0 > $1 } )

在这个例子中,$0和$1表示闭包中第一个和第二个String类型的参数。

嵌套函数中捕获值

闭包可以在其定义的上下文中捕获常量或变量。 即使定义这些常量和变量的原域已经不存在,闭包仍然可以在闭包函数体内引用和修改这些值。

Swift最简单的闭包形式是嵌套函数,也就是定义在其他函数的函数体内的函数。(我认为这样理解闭包非常直观) 嵌套函数可以捕获其外部函数所有的参数以及定义的常量和变量。

下例为一个叫做makeIncrementor的函数,其包含了一个叫做incrementor嵌套函数。 嵌套函数incrementor从上下文中捕获了两个值,runningTotal和amount。 之后makeIncrementor将incrementor作为闭包返回。 每次调用incrementor时,其会以amount作为增量增加runningTotal的值。

func makeIncrementor(forIncrement amount: Int) -> () -> Int {
    var runningTotal = 0
    func incrementor() -> Int {
        runningTotal += amount
        return runningTotal
    }
    return incrementor
}

makeIncrementor返回类型为() -> Int。 这意味着其返回的是一个函数,而不是一个简单类型值。 该函数在每次调用时不接受参数只返回一个Int类型的值。 关于函数返回其他函数的内容,请查看函数类型作为返回类型。

makeIncrementor函数定义了一个整型变量runningTotal(初始为0) 用来存储当前跑步总数。 该值通过incrementor返回。

makeIncrementor有一个Int类型的参数,其外部命名为forIncrement, 内部命名为amount,表示每次incrementor被调用时runningTotal将要增加的量。

incrementor函数用来执行实际的增加操作。 该函数简单地使runningTotal增加amount,并将其返回。

如果我们单独看这个函数,会发现看上去不同寻常:

func incrementor() -> Int {
    runningTotal += amount
    return runningTotal
}

incrementor函数并没有获取任何参数,但是在函数体内访问了runningTotal和amount变量。这是因为其通过捕获在包含它的函数体内已经存在的runningTotal和amount变量而实现。

由于没有修改amount变量,incrementor实际上捕获并存储了该变量的一个副本,而该副本随着incrementor一同被存储。

然而,因为每次调用该函数的时候都会修改runningTotal的值,incrementor捕获了当前runningTotal变量的引用,而不是仅仅复制该变量的初始值。捕获一个引用保证了当makeIncrementor结束时候并不会消失,也保证了当下一次执行incrementor函数时,runningTotal可以继续增加。

注意: Swift 会决定捕获引用还是拷贝值。 您不需要标注amount或者runningTotal来声明在嵌入的incrementor函数中的使用方式。 Swift 同时也处理runingTotal变量的内存管理操作,如果不再被incrementor函数使用,则会被清除。 下面代码为一个使用makeIncrementor的例子:

let incrementByTen = makeIncrementor(forIncrement: 10)

该例子定义了一个叫做incrementByTen的常量,该常量指向一个每次调用会加10的incrementor函数。 调用这个函数多次可以得到以下结果:

incrementByTen()
// 返回的值为10
incrementByTen()
// 返回的值为20
incrementByTen()
// 返回的值为30

如果您创建了另一个incrementor,其会有一个属于自己的独立的runningTotal变量的引用。 下面的例子中,incrementBySevne捕获了一个新的runningTotal变量,该变量和incrementByTen中捕获的变量没有任何联系:

let incrementBySeven = makeIncrementor(forIncrement: 7)
incrementBySeven()
// 返回的值为7
incrementByTen()
// 返回的值为40

闭包是引用,意味着如果您将闭包赋值给了两个不同的常量/变量,两个值都会指向同一个闭包:

let alsoIncrementByTen = incrementByTen
alsoIncrementByTen()
// 返回的值为50

-EOF-