The Swift Programming Language(一)——A Swift Tour

A Swift Tour

导航简介

通常对于一门新的语言,第一个程序都是hello world。在 Swift 中,只需要一行就能完成它:
println("Hello, world")

如果你以前用 C 或 Objective-C 写过程序的话,那么这个语法想必看起来是非常熟悉的。在 Swift 中,这个单行的代码就已经是一个完整的程序了。你并不需要另外再导入一个输入输出或字符串处理的库。写在全局作用域下的代码会被当做程序的入口点,所以你不需要写 main 函数。你也不必在每句句末加上分号。

本教程通过向您展示如果完成各种各样的编程任务,以此来引导您开始 Swift 编程之旅。如果中间有不明白的也请不要担心,这里的每一项内容在本书的后半部分都有详细讲解。

简单的赋值

使用 let 来定义常量,var 来定义变量。在编译的时候并不需要指定常量的值,但是你必须为它指定一个值,且仅指定一次。这意味着你可以用常量来为一个“单次赋值,却重复使用”的值来命名。
var myVariable = 42
myVariable = 50
let myConstant = 42

不论是常量还是变量,都必须与给他们分配的值的类型一致。但你并不需要每次都明确声明类型,在进行初始化赋值操作时,编译器会自动判断出它的类型。在上面的示例中,由于42是整数,编译器会自动判断出 myVariable 是整数类型。
如果初始值不能提供足够多的判断信息(或者根本就没有初始值),这时可以在变量后面手动指定类型,类型名与变量名用一个冒号隔开。
let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble: Double = 70

一个类型的值是不会隐式转换为另一种类型的,如果你需要给一个值转换类型,请明确指出它:
let label = "The width is "
let width = 94
let widthLabel = label + String(width)

这还有一个更简单的方法用来给字符串插值:用圆括号将要插入的值括起来,并在前面加上一个反斜杠(\)。例如:
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."

使用方括号创建数组和字典,并通过写在括号中的序号与键值来访问:
var shoppingList = ["catfish", "water", "tulips", "blue paint"]
shoppingList[1] = "bottle of water"
 
var occupations = [
    "Malcolm": "Captain",
    "Kaylee": "Mechanic",
]
occupations["Jayne"] = "Public Relations"

在创建空数组时,需要指定类型:
let emptyArray = String[]()
let emptyDictionary = Dictionary<String, Float>()

在可自动判断类型的情况下,空的数组可以被简写为[],而空的字典可以被简写为[:]。例如,当你为变量设置一个新的值或者向函数传递参数时:
shoppingList = []   // Went shopping and bought everything.


流程控制

ifswitch 进行条件判断,用 for-inforwhiledo-while 来写循环语句。判断与循环的条件表达式部分可以不加括号,但是被控制的流程主体必须加括号:
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
    if score > 50 {
        teamScore += 3
    } else {
        teamScore += 1
    }
}
teamScore

在 if 语句中,条件判断部分必须是一个布尔表达式。这就是说“if score { ... }”是一段会报错的代码,他不会隐式将 score 与 0 进行比较。

在处理可能为空的值时,您可以同时使用 if 和 let 。这些值被表示为可选的,它们或含有一个值,或在丢失的时候被表示为 nil 。在变量类型的后面加一个问号可以将其标记为可选的。
var optionalString: String? = "Hello"
optionalString == nil
 
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName {
    greeting = "Hello, \(name)"
}

如果可选值为 nil,那么条件表达式的值就为 false,大括号中的语句都会在执行时被跳过。如果可选值不为空,则将它赋值给 let 后面的变量,这一变量可以在后面的代码块中使用。

switch 语句支持任何类型的数据与任意比较操作——并不止局限于整数与相等判断。
let vegetable = "red pepper"
switch vegetable {
case "celery":
    let vegetableComment = "Add some raisins and make ants on a log."
case "cucumber", "watercress":
    let vegetableComment = "That would make a good tea sandwich."
case let x where x.hasSuffix("pepper"):
    let vegetableComment = "Is it a spicy \(x)?"
default:
    let vegetableComment = "Everything tastes good in soup."
}

在执行过与 switch 后的值相匹配 case 部分的代码后,程序就会从 switch 语句中自动跳出。程序不会像某些语言一样沿着下面的 case 内容继续执行,所以不必再每一个 case 后都 显式地加上类似于 break 的退出语句。

你可以使用 for-in 来遍历字典中的键值对,并可以通过提供一对形参来显式调用它们:
let interestingNumbers = [
    "Prime": [2, 3, 5, 7, 11, 13],
    "Fibonacci": [1, 1, 2, 3, 5, 8],
    "Square": [1, 4, 9, 16, 25],
]
var largest = 0
for (kind, numbers) in interestingNumbers {
    for number in numbers {
        if number > largest {
            largest = number
        }
    }
}
largest

