官方文档 #
官方文档:The Swift Programming Language
https://developer.apple.com/documentation/swift
语言特点 #
Swift is a general-purpose programming language that’s approachable for newcomers and powerful for experts. It is fast, modern, safe, and a joy to write.
变量声明 #
swift 使用 let
来声明常量,使用 var
来声明变量。常量只能赋值一次。即:let
的内存地址不能变,var
的内存地址可变。
// 只能赋值一次,再次赋值会报错
let hello = "world"
print(hello)
// hello 可以多次赋值
var hello = "world"
print(hello)
print("hello \(hello)")
声明时可以不指定类型,编译器会自动推断其类型,当然也可以像下面这样指定:
let label: String = "The width is"
数据类型 #
Int #
Int
是有符号整型,默认是当前平台的字长(如 32 位或 64 位),Uint
是无符号整型。
let age: Int = 25
let smallNumber: Int8 = 127
let largeNumber: Int64 = 9223372036854775807
Float 和 Double #
Double
是默认使用的浮点类型,64 位精度,Float
是 32 位。
let pi: Float = 3.14159
let pi: Double = 3.141592653589793
在 Swift 语言中,你可以使用下划线来提高长数字的可读性。比如:
let latitude: Double = 34.011_286
let longitude: Double = -118.495_308
print("Latitude: \(latitude)") // 输出: Latitude: 34.011286
print("Longitude: \(longitude)") // 输出: Longitude: -118.495308
另外,在 JavaScript 中,同样可以使用下划线来提高可读性:
const latitude = 34.011_286;
const longitude = -118.495_308;
console.log(`Latitude: ${latitude}`); // 输出: Latitude: 34.011286
console.log(`Longitude: ${longitude}`); // 输出: Longitude: -118.495308
Bool #
Bool
类型的可选值只有 true
和 false
。
let isSwiftAwesome: Bool = true
String #
双引号在 Swift 中用于定义字符串(String
)类型。字符串可以是多个字符的组合,也可以包含转义字符和字符串插值。
// 定义字符串
let greeting: String = "Hello, World!"
// 包含转义字符的字符串
let quote: String = "He said, \"Swift is awesome!\""
// 字符串插值
let name: String = "Alice"
let message: String = "Hello, \(name)"
Character #
特别需要注意的是,Swift 实际上不支持单引号来定义字符。单引号使用在 Swift 语言中并没有像某些其他编程语言(如 C 或 Java)那样的用途。在 Swift 中,字符也是用双引号表示的,只是字符类型是 Character
。
// 正确的字符定义(注意仍然使用双引号)
let letter: Character = "A"
let emoji: Character = "😊"
// 错误示例:let invalidCharacter: Character = "AB" // 会报编译错误
数据结构 #
数组 Array #
有序的集合,可以存储相同类型的元素。
let emptyArray: [String] = []
let numbers: [Int] = [1, 2, 3, 4, 5]
let names: [String] = ["Alice", "Bob", "Charlie"]
集合 Set #
无序且唯一的集合,可以存储相同类型的元素。
let uniqueNumbers: Set<Int> = [1, 2, 3, 4, 4, 5]
// uniqueNumbers = [1, 2, 3, 4, 5]
字典 Dictionary #
无序的键值对集合,其中键是唯一的。
let studentGrades: [String: String] = ["Alice": "A", "Bob": "B"]
let emptyDictionary: [String: Float] = [:]
元组 Tuple #
用于将多个值组合成一个复合值,不要求类型相同。
let http404Error = (404, "Not Found")
let person = (name: "Alice", age: 25)
print(person.name) // 输出: Alice
print(person.age) // 输出: 25
结构体 Struct #
struct ReplaxModel: Hashable, Codable {
var id: Int
var name: String
var park: String
var state: String
var description: String
private var imageName: String
}
下标操作(subscript) #
在 Swift 中,下标(Subscript)是用于访问集合、列表或序列中特定位置的元素的一种语法。下标允许你使用类似数组的语法来读取和写入集合内的值。下标可以用于各种数据结构,如数组(Array
)、字典(Dictionary
)、字符串(String
)等。除此之外,Swift 还允许自定义类型定义自己的下标操作。
数组中的下标 #
对于数组,Swift 提供了基于整数索引的下标操作,这和其他编程语言类似。
var fruits = ["Apple", "Banana", "Cherry"]
let firstFruit = fruits[0] // 访问第一个元素,返回 "Apple"
fruits[1] = "Blueberry" // 修改第二个元素的值为 "Blueberry"
print(fruits) // 输出 ["Apple", "Blueberry", "Cherry"]
字典中的下标 #
字典的下标操作允许使用键来访问或修改值。
var ages = ["Alice": 25, "Bob": 28]
let aliceAge = ages["Alice"] // 访问 Alice 的年龄,返回 25
ages["Bob"] = 30 // 修改 Bob 的年龄为 30
print(ages) // 输出 ["Alice": 25, "Bob": 30]
自定义下标 #
Swift 允许你为自己的自定义类型定义下标。通过实现下标,可以让你自己定义的数据结构也支持下标操作。
struct Matrix {
let rows: Int, columns: Int
var grid: [Double]
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
self.grid = Array(repeating: 0.0, count: rows * columns)
}
func indexIsValid(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
subscript(row: Int, column: Int) -> Double {
get {
assert(indexIsValid(row: row, column: column), "Index out of range")
return grid[(row * columns) + column]
}
set {
assert(indexIsValid(row: row, column: column), "Index out of range")
grid[(row * columns) + column] = newValue
}
}
}
var matrix = Matrix(rows: 2, columns: 2)
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2
print(matrix[0, 1]) // 输出 1.5
print(matrix[1, 0]) // 输出 3.2
在这个示例中,Matrix
结构体定义了一个二维数组(矩阵)的下标,使得你可以通过 matrix[row, column]
这种方式来访问和修改矩阵中的元素。
下标的定义 #
自定义下标时,可以在结构体、类、枚举中定义一个或多个下标。下标可以接受一个或多个输入参数,并可以返回一个值。下标语法如下:
subscript(parameters) -> ReturnType {
get {
// 返回适当的值
}
set(newValue) {
// 执行适当的赋值操作
}
}
- parameters:输入参数的名称和类型。
- ReturnType:下标操作返回值的类型。
- get 和 set 代码块:定义了如何获取和设置值。
下标定义支持计算属性的所有功能,例如可以读取和写入,也可以定义只读下标。
总结 #
- 在 Swift 中,下标(Subscript)提供了一种简洁的语法,用于访问集合类型(如数组和字典)中的元素。
- 下标可以重载,允许自定义类型提供自己的下标操作。
- 下标可以是读取和写入的,也可以是只读的。
通过下标,可以使代码更直观和简洁,特别是在处理集合类型的数据结构时。
恒等运算符 #
详细了解请参考:
在 Swift 中,恒等运算符(Identity Operators)用于判断两个对象实例是否引用同一个内存地址。为了进行这种比较,Swift 提供了两个恒等运算符:
===
(恒等): 判断两个引用类型(如类实例)是否引用同一个对象。!==
(不恒等): 判断两个引用类型是否引用不同的对象。
===
运算符用于判断两个引用类型实例是否完全相同,即它们是否引用了同一个内存地址。
class Person {
var name: String
init(name: String) {
self.name = name
}
}
let person1 = Person(name: "Alice")
let person2 = person1
let person3 = Person(name: "Alice")
// person1 和 person2 指向同一个实例
if person1 === person2 {
print("person1 and person2 refer to the same instance.")
} else {
print("person1 and person2 do not refer to the same instance.")
}
// person1 和 person3 指向不同的实例,但其内容相同
if person1 === person3 {
print("person1 and person3 refer to the same instance.")
} else {
print("person1 and person3 do not refer to the same instance.") // 这一句会执行
}
!==
运算符用于判断两个引用类型实例是否不同,即它们是否引用了不同的内存地址。
if person1 !== person3 {
print("person1 and person3 do not refer to the same instance.")
} else {
print("person1 and person3 refer to the same instance.")
}
注意事项
引用类型: 恒等运算符仅适用于引用类型(如类实例),因为它们比较的是内存地址。对于值类型(如结构体、枚举和基本数据类型),你应该使用等号
==
来比较其内容。内容比较: 如果你需要判断两个实例的内容是否相同(而不是它们是否引用同一个对象),你应当实现相应的
Equatable
协议并使用==
运算符。
值类型比较示例
struct Point: Equatable {
var x: Int
var y: Int
}
let point1 = Point(x: 1, y: 1)
let point2 = Point(x: 1, y: 1)
if point1 == point2 {
print("point1 and point2 have the same content.")
} else {
print("point1 and point2 have different content.")
}
实现 Equatable 协议 #
如果你创建了一个自定义类或结构体,并希望比较其内容相等性,你可以通过实现 Equatable
协议来实现内容比较。
class Person: Equatable {
var name: String
init(name: String) {
self.name = name
}
// 实现 Equatable 协议的方法
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name
}
}
let person1 = Person(name: "Alice")
let person2 = Person(name: "Alice")
if person1 == person2 {
print("person1 and person2 have the same content.")
} else {
print("person1 and person2 have different content.")
}
总之,使用恒等运算符(===
和 !==
)可以帮助你确保你在处理的是同一个对象实例,而在比较值类型或对象内容时,应使用 ==
和 !=
运算符。了解这两者的区别和使用场景可以帮助你编写更安全且高效的代码。
可选类型 ?
和 !
#
类型转换 #
值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换。
let label = "The width is"
let width = 94
let widthLabel = label + String(width)
也可以在通过反斜杠\()
在字符串中打印出来。
let apples = 3
let oranges = 5
let appleSummary = "I have \(apples) apples."
let fruitSummary = "I have \(apples + oranges) pieces of fruit."
程序控制 #
if 判断语句 #
let username = "admin"
let password = "1234"
if username == "admin" && password == "1234" {
print("Login successful")
} else {
print("Login failed")
}
if-let
语句
#
You can use if
and let
together to work with values that might be missing. These values are represented as optionals. An optional value either contains a value or contains nil
to indicate that a value is missing. Write a question mark (?
) after the type of a value to mark the value as optional.
if let name = optionalName {
greeting = "Hello, \(name)"
}
switch 语句 #
goto 关键字 #
for 语句 #
case 1
let names = ["Anna", "Alex", "Brian", "Jack"]
for name in names {
print("Hello, \(name)!")
}
case 2
for _ in 0..<10 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
}
同样支持 continue 和 break
do-catch
语句与 try
关键字
#
在 Swift 中,try
关键字用于异常处理(也称为错误处理)。它与 do-catch
语句结合使用,以处理可能在运行过程中发生的错误。Swift 的错误处理模型与许多其他编程语言类似,通过抛出和捕获异常来处理错误情况。
- 抛出错误(Throwing Errors):定义哪些函数或方法可以抛出错误。
- 处理错误(Handling Errors):捕获并处理抛出的错误。
在 Swift 中,try
关键字用于调用可能抛出错误的函数或方法。它可以与 do-catch
语句结合使用,也可以单独使用。除了在 do-catch
语句中使用 try
,Swift 还提供了 try?
和 try!
两种特殊的 try
用法:
1. try
与 do-catch
结合使用
#
同时可以看到 throw
关键字的使用。
enum FileError: Error {
case fileNotFound
case unreadable
case encodingFailed
}
func readFile(filename: String) throws -> String {
// 模拟抛出错误
throw FileError.fileNotFound
}
do {
let content = try readFile(filename: "example.txt")
print(content)
} catch FileError.fileNotFound {
print("File not found.")
} catch FileError.unreadable {
print("File is unreadable.")
} catch FileError.encodingFailed {
print("Failed to encode the file.")
} catch {
print("An unexpected error occurred: \(error).")
}
2. try?
#
try?
将尝试执行可能抛出错误的表达式,如果抛出错误,则返回 nil
,否则返回可选值。因此,它可以避免使用 do-catch
语句:
if let content = try? readFile(filename: "example.txt") {
print(content)
} else {
print("Failed to read the file.")
}
3. try!
#
try!
将强制执行可能抛出错误的表达式,并假定不会抛出错误。如果错误确实抛出,程序将崩溃。应谨慎使用:
let content = try! readFile(filename: "example.txt")
print(content)
自定义错误类型 #
可以通过实现 Error
协议来定义自定义的错误类型。以下是一个定义和使用自定义错误类型的示例:
enum DivisionError: Error {
case dividedByZero
}
func divide(_ num: Int, by divisor: Int) throws -> Int {
if divisor == 0 {
throw DivisionError.dividedByZero
}
return num / divisor
}
do {
let result = try divide(10, by: 2)
print("Result: \(result)")
} catch DivisionError.dividedByZero {
print("Cannot divide by zero.")
} catch {
print("An unexpected error occurred: \(error).")
}
guard-else
语句
#
guard
关键字是 Swift 中一个非常有用的控制语句,用于提前退出某个代码块,通常用于验证条件并确保程序流在有效状态下继续执行。如果条件不符合,guard
语句必须执行退出当前作用范围的操作,比如 return
、break
、continue
或 throw
。
guard
的基本语法
#
guard condition else {
// 如果条件不满足,则执行退出操作
return // 或其他退出操作
}
// 如果条件满足,继续执行下面的代码
使用场景 #
guard
关键字常用于以下几种情况:
- 条件验证: 用于检查函数参数或外部输入是否满足特定条件。
- 强制解包: 用于安全地解包可选值,并在解包失败时提前退出。
- 防御性编程: 提前处理错误或不期望的情况,使主代码路径更清晰。
使用示例 #
示例 1: 条件验证
func validateAge(age: Int) {
guard age >= 18 else {
print("You must be at least 18 years old.")
return
}
print("Access granted.")
}
validateAge(age: 20) // 输出: Access granted.
validateAge(age: 16) // 输出: You must be at least 18 years old.
示例 2: 强制解包
func printUsername(user: [String: String]) {
guard let username = user["username"] else {
print("No username found.")
return
}
print("Username is \(username).")
}
let user1 = ["username": "Alice"]
let user2 = ["email": "alice@example.com"]
printUsername(user: user1) // 输出: Username is Alice.
printUsername(user: user2) // 输出: No username found.
示例 3: 多个条件验证
func process(order: [String: Any]) {
guard
let productID = order["productID"] as? Int,
let quantity = order["quantity"] as? Int,
quantity > 0
else {
print("Invalid order.")
return
}
print("Processing order for productID \(productID) with quantity \(quantity).")
}
let order1 = ["productID": 123, "quantity": 10]
let order2 = ["productID": "abc", "quantity": 0]
process(order: order1) // 输出: Processing order for productID 123 with quantity 10.
process(order: order2) // 输出: Invalid order.
使用注意事项 #
guard
必须退出: 在else
块中必须执行退出操作,否则会引发编译错误。这保证了在条件不满足时程序的正确性。- 作用范围: 在
guard
语句中解包的值在验证通过后依然有效。这使得主代码路径更为简洁,更容易阅读和维护。
比较 guard
和 if
#
if
语句: 用于处理条件为真的情况。代码路径可能有多个分支,可能会导致嵌套和代码不易阅读。guard
语句: 用于处理条件为假的情况,提前退出,有助于减少嵌套,使主代码路径更为直观和清晰。
示例对比
// 使用 if 嵌套
func processIf(order: [String: Any]) {
if let productID = order["productID"] as? Int {
if let quantity = order["quantity"] as? Int {
if quantity > 0 {
print("Processing order for productID \(productID) with quantity \(quantity).")
} else {
print("Invalid quantity.")
}
} else {
print("Invalid quantity.")
}
} else {
print("Invalid productID.")
}
}
// 使用 guard 提前退出
func processGuard(order: [String: Any]) {
guard
let productID = order["productID"] as? Int,
let quantity = order["quantity"] as? Int,
quantity > 0
else {
print("Invalid order.")
return
}
print("Processing order for productID \(productID) with quantity \(quantity).")
}
总之,guard
关键字的引入是为了提高代码的可读性和可维护性,使得你的代码结构更加清晰,减少不必要的嵌套,处理错误条件更加明确。
枚举 #
函数 #
协议 Protocol #
结构体 #
类 Class #
关键字 self
#
在 Swift 中,关键字 self
有多种用途,主要用于引用当前实例、属性或方法。下面是 self
的几种常见用途:
1. 引用当前实例 #
- 在类或结构体的方法中,可以使用
self
来引用当前实例。
class Person {
var name: String
init(name: String) {
self.name = name
}
func greet() {
print("Hello, my name is \(self.name)")
}
}
2. 区分属性和参数 #
- 当实例的属性名与参数名相同时,可以使用
self
来区分它们。
class Point {
var x: Int
var y: Int
init(x: Int, y: Int) {
self.x = x
self.y = y
}
func setCoordinates(x: Int, y: Int) {
self.x = x
self.y = y
}
}
3. 避免闭包循环引用 #
- 在闭包内部引用当前实例时,可以使用
self
来避免循环引用。
class MyClass {
var closure: (() -> Void)?
func setupClosure() {
self.closure = {
self.doSomething()
}
}
func doSomething() {
print("Doing something")
}
}
4. 在闭包中弱化引用 #
- 在闭包中捕获
self
并使用弱引用来避免循环引用。
class SomeClass {
var closure: (() -> Void)?
func setupClosure() {
self.closure = { [weak self] in
self?.doSomething()
}
}
func doSomething() {
print("Doing something")
}
}
总结 #
self
是一个特殊的关键字,用于引用当前实例、区分属性和参数、避免循环引用以及在闭包内部使用。- 在大多数情况下,Swift 会自动推断
self
的引用,因此在许多情况下,可以省略self
关键字。
关键字 Self
#
在 Swift 中,Self
是一个类型别名,用于引用当前类型。它主要用于泛型和协议中,能够使代码更具灵活性和复用性。
1. 协议中的 Self #
在协议中,Self
用于表示遵循该协议的具体类型。这意味着在定义协议时,可以使用 Self
来指代具体的实现类型。这对于定义返回自身类型的方法非常有用。
protocol Clonable {
func clone() -> Self
}
class Person: Clonable {
var name: String
init(name: String) {
self.name = name
}
func clone() -> Self {
return Person(name: self.name) as! Self
}
}
let original = Person(name: "John")
let clone = original.clone()
print(clone.name) // 输出: John
在这个例子中,Clonable
协议定义了一个 clone
方法,返回类型为 Self
。具体实现这个协议的类(如 Person
)中的 clone
方法返回的实际类型也是 Person
,保持了类型的一致性。
2. 类和结构体中的 Self #
在类和结构体中,Self
可用于表示当前类型,尤其在泛型和动态类型中使用。
class Vehicle {
class func create() -> Self {
return self.init()
}
required init() {
// 初始化代码
}
}
class Car: Vehicle {
var model: String = "Sedan"
required init() {
super.init()
}
}
let myCar = Car.create()
print(type(of: myCar)) // 输出: Car
在这个示例中,Vehicle
类有一个类方法 create
,返回类型是 Self
。通过调用 self.init()
,create
将返回调用它的具体类型的实例。
3. 泛型中的 Self #
在泛型上下文中,Self
能够创建与当前对象类型一致的返回类型。
protocol Initializable {
init()
}
extension Initializable {
static func createInstance() -> Self {
return Self.init()
}
}
struct MyStruct: Initializable {
var value: Int = 10
}
let instance = MyStruct.createInstance()
print(type(of: instance)) // 输出: MyStruct
在这个例子中,Initializable
协议要求实现 init()
初始化方法,并在扩展中定义了一个返回类型为 Self
的静态方法 createInstance
。最终,MyStruct
作为具体类型实现了该协议,并通过 createInstance
方法生成了与 MyStruct
类型一致的实例。
官方文档 #
你可以在 Swift 的官方文档中找到更多关于 Self
的内容:
- Swift Programming Language - Protocols
- Swift Programming Language - Methods
- Swift Programming Language - Initialization
访问控制 #
- open
- public
- internal
- fileprivate
- private
条件编译 #
通过条件编译来控制哪些代码在那些情况下需要编译,而哪些代码不需要。
#if <condition>
举例:#if os(iOS) || os(watchOS) || os(tvOS)
#elseif <condition>
#else
#endif
其中 #elseif
和 #else
是可选的。
Platform condition | Valid arguments |
---|---|
os() | macOS , iOS , watchOS , tvOS , visionOS , Linux , Windows |
arch() | i386 , x86_64 , arm , arm64 |
swift() | >= or < followed by a version number |
compiler() | >= or < followed by a version number |
canImport() | A module name |
targetEnvironment() | simulator , macCatalyst |
断言 #
assert 使用断言进行调试 #
只在 debug 下生效
precondition 强制先决条件 #
debug 和 release 都生效
泛型 #
宏(Macro) #
宏在编译时执行,可以在代码中生成或变换其他代码。宏在 Swift 语言中的应用能够帮助开发者在编译时进行一些高级的代码操作,从而使开发流程更加高效和简洁。
Preview #
在 Xcode 15 和 Swift 5.9 中,Apple 引入了这一宏语法,以简化视图预览的配置,让开发者能够以更直观和便捷的方式设置 SwiftUI 视图的预览。通过使用 #Preview
宏,开发者可以直接用这种方式创建视图的预览,而不需要像以前那样遵循更复杂的预览提供器(@PreviewProvider
) 规范。这让代码更为简洁,同时提高了在 Xcode 中设计和调试用户界面的效率。
import SwiftUI
struct ContentView: View {
var body: some View {
Text("Hello, world!")
.padding()
}
}
#Preview {
ContentView()
}
这个示例代码定义了一个简单的包含 “Hello, world!” 文本的视图,并使用 #Preview
来告诉 Xcode 在预览区域中展示这个视图。通过这种方式,开发者可以立即看到视图的外观并进行相应的调整,而不需编译和运行整个应用程序。
available #
if #available(iOS 14, *) {
// 代码只在 iOS 14 及以上版本运行
} else {
// 其他版本的替代实现代码
}