Swift: 从入门到重学?

Xcode Jun 20, 2016

当时 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.)

Tags

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.