苹果Swift语言中文简明教程

  Swift是苹果推出的新语言,本语言教程翻译于苹果官方网站的Swift Tour

  传统上介绍一个新的语言都是输出显示"Hello, world"在屏幕上,Swift能使用单行实现:

println("Hello, world")

  如果你曾经编写过 C 或 Objective-C代码, 这个语法对于你比较熟悉,在Swift中,这行代码是一个完整的程序,你不必导入另外一个包或使用input/output 或 string之类处理,代码的作用域是全局的,可以整个程序都可以使用,你不必须需要一个 main函数,你也不必在每行代码后面加上分号作为结束。

  这个教程展示你Swift如何能完成一个语言程序的大部分任务。目录如下:

简单的值

  使用let能生成一个常量,使用var生成一个变量,常量的值在编译时间不必被知道,但是你还是要准确地分配一个值,这意味着你可以一次性给一个常量值,然后在其他地方反复使用。

  • var myVariable = 42
  • myVariable = 50
  • let myConstant = 42

  一个常量或变量必须和你分配的值有相同的类型,当然,你并不必须每次总是显式地声明类型,当你创建一个常量或变量时分配一个值给它,让编译器去推断它的类型,在上面案例中,编译器推断出myVariable是整数型integer,因为你分配了一个值的类型是整数型。

  如果初始值并不提供足够信息,或者说没有初始值,那么在变量后面要使用冒号声明其类型。

  • let implicitInteger = 70
  • let implicitDouble = 70.0
  • let explicitDouble: Double = 70
  

值并不总是能显式地转变为另外一种类型,如果你需要将值转变为一个不同的类型,请显式地为目标类型生成一个实例。 注意下面代码的String()是一种转换。

  • 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."

使用方括号([])创建一个数组或字典, 通过在方括号里面写入索引数字或关键字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<String, Float>()

如果类型信息能被推断,你就可以编写一个空的数组如[],使用 [:]作为空的字典,比如你可以为一个变量设置一个新值,或传递一个参数给一个函数。

  • shoppingList = [] // Went shopping and bought everything.

控制流

使用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表达式,这意味着单纯上面代码如果这样写 if score { ... } 就是错误的。

你能一起使用if 和 let,let的值是可选的,可以没有或有,可以包含一个值或包含nil以显示这个值丢失了,在值类型后面使用问号标记这个值是可选的。

  • var optionalString: String? = "Hello"
  • optionalString == nil
  • var optionalName: String? = "John Appleseed"
  • var greeting = "Hello!"
  • if let name = optionalName {
  •    greeting = "Hello, \(name) comes to jdon.com"
  • }

如果可选值是nil, 条件结果就是false,大括号中的代码忽视不执行,否则,可选值将被释放分配给let后面的常量,这会使得释放后的值对于大括号内的方法体可以使用它。

Switche支持各种数据和广泛的比较操作,不限于整数或相同测试。

  • 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可以重复执行一段代码块直至满足的条件改变,循环loop的条件是在循环尾部检查,这样能确保循环loop至少循环一次。

  • var n = 2
  • while n < 100 {
  •  n = n * 2
  • }
  • n
  • var m = 2
  • do {
  •  m = m * 2
  • } while m < 100
  • m

你能在循环中保留一个索引index —可以使用“ .. ”表示一段索引范围,或者编写显式的初始值,满足的条件和增长累计的情况,这两种都是同样的:

  • var firstForLoop = 0
  • for i in 0..3 {
  •   firstForLoop += i
  • }
  • firstForLoop
  • var secondForLoop = 0
  • for var i = 0; i < 3; ++i {
  •   secondForLoop += 1
  • }
  • secondForLoop

注意使用“..”类似于“ var i = 0; i < 3; ++i ”,最大值3是不会达到的,最小值0是开始起步值。

函数与闭包

使用 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()

函数是第一等公民,那意味着一个函数能够把另外一个函数作为值返回。

  • 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
  • })