你可以使用 while 语句来重复执行代码块,直到判断条件的状态变为 false。也可以用 do-while 将判断条件放在循环末尾,以此来保证循环代码至少被执行了一次。
var n = 2
while n < 100 {
    n = n * 2
}
n
 
var m = 2
do {
    m = m * 2
} while m < 100
m

你可以在循环中使用一个序列,不论是通过 a..b 的方式还是通过编写显式的初始化、条件和增量都可以。下面这两个循环的结果是相同的:
var firstForLoop = 0
for i in 0..3 {
    firstForLoop += i
}
firstForLoop
 
var secondForLoop = 0
for var i = 0; i < 3; ++i {
    secondForLoop += 1
}
secondForLoop


函数和闭包

使用 func 来定义一个函数。通过函数名加参数列表的方式来调用函数。使用 -> 符号来分开参数列表的类型与函数的返回类型。
func greet(name: String, day: String) -> String {
    return "Hello \(name), today is \(day)."
}
greet("Bob", "Tuesday")

可以通过元组让函数返回多个值:
func getGasPrices() -> (Double, Double, Double) {
    return (3.59, 3.69, 3.79)
}
getGasPrices()

函数还可以接收数量可变的参数,它们会被存入一个数组中:
func sumOf(numbers: Int...) -> Int {
    var sum = 0
    for number in numbers {
        sum += number
    }
    return sum
}
sumOf()
sumOf(42, 597, 12)

可以进行函数嵌套。内层的嵌套函数可以访问外层函数中声明的变量。你可以使用嵌套函数来简化那些长而复杂的函数代码:
func returnFifteen() -> Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen()

在这里,函数是 first-class 类型的。( 译者:这与JS中的函数相同,而与C中不同)这意味着一个函数可以将另一个函数作为其返回值:
func makeIncrementer() -> (Int -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)

函数的参数可以是另一个函数:
func hasAnyMatches(list: Int[], condition: Int -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}
func lessThanTen(number: Int) -> Bool {
    return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(numbers, lessThanTen)

函数实际上是闭包的一个特例。你可以通过使用大括号({})来实现一个没有名字的闭包,使用 in 来分割参数与返回值。
numbers.map({
    (number: Int) -> Int in
    let result = 3 * number
    return result
    })

你有好几种选择来编写一个简洁的闭包。当一个闭包的类型是类似委托回调那样的已知值时,你可以将它的参数类型或返回值类型省略掉。单条语句的闭包会隐式地返回它们自己的声明值:
numbers.map({ number in 3 * number })

你可以在闭包中用数字调用取代参数名调用,这种方法通常用于特别短的闭包中。闭包的代码段可以直接写在函数最后一个参数的圆括号后面:
sort([1, 5, 3, 12, 2]) { $0 > $1 }

对象和类

使用 class + 类名 的方式来创建一个类。除了跟上下文中有联系的部分,类中其余的常量和变量都可以用通常的方法来定义。类似的,函数与方法的定义也与别处相同。

class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

通过 类名() 的方式来声明一个类的实例,使用"."来访问类实例中的属性和方法。

var shape = Shape()
shape.numberOfSides = 7
var shapeDescription = shape.simpleDescription()

上面的 Shape 类中缺失了一个很重要的东西:一个构造函数。你可以通过 init 来创建一个:

class NamedShape {
    var numberOfSides: Int = 0
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    func simpleDescription() -> String {
        return "A shape with \(numberOfSides) sides."
    }
}

请注意理解上例中是怎样使用 self 来区分类中的属性name与init()中的局部变量name。当你创建一个类的实例时,参数就会以类似函数调用的方式传递给init方法。类中的每一个属性都需要初始化,这一操作可以是在类中定义的时候(像numberOfSides),或者在init中(像name),


当你需要在对象销毁前做一些操作的时候,使用 deinit 定义一个析构函数来进行它们吧。


定义子类时,需要在类名后面加一个冒号,然后写上它的父类名称。在进行类的继承时,并没有额外的要求,所以你可以根据需求选择是否需要进行继承。

如果子类中要覆盖父类的方法,需要加上 override 关键字。没有加 override 却意外覆盖了方法时,编译器会报错。当使用了 override 却没有覆盖任何父类的方法时,编译器同样会将它检测出来。
class Square: NamedShape {
    var sideLength: Double
    
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 4
    }
    
    func area() ->  Double {
        return sideLength * sideLength
    }
    
    override func simpleDescription() -> String {
        return "A square with sides of length \(sideLength)."
    }
}
let test = Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()

除了简单的存储属性,还可以有专门针对属性的 getter 与 setter。
class EquilateralTriangle: NamedShape {
    var sideLength: Double = 0.0
    
