根据 《The Swift Programming Language (Swift 2.1) 》 翻译整理。
原文参考: https://developer.apple.com/library/prerelease/ios/documentation/Swift/Conceptual/Swift_Programming_Language
=====================================================================================================
传统的编程语言书籍往往会以一个“Hello World”程序开始。在Swift里,仅仅需要一句话便可完成:
print(“Hello, world!”)
如果你写过C或Objective-C的代码,这看起来相当眼熟—在Swift里这就是一个完整的程序了!你不需要再导入库文件来支持诸如输入输出和字符串处理等功能。代码写在全局范围内被用来作为程序的入口点,因此你不再需要main()函数。你也不再需要在每行结尾写上分号。
这一章教程会提供给你足够的信息来开始编写一个完整的多功能程序。不要担心有些东西你现在不能理解—你可以在后续章节中找到他们的细致描述。
简单变量
使用 let 来定义一个常量,使用 var 来定义一个变量。常量的值在编译时候不需要知道,但是你必须在某个时间给定一个值。这意味着常量只能被赋值一次,但是可以在多个场合使用。
var myVariable = 42
myVariable = 50
let myConstant = 42
一个常量或变量必须使用相同的类型。然而,你并不是总是需要明确地指定类型。当你创建一个常量或者变量时候,通过赋值让编译器能猜出它的类型。在上述例子中,编译器猜测 myVariabel 是一个整形,因为它有一个初始的整形值。
如果初始值不能提供足够的信息(或者没有提供初始值),则可以通过在变量后面使用冒号+类型 来明确说明,比如:
let implicitInteger= 70
let implicitDouble= 70.0
let explicitDouble:Double = 70
EXPERIMENT 试一试 |
创建一个指定类型为Float值为4的变量 |
变量永远不会不明确地转换成另外一个类型。如果要将一个值转换成另外一个不同的类型,请明确地指定。例如:
let label = “thewidth is”
let width = 94
let widthLable =label + String(width)
EXPERIMENT 试一试 |
请将上述例子中的 String移除,看一看会发生什么样的错误? |
事实上,有另外一种简单的方法在字符串中引入变量:将要插入的变量放在圆括号中,然后在圆括号前放上一个反斜杠( \ )。例如:
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", "bluepaint"]
shoppingList[1] ="bottle of water"
var occupations = [
"Malcolm": "Captain",
"Kaylee": "Mechanic",
]
occupations["Jayne"]= "Public Relations"
如果要创建一个空的数组或者字典,使用如下初始化语法:
let emptyArray =[String]()
let emptyDictionary= [String: Float]()
如果类型信息能够被推断出来,则可以用 [] 和 [:] 来定义一个空的数组或者字典 – 举例来说,当年需要传新值给一个变量或者要给函数传递一个参数时就可以这样使用。
shoppingList = []
occupations = [:]
控制语句
使用 if 和 switch 来做条件判断,使用for-in, for, while 和 repeat-while 来做循环控制。判断语句或者循环语句中的圆括号有时可以省略,但必须使用花括号来包裹其执行语句体。
let individualScores = [75, 43, 103, 87, 12]
var teamScore = 0
for score in individualScores {
if score > 50 {
teamScore+= 3
} else {
teamScore+= 1
}
}
print(teamScore)
在一个if语句中,条件必须是一个布尔表达式– 这意味着像if score {…} 这种写法是一个明确的错误,而不是一种和0的含糊对比。
【译者注: 在C或JAVA等语言中,你可以使用 ifscore {} 这样的语句,当score非0时条件成立。】
可以组合使用 if 和 let 来指明那些变量也许不存在的情况。这些变量值可能表现为可选。一个可选变量(optional value)也许包含一个值,也许是一个空值(nil)。在这种类型的变量后面使用问号 (?)来指明这是一个可选变量。
var optionalString:String? = "Hello"
print(optionalString== nil)
var optionalName:String? = "John Appleseed"
var greeting ="Hello!"
if let name =optionalName {
greeting = "Hello, \(name)"
}
EXPERIMENT 试一试 |
请将optionName设置成nil。看看会得到什么样的 greeting?当optionName为nil时,增加一个else语句块来给出不同的greeting。 |
如果可选变量为nil,那么这个条件就是false, 紧跟着的花括号中的代码块就将被忽略,不会执行。否则,可选变量被展开并赋值给let后面的这个常量,从而使得这个展开的值体现在代码块中。
另外一种处理可选变量的方式是通过 ??操作符 来提供一个缺省值。如果可选变量缺失,则使用缺省变量来代替。
let nickName:String? = nil
let fullName:String = "John Appleseed"
letinformalGreeting = "Hi \(nickName ?? fullName)"
Swift中的Switch语句支持任何类型和多样的比较操作-- 他们并不像其他语言一样仅仅可以使用整形和相等比较。
let vegetable ="red pepper"
switch vegetable {
case"celery":
print("Add some raisins and make antson a log.")
case "cucumber","watercress":
print("That would make a good teasandwich.")
case let x wherex.hasSuffix("pepper"):
print("Is it a spicy \(x)?")
default:
print("Everything tastes good insoup.")
}
EXPERIMENT 试一试 |
试着移掉default语句,看看会有什么错误发生? |
Notice how let canbe used in a pattern to assign the value that matched that part of a pattern toa constant.
当执行相匹配的代码块后,程序直接退出swtich语句,不再执行其他部分case,因此不需要显性地使用break语句。
可以使用for-in语句,通过使用键值对的名字,来遍历一个字典中的所有值。由于字典是一种无序集合,因此它们的键和值将被随机地迭代输出。
letinterestingNumbers = [
"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
}
}
}
print(largest)
EXPERIMENT 试一试 |
增加一个变量来跟踪最大数值位于哪种类型数字中,和如何得出最大数值? |
使用 while 语句来重复执行某个代码块直到条件改变。条件判断也可以放在循环体的最后,以保证代码块至少被执行一次。
var n = 2
while n < 100 {
n = n * 2
}
print(n)
var m = 2
repeat {
m = m * 2
} while m < 100
print(m)
可以在循环体中使用索引 –使用 ..< 来指定一个索引范围,或者写出明确的 初始值/条件判断/步增机制 来实现。 以下这两种方式完成了同样的功能:
var firstForLoop =0
for i in 0..<4 {
firstForLoop += i
}
print(firstForLoop)
var secondForLoop =0
for var i = 0; i< 4; ++i {
secondForLoop += i
}
print(secondForLoop)
使用 ..< 来指明上界并指明不包含这个上界值,使用... 来定义一个范围,并且包含头尾两个值。
(Use ..<
to make a range that omits its upper value, anduse ...
to make a range that includes both values.)
【译者注: 1..<3 包含 1 2, 1...3 包含 1 2 3】
函数和闭包 (Functions andClosures)
使用 func 来定义一个函数。写出函数名及其参数来调用一个函数。使用 -> 来区分参数名字列表和函数返回值。
func greet(name:String, day: String) -> String {
return "Hello \(name), today is\(day)."
}
greet("Bob",day: "Tuesday")
EXPERIMENT 试一试 |
移除 day 变量,用一个今天午餐吃什么的变量代替。 |
使用元组变量 -- 例如,用来表示一个函数返回多个值。元组中的元素可以用名字或者序号来引用。
funccalculateStatistics(scores: [Int]) -> (min: Int, max: Int, sum: Int) {
var min = scores[0]
var max = scores[0]
var sum = 0
for score in scores {
if score > max {
max = score
} else if score < min {
min = score
}
sum += score
}
return (min, max, sum)
}
let statistics =calculateStatistics([5, 3, 100, 3, 9])
print(statistics.sum)
print(statistics.2)
函数同样可以接收多个可变参数,把它们作为一个数组处理。
func sumOf(numbers:Int...) -> Int {
var sum = 0
for number in numbers {
sum += number
}
return sum
}
sumOf()
sumOf(42, 597, 12)
EXPERIMENT 试一试 |
写一个函数来计算所有传入参数的平均值 |
可以使用嵌套函数,嵌套函数可以调用外函数中定义的变量。使用嵌套函数可以方便地组织那些又长又复杂的代码。
func returnFifteen()-> Int {
var y = 10
func add() {
y += 5
}
add()
return y
}
returnFifteen()
函数是Swift中的一等公民(先类类型第一类型 first-class type)。这意味着一个函数可以使用另外一个函数作为返回值。
funcmakeIncrementer() -> ((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
}
funclessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20,19, 7, 12]
hasAnyMatches(numbers,condition: lessThanTen)
函数实际上是一种特殊的闭包(closures): 代码块可以在以后再调用。闭包中的代码可以访问这样一些东西:当闭包创建时的可见范围内的变量和函数,即使当执行时闭包位于不同的代码范围-- 你已经在嵌套函数中看见过这样的例子。你也可以编写一段没有名字的闭包代码,使用花括号({})来包裹它们。使用 in 关键字来在代码块中区分参数和返回值类型。
numbers.map({
(number: Int) -> Int in
let result = 3 * number
return result
})
EXPERIMENT 试一试 |
重写这段闭包使得它对于奇数返回0 |
还有多种办法来书写更简洁的闭包代码。当一个闭包的类型已知时,比如作为一个代表的回调函数(callback for a delegate),你可以省略它的参数类型或者返回类型,或者都省略。但语句闭包隐含地返回他们唯一语句的值。
let mappedNumbers =numbers.map({ number in 3 * number })
print(mappedNumbers)
你可以使用序号代替使用名字来引用参数 -- 这种用法在一个非常短的闭包中尤其有效。一个闭包作为最后一个参数传递给一个函数可以立刻显示在插入处。当一个闭包是一个函数的唯一参数时,可以省略圆括号。
let sortedNumbers =numbers.sort { $0 > $1 }
print(sortedNumbers)
对象和类
使用关键字 class 跟上类名来创建一个类。类中一个属性的声明和声明一个普通常量或者变量一样,只是它位于一个类声明的上下文中。同样的方法和函数的写法也类似。
class Shape {
var numberOfSides = 0
func simpleDescription() -> String {
return "A shape with\(numberOfSides) sides."
}
}
EXPERIMENT 试一试 |
试着用let增加一个常量属性,并增加另外一个含有一个参数的方法 |
在类名后面跟上一对圆括号就能创建这个类的一个新实例。使用点( . ) 来访问这个实例的方法或属性。
var shape = Shape()
shape.numberOfSides= 7
varshapeDescription = 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 属性和 name参数变量的。当创建一个实例时,初始化模块中的参数传递就像函数调用一样。每一个属性都需要赋值-- 不管是在它们声明的时候(as with numberOfSides)或是在初始化模块(as with name)中。
当你需要在实例消除前做一些清除动作的时,可以使用deinit 关键字来创建一个析构函数(deinitializer)。
在类名后面跟着一个冒号和父类名字,这样可以定义个子类。并不强制要求每个类都要有一个根类,因此你可以在定义类时包含或者省略父类声明。
当在子类中需要覆写父类的一个方法时,使用 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 oflength \(sideLength)."
}
}
let test =Square(sideLength: 5.2, name: "my test square")
test.area()
test.simpleDescription()
EXPERIMENT 试一试 |
实现另外一个子类 Circle, 一个带 半径和名字的构造函数。实现面积(area)和简单描述(simpleDescriptions)方法。 |
除了简单存储属性之外,也可以针对属性设置 setter 和 getter 函数。
classEquilateralTriangle: 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 trianglewith sides of length \(sideLength)."
}
}
var triangle =EquilateralTriangle(sideLength: 3.1, name: "a triangle")
print(triangle.perimeter)
triangle.perimeter= 9.9
print(triangle.sideLength
在 perimeter 的setter中,新值有隐含的名字 newValue,也可以在set后面的圆括号中提供一个明确的名字。
注意类EquilateralTriangle的构造函数(initializer)由不同的3步来完成:
1.设置子类定义的属性的值
2.调用父类的构造函数
3.改变父类定义的属性值。任何其他的使用方法的设置工作如getter,setter也可以在这一步完成。
如果不需要对一个属性进行计算但是需要提供设置一个新值操作前后的代码,使用 willSet 和 didSet。这些代码会在值在构造函数之外改变时被运行。举例来说,下面这个保证了三角形的边长总是和正方形边长一致。
classTriangleAndSquare {
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)
}
}
vartriangleAndSquare = TriangleAndSquare(size: 10, name: "another testshape")
print(triangleAndSquare.square.sideLength)
print(triangleAndSquare.triangle.sideLength)
triangleAndSquare.square= Square(sideLength: 50, name: "larger square")
print(triangleAndSquare.triangle.sideLength)
当使用可选变量时,你可以在方法、属性和下标之前的操作符中写上问号( ?)。如果问号( ?)之前的变量为空(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.rawValue)
}
}
}
let ace = Rank.Ace
let aceRawValue =ace.rawValue
EXPERIMENT 试一试 |
写一个函数来实现通过比较两个范围类型值的原生值来比较他们的大小 Write a function that compares two Rank values by comparing their raw values. |
在上述例子中,这个枚举的原生值类型为整形,因此你只需要指定第一个原生值(raw value)。其余的原生值将根据顺序分配。你也可以使用字符串或者浮点型来作为一个枚举的原生值类型。通过调用 rawValue 属性来访问一个枚举的原生值。
使用init?(rawValue:)构造函数来从一个原生值获得枚举的一个实例。
if let convertedRank = Rank(rawValue: 3) {
let threeDescription =convertedRank.simpleDescription()
}
枚举的案例值(casevalue)是实际的值,而不是原生值的另外一个写法。实际上,即使存在没有实际意义的原生值,你也不必要提供一个。
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()
EXPERIMENT 试一试 |
增加一个 color()方法,对于 spades 和clubs返回黑色,其他返回红色。 |
注意上面例子中Hearts 元素的两种引用方法:当对于常量 hearts 赋值时,枚举元素 Suit.Hearts 是使用全名来引用的,因为这个常量(hearts)还没有被明确地指定一个类型。在switch语句内部,则仅仅使用缩写 .Hearts 来引用,因为self的值已知是一个suit类型。当类型已知时,你可以在任何时间使用这种缩写方式。
使用 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()
EXPERIMENT 试一试 |
为 Card 增加一个方法来实现一副牌:组合牌面值(3-10 JQKA)和花式(suit)。 |
一个枚举类型实例可以有和实例关联的值。同一个枚举类型实例可以有不同的相关联值。当创建一个实例时候你赋予了它一个值。关联值和原生值不同,同一个枚举的原生值在每个实例中都相同,你定义一个枚举类型时就提供了原生值。
举例来说,考虑到获取服务器开关机时间的情况。服务器即可以提供正常运行的信息,也可能提供错误情况信息。
enum ServerResponse {
case Result(String,String)
case Error(String)
}
let success = ServerResponse.Result("6:00 am", "8:09pm")
let failure = ServerResponse.Error("Out of cheese.")
switch success {
case let .Result(sunrise, sunset):
print("Sunrise is at\(sunrise) and sunset is at \(sunset).")
case let .Error(error):
print("Failure... \(error)")
}
EXPERIMENT 试一试 |
给Server和switch语句增加第三种情况 |
协议和扩展(Protocols andExtensions)
使用关键字 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
EXPERIMENT 试一试 |
定义一个遵守该协议的枚举类型 |
注意关键字mutating 在SimpleStructure声明中的使用,来标记一个方法可以改变这个结构。SimpleClass的声明中却不需要任何方法被标明可以变异,因为一个类中的方法总是可以改变该类。
使用关键字extension 来给一个已知类型增加功能,比如新方法或者熟悉运算。可以使用扩展来对一个声明在别处的类型增加协议遵守,甚至可以对一个框架或者库中导入的类型也可以这样使用。
extension Int: ExampleProtocol {
var simpleDescription:String {
return "Thenumber \(self)"
}
mutating func adjust() {
self += 42
}
}
print(7.simpleDescription)
EXPERIMENT 试一试 |
对于Double类型增加一个扩展:增加 绝对值 absoluteValue 属性 |
你可以像使用其他命名类型一样来使用协议 -- 比如,创建一个遵守相同协议的不同对象集合。当你处理一个协议类型变量值时,协议定义之外的方法将不再可用。
let protocolValue: ExampleProtocol = a
print(protocolValue.simpleDescription)
// print(protocolValue.anotherProperty) // 取消该注释观察发生什么错误
即使变量protocolValue有一个运行时类型SimpleClass,编译器也仅仅将它当作给定类型ExampleProtocol来处理。这意味着你不能偶然地(临时地)该类在遵守协议之外的其他方法或者属性实现。
泛型
在一对尖括号内写上一个名字来创建一个通用功能或类型(泛型)。
func repeatItem<Item>(item: Item, numberOfTimes: Int) ->[Item] {
var result = [Item]()
for _ in0..<numberOfTimes {
result.append(item)
}
return result
}
repeatItem("knock", numberOfTimes:4)
可以创建函数、方法的泛型模式,同样地类、枚举和结构也可以。
// Reimplement the Swift standard library's optional type
// Swift标准库中可选变量的另外一种实现方法
enum OptionalValue<Wrapped> {
case None
case Some(Wrapped)
}
var possibleInteger: OptionalValue<Int> = .None
possibleInteger = .Some(100)
在类型名字后面使用where 来指定一系列需求-- 比如,要求该类型遵守某个协议,要求两个类型一样,或要求一个类有某个特别的父类。
func anyCommonElements <T: SequenceType, U: SequenceType whereT.Generator.Element: Equatable, T.Generator.Element == U.Generator.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])
EXPERIMENT 试一试 |
修改anyCommonElements(_:_:)来增加一个功能:返回任意两个序列有共同点的元素数组 |
<T: Equatable> 也可以写成 <T where T: Equatable>