iOS开发基础74-Swift
Swift 入门指南
自 2014 年 WWDC 大会上苹果的大神 Chris Lattner 首次向我们展示 Swift 至今,已经过去一年多了。今年WWDC 上苹果又推出了 Swift 2.0,带来了 guard、错误处理、协议扩展等一大批新特性,Xcode 7 也默认集成了最新的 Swift 编译器。
虽然目前绝大部分公司的项目代码里还见不到一丁点 Swift 的影子,但各路大牛早已对 Swift 进行了各种挖掘。
本文基于 Xcode 7 + Swift 2.0 编写。Swift 这门语言还在快速迭代中,不同版本语法可能有差异,大家注意自己的 Xcode 版本。
一、Swift 和 OC 之间的联系
Swift 虽然是一门新语言,但它和 OC 有着千丝万缕的联系。很多概念是相通的,只是写法变了。下面我逐条对比一下。
1. 句尾不需要分号
var a = 10 // 不需要加分号
var b = 20; // 加了也不报错,但不推荐
除非你想在一行中写多行代码,才需要用分号隔开:
var a = 10; var b = 20; print(a + b)
2. 不需要写 main 函数
OC 里程序入口是 main.m 里的 main 函数,Swift 里没有这个概念,程序默认从上往下执行。你在一个 .swift 文件顶层写的代码就是程序入口,类似脚本语言。
// 这就是一个完整的 Swift 程序
print("Hello, World!")
当然在实际 iOS 项目中,@UIApplicationMain 标记了程序入口,原理是一样的。
3. 不分 .h 和 .m 文件
OC 里一个类要分 .h(头文件,声明)和 .m(实现文件,实现)两个文件,Swift 里一个类只有一个 .swift 文件,声明和实现都写在一起。
// Person.swift
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
func sayHello() {
print("你好,我叫 \(name),今年 \(age) 岁")
}
}
4. 不再有指针的概念
OC 里到处是指针(NSString *str),Swift 里没有指针的概念,也不需要 * 号。类本身就是引用类型,结构体和枚举是值类型,编译器帮你管理。
// OC: NSString *str = @"hello";
// Swift:
let str = "hello" // 没有 *
5. 数据类型自动判断,只区分变量 var 和常量 let
Swift 有类型推断,你不需要每次都写类型,编译器会根据你赋的值自动判断类型。但你必须明确告诉编译器这是变量还是常量:
var:变量,值可以改变let:常量,赋值后不能再改变
var variableString = "Horse" // 变量,可以改
variableString = "Horse and Carriage"
let constantString = "Highlander" // 常量,不能改
// constantString = "Another Highlander" // 报错!
建议:能用
let就尽量用let,编译器会帮你优化,而且代码更安全。Xcode 也会提示你"这个变量可以改成 let"。
6. 强制转换格式反过来了
OC 里强转是把类型写在前面加括号:
// OC 强转
int a = (int)3.14;
Swift 里强转是"类型(值)"的形式,像调用构造函数一样:
// Swift 强转
let a = Int(3.14) // a = 3
let b = Double(10) // b = 10.0
let c = String(123) // c = "123"
7. 整数类型可以通过 .min 和 .max 获得最大最小值
let minValue = UInt8.min // minValue = 0,UInt8 的最小值
let maxValue = UInt8.max // maxValue = 255,UInt8 的最大值
let intMin = Int32.min // -2147483648
let intMax = Int32.max // 2147483647
这在需要知道数值边界的时候非常方便。
8. 定义类型别名的语法变了
OC 里用 typedef:
// OC
typedef int MyInt;
Swift 里用 typealias:
// Swift
typealias MyInt = Int
typealias AudioSample = UInt16
9. 模除取余运算符支持小数了
OC 里 % 只能用于整数,Swift 里 % 可以用于浮点数了:
// OC 里不允许 5 % 1.5,Swift 里可以
let result = 5 % 1.5 // result = 0.5
原理是 5 = 1.5 * 3 + 0.5,余数是 0.5。
10. BOOL 类型更加严格
OC 里 BOOL 本质上是 signed char,非 0 就是真,所以可以写出这种代码:
// OC 里这样写不报错
if (1) { ... } // 非 0 就是真
if (someObject) { ... } // 对象不为空就是真
Swift 里 Bool 就是 Bool,只有 true 和 false,不会自动把整数或对象转成 Bool:
// Swift 里这样写会报错
// if (1) { ... } // 报错:'Int' is not convertible to 'Bool'
let flag = true
if flag { // 必须是 Bool 类型
print("真")
}
11. 赋值运算符没有返回值
和第 10 点相联系,Swift 的赋值运算符 = 没有返回值,这样可以防止你把 == 误写成 =:
// OC 里这样写不会报错,但逻辑错了(应该是 ==)
// if (a = b) { ... }
// Swift 里这样写直接报错,因为 = 没有返回值,不能用在 if 里
// if a = b { ... } // 报错
这是一个非常好的设计,能避免很多低级错误。
12. 可以多对多赋值(元组)
Swift 支持元组,可以一次性给多个变量赋值:
let (x, y) = (1, 2) // x = 1, y = 2
print("x = \(x), y = \(y)")
// 还可以用来交换两个变量的值,不需要临时变量
var a = 10
var b = 20
(a, b) = (b, a) // a = 20, b = 10
元组的详细介绍见后面的"Swift 独有的性质"部分。
13. 循环语句中必须加 {}
OC 里 if 或 for 后面只有一行代码时可以省略大括号:
// OC 里可以这样写
if (a > b)
NSLog(@"a 大");
Swift 里不管几行都必须加大括号:
// Swift 里必须加 {}
if a > b {
print("a 大")
}
for i in 0...5 {
print(i)
}
这也是一个好设计,避免了"悬挂 else"等问题。
14. switch 语句大升级
OC 里 switch 后面只能跟整数(int、NSInteger 等),而且每个 case 默认会穿透(fall through),需要手动写 break:
// OC 的 switch
switch (type) {
case 1:
NSLog(@"类型 1");
break; // 必须写 break,否则会继续执行下面的 case
case 2:
NSLog(@"类型 2");
break;
default:
break;
}
Swift 里 switch 大幅升级:
- 可以跟各种数据类型:整数、浮点数、字符串、元组都行
- 默认不穿透:每个 case 执行完自动 break,不需要手动写
- 如果想要穿透效果,用
fallthrough关键字 - case 里可以定义常量变量,但
fallthrough后面不能再定义
// Swift 的 switch,支持字符串
let status = "success"
switch status {
case "success":
print("成功")
case "failure":
print("失败")
default:
print("其他")
}
// 支持浮点数
let temperature = 36.5
switch temperature {
case 35.0...36.0:
print("体温偏低")
case 36.0...37.3:
print("体温正常")
case 37.3...38.0:
print("低烧")
default:
print("其他情况")
}
// fallthrough 穿透示例
let number = 1
switch number {
case 1:
print("1")
fallthrough // 继续执行下一个 case
case 2:
print("2") // 会执行到这里
default:
break
}
// 输出:1 2
注意:Swift 的 switch 必须覆盖所有情况,要么写 default,要么 case 覆盖全部可能值(比如枚举)。否则编译器会报错。
二、Swift 独有的性质
接下来这些是 Swift 独有的、OC 里没有的特性,也是 Swift 比 OC 更强大、更安全的地方。
1. 范围运算符
Swift 独有的范围运算符,用来表示一个区间:
- 闭区间运算符
a...b:表示[a, b],包含 a 和 b - 半开区间运算符
a..<b:表示[a, b),包含 a 不包含 b
// 闭区间:3, 4, 5
for i in 3...5 {
print(i) // 输出 3, 4, 5
}
// 半开区间:3, 4(不包含 5)
for i in 3..<5 {
print(i) // 输出 3, 4
}
// 用在 if 判断里
let score = 85
if 60...100 ~= score { // ~= 是范围匹配运算符
print("及格了")
}
// 也可以直接用 switch 匹配范围
switch score {
case 0..<60:
print("不及格")
case 60..<80:
print("良好")
case 80...100:
print("优秀")
default:
print("分数不合法")
}
范围运算符在遍历数组时也非常方便:
let names = ["张三", "李四", "王五", "赵六"]
for i in 0..<names.count {
print(names[i])
}
// 或者直接遍历
for name in names {
print(name)
}
2. 溢出运算符
默认情况下,当你往一个整型常量或变量赋一个它不能承载的大数时,Swift 会直接报错,而不是像 OC 那样静默溢出。这样在操作过大或过小的数的时候很安全:
var potentialOverflow = Int16.max
// potentialOverflow = 32767,这是 Int16 能承载的最大整数
// potentialOverflow += 1
// 报错!EXC_BAD_INSTRUCTION,不会让你溢出
当然,如果你有意在溢出时对有效位进行截断(类似 OC 的行为),可以使用 Swift 提供的溢出运算符,都是以 & 符号开头:
| 溢出运算符 | 说明 |
|---|---|
&+ |
溢出加法 |
&- |
溢出减法 |
&* |
溢出乘法 |
&/ |
溢出除法 |
&% |
溢出取余 |
var willOverflow = UInt8.max
// willOverflow = 255,UInt8 的最大整数
willOverflow = willOverflow &+ 1
// 这时候 willOverflow = 0(溢出了,从 255 变成 0,物极必反)
var willUnderflow = UInt8.min
// willUnderflow = 0
willUnderflow = willUnderflow &- 1
// willUnderflow = 255(下溢,从 0 变成 255)
原理就是二进制进位/借位后超出位数的部分被截断,和 C 语言的整数溢出行为一致。但 Swift 默认不让你这么干,必须显式用 & 运算符,更安全。
3. 元组类型(Tuple)
元组是 Swift 独有的类型,它允许你把多个值组合成一个复合值。元组内的值可以是任意类型,不要求是相同类型。
// 定义一个元组,包含 x 和 y 两个元素
var point = (x: 15, y: 20.2)
// 取值方式一:通过元素名
print(point.x) // 15
print(point.y) // 20.2
// 取值方式二:通过下标(类似数组)
print(point.0) // 15
print(point.1) // 20.2
// 修改值
point.x = 30
point.1 = 25.5
也可以省略内部元素的名称:
var point = (15, 20.2) // 没有元素名
// 只能通过下标取值
print(point.0) // 15
print(point.1) // 20.2
也可以明确指定元组内每一个元素的类型:
// 指定类型:第一个是 Int,第二个是 String
var person: (Int, String) = (18, "张三")
print(person.0) // 18
print(person.1) // 张三
注意:元组的名称和类型不能共存。如果你指定了类型,后面就不能再指定名称了:
// 这样写是错的 var point: (Int, String) = (x: 15, y: "hello") // 报错
元组还可以用下划线 _ 省略不需要的元素:
let (_, y) = (10, 20) // 只关心 y,x 用 _ 忽略
print(y) // 20
打印元组:
let point = (10, 20.2)
print(point) // 输出:(10, 20.2)
元组和结构体的区别:
- 元组是轻量级的,适合临时组合一组值(比如函数返回多个值)
- 结构体是正式的数据结构,有属性和方法,适合复杂数据模型
- 元组是值类型,结构体也是值类型
4. switch 中使用元组 + where 过滤条件
switch 配合元组使用时,还可以用类似 SQL 的 where 语法添加过滤条件,非常强大:
let point = (3, 3)
switch point {
case let (x, y) where x == y:
print("x 和 y 相等,都在 y=x 这条线上")
case let (x, y) where x == -y:
print("x 和 y 互为相反数,在 y=-x 这条线上")
case let (x, y) where x > 0 && y > 0:
print("第一象限")
case (-2...2, -2...2):
print("在原点附近的正方形区域内")
default:
print("其他区域")
}
还可以用 _ 匹配任意值:
let point = (1, 1)
switch point {
case (0, 0):
print("原点")
case (_, 0):
print("在 x 轴上")
case (0, _):
print("在 y 轴上")
case (-2...2, -2...2):
print("在原点附近")
default:
print("其他位置")
}
这种模式匹配能力是 OC 的 switch 完全做不到的,非常灵活。
5. 函数的外部参数名
OC 里方法调用时,参数名是方法签名的一部分:
// OC 方法
- (int)sumWithNum1:(int)num1 num2:(int)num2;
// 调用
[self sumWithNum1:10 num2:20];
Swift 里函数默认调用时不写参数名:
// Swift 函数
func sum(num1: Int, num2: Int) -> Int {
return num1 + num2
}
// 调用(默认不写参数名)
sum(10, 20)
但这样可读性不好,不知道 10 和 20 分别是什么意思。Swift 提供了外部参数名,写在内部参数名前面,调用时必须写外部参数名:
// 外部参数名:numone 和 numtwo
// 内部参数名:num1 和 num2(函数内部用)
func sum(numone num1: Int, numtwo num2: Int) -> Int {
return num1 + num2
}
// 调用时必须写外部参数名
sum(numone: 10, numtwo: 20)
如果你觉得写两个名字麻烦,可以让外部参数名和内部参数名一样,用 # 号前缀:
// # 号表示外部参数名和内部参数名相同
func sum(#num1: Int, #num2: Int) -> Int {
return num1 + num2
}
// 调用时写参数名
sum(num1: 10, num2: 20)
注意:
#号语法是 Swift 1.x/2.x 的写法。在 Swift 3 中,#号被去掉了,默认所有参数都有外部参数名(第一个参数除外,可以用_忽略外部参数名)。本文基于 Swift 2.0,所以用#号。
6. 函数的默认参数值
Swift 函数可以给参数设置默认值,调用时如果不传这个参数,就用默认值:
func addStudent(name: String, age: Int = 20) -> String {
return "学生姓名:\(name),年龄:\(age)"
}
// 调用时只传 name,age 用默认值 20
addStudent("张三") // 学生姓名:张三,年龄:20
// 也可以传 age
addStudent("李四", age: 18) // 学生姓名:李四,年龄:18
注意:使用了默认参数值的参数,系统会自动生成一个外部参数名。所以调用时如果要传这个参数,必须写外部参数名(如上面的
age: 18)。
默认参数值在实际开发中非常有用,可以简化函数调用,也方便扩展函数而不影响已有调用。
7. 函数的输入输出参数(inout)
默认情况下,函数的参数是值传递,函数内部修改参数不会影响外部的值。如果你想在函数内部修改外部变量的值,可以用 inout 关键字,类似 C 语言的传指针:
func change(inout num: Int) {
num = 10
}
var a = 20
change(&a) // 调用时加 & 符号
print(a) // 输出:10(a 被修改了)
注意:
- 写了输入输出参数(inout)就不能再用默认参数值的语法了。
- inout 参数不能是 let 常量,必须是 var 变量。
- 调用时必须加
&符号,表示传递的是引用。
用输入输出参数实现多个返回值:
OC 里函数只能返回一个值,如果要返回多个值,通常用指针参数或字典。Swift 里可以用 inout 参数实现多个返回值:
// 计算两个数的和与差,通过 inout 参数返回
func calculate(a: Int, b: Int, inout sum: Int, inout difference: Int) {
sum = a + b
difference = a - b
}
var sumResult = 0
var diffResult = 0
calculate(10, 5, sum: &sumResult, difference: &diffResult)
print("和:\(sumResult),差:\(diffResult)") // 和:15,差:5
不过 Swift 里更优雅的多返回值方式是用元组:
// 用元组返回多个值,更简洁
func calculate(a: Int, b: Int) -> (sum: Int, difference: Int) {
return (a + b, a - b)
}
let result = calculate(10, 5)
print("和:\(result.sum),差:\(result.difference)") // 和:15,差:5
// 或者直接解包
let (sum, diff) = calculate(10, 5)
print("和:\(sum),差:\(diff)")
元组作为返回值是 Swift 非常常用的模式,比 inout 参数更简洁、更安全。
三、Swift 2.0 新增的几个实用特性
今年 WWDC 发布的 Swift 2.0 带来了很多新特性,这里挑几个对 OC 开发者来说比较实用的简单介绍一下。
1. guard 语句
guard 是 Swift 2.0 新增的,用来提前退出。和 if 类似,但 guard 必须有 else 分支,且 else 分支必须退出当前作用域(return、break、continue 或 throw)。
// 不用 guard 的写法(嵌套深)
func buy(money: Double, price: Double) {
if money >= price {
if price > 0 {
print("购买成功")
} else {
print("价格不合法")
}
} else {
print("余额不足")
}
}
// 用 guard 的写法(提前退出,扁平化)
func buy(money: Double, price: Double) {
guard price > 0 else {
print("价格不合法")
return
}
guard money >= price else {
print("余额不足")
return
}
print("购买成功")
}
guard 的好处是:把不满足条件的情况提前处理掉,剩下的就是正常逻辑,代码层级更浅,可读性更好。而且 guard 里解包的可选值在后面的代码中可以直接使用:
func greet(person: [String: String]) {
guard let name = person["name"] else {
print("没有名字")
return
}
// name 在这里可以直接用,不需要再解包
print("你好,\(name)")
guard let location = person["location"] else {
print("希望你那里天气不错")
return
}
print("希望你在 \(location) 天气不错")
}
2. 错误处理(do-try-catch)
Swift 2.0 引入了类似 Java 的错误处理机制。函数可以用 throws 声明会抛出错误,调用时用 try,用 do-catch 捕获错误:
// 定义错误类型(枚举,遵循 ErrorType 协议)
enum VendingMachineError: ErrorType {
case InvalidSelection
case InsufficientFunds(required: Double)
case OutOfStock
}
// 用 throws 声明会抛错误的函数
func vend(itemName: String, price: Double, money: Double) throws {
guard money >= price else {
throw VendingMachineError.InsufficientFunds(required: price - money)
}
print("购买成功,找零 \(money - price)")
}
// 调用时用 do-try-catch
do {
try vend("可乐", price: 3.5, money: 2.0)
} catch VendingMachineError.InsufficientFunds(let required) {
print("钱不够,还差 \(required) 元")
} catch VendingMachineError.OutOfStock {
print("缺货")
} catch {
print("其他错误")
}
和 OC 的 NSError 指针传参相比,Swift 的错误处理更清晰、更安全,编译器会强制你处理错误(或者用 try! 强制不处理,但不推荐)。
3. 协议扩展(Protocol Extension)
OC 里的 protocol 只能声明方法,不能有实现。Swift 2.0 引入了协议扩展,可以给协议提供默认实现:
// 定义一个协议
protocol PrettyPrintable {
var description: String { get }
func prettyPrint()
}
// 协议扩展,提供默认实现
extension PrettyPrintable {
func prettyPrint() {
print("===== \(description) =====")
}
}
// 只要遵循协议,就自动获得 prettyPrint 的默认实现
struct Person: PrettyPrintable {
var name: String
var description: String {
return "人:\(name)"
}
}
let p = Person(name: "张三")
p.prettyPrint() // 输出:===== 人:张三 =====
协议扩展是 Swift 面向协议编程(POP, Protocol Oriented Programming)的基础。苹果在 WWDC 2015 上专门有一场演讲叫"Protocol-Oriented Programming in Swift",推荐大家去看看。
4. #available 可用性检查
Swift 2.0 引入了 #available,用来检查 API 是否在当前系统版本可用:
if #available(iOS 9.0, *) {
// iOS 9.0 及以上才有的 API
print("使用 iOS 9 新特性")
} else {
// 低版本的降级处理
print("使用旧版本兼容方案")
}
这比 OC 里用 respondsToSelector: 检查要优雅得多,编译器也会帮你检查 API 可用性。
5. defer 语句
defer 用来在当前作用域结束前执行一段代码,不管是正常返回还是报错退出,defer 里的代码都会执行。非常适合用来做资源清理:
func processFile() {
print("打开文件")
defer {
print("关闭文件") // 函数结束前一定会执行
}
print("处理文件")
// 即使这里 return 了,defer 也会执行
return
}
processFile()
// 输出顺序:
// 打开文件
// 处理文件
// 关闭文件
如果有多个 defer,执行顺序是倒序的(后定义的先执行),类似栈。
四、个人看法和总结
作为一个写了多年 OC 的开发者,我对 Swift 的看法是:
- Swift 比 OC 更安全:可选类型(Optional)、类型推断、赋值无返回值、Bool 严格化等设计,从语言层面避免了很多 OC 里常见的 bug(如野指针、类型不匹配、if 里写 = 等)。
- Swift 比 OC 更简洁:不需要 .h/.m 分文件、不需要头文件引用、类型推断减少大量重复代码、函数式语法更简洁。同样的功能,Swift 代码量大概只有 OC 的 60%~70%。
- Swift 比 OC 更现代:元组、范围运算符、模式匹配、函数式编程(map/filter/reduce)、面向协议编程等特性,让 Swift 的表达能力远超 OC。
- Swift 还在快速变化:从 Swift 1.0 到 2.0,语法有不少变化(比如 println 改成 print、#available 等),第三方库也在不断适配中。这是 Swift 目前最大的问题——不稳定,升级版本可能要改不少代码。
- 和 OC 混编是目前的最佳实践:对于已有 OC 项目,不需要全部重写成 Swift,可以在新模块中用 Swift,通过桥接头文件和 OC 互相调用。苹果也支持混编,这是迁移到 Swift 的最佳路径。
- Swift 是未来的趋势:苹果在大力推 Swift,WWDC 上的示例代码已经基本都是 Swift 了,新的 API 也优先考虑 Swift。虽然现在大部分公司还在用 OC,但一两年后 Swift 很可能成为主流。OC 开发者提前学习 Swift 是很有必要的。
最后提醒:本文基于 Swift 2.0(Xcode 7)编写。Swift 这门语言还在快速迭代中,如果你用的是更高版本的 Xcode,部分语法可能有差异,以官方文档为准。

浙公网安备 33010602011771号