Swift: 从入门到重学?

当时 Swift 才出来的时候就去体验过了这门新语言,也写过一篇文章介绍这门语言,而现在 Swift 3.0 发布,我决定重写这篇文章,你可以看到,前后两次我对这门语言的理解是有很大不同了。

同时这也说明了一个道理,如果你没有花一定的时间精力去认真体会一门语言,你是根本没有权利去评判一门语言的好坏的。

有很多人说,这 Swift 一年一个语法版本大更新,垃圾语言,在我看来不过是人云亦云的跟风狗罢了。

关于 Swift 我打算分成两部分讲:

  • 第一部分是语言部分,主要是这门编程语言的语法,还有内置的一些库,比如枚举类型、可选类型、协议等等;

  • 第二部分是函数式编程思想,这当中可能会涉及到函数式编程鼻祖 Haskell

0x00 基础语法 (The Basics)

let intValue = 10
let doubleValue = 3.1415926
let bigNumber = 1_000_000_000
let floatNumber: Float = 3
var ? = "Dog"

typealias Speed = (download: Double, upload: Double)
let speed: Speed = (300.21, 20.32)

func sayHello(to name: String) {
    print("hello \(name)")
}
sayHello(to: "ShinCurry")

let fruits = ["Apple", "Banana", "Lemon", "Pear"]
for fruit in fruits {
    print(fruit)
}
for i in 0..<5 {
    print(i)
}

以上就是 Swift 的最基础用法。

  • let 声明常量,var 声明变量
  • 类型推断,因此声明的时候不需要写明类型
  • typealias 可以给一组类型或者函数命名
  • 函数把返回值放到了最后,另外参数也有外部命名和内部名字
  • 移除了 C 语言风格的 for (;;) {} 循环,而用 for … in 取而代之。

Swift 是一门新语言,综合参考了其它很多的优秀语言,所以在语法上和传统语言有很大的差别,也因此有很多人都说 Swift 语法奇葩。

0x01 枚举类型 (Enum)

enum Fruit: String {
    case Apple
    case Banana
    case Lemon
    case Pear
}

let enumFruits: [Fruit] = [.Apple, .Banana, .Lemon, .Pear]
print(enumFruits[0].rawValue)


enum Card {
    case prc(number: String, name: String)
    case school(number: String, grade: Int)
    case market(number: String)
}

let idCard = Card.prc(number: "500224199208130001", name: "小明")
let schoolCard: Card = .school(number: "11403010330", grade: 2016)

func check(card: Card) {
    switch card {
    case .prc(let number):
        print("this idCard number is \(number).")
    case .market:
        print("This is a market card.")
    default:
        print("Other card.")
    }
}

Swift 的枚举类型非常强大,这也是我目前非常喜欢的一个特性。

0x02 可选类型 (Optional)

在 Objective-C 时代,一个变量可以是有值的也可以为 nil,所以在写 OC 的时候经常会写出以下这样的代码:

if (error != nil) {
  // do something
}

作为一个老司机尚且有时候会忘记检查一个值是否会空,更不要说一些刚开始学习编程的新手了。

而 Swift 提出了可选类型,就是为了解决这样的问题,在声明变量的时候,就明确的表示这个值是否可以为空。而在调用的时候,就会被强迫的去考虑空值问题。

var robotName: String?
if let name = robotName {
    print(robotName)
} else {
    print("No name")
}

robotName = "Atom"
print(robotName) // Optional("Atom")\n
print(robotName!) // Atom\n

在一个变量类型的后面添加上一个 ? ,就表示这是一个可选类型。

使用 if let 结构就可以安全解包。

而在调用这个变量的时候,使用 ? 或者 ! 来解包,如果有一串可选属性,甚至可以组成可选链:

struct Name {
    var last: String?
    var first: String?
}

struct Person {
    var name: Name?
}

var shin: Person?
shin = Person()
print(shin?.name?.last) // nil\n
shin?.name = Name()
shin?.name?.last = "Shin"
print(shin?.name?.last) // Optional("Shin")\n
print(shin?.name!.first) // nil\n
shin?.name?.first = "Yang"
print("\(shin?.name!.last!) \(shin?.name!.first!)") // Optional("Shin") Optional("Yang")\n

在一个可选链中,只有所有的可选项都不为空才会有值,不然会返回 nil

0x03 高阶函数 Map() Filter() Reduce()

有时候我们可能会得到一组数组类型的数据,我们希望从中提取到一些符合条件的数据,我们可以怎么做?

