Swift — 语法

官方文档 #

官方文档:The Swift Programming Language

https://developer.apple.com/documentation/swift

语言特点 #

https://www.swift.org/about/

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 类型的可选值只有 truefalse

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

注意事项

  1. 引用类型: 恒等运算符仅适用于引用类型(如类实例),因为它们比较的是内存地址。对于值类型(如结构体、枚举和基本数据类型),你应该使用等号 == 来比较其内容。

  2. 内容比较: 如果你需要判断两个实例的内容是否相同(而不是它们是否引用同一个对象),你应当实现相应的 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.")
}

总之,使用恒等运算符(===!==)可以帮助你确保你在处理的是同一个对象实例,而在比较值类型或对象内容时,应使用 ==!= 运算符。了解这两者的区别和使用场景可以帮助你编写更安全且高效的代码。

可选类型 ?! #

Swift 中可选类型的使用

类型转换 #

值永远不会被隐式转换为其他类型。如果你需要把一个值转换成其他类型,请显式转换。

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 的错误处理模型与许多其他编程语言类似,通过抛出和捕获异常来处理错误情况。

  1. 抛出错误(Throwing Errors):定义哪些函数或方法可以抛出错误。
  2. 处理错误(Handling Errors):捕获并处理抛出的错误。

在 Swift 中,try 关键字用于调用可能抛出错误的函数或方法。它可以与 do-catch 语句结合使用,也可以单独使用。除了在 do-catch 语句中使用 try,Swift 还提供了 try?try! 两种特殊的 try 用法:

1. trydo-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 语句必须执行退出当前作用范围的操作,比如 returnbreakcontinuethrow

guard 的基本语法 #

guard condition else {
    // 如果条件不满足,则执行退出操作
    return  // 或其他退出操作
}

// 如果条件满足,继续执行下面的代码

使用场景 #

guard 关键字常用于以下几种情况:

  1. 条件验证: 用于检查函数参数或外部输入是否满足特定条件。
  2. 强制解包: 用于安全地解包可选值,并在解包失败时提前退出。
  3. 防御性编程: 提前处理错误或不期望的情况,使主代码路径更清晰。

使用示例 #

示例 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 语句中解包的值在验证通过后依然有效。这使得主代码路径更为简洁,更容易阅读和维护。

比较 guardif #

  • 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 关键字的引入是为了提高代码的可读性和可维护性,使得你的代码结构更加清晰,减少不必要的嵌套,处理错误条件更加明确。

枚举 #

Swift 中的枚举(Enumerations)的使用

函数 #

Swift 函数(Functions)的使用

协议 Protocol #

Swift 中的协议(Protocol)的概念

结构体 #

Swift 中的结构体(Struct)概念

类 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 的内容:

访问控制 #

Access Control

  • open
  • public
  • internal
  • fileprivate
  • private

条件编译 #

通过条件编译来控制哪些代码在那些情况下需要编译,而哪些代码不需要。

#if <condition>
	举例:#if os(iOS) || os(watchOS) || os(tvOS)
#elseif <condition>

#else

#endif

其中 #elseif#else 是可选的。

Platform conditionValid arguments
os()macOSiOSwatchOStvOSvisionOSLinuxWindows
arch()i386x86_64armarm64
swift()>= or < followed by a version number
compiler()>= or < followed by a version number
canImport()A module name
targetEnvironment()simulatormacCatalyst

断言 #

assert 使用断言进行调试 #

只在 debug 下生效

precondition 强制先决条件 #

debug 和 release 都生效

泛型 #

Swift 泛型使用

宏(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 {
    // 其他版本的替代实现代码
}

属性 Properties #

Swift 的计算属性概念

本文共 5964 字,上次修改于 Dec 16, 2024
相关标签: Swift, Xcode, ByAI