你编写闭包时有几个选项能让你更精确,当一个闭包类型是已知,比如一个委托的回调,你能忽视它的参数类型,它的返回类型。单个statement闭包返回隐式表示返回的statement值。

  • 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 from jdon."
  •  }
  • }

创建一个类的实例是在在类名后加上括号,使用小数点访问实例的属性和方法。

  • 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 from jdon."
  •  }
  • }

注意到self是用来区分当前初始化方法的属性名和类的属性名,传递给初始化方法的参数像传递给函数调用一样,每个属性需要一个值分配,可以直接声明如numberOfSides,或者在初始化器中进行分配,如name.

使用 deinit来创建销毁解构,特别是你需要在这个对象被摧毁重新分配之前要做一些清理工作,释放一些资源等等。

子类在类名后包括子类的名称,使用冒号,对于一个继承根类的子类没有任何要求,你在需要时能包含或忽视一个超级类中任何元素。

在子类中方法能覆盖父类的实现,使用override—如果不是使用overriding,编译器将探测到错误,编译器也能发现使用带覆盖的方法其实并没有真正覆盖父类中的方法。

  • 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: "jdon test square")
  • test.area()
  • test.simpleDescription()

属性可以有getter 和 a setter,以便属性能被对象外界通过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. 设置子类自己的属性值

  2. 调用父类的初始化器

  3. 改变父类的属性值,其他方法或getters, 或setters都能实现这点。

如果你不需要计算属性值,在设置新值之前或之后你需要一些代码做些事情,那就使用 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)

当存在可选值时,你能在操作之前使用问号 ? 。这些操作包括方法 属性和subscripting. 如果问号之前的值使用 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 Jdon, Queen, King
  •  func simpleDescription() -> String {
  •   switch self {
  •    case .Ace:
  •     return "ace"
  •     case .Jdon:
  •      return "jdon"
  •    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成员被引用有两种方法: 当分配一个值给hearts常量时,枚举成员Suit.Hearts被用完整名称引用,因为常量并没有显式类型定义,在switch里面, 枚举使用缩写方式 .Heart引用,因为self的值已经知道是一个,在值类型已知情况下,你可以任何时候使用缩写。

使用 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) in jdon.com "
  • }

请注意 sunrise 和sunset时间是如何从ServerResponse释放,这是作为匹配值的一部分,而不是 switch cases。

protocol与extension

使用protocol声明一个协议。

  • protocol ExampleProtocol {
  •  var simpleDescription: String { get }
  •  mutating func adjust()
  • }

类 枚举和数据结构都可以使用协议:

  • class SimpleClass: ExampleProtocol {
  •  var simpleDescription: String = "A very simple class from jdon.com."
  •  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

注意到关键词 mutating是SimpleStructure声明,用来标记一个方法,这个方法是用来修改这个结构的,SimpleClass的声明不需要这样,因为一个类的方法总是可以修改类自身的。

使用 extension能为存在的类型增加功能,你能使用extension 为一个类型增加protocol 的一致性,也可以为从库包框架导入的类型这么做。

  • extension Int: ExampleProtocol {
  •  var simpleDescription: String {
  •   return "The number \(self)"
  •  }
  •  mutating func adjust() {
  •   self += 42
  •  }
  • }
  • 7.simpleDescription

你能使用protocol名称,创建一个对象集合,其中有不同类型,但是都有一致的统一协议protocol. 当你和这些类型是protocol的类型打交道时,协议定义以外的方法就无用了。

  • 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是指定一些需求—举例,为了需要这个类型实现一个protocol, 为了需要两个类型是同样的,或为了需要一个类有一个特别的超类。

  • 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 ,只是在冒号后简单写 protocol或 class类名 . 写 <T: Equatable> 等同于g <T where T: Equatable>.

苹果Swift语言简要介绍

面向函数

英文Swift在线文档