最传统的方法就是写上好几个 for 循环, 然后循环内加上一些条件语句进行筛选,这样做会非常的复杂,每一个步骤都要自己写精确控制,很容易出错,而实际上我们可以使用高阶函数来简化这一过程。

map()

var staffsMapName = [String]()
// old way
for staff in staffs {
    staffsMapName.append(" \(staff.firstname) \(staff.lastname)")
}
print(staffsMapName)

// functional way
staffsMapName = staffs.map {
    return "\($0.firstname) \($0.lastname)"
}

print(staffsMapName)

filter()

var staffsFilterWilliams = [Staff]()
// old way
for staff in staffs {
    if staff.firstname == "Williams" {
        staffsFilterWilliams.append(staff)
    }
}
print(staffsFilterWilliams)
// functional way
staffsFilterWilliams = staffs.filter { staff in
    return staff.firstname == "Williams"
}

reduce()

var staffsWilliamsCount = 0
// old way
for staff in staffs {
    if staff.firstname == "Williams" {
        staffsWilliamsCount++;
    }
}
print(staffsWilliamsCount)
// functional way
staffsWilliamsCount = staffs.reduce(0, combine: { $0 + (($1.firstname == "Williams") ? 1 : 0) })
print(staffsWilliamsCount)

  • map() 函数会提取出数组的每一个元素,然后让你对这个元素进行操作,最后返回一个新的元素,对每一个元素都进行自定义操作最后返回一个全新的数组。

  • filter() 函数同样会提取出数组的每一个元素,然后让你来自定义如何判断这个元素符合要求,通过返回一个布尔类型来筛选元素,最后返回一个筛选之后的新数组。

  • reduce() 函数则是做加和运算,这不仅限于数字加法,包括字符串拼接等运算。

比如定义以下一些数据:

enum Group { case Tech, Sale }

struct Employee {
    var name: String
    var group: Group
    var salary: Double
}

var employees = [
    Employee(name: "Jobs", group: .Sale, salary: 7000.0),
    Employee(name: "Fox", group: .Tech, salary: 6700.0),
    Employee(name: "Jobs", group: .Sale, salary: 7000.0),
    Employee(name: "Fox", group: .Tech, salary: 6700.0),
    Employee(name: "Max", group: .Tech, salary: 5000.0)
]

如何给科技部所有成员加薪 1200 并算出薪资总和:

var totalSalary = 0.0;

totalSalary = employees.filter({ employee in
    return employee.group == .Tech
}).map({ element -> Employee in
    var employee = element
    employee.salary += 1200.0
    return employee
}).reduce(0, combine: { $0 + $1.salary })

print(totalSalary)

这样写就不需要再去

PS: 要有什么时候 ACM 算法比赛支持 Swift 这类新语言就好了。

0x04 闭包 (Closures)

闭包表达式语法 (Closure Expression)

{ (parameters) -> returnType in
    statements
}

Swift 标准库提供了名为 sorted(isOrderedBefore:) 的方法,可以根据你提供的函数来对一个数组进行排序。

var names = ["Shin", "Dolia", "Kebe", "CrazyChen", "Rube"]
func backwards(last: String, next: String) -> Bool {
    return last < next
}
let reversed = names.sorted(isOrderedBefore: backwards)
// reversed 为 ["CrazyChen", "Dolia", "Kebe", "Rube", "Shin"]

而实际上根据 Swift 的语法特性,我们还可以把这个函数进行简写。

写成闭包形式:

names.sorted(isOrderedBefore: { (last: String, next: String) -> Bool in
    return last < next
})

根据上下文推断类型:

names.sorted(isOrderedBefore: { last, next in
    return last < next
})

单表达式闭包隐式返回:

names.sorted(isOrderedBefore: { last, next in last < next })

参数名称缩写:

names.sorted(isOrderedBefore: { $0 < $1 })

运算符函数:

names.sorted(isOrderedBefore: <)

尾随闭包:

names.sorted() { $0 < $1 }

0xFF Swifty Style

成句式的函数调用语法风格,比如:

func play(videoGame game: Game, with person: Person, on platform: Platform) {
  // do something...
}
play(game: .fallout4, with: .shin, on: .ps4)

函数命名规则:

  • 如果这个函数执行的会产生副作用 (Side Effect),那么就使用动词 (Verb.)
  • 而如果这个函数执行的不会产生副作用,那么就使用名刺 (Noun.)