    init(sideLength: Double, name: String) {
        self.sideLength = sideLength
        super.init(name: name)
        numberOfSides = 3
    }
    
    var perimeter: Double {
    get {
        return 3.0 * sideLength
    }
    set {
        sideLength = newValue / 3.0
    }
    }
    
    override func simpleDescription() -> String {
        return "An equilateral triagle with sides of length \(sideLength)."
    }
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: "a triangle")
triangle.perimeter
triangle.perimeter = 9.9
triangle.sideLength

上例中,perimeter的setter中隐式地将它命名为 newValue 。你也可以在 set 后的括号中显式地为其指定名称。

请注意,  EquilateralTriangle 类的构造函数会执行三步不同的操作:
1. 给子类中定义的属性sideLength赋值
2. 调用父类的构造函数
3. 改变父类中定义的属性numberOfSides的值。任何使用额外 方法/setter/getter 进行设置的操作也会在这一步中进行

如果你不需要计算该属性,但仍然需要在属性改变的前后做一些操作的话,请使用 willSet 与 didSet。例如下面的类,它将保证三角形的边长始终与正方形的边长相等。
class TriangleAndSquare {
    var triangle: EquilateralTriangle {
    willSet {
        square.sideLength = newValue.sideLength
    }
    }
    var square: Square {
    willSet {
        triangle.sideLength = newValue.sideLength
    }
    }
    init(size: Double, name: String) {
        square = Square(sideLength: size, name: name)
        triangle = EquilateralTriangle(sideLength: size, name: name)
    }
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: "another test shape")
triangleAndSquare.square.sideLength
triangleAndSquare.triangle.sideLength
triangleAndSquare.square = Square(sideLength: 50, name: "larger square")
triangleAndSquare.triangle.sideLength

类中的方法与函数有一个重要区别:函数中的参数名只能在函数体内使用,而方法中的参数名在你调用这个方法的时候就可以使用(除了第一个参数)。默认情况下,在你调用一个方法时,参数名与方法体内使用的参数名称是相同的,你也可以为方法体内使用的参数名称指定另一个名字。
class Counter {
    var count: Int = 0
    func incrementBy(amount: Int, numberOfTimes times: Int) {
        count += amount * times
    }
}
var counter = Counter()
counter.incrementBy(2, numberOfTimes: 7)

当使用可选值时(optional value),你可以在方法、属性或下标操作之前写上一个问号,这时,如果问号前的值为 nil,那么问号之后的表达式均会被忽略,且这整句的值都为nil。如果问号前的值不为空,那么整个表达式会正常运行。综合来看这两者,含有问号的表达式的值也是可选的。
let optionalSquare: Square? = Square(sideLength: 2.5, name: "optional square")
let sideLength = optionalSquare?.sideLength

枚举与结构体

使用 enum 来创建一个枚举类型。就像类和其他命名类型一样,枚举类型也可以有与之关联的方法:
enum Rank: Int {
    case Ace = 1
    case Two, Three, Four, Five, Six, Seven, Eight, Nine, Ten
    case Jack, Queen, King
    func simpleDescription() -> String {
        switch self {
        case .Ace:
            return "ace"
        case .Jack:
            return "jack"
        case .Queen:
            return "queen"
        case .King:
            return "king"
        default:
            return String(self.toRaw())
        }
    }
}
let ace = Rank.Ace
let aceRawValue = ace.toRaw()

在上面的例子中,枚举的原始类型为 Int,所以你只需要显式给出第一个的值,其余的原始值都会按顺序自动分配。你也可以使用字符串或者浮点数作为枚举的原始值。

使用 toRaw fromRaw 来进行原始值与枚举值的转换。
if let convertedRank = Rank.fromRaw(3) {
    let threeDescription = convertedRank.simpleDescription()
}

枚举的成员值就是真实值,它并不是原始值的另一种表示方式。事实上,有的时候原始值是没有实际意义的,在这时你也没有必要去主动设置原始值。
enum Suit {
    case Spades, Hearts, Diamonds, Clubs
    func simpleDescription() -> String {
        switch self {
        case .Spades:
            return "spades"
        case .Hearts:
            return "hearts"
        case .Diamonds:
            return "diamonds"
        case .Clubs:
            return "clubs"
        }
    }
}
let hearts = Suit.Hearts
let heartsDescription = hearts.simpleDescription()

