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