译自苹果Swift官方文档《The Swift Programming Language》第一章“A Swift Tour”。
依照传统,对于一门新的编程语言,第一个程序都是以在屏幕上输出“Hello,World”为例。在Swift编程语言中,我们可以用下面一行语句来实现:
println(“Hello, world”)
如果你用C或者Object-C写过代码的话,这个语法看起来应该不会陌生——在Swift语言中,这一行语句就构成了一个完整的程序。你不需要去引入(import)一个单独的类库来引用输入/输出(input/output)函数,或者是字符串处理函数等等。在全局作用域中编写的代码就被认为是程序入口,所以你也不再需要一个main方法了。此外,你也不需要在每一个表达式后面写分号了。
这个教程会向你介绍一系列的方法来教会你如何实现各种编程任务,当你学完整个教程后,你掌握的知识就足以让你开始着手Swift开发了。如果你还有什么不清楚的,也别担心,这个教程中涉及到的任何内容,都会在本书的后续部分有更详细的解释。
简单类型(Simple Values)
使用let关键字来定义常量,用var来定义变量。常量的值不必在编译时指定,但是必须且只能为它赋一次值,也就是说你可以使用常量来定义一个值,但是这个常量赋值一次,但是可以用在很多地方。
var myVariable = 42
myVariable = 50
let myConstant = 42
在为常量或者变量赋值时,一定要保证类型一致。然而,你不必显示地指明类型,在创建常量或者变量的时候指定一个值,让编译器来推断它的类型就可以了。在上面的例子中,编译器会推断出myVariable是一个整型(integer),因为它的初始值就是一个整数。
如果初始值并没有提供足够的信息(或者没有初始值),那么可以在变量的后面指定其类型,中间使用分号隔开。
let implicitInteger = 70
let implicitDouble = 70.0
let explicitDouble : Double = 70
对变量来说永远不存在隐式类型转换,如果你需要将一个变量转换为另一种类型,显式地创建一个目标类型的实例。
let label = “The width is “
let width = 94
let widthLabel = label + String(width)
除了上述方法外,还有一种更简单的方法用来将变量的值组装到字符串中:用圆括号将变量括起来,并且在圆括号的前面添加反斜线(backslash)。例如:
let apples = 3
let oranges = 5
let appleSummary = “I have \(apples) apples.”
let fruitSummary = “I have \(apples + oranges) pieces of fruit.”
使用方括号([])来创建数组(array)和字典(dictionary),要访问数组或字典的元素就在方括号中写上索引(index)或者键值(key)。
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
如果类型信息能够被推断出来,你可以将空数组写为[],将空字典写为[:]——例如,当你为一个变量赋予新的值或者向一个方法传参的时候。
shoppingList = [] // Wentshopping and bought everything.
流程控制(Control Flow)
使用if语句和switch语句来设置条件,使用for-in,for,while和do-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语句的条件必须是Boolean表达式——也就是说类似于“ifscore {…}”这种写法是错误的,不会隐式地将条件和0进行比较。
你可以结合使用if语句和let来处理那些值有可能不存在的变量。这些变量用optional类型来表示。一个optional变量的值可能是一个具体的值也可能是nil,nil表示变量的值不存在(或者说值为空)。在类型后面写一个问号(?)来表明变量为optional类型。
var optionalString: String? = “Hello”
optionalString == nil
var optionalName: String? = “John Appleseed”
var greeting = “Hello!”
if let name = optionalName {
greeting = “Hello, \(name)”
}
(上面的代码中),如果optional变量的值为nil,那么判断条件结果为false,括号中的语句就被跳过了。否则的话,optional变量的值就被解析出来然后赋给let后面声明的常量,使得if代码块中的计算结果有效。
switch语句支持任何数据类型和多种比较操作——不仅仅是整数值和相等的判断。
let vegetable = “red pepper”
switch vegetable {
case “celery”:
let vegetableComment = “Addsome raisins and make ants on a log.”
case “cucumber”, “watercress”:
let vegetableComment = “Thatwould make a good tea sandwich.”
case “let x where x.hasSuffix(“pepper”):
let vegetableComment = “It is aspicy \(x)?”
default:
let vegetableComment =“Everything tastes good in soup.”
}
执行完switch中匹配条件的case语句后,程序会从switch语句中跳出,而不是继续执行下一个case语句中的内容,所以不需要在每一个case代码块后面显式地添加break语句来跳出。
你可以使用for-in来遍历一个字典中的每一项,只需要为键值对(key-valuepair)提供一对名字即可。
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循环来重复执行一段代码,直到条件改变。条件也可以放到后面,以保证循环至少被执行一次(do-while循环)。
var n = 2
while n < 100 {
n = n * 2
}
n
var m = 3
do {
m = m * 2
} while m < 100
m
你可以在循环中保存索引值——或者是使用“..”来创建一个索引范围,或者是显式地写出初始值,条件和增量。下面例子中的两个循环实现了相同的效果:
var firstForLoop = 0
for i in 0..3 {
firstForLoop +=i
}
firstForLoop
var secondForLoop = 0
for var i = 0; i < 3; ++i {
secondForLoop += 1
}
secondForLoop
使用“..”来创建一个范围,上限值被忽略掉(译注:例如1..3仅代表1和2,3被忽略),使用“…”来创建一个范围,这个范围将包括上限值。
函数(function)和闭包(closure)
使用func来声明函数。在调用函数的时候,首先指定函数名称,接着在名称后面使用圆括号将参数列出。使用“->”来将参数名称、类型与函数返回值分隔开。
func greet(name: String, day: String) -> String {
return “Hello \(name), today is \(day).”
}
greet(“Bob”, “Tuesday”)
使用元组(tuple)来从函数中返回多个值。
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)的类型,换句话说一个函数可以作为另一个函数的返回值。
func makeIncrementer() -> (Int -> Int) {
func addOne(number: Int) -> Int {
return 1 + numer
}
return addOne
}
var increment = makeIncrementer()
increment(7)
一个函数也可以使用另一个函数作为参数。
func hasAnyMatches(list: Int[], condition: Int -> Bool) ->Bool {
for item in list {
if condition(item) {
returntrue
}
}
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 }
对象(object)与类(class)
使用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属性和初始化方法的name参数。当创建类的实例的时候,初始化方法的参数被以函数调用的方式传入。每个属性都需要被赋值——不管是在声明的时候(例如上面的numberOfSides属性)或者是在初始化方法中(例如name属性)。
当对象释放之前,如果需要做一些清理工作,可以使用deinit关键字来创建一个deinitializer(译注:也就是析构方法)。
如果要声明子类,在子类名后面用分号分隔开父类的类名即可。并不强制要求一个类必须作为某个标准的根类的子类,所以你可以根据需要来确定是否要引入一个父类。
子类重写父类的方法实现需要使用override关键字来标记——如果无意中重写了父类的方法而没有使用override关键字,编译器会抱错(译注:swift的这个特性还是很赞的,避免了很多潜在的问题)。编译器也会检查出那些使用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()
除了简单地在类中存储属性以外,属性还可以有getter和setter(译注:也就是属性访问器,propertyaccessors)
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 trianglewith sides of length \(sideLength).”
}
}
var triangle = EquilateralTriangle(sideLength: 3.1, name: “atriangle”)
triangle.perimeter
triangle.perimeter = 9.9
triangle.sideLength
在perimeter(周长)的setter方法中,newValue是新值(译注:也就是赋值语句等号右边的值)的隐含的变量名称,当然你也可以通过圆括号来显式地为新值指定一个名称。
注意上面EquilateralTriangle类的初始化方法包含了三个不同的步骤:
1. 为子类中的属性赋值
2. 调用父类的初始化方法
3. 修改父类中的属性的值。任何利用方法,getter或者setter来做的额外的初始化工作都可以在这个时候进行。
如果你不需要计算或者设置属性值,但是需要提供一段可以再属性赋值之前或者之后执行的代码的话,可以使用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 =EqualateralTriangle(sideLength: size, name: name)
}
}
var triangleAndSquare = TriangleAndSquare(size: 10, name: “anothertest shape”)
triangleAndSquare.square.sideLength
triangleAndSquare.triangle.sideLength
triangleAndSquare.square = Square(sideLength: 50, name: “largersquare”)
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类型的变量,你可以在例如方法,属性或者子脚本(subscripting)这些操作前面加上“?”来修饰。如果问号前面的变量值是nil,问号后面所有的东西都被忽略掉了(译注:一直到当前表达式的末尾),当前表达式的值为nil。否则(如果问号前面的值不是nil),optional变量的值被解析出来,所有问号后面的操作都使用解析后的变量来执行。无论在哪种情况下,整个表达式的值都是一个optional类型。
let optionalSquare: Square? = Square(sideLength: 2.5, name:“optional square”)
let sideLength = optionalSquare?.sideLength
枚举(enumeration)和结构体(structure)
使用enum关键字来定义枚举。和类以及其他命名(named)类型一样,枚举中也可以定义方法。
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:
returnString(self.toRaw())
}
}
}
let ace = Rand.Ace
let aceRawValue = ace.toRaw()
上面的例子中,枚举类型的原始值类型(raw valuetype)是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的两种方式:当为名为hearts的常量赋值的时候,枚举成员Suit.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()
对枚举成员的实例对象我们可以声明一些值与之关联,相同枚举成员的不同实例对象可以关联不同的值。在创建实例对象的时候可以提供这些关联值。关联值和原始值是不同的:枚举成员的原始值对于其所有的实例对象都是相同的,并且原始值是在定义枚举类型的时候就指定好了。
例如,考虑下面这个例子:我们需要从服务器请求日出和日落的时间,服务器要么返回我们需要的信息,要么返回错误信息。
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) andsunset is at \(sunset).”
case let .Error(error):
let serverResponse = “Failure… \(error)”
}
注意一下上面代码中我们从ServerResponse的值中提取出日出和日落时间的方法:通过switch语句case的值的一部分来获取。
协议(protocol)和扩展(extension)
使用protocol关键字来声明协议。
protocol ExampleProtocol {
var simpleDescription: String { get }
mutating func adjust()
}
类,枚举和结构体都可以遵循协议(译注:可以认为是继承)。
class SimpleClass: ExampleProtocol {
var simpleDescription: String = “A very simpleclass.”
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 simplestructure”
mutating func adjust() {
simpleDescription += “(adjusted)”
}
}
var b = SimpleStructure()
b.adjust()
let bDescription = b.simpleDescription
注意mutating关键字的用法:该关键字在定义SimpleStructure的时候用来标记用来修改结构体的方法。而SimpleClass的定义中并不需要任何方法用mutating关键字来标记,因为类中的方法总是可以修改类的。
使用extension关键字来为已知类型添加函数,比如新的方法或者计算属性(译注:getter,setter之类)。你可以使用扩展来让别处定义的一个类型遵循某个协议,甚至是某个你从类库或框架中引入的类型。
extension Int: ExampleProtocol {
var simpleDescription: String {
return “The number \(self)”
}
mutating func adjust() {
self += 42
}
}
7.simpleDescription
你可以像使用其他命名(named)类型一样来使用协议名——例如我们可以创建一个由不同类型的对象组成的集合,只要所有类型都遵循相同的协议就可以。当你处理类型是协议类型的变量时,不在协议的定义中的方法就不可用了(译注:也就是说,如果想要调用类中单独定义的方法,需要进行类型转换了)。
let protocolValue: ExampleProtocol = a
protocolValue.simpleDescription
// protocolValue.anotherProperty // Uncomment tosee the error
尽管变量protocolValue在运行时实际类型是SimpleClass,但是编译器仍然将它当做ExampleProtocol类型来处理。这就意味着你不能临时地去访问一个仅在类中定义,但是没有在其遵循的协议中定义的方法或者属性。
泛型(generics)
在尖括号中取一个名字用来声明一个泛型方法或者泛型类型。
func repeat
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
case None
case Some(T)
}
var possibleInteger: OptionalValue
possibleInteger = .Some(100)
在类型名后面使用where关键字来指定一系列的约束条件——例如,需要强制某个类型实现遵循某个协议,或者需要两种类型相同,或者需要某个类继承自特定的父类。
func anyCommonElements
for lhsItem in lhs {
for rhsItem in rhs {
iflhsItem == rhsItem {
return true
}
}
}
return false
}
anyCommonElements([1, 2, 3], [3])
在上面这个简单的例子中,你可以省略where关键字,仅仅使用协议名称或者类名称,并用分号隔开,写法