Swift 基本语法
Swift 的语法设计目标是「简洁且安全」,很多在 C 系语言里靠程序员自觉遵守的规则,在 Swift 里由编译器强制保证。
本篇讲最基础的语法元素:导入模块、分号、注释、标识符、关键字、语句与表达式、输入输出,以及条件编译。
这些内容看起来琐碎,但后面每一篇都会用到。
import 与 Foundation
Swift 用 import 引入其他模块,用法和 Python 的 import 类似。
标准库是自动可用的,不需要导入,print、Array、String 这些都属于标准库。
一旦需要日期、文件、网络、JSON 解析这类能力,就要导入 Foundation。
实例
// 使用 Foundation 提供的 Date 与 DateFormatter
let now = Date(timeIntervalSince1970: 0)
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
formatter.timeZone = TimeZone(identifier: "UTC")
print(formatter.string(from: now))
运行结果:
1970-01-01
常用的导入写法还有几种:
| 写法 | 含义 |
|---|---|
| import Foundation | 导入整个模块,使用其中的公开符号 |
| import func Foundation.sqrt | 只导入一个函数,避免命名冲突 |
| import struct Foundation.Date | 只导入一个类型 |
| @testable import RunoobKit | 导入并开放 internal 成员,仅用于测试 |
注意:不要把 import Cocoa 写进示例代码,那是 macOS 专属的 UI 框架,在 Linux 或 iOS 上都无法编译。跨平台代码统一用 import Foundation。
分号
Swift 不要求每条语句以分号结尾,这一点和 Go 类似,与 C、Java 不同。
只有把多条语句写在同一行时,才必须用分号分隔。
实例
let a = 1
let b = 2
print(a + b)
// 同一行写多条语句时必须用分号分隔
let c = 3; let d = 4; print(c + d)
运行结果:
3 7
虽然分号可以省略,但加上也不会报错,所以从其他语言转过来的开发者不会因为习惯而踩坑。
注释
Swift 支持单行注释和多行注释,而且多行注释可以嵌套,这是很多语言没有的特性。
单行注释以两个斜杠开头,可以写在代码后面。
实例
let site = "www.runoob.com" // 这是行尾注释
print(site)
多行注释用 /* 和 */ 包裹,并且允许嵌套,因此可以放心地注释掉一大段已经含有注释的代码。
实例
/* 内层注释:Swift 允许嵌套 */
外层注释结束 */
print("RUNOOB 嵌套注释测试通过")
运行结果:
RUNOOB 嵌套注释测试通过
如果换成 C 或 Java,嵌套的多行注释会在第一个 */ 处提前结束,导致编译错误,Swift 没有这个问题。
此外,写在声明上方的 /// 文档注释会被 Xcode 识别,用于生成快速帮助和文档。
标识符与命名约定
标识符就是给变量、常量、函数、类型起的名字。
Swift 的标识符区分大小写,并且支持几乎全部 Unicode 字符,包括中文和希腊字母。
实例
let 课程名称 = "Swift 教程"
// 希腊字母标识符
let π = 3.14159
print(课程名称, π)
运行结果:
Swift 教程 3.14159
如果非要用关键字作为名字,可以用反引号包裹,但不建议这么做。
实例
let `class` = "Swift 6"
print(`class`)
运行结果:
Swift 6
实际项目中,社区约定比「能编译」更重要,下面这些命名规范建议从一开始就养成。
| 对象 | 规范 | 示例 |
|---|---|---|
| 变量、常量、函数 | 小驼峰,首字母小写 | siteName、fetchData() |
| 类型(类、结构体、枚举、协议) | 大驼峰,首字母大写 | SiteManager、HTTPClient |
| 布尔值 | 用 is、has、can、should 开头 | isLoading、hasNext |
| 常量 | 小驼峰,不用全大写 | maxRetryCount |
| 缩写词 | 统一大小写,不混用 | url、URLSession |
注意:Swift 允许用 emoji 作为标识符,但正式项目不要这么写,会让代码难以搜索和维护。
关键字
关键字是语言保留的单词,不能直接作为标识符使用。
Swift 的关键字数量不少,但按用途分类后并不难记,下面这张表按类别列出。
| 类别 | 关键字 |
|---|---|
| 声明 | class、struct、enum、protocol、extension、func、let、var、init、deinit、subscript、typealias、associatedtype、import、operator、precedencegroup、macro、inout |
| 访问控制 | open、public、package、internal、fileprivate、private |
| 语句 | if、else、guard、switch、case、default、for、while、repeat、break、continue、fallthrough、return、throw、defer、do、catch、where、in |
| 表达式与类型 | as、is、try、await、async、throws、rethrows、some、any、self、Self、super、nil、true、false |
| 修饰符 | static、class、final、override、lazy、weak、unowned、mutating、nonmutating、indirect、required、convenience、dynamic |
其中一部分是「上下文关键字」,比如 get、set、willSet、didSet、lazy、weak,它们只在特定位置有特殊含义,其他地方仍可作为普通名字。
另外有一组以 # 开头的特殊字面量,用来在编译期获取代码位置信息。
| 特殊字面量 | 含义 |
|---|---|
| #file | 当前文件的完整路径 |
| #fileID | 当前文件的模块名加文件名,比 #file 更短 |
| #line | 当前行号 |
| #column | 当前列号 |
| #function | 当前函数名 |
| #dsohandle | 当前的动态共享对象句柄 |
实例
print("当前函数名:\(#function)")
}
runoob()
运行结果:
当前函数名:runoob()
注意:这些字面量在 Swift 3 之前写作 _FILE_、_LINE_、_FUNCTION_,全部带下划线且全大写。老教程里的这种写法已经无法编译。
语句与表达式
区分语句和表达式是理解 Swift 语法的一把钥匙。
表达式会计算出一个值,语句则是执行一个动作,本身没有值。
实例
var total = 0
// 表达式:有值,可以赋给别的变量
let sum = 1 + 2
total = sum * 10
print("total =", total)
运行结果:
total = 30
1 + 2 是表达式,它的值是 3;total = sum * 10 是语句,它完成赋值这个动作。
Swift 里 if、switch、do 这些结构在特定场景下也可以作为表达式返回值,例如给变量赋一个分支结果,但这不是本篇的重点。
另外要注意,Swift 的表达式不会「意外求值」:一行单独的表达式如果编译器发现它没有任何副作用,会给出警告,提醒你可能忘了使用结果。
print 与 readLine
标准库提供了一对最简单的输入输出函数:print 负责输出,readLine 负责读入一行。
print 的现代签名是 print(_ items: Any..., separator: String = " ", terminator: String = "\n")。
实例
print("Runoob", "Swift", "6")
// 自定义分隔符与结束符
print("RUNOOB", "教程", separator: " - ", terminator: "!\n")
运行结果:
Runoob Swift 6 RUNOOB - 教程!
参数前面有下划线 _,表示调用时不需要写参数标签,所以直接写 print("Hello") 即可,不能写成 print(items: "Hello")。
readLine 从标准输入读一行,返回类型是 String?,因为输入可能结束。
实例
if let input = readLine() {
print("你输入的是:\(input)")
}
运行结果(输入 RUNOOB 后回车):
请输入站点名称:你输入的是:RUNOOB
用 if let 处理返回值,就能避免强制解包可能导致的崩溃。
注意:老教程里的 println 已经在 Swift 2 之后被 print 取代,现在的 print 默认就会换行。
条件编译
Swift 用 #if 系列指令在编译期选择性地包含代码,运行时不会执行被排除的分支。
这对跨平台代码和多环境配置非常有用。
实例
print("当前运行在 macOS 上")
#elseif os(Linux)
print("当前运行在 Linux 上")
#else
print("其他平台")
#endif
#if arch(arm64)
print("arm64 架构")
#elseif arch(x86_64)
print("x86_64 架构")
#endif
运行结果:
当前运行在 macOS 上 arm64 架构
常用的编译条件有下面这些。
| 条件 | 判断内容 | 示例 |
|---|---|---|
| os() | 操作系统 | macOS、iOS、Linux、Windows |
| arch() | CPU 架构 | arm64、x86_64 |
| swift() | Swift 语言版本 | swift(>=5.9) |
| compiler() | 编译器版本 | compiler(>=6.0) |
| canImport() | 模块是否可导入 | canImport(UIKit) |
| targetEnvironment() | 目标环境 | targetEnvironment(simulator) |
swift() 和 compiler() 很容易混淆,前者看的是当前的语言模式,后者看的是编译器版本。
实例
print("编译器版本不低于 6.0")
#endif
#if swift(>=6.0)
print("语言模式不低于 6.0")
#else
print("当前语言模式低于 6.0")
#endif
在 Swift 6.3 编译器上用默认模式运行的结果是:
编译器版本不低于 6.0 当前语言模式低于 6.0
原因是 Xcode 16 新建项目默认使用 Swift 5 语言模式,只有把语言模式切到 Swift 6,swift(>=6.0) 才成立。
判断「系统版本是否支持某个 API」要用另一个指令 #available,它在运行时判断。
实例
print("系统支持 macOS 11.0 及以上特性")
} else {
print("系统版本过低")
}
运行结果:
系统支持 macOS 11.0 及以上特性
末尾的星号 * 表示「其他平台不做版本限制」,不能省略。
注意:#if 是编译期分支,被排除的代码不参与编译;#available 是运行期判断,两个分支的代码都会参与编译,只是执行时选一个。
常见问题
下面是初学者在基本语法阶段最常问的几个问题。
为什么我的 if 条件里写数字会报错
Swift 的条件必须是 Bool 类型,if 1 这样的写法不合法,也不存在「0 为假、非 0 为真」的规则。
能不能不写 import 就用 Date
不能,Date 属于 Foundation,必须先 import Foundation;标准库的 String、Int、Array 才不需要导入。
注释里能写注释吗
可以,Swift 的多行注释支持嵌套,这是它相比 C 系语言的一个便利之处。
print 能打印自定义类型吗
可以,默认会输出类型名和成员信息,让类型遵循 CustomStringConvertible 协议后就能自定义输出内容。