请注意上例中的两种引用 Hearts 成员的方式:( 这一段暂时搁置,实在是看不太懂,看过后面的内容后再回来补充
When assigning a value to the  hearts constant, the enumeration member  Suit.Hearts is referred to by its full name because the constant doesn’t have an explicit type specified. Inside the switch, the enumeration is referred to by the abbreviated form  .Hearts because the value of  self is already known to be a suit. You can use the abbreviated form anytime the value’s type is already known.

使用 struct 来创建结构体。结构体支持许多与类相同的行为,例如初始化和方法。结构体与类的一个最大的区别是:结构体始终是通过拷贝的方法在代码间传递,而类是通过引用传递。
struct Card {
    var rank: Rank
    var suit: Suit
    func simpleDescription() -> String {
        return "The \(rank.simpleDescription()) of \(suit.simpleDescription())"
    }
}
let threeOfSpades = Card(rank: .Three, suit: .Spades)
let threeOfSpadesDescription = threeOfSpades.simpleDescription()

一个枚举成员的实例可以指定与该实例相关联的值。同一枚举成员的不同实例可以有不同的关联值,这些值需要在创建枚举实例的时候提供。关联值与原始值是不同的:枚举成员的原始值对所有实例都是相同的,它们在定义枚举的时候就被确定了,跟实例化阶段没有关系。
例如,想象一下这样的情景:你向服务器来发送请求查看日出日落的时刻,服务器会告诉你一个时刻,或者它会给你返回一段错误信息:
enum ServerResponse {
    case Result(String, String)
    case Error(String)
}
 
let success = ServerResponse.Result("6:00 am", "8:09 pm")
let failure = ServerResponse.Error("Out of cheese.")
 
switch success {
case let .Result(sunrise, sunset):
    let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
case let .Error(error):
    let serverResponse = "Failure...  \(error)"
}
请注意在 switch 语句中,ServerResponse 的值是如何与 case 中的匹配值对应起来的。

协议与扩展

使用 protocol 来定义协议。
protocol ExampleProtocol {
    var simpleDescription: String { get }
    mutating func adjust()
}

类,枚举,结构体都可以采用协议。
class SimpleClass: ExampleProtocol {
    var simpleDescription: String = "A very simple class."
    var anotherProperty: Int = 69105
    func adjust() {
        simpleDescription += "  Now 100% adjusted."
    }
}
var a = SimpleClass()
a.adjust()
let aDescription = a.simpleDescription
 
struct SimpleStructure: ExampleProtocol {
    var simpleDescription: String = "A simple structure"
    mutating func adjust() {
        simpleDescription += " (adjusted)"
    }
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription

请注意上面在定义 SimpleStructure 时,使用了关键字  mutating 来标记一个可以修改结构体的方法。而在SimpleClass中没有这样做,这是因为类中的方法一直都是可以修改类的。

使用  extension 来将给已有类型添加功能,比如新的方法或者运算。你可以用它来为一个在别处定义的类型增添协议一致性,即使这个类型是从一个库或者框架中导入的也不例外。
extension Int: ExampleProtocol {
    var simpleDescription: String {
    return "The number \(self)"
    }
    mutating func adjust() {
        self += 42
    }
}
7.simpleDescription

你可以像使用其他类型名称那样使用协议的名称。例如,你可以创建一系列的不同类型对象,并让它们遵守一个相同的协议。当你使用一个类型为协议的值时,协议中未定义的方法均是不可用的。
let protocolValue: ExampleProtocol = a
protocolValue.simpleDescription
// protocolValue.anotherProperty  // Uncomment to see the error

尽管变量 protocolValue 是在 SimpleClass 运行时进行类型识别的,编译器在运行时会将它当做已经声明过的类型 ExampleProtocol 对待的。这意味着你不会意外访问到协议类型定义之外的类属方法与属性。

泛型

使用在尖括号中写入方法名的方式来定义泛型函数或类型。
func repeat<ItemType>(item: ItemType, times: Int) -> ItemType[] {
    var result = ItemType[]()
    for i in 0..times {
        result += item
    }
    return result
}
repeat("knock", 4)

你可以以此来构造一般形式的函数、方法、类、枚举和结构体。
// Reimplement the Swift standard library's optional type
enum OptionalValue<T> {
    case None
    case Some(T)
}
var possibleInteger: OptionalValue<Int> = .None
possibleInteger = .Some(100)

通过在类型名后面加上 where 来定义一个规则列表。例如,规定必须有一个协议类型,或规定要有两种类型是相同的,或者要求含有一个具有某一特定父类的类。
func anyCommonElements <T, U where T: Sequence, U: Sequence, T.GeneratorType.Element: Equatable, T.GeneratorType.Element == U.GeneratorType.Element> (lhs: T, rhs: U) -> Bool {
    for lhsItem in lhs {
        for rhsItem in rhs {
            if lhsItem == rhsItem {
                return true
            }
        }
    }
    return false
}
anyCommonElements([1, 2, 3], [3])

在一些比较简单的情况下,你可以忽略 where,只在冒号后面写上协议或者类名即可。  <T: Equatable> 与 <T where T: Equatable> 是等价的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值