现在位置: 首页 > Swift 教程 > 正文

Swift 基本语法

Swift 的语法设计目标是「简洁且安全」,很多在 C 系语言里靠程序员自觉遵守的规则,在 Swift 里由编译器强制保证。

本篇讲最基础的语法元素:导入模块、分号、注释、标识符、关键字、语句与表达式、输入输出,以及条件编译。

这些内容看起来琐碎,但后面每一篇都会用到。


import 与 Foundation

Swift 用 import 引入其他模块,用法和 Python 的 import 类似。

标准库是自动可用的,不需要导入,print、Array、String 这些都属于标准库。

一旦需要日期、文件、网络、JSON 解析这类能力,就要导入 Foundation。

实例

import 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

如果非要用关键字作为名字,可以用反引号包裹,但不建议这么做。

实例

// class 是关键字,用反引号包裹后可以作为变量名
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当前的动态共享对象句柄

实例

func runoob() {
    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")。

实例

// 默认 separator 是空格,terminator 是换行
print("Runoob", "Swift", "6")
// 自定义分隔符与结束符
print("RUNOOB", "教程", separator: " - ", terminator: "!\n")

运行结果:

Runoob Swift 6
RUNOOB - 教程!

参数前面有下划线 _,表示调用时不需要写参数标签,所以直接写 print("Hello") 即可,不能写成 print(items: "Hello")。

readLine 从标准输入读一行,返回类型是 String?,因为输入可能结束。

实例

print("请输入站点名称:", terminator: "")
if let input = readLine() {
    print("你输入的是:\(input)")
}

运行结果(输入 RUNOOB 后回车):

请输入站点名称:你输入的是:RUNOOB

用 if let 处理返回值,就能避免强制解包可能导致的崩溃。

注意:老教程里的 println 已经在 Swift 2 之后被 print 取代,现在的 print 默认就会换行。


条件编译

Swift 用 #if 系列指令在编译期选择性地包含代码,运行时不会执行被排除的分支。

这对跨平台代码和多环境配置非常有用。

实例

#if os(macOS)
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() 很容易混淆,前者看的是当前的语言模式,后者看的是编译器版本。

实例

#if compiler(>=6.0)
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,它在运行时判断。

实例

if #available(macOS 11.0, *) {
    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 协议后就能自定义输出内容。