Swift 新元素
1.2.1 柯里化 (Currying)
重点说明
柯里化是将一个多参数的函数,拆解成多个单参数函数的链式调用的技术。每个函数接收一个参数,返回一个接收下一个参数的新函数,直到所有参数接收完毕,返回最终结果。Swift支持柯里化函数的定义,适合参数分步传入、函数复用的场景。
代码示例
// 1. 基础柯里化函数:加法函数
func add(_ a: Int) -> (Int) -> Int {
return { b in
return a + b
}
}
// 分步调用
let add5 = add(5) // 先传入第一个参数,返回一个接收第二个参数的函数
let result1 = add5(3) // 8
let result2 = add5(10) // 15
// 2. 多参数柯里化
func createUser(name: String) -> (Int) -> (Bool) -> String {
return { age in
return { isVip in
return "用户:\(name),年龄:\(age),VIP:\(isVip ? "是" : "否")"
}
}
}
// 分步传入参数
let userWithName = createUser(name: "王五")
let userWithNameAndAge = userWithName(30)
let userInfo = userWithNameAndAge(true)
print(userInfo) // 用户:王五,年龄:30,VIP:是
1.2.2 将 protocol 的方法声明为 mutating
重点说明
Swift中值类型(struct、enum)在方法内修改自身属性时,必须用mutating修饰方法。协议中声明mutating方法,可让遵循协议的值类型和引用类型都能正确实现修改自身的逻辑;引用类型(class)实现时无需加mutating,因为class实例可自由修改自身属性。
代码示例
// 定义带mutating方法的协议
protocol Toggleable {
mutating func toggle()
}
// 枚举(值类型)实现协议
enum Switch: Toggleable {
case on, off
mutating func toggle() {
self = self == .on ? .off : .on
}
}
// 类(引用类型)实现协议,无需mutating
class Light: Toggleable {
var isOn = false
func toggle() {
isOn = !isOn
}
}
// 使用
var mySwitch = Switch.off
mySwitch.toggle() // 变为.on
let myLight = Light()
myLight.toggle() // isOn变为true
1.2.3 Sequence
重点说明
Sequence协议是Swift集合类型的基础,遵循该协议可获得for-in循环能力,只需实现makeIterator()方法返回一个遵守IteratorProtocol协议的迭代器。迭代器需实现next()方法,返回序列的下一个元素,无元素时返回nil。
代码示例
// 定义自定义序列,生成从0到指定最大值的偶数序列
struct EvenNumberSequence: Sequence {
let maxValue: Int
func makeIterator() -> EvenNumberIterator {
return EvenNumberIterator(max: maxValue)
}
}
// 定义迭代器
struct EvenNumberIterator: IteratorProtocol {
let max: Int
var current = 0
mutating func next() -> Int? {
guard current <= max else { return nil }
defer { current += 2 }
return current
}
}
// 使用,支持for-in循环
for evenNum in EvenNumberSequence(maxValue: 10) {
print(evenNum) // 输出0,2,4,6,8,10
}
1.2.4 tuple(元组)
重点说明
元组是Swift中轻量级的复合类型,可将多个不同类型的值组合成一个复合值,无需提前定义结构体/类。支持元素命名、解包、作为函数返回值(可返回多个值),适合临时数据组合,不适合复杂数据结构。
代码示例
// 1. 基础元组,带元素命名
let userInfo = (name: "张三", age: 25, isVip: true)
// 访问元素
print(userInfo.name) // 张三
print(userInfo.1) // 25
// 2. 元组解包
let (userName, userAge, _) = userInfo
print("用户名:\(userName),年龄:\(userAge)")
// 3. 作为函数返回值,返回多个结果
func calculateStatistics(_ numbers: [Int]) -> (sum: Int, average: Double, max: Int)? {
guard !numbers.isEmpty else { return nil }
let sum = numbers.reduce(0, +)
let average = Double(sum) / Double(numbers.count)
let max = numbers.max()!
return (sum, average, max)
}
if let result = calculateStatistics([1,2,3,4,5]) {
print("总和:\(result.sum),平均值:\(result.average),最大值:\(result.max)")
}
1.2.5 @autoclosure 和 ??
重点说明
@autoclosure可将传入的表达式自动封装成闭包,延迟执行,避免不必要的性能开销。空合运算符??底层使用了@autoclosure,当左侧可选值为nil时,才会执行右侧的默认值表达式,否则直接解包左侧值,右侧不会执行。
代码示例
// 1. @autoclosure基础使用
func logIfError(_ condition: Bool, errorMessage: @autoclosure () -> String) {
if condition {
print("错误:\(errorMessage())")
}
}
// 只有condition为true时,才会执行拼接字符串的表达式
logIfError(1 > 2, errorMessage: "数值超出范围:\(1) 大于 \(2)") // 无输出,右侧不执行
logIfError(2 > 1, errorMessage: "数值超出范围:\(2) 大于 \(1)") // 执行,输出错误信息
// 2. ??运算符使用
let defaultName = "未知用户"
var inputName: String? = nil
let finalName = inputName ?? defaultName // inputName为nil,执行defaultName,结果为"未知用户"
inputName = "李四"
let finalName2 = inputName ?? defaultName // inputName非nil,直接解包,defaultName不执行
1.2.6 @escaping
重点说明
@escaping(逃逸闭包)用于修饰函数参数中的闭包,当闭包的生命周期超过函数本身(比如被保存到外部变量、异步执行)时,必须用@escaping标记。逃逸闭包内需要显式引用self,避免循环引用;非逃逸闭包默认不能持有self,生命周期随函数结束而结束。
代码示例
import Foundation
// 定义存储逃逸闭包的数组
var completionHandlers: [() -> Void] = []
// 1. 逃逸闭包:闭包被保存到外部数组,生命周期超过函数
func addCompletionHandler(handler: @escaping () -> Void) {
completionHandlers.append(handler)
}
// 2. 非逃逸闭包:闭包在函数内同步执行,生命周期随函数结束
func syncExecute(handler: () -> Void) {
handler()
}
// 3. 异步场景的逃逸闭包
func fetchUserData(completion: @escaping (String) -> Void) {
// 模拟网络请求,异步执行
DispatchQueue.global().async {
let userName = "异步获取的用户"
DispatchQueue.main.async {
completion(userName) // 闭包在函数返回后才执行,必须标记@escaping
}
}
}
// 使用
addCompletionHandler {
print("逃逸闭包执行")
}
completionHandlers.first?() // 执行保存的逃逸闭包
fetchUserData { name in
print(name)
}
1.2.7 Optional Chaining(可选链式调用)
重点说明
可选链式调用是一种在可选值上调用属性、方法、下标的方式,若可选值为nil,整个链式调用直接返回nil,不会崩溃;若可选值有值,则解包并执行后续调用,返回结果自动包装为可选值。支持多层链式调用,任意一层为nil则整个链返回nil。
代码示例
// 定义模型
class User {
var address: Address?
}
class Address {
var street: String?
func getFullAddress() -> String? {
return street
}
}
// 可选链式调用
let user = User()
// 1. 访问属性,address为nil,直接返回nil,不会崩溃
let street = user.address?.street
print(type(of: street)) // String?,值为nil
// 2. 调用方法
let fullAddress = user.address?.getFullAddress() // nil
// 3. 赋值,address为nil,赋值操作不会执行
user.address?.street = "中关村大街"
print(user.address?.street) // nil
// 赋值后正常执行
user.address = Address()
user.address?.street = "中关村大街"
print(user.address?.street) // Optional("中关村大街")
1.2.8 操作符
重点说明
Swift支持自定义操作符,可分为前缀、中缀、后缀操作符,需先声明操作符,再实现对应的函数。支持对已有操作符进行重载,为自定义类型添加运算能力;也可定义全新的操作符,指定优先级和结合性,适配自定义业务逻辑。
代码示例
import Foundation
// 1. 重载已有操作符,为二维向量实现加法
struct Vector2D {
var x: Double
var y: Double
}
// 重载+运算符
func + (left: Vector2D, right: Vector2D) -> Vector2D {
return Vector2D(x: left.x + right.x, y: left.y + right.y)
}
// 2. 自定义前缀操作符,实现向量取反
prefix operator -
prefix func - (vector: Vector2D) -> Vector2D {
return Vector2D(x: -vector.x, y: -vector.y)
}
// 3. 自定义中缀操作符,指定优先级
infix operator **: MultiplicationPrecedence // 乘法优先级
func ** (base: Double, power: Double) -> Double {
return pow(base, power)
}
// 使用
let v1 = Vector2D(x: 1, y: 2)
let v2 = Vector2D(x: 3, y: 4)
let sumVector = v1 + v2 // (4,6)
let reverseVector = -v1 // (-1,-2)
let powerResult = 2 ** 3 // 8.0
1.2.9 func 的参数修饰
重点说明
Swift函数参数默认是let常量,不可在函数内修改。常用参数修饰符包括:inout(输入输出参数,可修改外部变量的值,调用时加&)、_(省略参数标签,调用时无需写参数名)、自定义参数标签(区分外部标签和内部参数名)。Swift 3+后移除了var参数修饰,需在函数内手动定义可变变量。
代码示例
// 1. 省略参数标签,用_修饰
func sum(_ a: Int, _ b: Int) -> Int {
return a + b
}
sum(1, 2) // 调用时无需写参数标签
// 2. 自定义外部标签和内部参数名
func greet(to userName: String, from sender: String) {
// 内部使用userName和sender,外部调用用to和from
print("\(sender) 向 \(userName) 问好")
}
greet(to: "张三", from: "李四")
// 3. inout参数,修改外部变量
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temp = a
a = b
b = temp
}
var num1 = 10
var num2 = 20
swapTwoInts(&num1, &num2) // 调用时加&
print(num1, num2) // 20 10
1.2.10 字面量表达
重点说明
Swift通过字面量协议,让自定义类型可直接通过字面量(如数字、字符串、布尔值、数组、字典)初始化,无需显式调用初始化方法。常用字面量协议:ExpressibleByIntegerLiteral、ExpressibleByStringLiteral、ExpressibleByArrayLiteral等,只需实现协议对应的初始化方法。
代码示例
// 让自定义的用户ID类型支持字符串字面量初始化
struct UserID: ExpressibleByStringLiteral {
let idString: String
// 实现字符串字面量初始化方法
init(stringLiteral value: StringLiteralType) {
self.idString = value
}
}
// 让自定义的分数类型支持整数字面量初始化
struct Score: ExpressibleByIntegerLiteral {
let value: Int
init(integerLiteral value: IntegerLiteralType) {
self.value = value
}
}
// 使用,直接通过字面量赋值
let userId: UserID = "user_123456" // 无需调用UserID(stringLiteral:)
print(userId.idString) // user_123456
let myScore: Score = 99
print(myScore.value) // 99
1.2.11 下标
重点说明
下标是Swift中访问集合、序列、结构体/类元素的快捷方式,可通过subscript关键字为自定义类型添加下标能力,支持单个/多个参数、读写/只读、重载,无需调用专门的get/set方法,语法更简洁。
代码示例
// 为班级类添加下标,通过学号访问学生
class ClassRoom {
private var students: [Int: String] = [:]
// 定义下标,读写类型
subscript(studentID: Int) -> String? {
get {
return students[studentID]
}
set {
students[studentID] = newValue
}
}
// 重载下标,通过姓名首字母筛选学生
subscript(firstLetter: Character) -> [String] {
return students.values.filter { $0.hasPrefix(String(firstLetter)) }
}
}
// 使用
let classRoom = ClassRoom()
classRoom[1001] = "张三"
classRoom[1002] = "李四"
classRoom[1003] = "张四"
print(classRoom[1001] ?? "不存在") // 张三
print(classRoom["张"]) // ["张三", "张四"]
1.2.12 方法嵌套
重点说明
Swift支持在函数/方法内部定义另一个函数,即嵌套方法。嵌套方法仅能在外层函数内部访问,外部无法调用;可访问外层函数的参数和变量,适合封装仅在外层函数内使用的逻辑,简化代码结构,避免全局命名污染。
代码示例
// 外层函数:计算阶乘
func calculateFactorial(of number: Int) -> Int {
// 嵌套方法:校验输入是否合法
func validateInput() -> Bool {
return number >= 0
}
// 嵌套方法:递归计算阶乘
func factorial(_ n: Int) -> Int {
return n <= 1 ? 1 : n * factorial(n-1)
}
// 调用嵌套方法
guard validateInput() else {
fatalError("输入不能为负数")
}
return factorial(number)
}
// 使用
print(calculateFactorial(of: 5)) // 120
// 外部无法访问嵌套方法,factorial()和validateInput()在外部不可见
1.2.13 命名空间
重点说明
Swift没有传统的全局命名空间关键字,通过模块、嵌套类型、枚举(无case的空枚举)实现命名空间功能,避免不同模块/代码间的命名冲突。推荐使用空枚举作为命名空间载体,因为空枚举无法实例化,仅作为命名空间使用,更安全。
代码示例
import UIKit
// 定义命名空间,使用空枚举
enum MyAppNamespace {
// 嵌套类型,仅在命名空间内生效
enum Network {
static func getRequest(url: String) {
print("发送GET请求到:\(url)")
}
}
enum UI {
static func createButton(title: String) -> UIButton {
let btn = UIButton()
btn.setTitle(title, for: .normal)
return btn
}
}
}
// 使用命名空间,避免冲突
MyAppNamespace.Network.getRequest(url: "https://api.example.com")
let button = MyAppNamespace.UI.createButton(title: "登录")
// 模块级命名空间:每个Swift模块(如App、Framework)本身就是一个顶级命名空间,可通过模块名.类型访问
1.2.14 typealias
重点说明
typealias用于为已有类型定义别名,简化复杂类型的书写,提高代码可读性,也可用于给协议组合、函数类型、泛型类型起别名。不会创建新的类型,只是给已有类型起了一个新名字,别名和原类型完全等价。
代码示例
import UIKit
// 1. 为基础类型起别名,适配业务场景
typealias UserID = String
typealias Price = Double
let userId: UserID = "user_654321"
let goodsPrice: Price = 99.9
// 2. 为函数类型起别名,简化闭包参数书写
typealias CompletionHandler = (Bool, String) -> Void
func fetchData(completion: CompletionHandler) {
completion(true, "请求成功")
}
// 3. 为协议组合起别名
typealias FullStack = UITableViewDelegate & UITableViewDataSource
// 4. 为泛型类型起别名
typealias StringDictionary<T> = Dictionary<String, T>
let userDict: StringDictionary<Any> = ["name": "张三", "age": 25]
1.2.15 associatedtype
重点说明
associatedtype(关联类型)用于协议中,给协议中用到的类型定义一个占位名称,具体的类型由遵循该协议的类型来指定。实现协议时,通过typealias指定关联类型的实际类型,也可通过编译器自动推断,让协议支持泛型能力,适配不同类型的实现。
代码示例
// 定义带关联类型的协议
protocol Container {
// 定义关联类型,占位符
associatedtype Item
var count: Int { get }
mutating func append(_ item: Item)
subscript(index: Int) -> Item { get }
}
// 实现协议,指定关联类型为Int
struct IntContainer: Container {
// 显式指定关联类型,也可省略,由编译器自动推断
typealias Item = Int
private var items: [Int] = []
var count: Int { items.count }
mutating func append(_ item: Int) {
items.append(item)
}
subscript(index: Int) -> Int {
items[index]
}
}
// 泛型实现协议,自动推断关联类型
struct Stack<T>: Container {
private var elements: [T] = []
// 无需显式指定typealias,编译器通过append方法的参数自动推断Item为T
var count: Int { elements.count }
mutating func append(_ item: T) {
elements.append(item)
}
subscript(index: Int) -> T {
elements[index]
}
}
// 使用
var intContainer = IntContainer()
intContainer.append(1)
intContainer.append(2)
print(intContainer[0]) // 1
var stringStack = Stack<String>()
stringStack.append("Swift")
print(stringStack[0]) // Swift
1.2.16 可变参数函数
重点说明
可变参数函数可接收零个或多个指定类型的输入值,通过在参数类型后添加...来定义。函数内部会将可变参数转换为对应类型的数组,可通过数组的方式访问。一个函数最多只能有一个可变参数,可变参数可放在参数列表的任意位置,通常放在末尾。
代码示例
// 1. 计算多个数字的平均值
func calculateAverage(_ numbers: Double...) -> Double {
// numbers在函数内是[Double]数组
guard !numbers.isEmpty else { return 0 }
let sum = numbers.reduce(0, +)
return sum / Double(numbers.count)
}
// 调用,可传入任意数量的参数
print(calculateAverage(1,2,3,4,5)) // 3.0
print(calculateAverage(10,20)) // 15.0
print(calculateAverage()) // 0
// 2. 拼接多个字符串
func joinStrings(separator: String, strings: String...) -> String {
return strings.joined(separator: separator)
}
print(joinStrings(separator: "-", strings: "2024", "05", "20")) // 2024-05-20
1.2.17 初始化方法顺序
重点说明
Swift类的初始化有严格的安全检查顺序,分为两个阶段:第一阶段,子类必须先初始化自身的所有存储属性,再调用父类的指定初始化方法;第二阶段,父类初始化完成后,子类才能修改继承来的属性、调用实例方法。避免在属性未初始化时访问,防止崩溃。
代码示例
class Person {
var name: String
init(name: String) {
self.name = name // 第一阶段:初始化自身存储属性
}
}
class Student: Person {
var studentID: Int
var grade: Int
init(name: String, studentID: Int, grade: Int) {
// 第一阶段第一步:初始化子类自身的所有存储属性
self.studentID = studentID
self.grade = grade
// 第一阶段第二步:调用父类的指定初始化方法
super.init(name: name)
// 第二阶段:父类初始化完成后,才能修改属性、调用方法
self.name = "学生:\(name)" // 可修改继承的属性
}
}
// 错误示例:先调用super.init,再初始化子类属性,会编译报错
// init(name: String, studentID: Int, grade: Int) {
// super.init(name: name) // 错误:子类属性未初始化
// self.studentID = studentID
// self.grade = grade
// }
1.2.18 Designated, Convenience 和 Required
重点说明
- Designated(指定初始化方法):类的主初始化方法,必须初始化所有未赋值的存储属性,最终必须调用父类的指定初始化方法,每个类至少有一个。
- Convenience(便利初始化方法):辅助初始化方法,用
convenience修饰,必须调用同一个类中的指定初始化方法,只能横向调用,不能直接调用父类的初始化方法,用于简化初始化流程。 - Required(必要初始化方法):用
required修饰,所有子类必须重写该初始化方法,重写时无需加override,必须加required,确保继承链上所有子类都实现该方法。
代码示例
class Person {
var name: String
var age: Int
// 指定初始化方法
init(name: String, age: Int) {
self.name = name
self.age = age
}
// 便利初始化方法
convenience init(name: String) {
self.init(name: name, age: 0) // 必须调用本类的指定初始化方法
}
// 必要初始化方法
required init() {
self.name = "未知"
self.age = 0
}
}
class Student: Person {
var studentID: Int
// 子类指定初始化方法
init(name: String, age: Int, studentID: Int) {
self.studentID = studentID
super.init(name: name, age: age) // 调用父类指定初始化方法
}
// 必须重写父类的必要初始化方法
required init() {
self.studentID = 0
super.init()
}
// 子类便利初始化方法
convenience init(studentID: Int) {
self.init(name: "学生", age: 18, studentID: studentID)
}
}
// 使用
let person1 = Person(name: "张三", age: 25) // 指定初始化
let person2 = Person(name: "李四") // 便利初始化
let student1 = Student() // 必要初始化方法
let student2 = Student(studentID: 1001) // 子类便利初始化
1.2.19 初始化返回 nil
重点说明
Swift支持可失败初始化方法,在init后加?,可在初始化过程中校验条件,不满足时返回nil,中断初始化流程。可失败初始化方法可用于值类型和引用类型,常用于参数校验、资源加载失败等场景,返回的实例是可选类型,需要解包使用。
代码示例
// 1. 结构体的可失败初始化方法
struct User {
let userID: String
init?(userID: String) {
// 校验条件,ID不能为空,且长度必须大于6
guard !userID.isEmpty, userID.count > 6 else {
return nil // 不满足条件,返回nil,初始化失败
}
self.userID = userID
}
}
// 2. 类的可失败初始化方法
class Student {
let studentID: Int
init?(studentID: Int) {
guard studentID > 0 else { return nil }
self.studentID = studentID
}
}
// 使用
let validUser = User(userID: "user_123456") // 初始化成功,User?类型
let invalidUser = User(userID: "user") // 初始化失败,返回nil
let validStudent = Student(studentID: 1001) // 成功
let invalidStudent = Student(studentID: 0) // nil
// 解包使用
if let user = validUser {
print("用户ID:\(user.userID)")
}
1.2.20 static 和 class
重点说明
static和class都用于定义类型级别的属性和方法,核心区别:
static:可用于类、结构体、枚举、协议,定义的方法/属性不能被子类重写;在协议中定义类型方法/属性时,必须用static。class:仅可用于类,定义的方法/计算属性可以被子类重写;不能用于存储属性,类的类型存储属性只能用static定义。
代码示例
// 1. 结构体/枚举中只能用static
struct AppConfig {
static let appVersion = "1.0.0" // 类型存储属性
static func printVersion() {
print("App版本:\(appVersion)")
}
}
AppConfig.printVersion()
// 2. 类中static和class的区别
class Person {
// 类的类型存储属性,只能用static
static let species = "人类"
// static方法,不可被子类重写
static func printSpecies() {
print("物种:\(species)")
}
// class方法,可被子类重写
class func greet() {
print("你好,我是人类")
}
// class计算属性,可被子类重写
class var averageAge: Int {
return 75
}
}
class Student: Person {
// 重写class方法,不能重写static方法
override class func greet() {
print("你好,我是学生")
}
override class var averageAge: Int {
return 18
}
}
Person.greet() // 你好,我是人类
Student.greet() // 你好,我是学生
print(Student.averageAge) // 18
1.2.21 多类型和容器
重点说明
Swift中可通过Any、AnyObject、协议组合来实现多类型容器:
Any:可表示任意类型(值类型、引用类型、函数类型)。AnyObject:可表示任意类类型的实例。- 协议组合:限定容器内的元素必须遵循指定的多个协议,比
Any/AnyObject更安全,保留类型约束。
代码示例
// 1. Any类型的数组,可存储任意类型
var anyArray: [Any] = []
anyArray.append(10) // Int
anyArray.append("Swift") // String
anyArray.append((1, 2)) // 元组
anyArray.append({ print("闭包") }) // 闭包
print(anyArray)
// 2. AnyObject数组,仅能存储类实例
class Person {}
class Student {}
var objectArray: [AnyObject] = []
objectArray.append(Person())
objectArray.append(Student())
// 3. 协议组合的数组,类型安全,限定必须遵循指定协议
protocol Runnable {
func run()
}
protocol Flyable {
func fly()
}
class Bird: Runnable, Flyable {
func run() { print("鸟跑") }
func fly() { print("鸟飞") }
}
class Dog: Runnable {
func run() { print("狗跑") }
}
// 数组元素必须同时遵循Runnable和Flyable协议
var flyAndRunArray: [Runnable & Flyable] = []
flyAndRunArray.append(Bird())
// flyAndRunArray.append(Dog()) // 编译报错,Dog不遵循Flyable
1.2.22 default 参数
重点说明
Swift支持为函数参数设置默认值,在参数定义时给参数赋值,调用时若未传入该参数,自动使用默认值。带默认值的参数可放在参数列表的任意位置,调用时可选择性省略,简化函数调用,无需为不同参数组合重载多个函数。
代码示例
import UIKit
// 带默认参数的函数
func greetUser(name: String, greeting: String = "你好", isVip: Bool = false) {
let prefix = isVip ? "尊贵的VIP用户" : "用户"
print("\(greeting),\(prefix)\(name)")
}
// 调用方式
greetUser(name: "张三") // 使用所有默认值
greetUser(name: "李四", greeting: "早上好") // 自定义greeting,isVip用默认值
greetUser(name: "王五", isVip: true) // 自定义isVip,greeting用默认值
greetUser(name: "赵六", greeting: "晚上好", isVip: true) // 所有参数都自定义
// 带默认参数的初始化方法
class Button {
var title: String
var frame: CGRect
init(title: String, frame: CGRect = .zero) {
self.title = title
self.frame = frame
}
}
let button1 = Button(title: "登录") // frame用默认值.zero
let button2 = Button(title: "注册", frame: CGRect(x: 0, y: 0, width: 100, height: 50))
1.2.23 正则表达式
重点说明
Swift中通过NSRegularExpression实现正则表达式功能,也可通过~=运算符、range(of:options:)快速匹配。Swift 5.7+引入了更简洁的正则字面量语法,支持直接用/.../定义正则。正则常用于字符串校验、匹配、替换、提取等场景,需处理初始化和匹配时的异常。
代码示例
import Foundation
// 1. 邮箱格式校验
func validateEmail(_ email: String) -> Bool {
// 正则表达式:邮箱格式
let emailRegex = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$"
// 匹配
return email.range(of: emailRegex, options: .regularExpression) != nil
}
print(validateEmail("test@example.com")) // true
print(validateEmail("invalid-email")) // false
// 2. 提取字符串中的所有数字
func extractNumbers(from string: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: "\\d+", options: [])
let matches = regex.matches(in: string, range: NSRange(string.startIndex..., in: string))
return matches.map {
String(string[Range($0.range, in: string)!])
}
} catch {
print("正则初始化失败:\(error)")
return []
}
}
print(extractNumbers(from: "订单号12345,金额99元")) // ["12345", "99"]
// 3. Swift 5.7+ 正则字面量
let phoneRegex = /^1[3-9]\d{9}$/
let phoneNumber = "13812345678"
if phoneNumber.wholeMatch(of: phoneRegex) != nil {
print("手机号格式正确")
}
1.2.24 模式匹配
重点说明
Swift的模式匹配是核心语法特性,核心用于switch语句,也可用于if case、guard case、for case、while等场景。支持值匹配、区间匹配、元组匹配、可选值匹配、类型匹配、where条件过滤等,可灵活匹配复杂的条件,简化多分支判断代码。
代码示例
// 1. switch基础匹配+区间匹配
let score = 85
switch score {
case 0..<60: print("不及格")
case 60..<80: print("及格")
case 80..<90: print("良好")
case 90...100: print("优秀")
default: print("无效分数")
}
// 2. 元组匹配+值绑定
let userPoint = (x: 10, y: 0)
switch userPoint {
case (0, 0): print("原点")
case (_, 0): print("在X轴上,x值为\(userPoint.x)")
case (0, _): print("在Y轴上")
case (-10...10, -10...10): print("在10x10的范围内")
default: print("超出范围")
}
// 3. if case匹配,替代单分支switch
let age: Int? = 25
if case 20...30 = age {
print("年龄在20-30之间")
}
// 4. for case匹配,遍历数组时过滤元素
let numbers: [Int?] = [1, nil, 3, nil, 5, 6]
for case let num? in numbers where num % 2 == 1 {
print("奇数:\(num)") // 输出1,3,5
}
// 5. 类型匹配
let anyValue: Any = "Swift字符串"
switch anyValue {
case let str as String: print("是字符串,内容:\(str)")
case let num as Int: print("是整数:\(num)")
default: print("其他类型")
}
1.2.25 ... 和 ..<(闭区间与半开区间)
重点说明
Swift提供两种区间运算符,用于表示一个范围的值:
...闭区间运算符:a...b表示包含a和b的所有值,范围从a到b,a不能大于b,适用于闭合范围的场景。..<半开区间运算符:a..<b表示包含a但不包含b的所有值,范围从a到b-1,特别适合遍历数组、集合等从0开始的索引场景,避免越界。 同时支持单侧区间,如a...、...b、..<b,用于表示从某个值开始/结束的无限范围。
代码示例
// 1. 闭区间运算符
for number in 1...5 {
print(number) // 输出1,2,3,4,5
}
// 2. 半开区间运算符,遍历数组
let fruits = ["苹果", "香蕉", "橙子", "葡萄"]
for index in 0..<fruits.count {
print("索引\(index):\(fruits[index])")
} // 避免index等于fruits.count导致越界
// 3. 单侧区间
let names = ["张三", "李四", "王五", "赵六"]
// 从索引2开始到末尾
print(names[2...]) // ["王五", "赵六"]
// 从开头到索引2(不包含)
print(names[..<2]) // ["张三", "李四"]
// 从开头到索引2(包含)
print(names[...2]) // ["张三", "李四", "王五"]
// 4. 区间匹配
let temperature = 25
switch temperature {
case ..<0: print("严寒")
case 0...15: print("寒冷")
case 16...28: print("舒适")
case 29...: print("炎热")
default: break
}
1.2.26 AnyClass, 元类型和 .self
重点说明
- 元类型:是类型的类型,用
Type表示,如Int.Type就是Int的元类型,可存储类型本身,而非实例。 - .self:两种用法,1. 实例.self返回实例本身;2. 类型.self返回该类型的元类型实例,可用于获取类型、动态创建实例、传递类型参数。
- AnyClass:是
AnyObject.Type的别名,代表任意类的元类型,可存储任意类类型。
代码示例
// 1. 类型.self获取元类型
let intType: Int.Type = Int.self
print(intType) // Int
// 2. AnyClass存储任意类的元类型
class Person {}
class Student: Person {}
var classType: AnyClass = Person.self
classType = Student.self // 可赋值为任意类的元类型
// 3. 通过元类型动态创建实例
protocol InitProtocol {
init()
}
class Animal: InitProtocol {
required init() {}
func say() {
print("动物叫")
}
}
class Cat: Animal {
override func say() {
print("猫叫")
}
}
// 接收元类型参数,动态创建实例
func createInstance(_ type: Animal.Type) -> Animal {
return type.init()
}
let cat = createInstance(Cat.self)
cat.say() // 猫叫
// 4. 实例.self返回自身
let number = 10
print(number.self == 10) // true
1.2.27 协议和类方法中的 Self
重点说明
Self(大写S)是Swift中的动态类型,代表当前遵循/继承的类型本身,在协议、类、结构体、枚举中使用。
- 协议中:
Self表示遵循该协议的具体类型,用于约束方法的参数/返回值必须和遵循者类型一致,常用于协议的相等性判断、工厂方法等。 - 类中:
Self表示当前类的类型,在子类中会自动适配为子类的类型,可用于返回当前类的实例,确保子类调用时返回子类类型。
代码示例
// 1. 协议中使用Self
protocol Copyable {
// 方法返回值必须是遵循该协议的类型本身
func copy() -> Self
}
// 结构体实现协议,Self代表Person结构体
struct Person: Copyable {
var name: String
func copy() -> Self {
return Person(name: self.name)
}
}
// 类实现协议,必须用required初始化确保子类可返回Self
class Animal: Copyable {
var name: String
required init(name: String) {
self.name = name
}
func copy() -> Self {
return type(of: self).init(name: self.name)
}
}
class Dog: Animal {
var breed: String
required init(name: String) {
self.breed = "中华田园犬"
super.init(name: name)
}
}
// 使用
let person = Person(name: "张三")
let copiedPerson = person.copy()
print(type(of: copiedPerson)) // Person
let dog = Dog(name: "旺财")
let copiedDog = dog.copy()
print(type(of: copiedDog)) // Dog,返回子类类型,而非Animal
// 2. 类方法中使用Self
class BaseClass {
class func createInstance() -> Self {
return self.init()
}
required init() {}
}
class SubClass: BaseClass {
var subProperty = 10
}
let subInstance = SubClass.createInstance()
print(type(of: subInstance)) // SubClass,返回子类类型
1.2.28 动态类型和多方法
重点说明
Swift是静态类型语言,但支持动态类型获取,通过type(of:)方法获取实例的动态运行时类型,而非静态声明类型。多方法(方法重载)是静态派发,根据参数的静态类型决定调用哪个方法;而类的继承重写是动态派发,根据实例的动态类型决定调用哪个方法。可通过type(of:)实现动态类型判断和处理。
代码示例
// 1. type(of:)获取动态类型
class Animal {
func say() {
print("动物叫")
}
}
class Cat: Animal {
override func say() {
print("喵")
}
}
class Dog: Animal {
override func say() {
print("汪")
}
}
// 静态类型是Animal,动态类型是Cat
let animal: Animal = Cat()
print("静态类型:Animal")
print("动态类型:\(type(of: animal))") // Cat
animal.say() // 喵,动态派发,调用动态类型的方法
// 2. 多方法的静态派发
func printType(_ animal: Animal) {
print("传入的是Animal类型")
}
func printType(_ cat: Cat) {
print("传入的是Cat类型")
}
let cat: Animal = Cat()
printType(cat) // 输出"传入的是Animal类型",根据静态类型派发
let realCat = Cat()
printType(realCat) // 输出"传入的是Cat类型"
// 3. 根据动态类型处理
func handleAnimal(_ animal: Animal) {
switch type(of: animal) {
case is Cat.Type:
print("处理猫")
case is Dog.Type:
print("处理狗")
default:
print("处理其他动物")
}
}
handleAnimal(Dog()) // 处理狗
1.2.29 属性观察
重点说明
属性观察用于监听和响应属性值的变化,可给存储属性添加willSet和didSet两个观察器:
willSet:在属性值即将被设置时调用,可通过newValue获取即将设置的新值,此时旧值还未改变。didSet:在属性值已经被设置后调用,可通过oldValue获取设置前的旧值,此时新值已生效,可在didSet中修改属性值(不会再次触发观察器)。 属性观察不能用于lazy存储属性、计算属性,可用于继承的属性重写时添加。
代码示例
class User {
// 带属性观察的存储属性
var username: String {
willSet {
print("即将将用户名从\(username)修改为\(newValue)")
}
didSet {
print("用户名已从\(oldValue)修改为\(username)")
// 可在didSet中修正值,不会再次触发观察器
if username.isEmpty {
username = "默认用户名"
}
}
}
var age: Int {
didSet {
// 限制年龄范围
age = max(0, min(120, age))
}
}
init(username: String, age: Int) {
self.username = username
self.age = age
}
}
// 使用
let user = User(username: "张三", age: 25)
user.username = "李四"
// 输出:
// 即将将用户名从张三修改为李四
// 用户名已从张三修改为李四
user.age = 150
print(user.age) // 120,被didSet修正
user.age = -5
print(user.age) // 0
user.username = ""
print(user.username) // 默认用户名
1.2.30 final
重点说明
final修饰符用于限制类、方法、属性的继承和重写,可用于类、类的方法、属性、下标。
- 修饰类:
final class,该类不能被任何子类继承,无法作为父类,可提高编译性能,避免意外继承。 - 修饰类的方法/属性/下标:
final func、final var,子类不能重写该方法/属性/下标,确保父类的实现不会被子类修改,保证代码的安全性和稳定性。
代码示例
// 1. final修饰的类,不能被继承
final class FinalClass {
func doSomething() {
print("final类的方法")
}
}
// 编译报错:Cannot inherit from final class 'FinalClass'
// class SubClass: FinalClass {}
// 2. final修饰方法和属性,禁止子类重写
class Person {
final let species = "人类" // final属性,不能被子类重写
var name: String
init(name: String) {
self.name = name
}
final func greet() { // final方法,不能被子类重写
print("你好,我是\(name)")
}
}
class Student: Person {
var studentID: Int
init(name: String, studentID: Int) {
self.studentID = studentID
super.init(name: name)
}
// 编译报错:Cannot override a final method
// override func greet() {}
// 编译报错:Cannot override a final property
// override let species = "学生"
}
// 使用
let student = Student(name: "张三", studentID: 1001)
student.greet() // 调用父类的final方法
1.2.31 lazy 修饰符和 lazy 方法
重点说明
lazy(懒加载)用于延迟存储属性的初始化,只有当属性第一次被访问时,才会执行初始化代码并赋值,之后不会再次执行初始化。lazy属性必须用var声明,不能用let,因为初始化时机不是在实例初始化时完成。适合初始化开销大、不一定会用到的属性,非线程安全,多线程同时第一次访问可能会多次初始化。
代码示例
// 1. lazy存储属性基础使用
class DataManager {
// 懒加载属性,第一次访问时才初始化
lazy var bigDataArray: [String] = {
print("正在初始化大数据数组")
var array: [String] = []
for i in 0..<1000 {
array.append("数据\(i)")
}
return array
}()
// 懒加载属性,也可调用方法初始化
lazy var config: [String: Any] = loadConfig()
private func loadConfig() -> [String: Any] {
print("正在加载配置")
return ["version": "1.0.0", "env": "prod"]
}
}
// 使用
let manager = DataManager()
// 实例初始化完成,lazy属性还未初始化,无打印输出
print("实例已创建")
// 第一次访问,执行初始化,打印"正在初始化大数据数组"
print(manager.bigDataArray.count) // 1000
// 第二次访问,直接使用已初始化的值,不会再次执行初始化
print(manager.bigDataArray.count) // 1000
// 访问config,执行loadConfig方法
print(manager.config["version"] ?? "未知")
1.2.32 Reflection 和 Mirror
重点说明
Swift通过Mirror类型实现反射(Reflection)功能,可在运行时获取实例的属性名、属性值、类型信息、继承关系等,无需提前知道类型结构。Mirror常用于调试、模型转字典、JSON解析、动态获取对象信息等场景,Swift的反射是只读的,不能通过反射修改属性值。
代码示例
// 定义模型
struct User {
let userID: String
var name: String
var age: Int
private var isVip: Bool
}
// 通过Mirror获取实例的反射信息
let user = User(userID: "user_123", name: "张三", age: 25, isVip: true)
let mirror = Mirror(reflecting: user)
// 1. 获取类型信息
print("类型:\(mirror.subjectType)") // User
print("类型分类:\(mirror.displayStyle!)") // struct
// 2. 遍历所有属性和值
print("\n属性列表:")
for case let (label?, value) in mirror.children {
print("\(label):\(value)")
}
// 输出:
// userID:user_123
// name:张三
// age:25
// isVip:true
// 3. 模型转字典
func convertToDictionary(from object: Any) -> [String: Any] {
let mirror = Mirror(reflecting: object)
var dict: [String: Any] = [:]
for case let (label?, value) in mirror.children {
dict[label] = value
}
return dict
}
let userDict = convertToDictionary(from: user)
print("\n转字典结果:\(userDict)")
// 4. 递归获取继承的属性(类类型)
class Person {
var name: String = ""
var age: Int = 0
}
class Student: Person {
var studentID: Int = 0
}
let student = Student()
student.name = "李四"
student.age = 20
student.studentID = 1001
func getAllProperties(_ object: Any) -> [String: Any] {
var result: [String: Any] = [:]
var mirror: Mirror? = Mirror(reflecting: object)
while let currentMirror = mirror {
for case let (label?, value) in currentMirror.children {
result[label] = value
}
mirror = currentMirror.superclassMirror // 获取父类的Mirror
}
return result
}
print("\n学生所有属性:\(getAllProperties(student))")
// 包含studentID、name、age
1.2.33 隐式解包 Optional
重点说明
隐式解包可选类型(Implicitly Unwrapped Optionals),在类型后加!声明,是一种特殊的可选类型。声明时告诉编译器,该值在使用时一定会有值,无需手动解包,访问时会自动解包。若访问时值为nil,会直接崩溃。常用于初始化流程中无法立即赋值,但之后一定会赋值的属性(如IBOutlet),避免频繁解包,非必要不使用。
代码示例
// 1. 隐式解包可选类型声明
var username: String! = nil
// 赋值后,访问时自动解包,无需?或!
username = "张三"
print(username.count) // 2,自动解包,无需手动解包
print(type(of: username)) // Optional<String>,本质还是可选类型
// 2. 常见场景:IBOutlet
// @IBOutlet weak var titleLabel: UILabel!
// 初始化时为nil,xib/storyboard加载后会赋值,之后使用时无需解包
// 3. 风险:值为nil时访问直接崩溃
var age: Int! = nil
// print(age + 1) // 运行时崩溃,Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
// 4. 依然可以用可选绑定、可选链式调用处理
var address: String! = nil
if let address = address {
print(address)
} else {
print("地址为空")
}
address?.append("北京市") // 为nil时,链式调用返回nil,不会崩溃
1.2.34 多重 Optional
重点说明
多重可选类型,即可选类型的嵌套,一个可选值的包装值还是可选类型,如String??、Int???等。每一层?代表一层可选包装,可能存在多层nil的情况,直接解包需要逐层处理,常用于可选链式调用、字典取值、函数返回值等场景,需注意区分不同层级的nil。
代码示例
// 1. 基础多重可选类型
let optionalString: String? = "Swift"
let doubleOptional: String?? = optionalString // 双重可选
let tripleOptional: String??? = doubleOptional // 三重可选
print(doubleOptional as Any) // Optional(Optional("Swift"))
print(tripleOptional as Any) // Optional(Optional(Optional("Swift")))
// 2. 多重可选的nil情况
var a: String? = nil
var b: String?? = a // 内层为nil,外层有包装
var c: String?? = nil // 外层直接为nil
print(a as Any) // nil
print(b as Any) // Optional(nil)
print(c as Any) // nil
// 3. 解包多重可选,需要逐层解包
if let firstLevel = b {
if let secondLevel = firstLevel {
print(secondLevel)
} else {
print("内层为nil")
}
} else {
print("外层为nil")
}
// 输出:内层为nil
// 4. 可选绑定一次性解包多层
if let value = tripleOptional {
print(value)
}
// 5. 常见场景:字典中存储可选值
let dict: [String: String?] = ["name": "张三", "address": nil]
let name = dict["name"] // String??类型,双重可选
// 第一层:字典取值是否存在key,第二层:key对应的value是否为nil
if let nameValue = dict["name"], let realName = nameValue {
print(realName) // 张三
}
1.2.35 Optional Map
重点说明
Optional的map方法,用于对可选值进行转换:当可选值有值时,执行闭包中的转换逻辑,返回包装后的新值;当可选值为nil时,直接返回nil,不会执行闭包。相比可选绑定,map可更简洁地处理可选值的转换,无需手动解包;flatMap方法用于转换后依然是可选值的场景,避免生成多重可选。
代码示例
// 1. map基础使用
let numberString: String? = "123"
// 可选值有值时,执行转换,返回Int?
let number = numberString.map { Int($0) }
print(type(of: number)) // Int??,map返回的是包装后的可选值,这里转换后还是Int?,所以变成双重可选
// 2. flatMap处理转换后依然是可选值的场景,扁平化处理
let flatNumber = numberString.flatMap { Int($0) }
print(type(of: flatNumber)) // Int?,flatMap自动解包,避免多重可选
// 3. 对比可选绑定,map更简洁
let input: Int? = 5
// 可选绑定写法
var squared: Int?
if let num = input {
squared = num * num
}
// map写法
let squaredMap = input.map { $0 * $0 }
print(squaredMap ?? 0) // 25
// 4. nil时的处理
let nilInput: Int? = nil
let nilResult = nilInput.map { $0 * 10 }
print(nilResult as Any) // nil,闭包不会执行
// 5. 链式调用
let priceString: String? = "99.9"
let finalPrice = priceString
.flatMap { Double($0) }
.map { $0 * 0.8 } // 打8折
.map { String(format: "%.2f", $0) }
print(finalPrice ?? "无效价格") // 79.92
1.2.36 Protocol Extension(协议扩展)
重点说明
协议扩展是Swift的核心特性,可给协议提供默认的方法实现、计算属性实现,遵循该协议的类型无需自己实现,自动获得默认实现;也可给协议扩展添加额外的方法,所有遵循者都可使用。可通过where子句给扩展添加约束,只给满足条件的遵循者提供实现,实现面向协议编程。
代码示例
// 定义协议
protocol Greetable {
var name: String { get }
func greet() // 协议方法
}
// 协议扩展,提供默认实现
extension Greetable {
// 协议方法的默认实现
func greet() {
print("你好,我是\(name)")
}
// 扩展添加额外的方法,所有遵循者都可使用
func greetWithTitle(_ title: String) {
print("\(title) \(name),向你问好")
}
}
// 遵循协议,无需实现greet方法,自动获得默认实现
struct Person: Greetable {
var name: String
}
struct Student: Greetable {
var name: String
var studentID: Int
// 重写协议方法,覆盖默认实现
func greet() {
print("你好,我是学生\(name),学号\(studentID)")
}
}
// 使用
let person = Person(name: "张三")
person.greet() // 你好,我是张三,使用默认实现
person.greetWithTitle("先生") // 先生 张三,向你问好
let student = Student(name: "李四", studentID: 1001)
student.greet() // 你好,我是学生李四,学号1001,使用重写的实现
// 带where约束的协议扩展
extension Greetable where Self: Student {
func greetToTeacher() {
print("老师好,我是\(name)")
}
}
student.greetToTeacher() // 只有Student类型可调用
// person.greetToTeacher() // 编译报错,Person不满足约束
1.2.37 where 和模式匹配
重点说明
where子句可用于模式匹配中,给匹配添加额外的约束条件,过滤不符合条件的情况,让模式匹配更灵活精准。可用于switch、if case、for case、guard case、协议扩展、泛型约束等场景,支持多个条件组合,简化复杂的条件判断代码。
代码示例
// 1. switch中使用where添加约束
let number = 15
switch number {
case let n where n % 2 == 0 && n > 10:
print("大于10的偶数")
case let n where n % 2 == 1 && n > 10:
print("大于10的奇数")
default:
print("小于等于10")
}
// 输出:大于10的奇数
// 2. for case + where,遍历过滤
let scores = [59, 85, 92, 76, 45, 98]
// 只遍历大于80的分数
for case let score in scores where score > 80 {
print("高分:\(score)")
}
// 输出85、92、98
// 3. if case + where
let age: Int? = 22
if case let a? in age where a >= 18 && a <= 30 {
print("年龄在18-30之间,符合要求")
}
// 4. 元组匹配 + where
let userInfo = (name: "张三", age: 25, isVip: true)
switch userInfo {
case let (name, age, _) where age < 18:
print("\(name)是未成年人")
case let (name, _, isVip) where isVip:
print("\(name)是VIP用户")
default:
print("普通用户")
}
// 5. 泛型约束中使用where
func compare<T: Equatable>(_ a: T, _ b: T) -> Bool where T: Comparable {
return a > b
}
1.2.38 indirect 和嵌套 enum
重点说明
- 嵌套enum:Swift支持在枚举、结构体、类中嵌套定义枚举,将相关的枚举封装在所属的类型中,避免命名冲突,优化代码结构,提高代码的封装性。
- indirect:用于修饰枚举的case或整个枚举,告诉编译器该枚举case包含递归的关联值(即关联值类型是枚举自身),需要添加一层间接引用,解决递归枚举的内存布局问题,常用于树形结构、链表、表达式等递归数据结构。
代码示例
// 1. 嵌套枚举
class Order {
// 嵌套枚举,仅在Order类内部使用,也可开放访问
enum OrderStatus {
case pending
case paid
case shipped
case completed
case cancelled
}
enum PayType {
case wechat
case alipay
case card
}
var status: OrderStatus = .pending
var payType: PayType = .wechat
}
// 使用
let order = Order()
order.status = .paid
order.payType = .alipay
print(order.status) // paid
// 2. indirect修饰递归枚举,实现表达式计算
// 整个枚举添加indirect,所有case都支持递归
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
// 也可给单个case添加indirect
// enum ArithmeticExpression {
// case number(Int)
// indirect case addition(ArithmeticExpression, ArithmeticExpression)
// indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
// }
// 实现表达式计算
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case .number(let value):
return value
case .addition(let left, let right):
return evaluate(left) + evaluate(right)
case .multiplication(let left, let right):
return evaluate(left) * evaluate(right)
}
}
// 计算 (5 + 3) * 2
let five = ArithmeticExpression.number(5)
let three = ArithmeticExpression.number(3)
let sum = ArithmeticExpression.addition(five, three)
let two = ArithmeticExpression.number(2)
let result = ArithmeticExpression.multiplication(sum, two)
print(evaluate(result)) // 16
从Object-c/c 到Swift
1.3.1 Selector
重点说明
- Swift 中使用
#selector替代 Objective-C 的@selector关键字,用于将方法转换为Selector结构体类型,本质是 Objective-C 运行时的方法选择器。 - Swift 4 及以上版本默认关闭了对 NSObject 子类非私有方法的
@objc自动推断,必须手动为需要生成 selector 的方法添加@objc关键字,将方法暴露给 Objective-C 运行时。 - 私有方法、非 NSObject 子类的方法,无法自动生成 selector,必须手动标记
@objc,否则会出现运行时unrecognized selector崩溃。 - 同一作用域内存在同名但参数不同的方法时,需通过强制类型转换为对应函数签名,消除 selector 的歧义。
代码示例
import UIKit
class MyClass: NSObject {
// 标记@objc暴露给OC运行时
@objc func callMe() {
print("Hi")
}
@objc func callMeWithParam(obj: AnyObject!) {
print("带参数的方法")
}
// 同名不同参数的方法
@objc func commonFunc() {
print("无参方法")
}
@objc func commonFunc(input: Int) -> Int {
return input
}
}
// 基础selector生成
let someMethod = #selector(MyClass.callMe)
let anotherMethod = #selector(MyClass.callMeWithParam(obj:))
// 多参数selector生成
@objc func turn(by angle: Int, speed: Float) {}
let method = #selector(turn(by:speed:))
// 消除同名方法的selector歧义
let method1 = #selector(MyClass.commonFunc as ()->())
let method2 = #selector(MyClass.commonFunc as (Int)->Int)
// 定时器中使用selector示例
let object = MyClass()
Timer.scheduledTimer(timeInterval: 1, target: object, selector: #selector(MyClass.callMe), userInfo: nil, repeats: true)
1.3.2 实例方法的动态调用
重点说明
- Swift 支持通过类型名.实例方法的语法,获取该实例方法的柯里化函数,实现实例方法的动态调用。
- 该语法生成的柯里化函数格式为:
(实例类型) -> (方法参数) -> 返回值,先传入实例对象,再传入方法参数,即可完成方法调用。 - 该特性仅适用于实例方法,属性的 getter/setter 无法使用此方式调用。
- 当类方法与实例方法重名时,可通过显式声明函数类型,区分获取的是类方法还是实例方法的柯里化函数。
代码示例
class MyClass {
func method(number: Int) -> Int {
return number + 1
}
// 与实例方法重名的类方法
class func method(number: Int) -> Int {
return number
}
}
// 常规实例方法调用
let object = MyClass()
let result1 = object.method(number: 1)
print(result1) // 输出2
// 动态获取实例方法的柯里化函数
let methodFunc: (MyClass) -> (Int) -> Int = MyClass.method
let result2 = methodFunc(object)(1)
print(result2) // 输出2
// 区分重名的类方法与实例方法
// 获取类方法
let classMethod = MyClass.method
// 获取实例方法
let instanceMethod: (MyClass) -> (Int) -> Int = MyClass.method
1.3.3 单例
重点说明
- Swift 1.2 之后支持静态类变量,提供了极简的单例实现方式,底层会通过
swift_once保证初始化的线程安全,与 GCD 的dispatch_once效果一致。 - 标准单例实现需添加私有初始化方法,防止外部通过
init()创建新实例,保证单例的唯一性。 - 若需要支持子类继承,可移除私有初始化方法;若需要类似
default形式的单例(允许外部创建实例),也可去掉私有init。 - Swift 1.2 之前的全局变量、嵌套结构体等实现方式已被淘汰,不推荐使用。
代码示例
// Swift 推荐的标准单例实现
class MyManager {
// 静态常量,保证线程安全的唯一实例
static let shared = MyManager()
// 私有初始化方法,禁止外部创建实例
private init() {}
// 单例的业务方法
func doSomething() {
print("单例方法执行")
}
}
// 单例使用
MyManager.shared.doSomething()
// 允许继承的单例实现
class BaseManager {
class var shared: BaseManager {
struct Static {
static let instance = BaseManager()
}
return Static.instance
}
init() {}
}
class ChildManager: BaseManager {
override class var shared: BaseManager {
struct Static {
static let instance = ChildManager()
}
return Static.instance
}
}
1.3.4 条件编译
重点说明
- Swift 中使用
#if/#elseif/#else/#endif实现条件编译,替代 C 系语言的宏定义条件分支。 - 内置了平台、架构、Swift 版本三类条件判断参数,大小写敏感,分别为:
- 平台:
os(),可选值macOS/iOS/tvOS/watchOS/Linux - 架构:
arch(),可选值x86_64/arm/arm64/i386 - Swift 版本:
swift(),格式为>= 版本号
- 平台:
- 支持自定义编译符号,需在项目
Build Settings-Other Swift Flags中添加-D 符号名启用。 - 条件编译仅能针对编译时可确定的符号,无法使用运行时的变量进行判断。
代码示例
import UIKit
// 平台条件编译
#if os(macOS)
import AppKit
typealias Color = NSColor
#elseif os(iOS)
import UIKit
typealias Color = UIColor
#endif
// 架构条件编译
#if arch(arm64)
print("64位ARM架构")
#elseif arch(x86_64)
print("64位x86架构")
#endif
// Swift版本条件编译
#if swift(>=5.0)
print("Swift 5.0及以上版本")
#else
print("Swift 5.0以下版本")
#endif
// 自定义编译符号条件编译
#if FREE_VERSION
// 免费版功能代码
print("免费版本")
#else
// 付费版功能代码
print("付费版本")
#endif
1.3.5 编译标记
重点说明
- Swift 中使用
// MARK:替代 Objective-C 的#pragma mark,用于在 Xcode 导航栏中对代码进行分块标记,提升代码可读性。 // MARK: -会在标记位置添加分隔线,更清晰地区分代码模块。- 支持
// TODO:和// FIXME:标记,分别用于标记待完成的工作和待修复的问题,会在 Xcode 导航栏中同步显示。 - Swift 暂不支持 Objective-C 中的
#warning编译警告标记,无法在编译时生成自定义警告。
代码示例
import UIKit
class SimpleTimer: NSObject {
// MARK: 公共属性
var running: Bool = false
var leftTime: TimeInterval = 0
var leftTimeString: String {
return String(format: "%.1f", leftTime)
}
// MARK: 初始化方法
public init(timeInterval: TimeInterval) {
self.leftTime = timeInterval
super.init()
}
// MARK: 计时器控制方法
public func start(updateTick: (TimeInterval) -> Void, stopHandler: () -> Void) {
// 计时器启动逻辑
}
public func stop() {
// 计时器停止逻辑
}
// MARK: 内部私有方法
private func countTick() {
// 计时逻辑
// TODO: 优化计时精度
// FIXME: 修复负数时间异常
}
}
1.3.6 @UIApplicationMain
重点说明
@UIApplicationMain标记用于修饰 AppDelegate 类,替代 Objective-C 中的main.m入口文件,编译器会自动为其生成 main 函数,作为 iOS 应用的程序入口。- 该标记的核心作用是:创建 UIApplication 单例、设置 AppDelegate 为应用的生命周期委托、启动应用的主 Runloop。
- 若需要自定义 UIApplication 的子类,需移除
@UIApplicationMain标记,手动创建main.swift文件,在其中编写程序入口代码。 main.swift是 Swift 项目中特殊的文件,无需定义作用域,可直接书写执行代码,其内容会被作为 main 函数执行。
代码示例
// 1. 默认使用@UIApplicationMain的AppDelegate
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
return true
}
}
// 2. 自定义UIApplication子类,移除@UIApplicationMain,创建main.swift
// main.swift 文件内容
import UIKit
// 自定义UIApplication子类
class MyApplication: UIApplication {
override func sendEvent(_ event: UIEvent) {
super.sendEvent(event)
print("事件发送: \(event)")
}
}
// 程序入口,等价于main函数
UIApplicationMain(
CommandLine.argc,
CommandLine.unsafeArgv,
NSStringFromClass(MyApplication.self),
NSStringFromClass(AppDelegate.self)
)
1.3.7 @objc 和 dynamic
重点说明
@objc用于将 Swift 类型、方法、属性暴露给 Objective-C 运行时,编译器会为其生成 Objective-C 可识别的符号。- NSObject 的子类,Swift 不会自动为私有方法、属性添加
@objc,必须手动标记;Swift 4 及以上版本,即使是 NSObject 子类的公开方法,也需要手动添加@objc才能暴露给 OC。 @objc(OC名称)可重命名 Swift 类型/方法在 Objective-C 中的名称,解决中文命名、方法名冲突等问题。dynamic用于强制开启 Swift 方法的 Objective-C 动态派发,替代 OC 运行时的消息转发机制。仅添加@objc不会开启动态派发,编译器仍可能将其优化为静态调用。- KVO、Runtime 方法交换、动态方法解析等特性,必须依赖
dynamic开启的动态派发才能实现。
代码示例
import UIKit
// 重命名Swift类在OC中的名称
@objc(MyClassOC)
class 我的类: NSObject {
var num = 1
// 重命名Swift方法在OC中的名称
@objc(greeting:)
func 打招呼(名字: String) {
print("哈喽,\(名字)")
}
// 开启动态派发,支持KVO/方法交换
@objc dynamic var date = Date()
}
// OC中调用方式:
// [[MyClassOC new] greeting:@"小明"];
// 仅@objc不开启动态派发,dynamic强制动态派发
class KVOObject: NSObject {
// 必须标记@objc dynamic才能支持KVO
@objc dynamic var value: Int = 0
}
1.3.8 可选协议和协议扩展
重点说明
- 原生 Swift 协议默认要求所有方法、属性必须被实现,无可选概念;有两种方式实现可选协议:
- 方式一:为协议和可选方法添加
@objc,使用optional关键字标记可选方法,仅能被 class 实现,struct/enum 无法使用。 - 方式二:通过协议扩展为协议方法提供默认实现,实现类可选择性重写,是 Swift 推荐的纯原生实现方式,支持值类型。
- 方式一:为协议和可选方法添加
- 协议扩展中实现了协议未定义的方法时,调用规则为:
- 实例类型被推断为协议类型时,永远调用协议扩展中的默认实现。
- 实例类型被推断为实际实现类型时,优先调用类型中的实现,无实现则调用协议扩展默认实现。
- 协议中定义的方法,会通过动态派发调用类型中的重写实现,无论实例被推断为协议类型还是实际类型。
代码示例
import UIKit
// 方式一:@objc + optional 实现可选协议
@objc protocol OptionalProtocolOC {
@objc optional func optionalMethod() // 可选
func necessaryMethod() // 必须实现
@objc optional func anotherOptionalMethod() // 可选
}
// 实现类
class ClassOC: NSObject, OptionalProtocolOC {
// 仅实现必须方法,可选方法可选择不实现
func necessaryMethod() {
print("必须实现的方法")
}
}
// 方式二:协议扩展实现可选方法(推荐)
protocol OptionalProtocol {
func optionalMethod() // 可选
func necessaryMethod() // 必须
func anotherOptionalMethod() // 可选
}
// 协议扩展提供默认实现
extension OptionalProtocol {
func optionalMethod() {
print("协议扩展中的默认实现")
}
func anotherOptionalMethod() {
print("协议扩展中的默认实现")
}
}
// 实现类,仅需实现必须方法
class MyStruct: OptionalProtocol {
func necessaryMethod() {
print("结构体中实现的必须方法")
}
// 选择性重写可选方法
func optionalMethod() {
print("结构体中重写的可选方法")
}
}
// 调用测试
let obj = MyStruct()
obj.necessaryMethod() // 结构体中实现的必须方法
obj.optionalMethod() // 结构体中重写的可选方法
obj.anotherOptionalMethod() // 协议扩展中的默认实现
// 类型推断为协议时的调用差异
let proto: OptionalProtocol = MyStruct()
proto.anotherOptionalMethod() // 协议扩展中的默认实现(协议未定义该方法)
proto.optionalMethod() // 结构体中重写的方法(协议中定义了该方法)
1.3.9 内存管理,weak 和 unowned
重点说明
- Swift 基于自动引用计数(ARC)管理内存,当对象无任何强引用时,内存会被自动回收;循环引用是 ARC 最核心的问题,需通过
weak和unowned打破循环。 weak:弱引用,不会增加对象的引用计数,当引用的对象被释放后,会自动被置为nil,因此必须声明为可选类型。适用于引用对象生命周期可能短于当前对象的场景,如 delegate、闭包中对 self 的引用。unowned:无主引用,不会增加对象的引用计数,引用的对象被释放后,不会被置为nil,仍会保留无效的内存地址。访问已释放的 unowned 引用会直接崩溃,类似 OC 的unsafe_unretained。适用于引用对象与当前对象生命周期一致的场景。- 闭包中对 self 的引用会自动持有,若 self 直接/间接持有闭包,会形成
self->闭包->self的循环引用,需通过闭包捕获列表[weak self]/[unowned self]解决。
代码示例
import UIKit
// 循环引用示例
class A: NSObject {
var b: B!
override init() {
super.init()
b = B()
b.a = self
}
deinit {
print("A deinit")
}
}
class B: NSObject {
// 用weak打破循环引用
weak var a: A?
deinit {
print("B deinit")
}
}
// 测试:无循环引用,对象正常释放
var obj: A? = A()
obj = nil
// 输出:A deinit、B deinit
// 闭包循环引用示例
class Person {
let name: String
lazy var printName: ()->() = {
// 捕获列表声明weak self
[weak self] in
guard let self = self else { return }
print("The name is \(self.name)")
}
init(personName: String) {
name = personName
}
deinit {
print("Person deinit \(self.name)")
}
}
// 测试:闭包无循环引用,对象正常释放
var xiaoMing: Person? = Person(personName: "XiaoMing")
xiaoMing!.printName()
xiaoMing = nil
// 输出:The name is XiaoMing、Person deinit XiaoMing
// unowned使用示例:确保引用对象生命周期一致
class Student {
unowned let card: StudentCard
init(card: StudentCard) {
self.card = card
}
}
class StudentCard {
let student: Student
init() {
self.student = Student(card: self)
}
}
1.3.10 @autoreleasepool
重点说明
- Swift 中可通过
autoreleasepool函数创建自动释放池,替代 Objective-C 的@autoreleasepool {}语法,用于管理 autorelease 对象的生命周期。 - 自动释放池会在作用域结束时,对池内的 autorelease 对象统一发送 release 消息,减少内存峰值,避免循环中大量 autorelease 对象堆积导致内存溢出。
- Swift 中推荐使用初始化方法替代类工厂方法,减少 autorelease 对象的生成;仅在循环中生成大量临时对象时,需要手动添加自动释放池。
autoreleasepool函数接受一个无返回值的闭包,利用尾随闭包语法可简化书写。
代码示例
import UIKit
// 循环中大量生成autorelease对象,手动添加自动释放池
func loadBigData() {
guard let path = Bundle.main.path(forResource: "big", ofType: "jpg") else {
return
}
for _ in 1...10000 {
// 每次循环创建自动释放池,及时释放内存
autoreleasepool {
let data = NSData(contentsOfFile: path)
// 数据处理逻辑
Thread.sleep(forTimeInterval: 0.001)
}
}
}
// 折衷方案:每10次循环释放一次,平衡性能与内存
func loadBigDataOptimized() {
guard let path = Bundle.main.path(forResource: "big", ofType: "jpg") else {
return
}
for i in 1...10000 {
if i % 10 == 0 {
autoreleasepool {
let data = NSData(contentsOfFile: path)
Thread.sleep(forTimeInterval: 0.001)
}
} else {
let data = NSData(contentsOfFile: path)
Thread.sleep(forTimeInterval: 0.001)
}
}
}
1.3.11 值类型和引用类型
重点说明
- Swift 中类型分为两大类:
- 值类型:
struct/enum,包括 Swift 所有内建类型(Int/String/Array/Dictionary 等),赋值、传参时会进行值拷贝,内存分配在栈上。 - 引用类型:
class,赋值、传参时仅传递引用指针,内存分配在堆上。
- 值类型:
- Swift 对值类型的拷贝做了优化,采用**写时复制(Copy-On-Write, COW)**机制:仅当值类型的内容发生修改时,才会执行真正的内存拷贝,未修改时多个变量共享同一块内存,大幅降低拷贝开销。
- 值类型的优势:线程安全、无循环引用风险、栈内存分配效率远高于堆,适合轻量数据模型、集合类型。
- 引用类型的优势:支持继承、共享状态、适合生命周期复杂的对象;当数据量极大、频繁修改时,引用类型可避免大量拷贝开销。
代码示例
import UIKit
// 值类型:struct,赋值时拷贝
struct User {
var name: String
var age: Int
}
var user1 = User(name: "张三", age: 20)
var user2 = user1 // 写时复制,此时共享内存
user2.name = "李四" // 修改时才执行真正拷贝
print(user1.name) // 张三
print(user2.name) // 李四
// 数组的写时复制特性
var array1 = [1,2,3,4,5]
var array2 = array1 // 共享内存,无拷贝
print(array1.baseAddress == array2.baseAddress) // true
array2.append(6) // 修改时拷贝
print(array1.baseAddress == array2.baseAddress) // false
// 引用类型:class,赋值时仅传递引用
class Person {
var name: String
var age: Int
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
var p1 = Person(name: "张三", age: 20)
var p2 = p1 // 传递引用,指向同一块堆内存
p2.name = "李四"
print(p1.name) // 李四
print(p2.name) // 李四
1.3.12 String 还是 NSString
重点说明
- Swift 原生
String与 Foundation 的NSString可无缝桥接转换,推荐优先使用原生String类型。 - 原生
String的优势:- 是值类型,赋值、传参时遵循写时复制,线程安全。
- 实现了
Collection协议,支持for...in遍历、序列相关操作,NSString不支持。 - 基于 Unicode 正确处理字符,避免
NSString按 UTF-16 编码计算长度导致的字符处理错误。
NSString的适用场景:- 使用部分
NSString特有 API,如contains(_:)(Swift 3 后 String 已原生支持)、substring(with:)、与 C 字符串交互等。 - 与 Objective-C API 交互时,需显式桥接为
NSString。
- 使用部分
- Swift 中
String与NSRange配合使用较为繁琐,需转换为String.Index,简单场景可先桥接为NSString简化操作。
代码示例
import UIKit
let str = "Hello Swift"
let nsStr = str as NSString
// String原生遍历(NSString不支持for...in)
for char in str {
print(char)
}
// String与NSRange转换
let levels = "ABCDE"
// 原生String方式
let start = levels.index(levels.startIndex, offsetBy: 1)
let end = levels.index(levels.startIndex, offsetBy: 3)
let swiftRange = start..<end
let swiftResult = levels.replacingCharacters(in: swiftRange, with: "12")
print(swiftResult) // A12DE
// 桥接为NSString简化NSRange操作
let nsRange = NSMakeRange(1, 2)
let nsResult = (levels as NSString).replacingCharacters(in: nsRange, with: "12")
print(nsResult) // A12DE
// 字符串包含判断
print(levels.contains("BC")) // true,Swift原生支持
print((levels as NSString).contains("BC")) // true,NSString API
1.3.13 UnsafePointer
重点说明
- Swift 中通过
UnsafePointer<T>/UnsafeMutablePointer<T>对应 C 中的const Type */Type *指针,提供对 C 指针的类型安全访问。 - 指针类型前缀说明:
UnsafePointer:不可变指针,对应 C 常量指针,无法修改指向内存的内容。UnsafeMutablePointer:可变指针,对应 C 普通指针,可修改指向内存的内容。pointee属性:用于访问/修改指针指向的内存内容,替代 C 中的*解引用操作。
- 传入指针参数时,在变量前添加
&符号,与 C 语法一致,可将 Swift 变量转换为对应类型的指针。 unsafeBitCast可强制按位转换指针类型,完全绕过编译器类型检查,属于极度不安全的操作,仅在明确内存布局时使用。- Swift 官方将指针类型冠以
Unsafe前缀,明确提示其不安全性,非必要场景应避免直接操作指针。
代码示例
import UIKit
// C函数示例:void method(const int *num) { printf("%d",*num); }
// Swift中对应的函数声明
func method(_ num: UnsafePointer<CInt>) {
print(num.pointee)
}
// 基础指针使用
var a: CInt = 123
method(&a) // 输出123
// 可变指针修改内存内容
func modifyValue(_ num: UnsafeMutablePointer<CInt>) {
num.pointee += 100
}
var b: CInt = 50
modifyValue(&b)
print(b) // 150
// unsafeBitCast强制类型转换
let arr = NSArray(object: "meow")
let str = unsafeBitCast(CFArrayGetValueAtIndex(arr, 0), to: CFString.self)
print(str) // meow
1.3.14 C 指针内存管理
重点说明
- Swift 中手动创建的指针,无法被 ARC 自动管理内存,需手动申请、释放内存,遵循谁创建谁释放的原则。
- 指针内存操作的完整流程:
allocate(capacity:):申请指定容量的内存空间。initialize(to:):初始化内存,为其赋值。- 访问/修改
pointee属性,操作内存内容。 deinitialize():反初始化内存,销毁内存中的对象。deallocate(capacity:):释放内存空间,与allocate成对出现。
- 若通过
malloc/calloc申请内存,需使用free释放,不可使用deallocate。 - 访问已释放的指针、重复释放内存,都会导致程序崩溃,与 C 语言手动内存管理的风险一致。
代码示例
import UIKit
class MyClass {
var a = 1
deinit {
print("deinit")
}
}
// 完整的指针内存管理流程
// 1. 申请内存
let pointer = UnsafeMutablePointer<MyClass>.allocate(capacity: 1)
// 2. 初始化内存
pointer.initialize(to: MyClass())
// 3. 访问内存内容
print(pointer.pointee.a) // 1
// 4. 反初始化
pointer.deinitialize(count: 1)
// 5. 释放内存
pointer.deallocate(capacity: 1)
// 错误示例:内存泄漏,未释放内存
let leakPointer = UnsafeMutablePointer<MyClass>.allocate(capacity: 1)
leakPointer.initialize(to: MyClass())
// 未调用deinitialize和deallocate,对象无法释放,内存泄漏
// malloc/free 内存管理
let mallocPointer = malloc(MemoryLayout<Int>.size)!
let intPointer = mallocPointer.bindMemory(to: Int.self, capacity: 1)
intPointer.pointee = 100
print(intPointer.pointee) // 100
free(mallocPointer) // 对应malloc使用free释放
1.3.15 COpaquePointer 和 C convention
重点说明
COpaquePointer对应 C 中的不透明指针(Opaque Pointer),用于表示 Swift 无法描述具体类型的 C 指针,即 C 中仅声明类型名、未暴露实现细节的结构体指针。COpaquePointer可与具体的UnsafePointer<T>互相转换,用于不同指针类型间的强制转换,属于不安全操作。- C 中的函数指针,在 Swift 中通过
@convention(c)标记的闭包对应,可直接将 Swift 闭包传递给接受 C 函数指针的 API。 @convention(c)用于标记闭包遵循 C 函数调用约定,与 Swift 原生闭包区分,可与 C 函数指针无缝桥接。
代码示例
import UIKit
import Darwin
// COpaquePointer 与 UnsafePointer 转换
let intPtr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
intPtr.pointee = 100
// 转换为不透明指针
let opaquePtr = COpaquePointer(intPtr)
// 转回具体类型指针
let intPtr2 = UnsafeMutablePointer<Int>(opaquePtr)
print(intPtr2.pointee) // 100
intPtr.deallocate(capacity: 1)
// C函数指针与Swift闭包
// C函数示例:int cFunction(int (*callback)(int x, int y)) { return callback(1,2); }
// Swift中对应的函数声明
func cFunction(_ callback: @convention(c) (Int32, Int32) -> Int32) -> Int32 {
return callback(1, 2)
}
// 定义遵循C调用约定的闭包
let callback: @convention(c) (Int32, Int32) -> Int32 = { x, y in
return x + y
}
// 调用C函数,传入闭包
let result = cFunction(callback)
print(result) // 3
// 简化写法:直接传入尾随闭包
let result2 = cFunction { x, y in
return x * y
}
print(result2) // 2
1.3.16 GCD 和延时调用
重点说明
- Swift 3 及以上版本对 GCD 进行了面向对象的重构,替代了原有的 C 语言风格 API,使用更简洁。
- 核心队列类型
DispatchQueue,分为主队列(main,主线程执行,用于UI更新)、串行队列、并行队列,通过async/sync执行任务。 - 延时执行通过
asyncAfter(deadline:execute:)实现,替代 OC 中的dispatch_after,基于DispatchTime控制延时时间。 - 可通过
DispatchWorkItem封装任务,支持取消操作,实现可取消的延时执行。 - GCD 任务执行规则:UI 相关操作必须在主队列执行;耗时操作需放在后台串行/并行队列,避免阻塞主线程。
代码示例
import UIKit
// MARK: 基础GCD使用
// 创建串行队列
let workingQueue = DispatchQueue(label: "com.example.serial_queue")
// 异步执行耗时任务
workingQueue.async {
print("后台执行耗时任务", Thread.current)
Thread.sleep(forTimeInterval: 2) // 模拟耗时操作
// 回到主线程更新UI
DispatchQueue.main.async {
print("回到主线程更新UI", Thread.current)
}
}
// MARK: 基础延时执行
let delayTime: TimeInterval = 2.0
DispatchQueue.main.asyncAfter(deadline: .now() + delayTime) {
print("2秒后执行")
}
// MARK: 可取消的延时执行
typealias DelayTask = (_ cancel: Bool) -> Void
func delay(_ time: TimeInterval, task: @escaping ()->()) -> DelayTask? {
var closure: (()->Void)? = task
var result: DelayTask?
let delayedClosure: DelayTask = { cancel in
if let internalClosure = closure, !cancel {
DispatchQueue.main.async(execute: internalClosure)
}
closure = nil
result = nil
}
result = delayedClosure
DispatchQueue.main.asyncAfter(deadline: .now() + time) {
delayedClosure(false)
}
return result
}
// 取消任务的方法
func cancel(_ task: DelayTask?) {
task?(true)
}
// 使用示例
let task = delay(5) {
print("5秒后执行的任务")
}
// 提前取消任务
cancel(task)
1.3.17 获取对象类型
重点说明
- Swift 中通过
type(of:)函数获取对象的动态类型(运行时实际类型),替代 OC 中的class方法、Swift 旧版本的dynamicType属性。 type(of:)同时支持 Swift 原生类型、NSObject 子类、值类型(struct/enum),是获取类型的通用方法。- 对于类继承体系,
type(of:)会返回对象的实际子类类型,而非声明的父类类型,体现动态特性。 - 可通过
String(describing:)将类型转换为字符串,用于打印、日志输出;debugPrint会输出包含模块名的完整类型名称。
代码示例
import UIKit
// 原生Swift类型
let string = "Hello World"
let stringType = type(of: string)
print(stringType) // String
debugPrint(stringType) // Swift.String
// NSObject子类
let date = NSDate()
let dateType = type(of: date)
print(dateType) // __NSDate
// 继承体系的动态类型
class Animal {
func speak() {}
}
class Cat: Animal {
override func speak() {
print("Meow")
}
}
let animal: Animal = Cat()
print(type(of: animal)) // Cat,返回实际运行时类型,而非声明的Animal类型
// 值类型
struct Person {
var name: String
}
let person = Person(name: "小明")
print(type(of: person)) // Person
1.3.18 自省
重点说明
- 自省(Introspection)指在运行时判断对象是否属于某个类型/是否遵循某个协议,是面向对象编程的常用特性。
- Swift 中使用
is关键字判断对象是否属于某个类型(包括子类),功能等价于 OC 中的isKindOfClass:。 - 使用
as?/as!进行类型转换,as?为可选转换,失败返回 nil;as!为强制转换,失败直接崩溃,推荐优先使用as?+ 可选绑定。 is关键字不仅支持 class 类型,还可用于 struct、enum、protocol 等 Swift 所有类型,比 OC 的自省能力更全面。- 编译器会对编译时可确定的类型判断给出警告,避免无意义的类型检查。
代码示例
import UIKit
// 类型继承体系
protocol Runnable {
func run()
}
class Animal {
var name: String
init(name: String) {
self.name = name
}
}
class Dog: Animal, Runnable {
func run() {
print("\(name) 跑起来了")
}
}
class Cat: Animal {
func speak() {
print("Meow")
}
}
// 基础类型判断
let animal: Animal = Dog(name: "旺财")
print(animal is Dog) // true,属于Dog类
print(animal is Animal) // true,属于Animal父类
print(animal is Cat) // false,不属于Cat类
// 协议判断
print(animal is Runnable) // true,遵循Runnable协议
// 类型转换+可选绑定,安全使用子类方法
if let dog = animal as? Dog {
dog.run() // 旺财 跑起来了
}
// 强制类型转换,确定类型时使用
let dog = animal as! Dog
dog.run()
// 多类型分支判断
func checkAnimalType(_ animal: Animal) {
switch animal {
case let dog as Dog:
print("这是狗:\(dog.name)")
case let cat as Cat:
print("这是猫:\(cat.name)")
default:
print("未知动物")
}
}
checkAnimalType(Dog(name: "大黄")) // 这是狗:大黄
checkAnimalType(Cat(name: "咪酱")) // 这是猫:咪酱
1.3.19 KeyPath 和 KVO
重点说明
- KVO(Key-Value Observing)是 Objective-C 运行时的键值观察特性,用于监听对象属性的变化,Swift 中仅支持 NSObject 子类的 KVO。
- Swift 4 引入了原生
KeyPath语法,格式为\类型名.属性名,替代了 OC 中的字符串键路径,提供编译时类型安全,避免拼写错误。 - 被监听的属性必须标记
@objc dynamic,强制开启 Objective-C 动态派发,否则无法触发 KVO 回调。 - Swift 中提供了基于闭包的 KVO 监听 API,替代了 OC 中重写
observeValue(forKeyPath:of:change:context:)的方式,代码更简洁、回调更集中。 - 监听结果会通过
NSKeyValueObservation实例持有,实例释放后监听自动失效,无需手动移除,避免了 OC 中忘记移除监听导致的崩溃。
代码示例
import UIKit
// 支持KVO的类,必须继承自NSObject
class MyClass: NSObject {
// 必须标记@objc dynamic才能支持KVO
@objc dynamic var date = Date()
@objc dynamic var count = 0
}
// MARK: Swift 4 原生KeyPath+闭包KVO(推荐)
class ObserverClass: NSObject {
var observedObject: MyClass!
// 持有监听实例,释放后自动停止监听
var observation: NSKeyValueObservation?
override init() {
super.init()
observedObject = MyClass()
print("初始化,当前日期:\(observedObject.date)")
// 基于KeyPath添加监听
observation = observedObject.observe(\MyClass.date, options: [.new, .old]) { _, change in
if let newDate = change.newValue {
print("日期发生变化:\(newDate)")
}
if let oldDate = change.oldValue {
print("变化前日期:\(oldDate)")
}
}
// 1秒后修改属性,触发KVO回调
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
self.observedObject.date = Date()
}
}
}
// 启动监听
let observer = ObserverClass()
// MARK: 传统OC风格KVO(兼容)
class TraditionalObserver: NSObject {
var myObject: MyClass!
private var myContext = 0
override init() {
super.init()
myObject = MyClass()
// 添加监听
myObject.addObserver(self, forKeyPath: "count", options: .new, context: &myContext)
}
// 重写监听回调方法
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
guard context == &myContext, keyPath == "count" else {
super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
return
}
if let newCount = change?[.newKey] as? Int {
print("count 变化为:\(newCount)")
}
}
// 销毁时移除监听
deinit {
myObject.removeObserver(self, forKeyPath: "count", context: &myContext)
}
}
1.3.20 局部 scope
重点说明
- 局部 scope 用于将代码按功能分块,限制临时变量的作用域,避免命名污染,同时让超出作用域的变量尽早释放,优化内存使用。
- Swift 中无法直接使用
{}创建局部作用域(与闭包语法冲突),有三种实现方式:- 方式一:使用
do {}代码块,Swift 2.0 及以上支持,是官方推荐的原生方式。 - 方式二:定义全局
local函数,接受闭包并执行,模拟局部作用域。 - 方式三:使用立即执行的匿名闭包,常用于属性初始化。
- 方式一:使用
- 局部作用域的优势:让代码逻辑更清晰,临时变量仅在块内可见,减少代码维护成本;同时让 ARC 能及时回收块内的临时对象,降低内存峰值。
代码示例
import UIKit
// MARK: 方式一:do 代码块(推荐)
func setupUI() {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
view.backgroundColor = .white
// 局部作用域:配置标题标签
do {
let titleLabel = UILabel(frame: CGRect(x: 150, y: 30, width: 200, height: 40))
titleLabel.textColor = .red
titleLabel.text = "Title"
view.addSubview(titleLabel)
}
// 局部作用域:配置内容标签
do {
let textLabel = UILabel(frame: CGRect(x: 150, y: 80, width: 200, height: 40))
textLabel.textColor = .black
textLabel.text = "Text"
view.addSubview(textLabel)
}
// 此处无法访问 titleLabel 和 textLabel,避免命名冲突
}
// MARK: 方式二:local 函数实现
func local(_ closure: () -> ()) {
closure()
}
func setupUIV2() {
let view = UIView(frame: CGRect(x: 0, y: 0, width: 320, height: 480))
local {
let titleLabel = UILabel()
// 配置逻辑
view.addSubview(titleLabel)
}
local {
let button = UIButton()
// 配置逻辑
view.addSubview(button)
}
}
// MARK: 方式三:立即执行闭包,常用于属性初始化
class ViewController: UIViewController {
// 立即执行闭包,初始化属性,代码集中
let titleLabel: UILabel = {
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 200, height: 40))
label.textColor = .red
label.font = .systemFont(ofSize: 16)
label.textAlignment = .center
return label
}()
}
1.3.21 判等
重点说明
- Swift中内容判等统一使用
==操作符,替代Objective-C的isEqual:系列方法;!=由标准库自动基于==取反实现,无需单独定义。 ==是Equatable协议的核心方法,自定义类型需实现该协议才能使用判等操作,判等逻辑由业务需求决定(如通过唯一ID判断对象相等)。- NSObject子类的特殊规则:
- 若未重载
==,Swift会自动调用该类的-isEqual:方法。 - 若重写了
-isEqual:,必须同步重写-hash方法,保证"相等对象哈希值相同",否则作为字典key时会出现异常。
- 若未重载
- Swift中使用
===判断引用相等(两个变量是否指向同一个对象实例),对应Objective-C的指针比较==。
代码示例
// 1. 基础类型判等
let str1 = "快乐的字符串"
let str2 = "快乐的字符串"
print(str1 == str2) // true
// 2. 自定义类型实现Equatable
class TodoItem {
let uuid: String
var title: String
init(uuid: String, title: String) {
self.uuid = uuid
self.title = title
}
}
extension TodoItem: Equatable {
static func == (lhs: TodoItem, rhs: TodoItem) -> Bool {
return lhs.uuid == rhs.uuid // 仅通过唯一ID判等
}
}
// 3. 引用相等判断
let item1 = TodoItem(uuid: "123", title: "写代码")
let item2 = TodoItem(uuid: "123", title: "写代码")
let item3 = item1
print(item1 == item2) // true(内容相等)
print(item1 === item2) // false(不同实例)
print(item1 === item3) // true(同一实例)
1.3.22 哈希
重点说明
- Swift字典的key必须实现
Hashable协议,该协议继承自Equatable,要求提供hashValue属性。 - 核心规则:判等结果为
true的两个对象,必须拥有相同的哈希值;但哈希值相同不代表对象一定相等(哈希冲突)。 - NSObject子类的
hashValue对应Objective-C的-hash方法,重写-isEqual:时必须同步重写-hash。 - 哈希值是单向且不稳定的:不可依赖哈希值做持久化存储或全局唯一性判断,其实现可能随系统版本、平台变化。
代码示例
// 1. 基础类型的哈希值
let num = 19
print(num.hashValue) // 19(Int的哈希值为自身)
// 2. 自定义类型实现Hashable(结合判等)
class TodoItem: Equatable, Hashable {
let uuid: String
var title: String
init(uuid: String, title: String) {
self.uuid = uuid
self.title = title
}
static func == (lhs: TodoItem, rhs: TodoItem) -> Bool {
return lhs.uuid == rhs.uuid
}
func hash(into hasher: inout Hasher) {
hasher.combine(uuid) // 仅用唯一ID生成哈希
}
}
// 3. 作为字典key使用
var todoDict: [TodoItem: String] = [:]
let item = TodoItem(uuid: "123", title: "写代码")
todoDict[item] = "待完成"
print(todoDict[item]!) // 待完成
1.3.23 类簇
重点说明
- 类簇是Cocoa核心设计模式:用统一的公共父类暴露接口,底层由多个私有子类实现具体逻辑,隐藏实现细节并简化接口。
- Objective-C中通过在
init方法中替换self实现类簇;但Swift的init是真正的初始化方法,只能返回当前类实例,因此Swift中通过工厂方法实现类簇。 - 工厂方法根据输入参数返回对应私有子类的实例,对外只暴露公共父类的接口。
代码示例
import UIKit
// 公共父类
class Drinking {
typealias LiquidColor = UIColor
var color: LiquidColor {
return .clear
}
// 工厂方法:类簇的核心
class func drinking(name: String) -> Drinking {
switch name {
case "Coke":
return Coke()
case "Beer":
return Beer()
default:
return Drinking()
}
}
}
// 私有子类:可乐
class Coke: Drinking {
override var color: LiquidColor {
return .black
}
}
// 私有子类:啤酒
class Beer: Drinking {
override var color: LiquidColor {
return .yellow
}
}
// 使用
let coke = Drinking.drinking(name: "Coke")
print(coke.color) // black
let beer = Drinking.drinking(name: "Beer")
print(beer.color) // yellow
1.3.24 调用C动态库
重点说明
- Swift无法直接导入C头文件,需通过Objective-C桥接文件(
{product-module-name}-Bridging-Header.h)导入C库的头文件。 - 系统内置C动态库(如
libz、CommonCrypto)无需额外链接,仅需在桥接文件中导入对应头文件即可调用。 - 也可使用纯Swift重写C库功能,但推荐优先使用经过长期验证的系统C库,以保证稳定性和减小安装包体积。
代码示例
// 桥接文件:TargetName-Bridging-Header.h
#import <CommonCrypto/CommonCrypto.h>
// Swift代码:实现String的MD5扩展
import Foundation
extension String {
var MD5: String {
var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
if let data = data(using: .utf8) {
data.withUnsafeBytes { bytes in
CC_MD5(bytes.baseAddress, CC_LONG(data.count), &digest)
}
}
var digestHex = ""
for index in 0..<Int(CC_MD5_DIGEST_LENGTH) {
digestHex += String(format: "%02x", digest[index])
}
return digestHex
}
}
// 测试
print("swifter.tips".MD5) // dff88de99ff03d109de22fed4f71a273
1.3.25 输出格式化
重点说明
- Swift的
print支持字符串插值,无需记忆C风格的类型占位符,但不支持直接在插值中指定格式(如小数位数、对齐方式)。 - 格式化输出需使用
String(format:)方法,语法与C的printf完全兼容。 - 可通过扩展常用类型(如
Double、Int)封装格式化方法,简化重复的格式化调用。
代码示例
let a = 3
let b = 1.234567
// 1. 基础格式化
let format = String(format: "int:%d double:%.2f", a, b)
print(format) // int:3 double:1.23
// 2. 扩展Double封装格式化
extension Double {
func format(_ f: String) -> String {
return String(format: "%\(f)f", self)
}
}
print("double:\(b.format(".2"))") // double:1.23
print("double:\(b.format(".4"))") // double:1.2346
1.3.26 Options
重点说明
- Swift中没有Objective-C的
NS_OPTIONS宏,使用**实现OptionSet协议的struct**替代。 OptionSet遵循SetAlgebra协议,支持集合运算(并集、交集、差集),多个选项使用数组字面量组合。- 无选项时使用**空集合
[]**表示,对应Objective-C的kNilOptions。
代码示例
import UIKit
// 1. 系统API的Options使用
UIView.animate(withDuration: 0.3, delay: 0, options: [.curveEaseIn, .allowUserInteraction], animations: {
// 动画代码
}, completion: nil)
// 2. 自定义Options
struct YourOption: OptionSet {
let rawValue: UInt
static let none = YourOption(rawValue: 0)
static let option1 = YourOption(rawValue: 1 << 0)
static let option2 = YourOption(rawValue: 1 << 1)
static let option3 = YourOption(rawValue: 1 << 2)
}
// 使用自定义Options
let options: YourOption = [.option1, .option2]
if options.contains(.option1) {
print("包含option1")
}
1.3.27 数组enumerate
重点说明
- Objective-C的
enumerateObjectsUsingBlock:在Swift中仍可使用,但依赖NSArray且性能不佳,不推荐使用。 - Swift原生推荐使用数组的
enumerated()方法,返回包含(下标, 元素)的多元组,类型安全且效率更高。 enumerated()配合for-in循环可同时获取索引和元素,支持break/continue控制循环流程。
代码示例
// 1. 不推荐:NSArray的enumerateObjects
let arr: NSArray = [1,2,3,4,5]
var result = 0
arr.enumerateObjects { (num, idx, stop) in
result += num as! Int
if idx == 2 {
stop.pointee = true // 中断循环
}
}
print(result) // 6
// 2. 推荐:Swift原生enumerated()
result = 0
for (idx, num) in [1,2,3,4,5].enumerated() {
result += num
if idx == 2 {
break // 中断循环
}
}
print(result) // 6
1.3.28 类型编码@encode
重点说明
- Objective-C的
@encode用于获取类型对应的C字符串编码,Swift中无对应关键字。 - Swift中可通过
NSValue的objcType属性获取值的类型编码指针,再转换为字符串。 - 类型编码常用于运行时类型信息传递,如
NSValue的valueWithBytes:objcType:方法。
代码示例
import UIKit
// 1. 基础数字类型的编码
let int: Int = 0
let float: Float = 0.0
let double: Double = 0.0
let intType = String(validatingUTF8: (int as NSNumber).objcType)
let floatType = String(validatingUTF8: (float as NSNumber).objcType)
let doubleType = String(validatingUTF8: (double as NSNumber).objcType)
print(intType!) // q(64位Int)
print(floatType!) // f
print(doubleType!) // d
// 2. 结构体类型的编码
let point = NSValue(cgPoint: CGPoint(x: 3, y: 3))
let pointType = String(validatingUTF8: point.objcType)
print(pointType!) // {CGPoint=dd}
let transform = NSValue(cgAffineTransform: .identity)
let transformType = String(validatingUTF8: transform.objcType)
print(transformType!) // {CGAffineTransform=dddddd}
1.3.29 C代码调用和@asmname
重点说明
- 调用自定义C代码的标准方式:将C头文件导入桥接文件,Swift中直接调用C函数。
@asmname(现已更名为@_silgen_name)可直接映射C函数到Swift,无需桥接文件;还可重命名函数,解决命名冲突。- 系统C库(如
Darwin)的函数无需额外导入,Swift中可直接调用。
代码示例
// test.h
int test(int a);
// test.c
int test(int a) {
return a + 1;
}
// 桥接文件导入(标准方式)
#import "test.h"
// 1. 标准方式调用
func testSwift(input: Int32) {
let result = test(input)
print(result)
}
testSwift(input: 1) // 2
// 2. 使用@asmname直接映射(无需桥接文件)
@_silgen_name("test") func c_test(a: Int32) -> Int32
func testSwift2(input: Int32) {
let result = c_test(input)
print(result)
}
testSwift2(input: 2) // 3
1.3.30 delegate
重点说明
- Swift中协议默认可被
class、struct、enum实现,但weak只能修饰引用类型,因此直接声明weak var delegate: MyProtocol?会编译错误。 - 需将协议限制为仅能由class实现,有两种方式:
- 添加
@objc修饰符(兼容Objective-C,但会引入不必要的运行时开销)。 - 在协议名后加
: class(Swift原生方式,推荐使用)。
- 添加
代码示例
// ❌ 错误:weak不能修饰非class类型的协议
protocol MyClassDelegate {
func method()
}
class MyClass {
weak var delegate: MyClassDelegate? // 编译错误
}
// ✅ 正确:限制协议仅能由class实现
protocol MyClassDelegate: class {
func method()
}
class MyClass {
weak var delegate: MyClassDelegate?
}
// 使用
class ViewController: UIViewController, MyClassDelegate {
var someInstance: MyClass!
override func viewDidLoad() {
super.viewDidLoad()
someInstance = MyClass()
someInstance.delegate = self
}
func method() {
print("Do something")
}
}
1.3.31 Associated Object
重点说明
- Swift中无法通过extension给类添加存储属性,需借助Objective-C运行时的关联对象实现。
- 核心API:
objc_getAssociatedObject:读取关联值objc_setAssociatedObject:设置关联值,需指定关联策略(如OBJC_ASSOCIATION_RETAIN_NONATOMIC)
- 关联对象的key通常声明为
private Void?类型,通过&取地址作为全局唯一标识。
代码示例
import ObjectiveC
class MyClass {}
// 关联对象的key
private var titleKey: Void?
extension MyClass {
var title: String? {
get {
return objc_getAssociatedObject(self, &titleKey) as? String
}
set {
objc_setAssociatedObject(
self,
&titleKey,
newValue,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
}
}
// 使用
let a = MyClass()
print(a.title ?? "没有设置") // 没有设置
a.title = "Swifter.tips"
print(a.title!) // Swifter.tips
1.3.32 Lock
重点说明
- Swift中没有Objective-C的
@synchronized关键字,可通过objc_sync_enter和objc_sync_exit实现相同的互斥锁功能。 - 可封装全局
synchronized方法,配合Swift的尾随闭包特性,简化使用语法。 - 加锁会带来性能开销,应仅在多线程共享资源访问时使用,避免过度加锁导致性能下降。
代码示例
import ObjectiveC
// 封装全局synchronized方法
func synchronized(_ lock: AnyObject, closure: () -> ()) {
objc_sync_enter(lock)
closure()
objc_sync_exit(lock)
}
// 1. 基础使用
func myMethodLocked(anObj: AnyObject) {
synchronized(anObj) {
// 临界区:持有anObj的锁
}
}
// 2. 线程安全的setter示例
class ThreadSafeObj {
private var _str = "123"
var str: String {
get {
return _str
}
set {
synchronized(self) {
_str = newValue
}
}
}
}
1.3.33 Toll-Free Bridging 和 Unmanaged
重点说明
- Toll-Free Bridging(免费桥接)是 Core Foundation 与 Foundation 类型之间的无缝转换机制,如
NSString与CFString、NSURL与CFURL等,二者内存结构一致,可直接桥接转换。 - Swift 中对系统 Core Foundation API 进行了内存管理自动桥接,遵循 CF 命名规则的 API(含 Create/Copy/Retain)会自动纳入 ARC 管理,无需手动调用
CFRelease。 - 非系统、未标注内存管理规则的第三方 CF API,返回的对象会被包装为
Unmanaged<T>类型,需手动管理内存。 Unmanaged类型的内存管理方法:takeUnretainedValue():不改变引用计数,获取对象值,适用于非 Create/Copy 方法返回的对象。takeRetainedValue():引用计数+1,获取对象值,使用完后需手动调用release()释放,适用于 Create/Copy 方法返回的对象。
代码示例
import UIKit
import CoreFoundation
// MARK: 系统CF类型与Foundation类型的免费桥接,自动内存管理
// NSString 与 CFString 无缝转换
let nsString: NSString = "Hello Swift"
let cfString: CFString = nsString as CFString
// 反向转换
let nsString2 = cfString as NSString
print(nsString2) // Hello Swift
// NSURL 与 CFURL 转换
let nsUrl = NSURL(string: "https://www.apple.com")!
let cfUrl = nsUrl as CFURL
let nsUrl2 = cfUrl as NSURL
// 系统CF API,自动内存管理,无需手动CFRelease
let font = CTFontCreateWithName("PingFangSC-Regular" as CFString, 16, nil)
// 无需调用CFRelease(font),ARC自动管理
// MARK: Unmanaged 手动内存管理
// 模拟第三方CF API,未标注内存规则,返回Unmanaged类型
// 示例:非Create方法,不转移所有权
func unmanagedGetObject() -> Unmanaged<CFString> {
let str = "Unmanaged String" as CFString
return Unmanaged.passUnretained(str)
}
// 示例:Create方法,转移所有权,需手动释放
func unmanagedCreateObject() -> Unmanaged<CFString> {
let str = CFStringCreateWithCString(kCFAllocatorDefault, "Created String", kCFStringEncodingUTF8)
return Unmanaged.passRetained(str!)
}
// 1. 非Create方法,使用takeUnretainedValue
let unmanagedStr1 = unmanagedGetObject()
let str1 = unmanagedStr1.takeUnretainedValue()
print(str1) // Unmanaged String
// 无需手动释放
// 2. Create方法,使用takeRetainedValue,手动释放
let unmanagedStr2 = unmanagedCreateObject()
let str2 = unmanagedStr2.takeRetainedValue()
print(str2) // Created String
// 使用完后手动释放
unmanagedStr2.release()
Swift 与开发环境及一些实践
1.4.1 Swift命令行工具
重点说明
- 支持REPL交互式环境,输入语句即时编译执行,适合快速验证语言特性
- 可直接运行
.swift文件,添加#!/usr/bin/env swift并修改权限后可作为可执行脚本 - 使用
swiftc编译多个.swift文件,生成独立的二进制可执行文件 - 支持生成汇编级代码,用于分析编译器优化行为和底层实现
代码示例
// 1. 脚本方式运行(hello.swift)
#!/usr/bin/env swift
print("hello")
// 终端执行:chmod 755 hello.swift && ./hello.swift
// 2. 编译生成可执行文件
// MyClass.swift
class MyClass {
let name = "XiaoMing"
func hello() { print("Hello \(name)") }
}
// main.swift
let object = MyClass()
object.hello()
// 终端编译:swiftc MyClass.swift main.swift
// 运行:./main
// 3. 生成汇编代码
// swiftc -O hello.swift -o hello.asm
1.4.2 随机数生成
重点说明
arc4random()返回UInt32,32位平台转Int可能溢出,不推荐直接使用- 推荐使用
arc4random_uniform(n),返回0..<n的UInt32,避免溢出和模运算偏差 - 可封装
Range的随机数生成方法,提升代码复用性
代码示例
// ❌ 错误用法:32位平台可能溢出崩溃
let diceFaceCount = 6
let randomRoll = Int(arc4random()) % diceFaceCount + 1
// ✅ 正确用法
let randomRoll = Int(arc4random_uniform(UInt32(diceFaceCount))) + 1
// 封装Range随机数方法
func random(in range: Range<Int>) -> Int {
let count = UInt32(range.endIndex - range.startIndex)
return Int(arc4random_uniform(count)) + range.startIndex
}
// 调用
for _ in 0...10 {
print(random(in: 1..<7))
}
1.4.3 print 和 debugPrint
重点说明
- 普通
class对象print默认只输出类型名,struct会自动输出所有成员名和值 - 实现
CustomStringConvertible协议可自定义print的输出格式 - 实现
CustomDebugStringConvertible协议可自定义调试时(如po命令)的输出 - 推荐通过
extension实现协议,保持核心代码整洁
代码示例
struct Meeting {
var date: Date
var place: String
var attendeeName: String
}
// 自定义输出格式
extension Meeting: CustomStringConvertible {
var description: String {
return "于 \(self.date) 在 \(self.place) 与 \(self.attendeeName) 进行会议"
}
}
// 调用
let meeting = Meeting(date: Date(), place: "会议室B1", attendeeName: "小明")
print(meeting)
// 输出:于 2026-04-11 08:00:00 +0000 在 会议室B1 与 小明 进行会议
1.4.4 错误和异常处理
重点说明
- Swift异常本质是错误处理,仅适合同步方法,异步API仍使用
Error回调 - 用
throws标记可抛出错误的方法,调用时必须使用try/do-catch - 三种
try用法:try:正常调用,需配合do-catch捕获错误try!:强制执行,出错直接崩溃try?:返回Optional,出错返回nil
rethrows用于高阶函数,表示仅当参数闭包抛出错误时,自身才会抛出- 推荐用泛型
Result枚举处理异步错误,统一成功/失败状态
代码示例
// 定义错误类型
enum LoginError: Error {
case UserNotFound
case UserPasswordNotMatch
}
// 标记可抛出错误的方法
func login(user: String, password: String) throws {
let users = ["onevcat": "123"]
guard users.keys.contains(user) else { throw LoginError.UserNotFound }
guard users[user] == password else { throw LoginError.UserPasswordNotMatch }
print("Login successfully.")
}
// 调用
do {
try login(user: "onevcat", password: "123")
} catch LoginError.UserNotFound {
print("用户不存在")
} catch LoginError.UserPasswordNotMatch {
print("密码错误")
}
// 泛型Result处理异步错误
enum Result<T> {
case Success(T)
case Failure(Error)
}
1.4.5 断言
重点说明
- 仅在Debug编译时生效,Release版本自动禁用,不影响运行时性能
- 用于检查输入参数的边界条件,提前暴露开发阶段的逻辑错误
- 可通过编译标记强制控制断言:
-assert-config Debug强制启用,-assert-config Release强制禁用 - Objective-C的
NSAssert已被Swift彻底移除
代码示例
let absoluteZeroInCelsius = -273.15
func convertToKelvin(_ celsius: Double) -> Double {
// 断言:输入温度不能低于绝对零度
assert(celsius > absoluteZeroInCelsius, "输入的摄氏温度不能低于绝对零度")
return celsius - absoluteZeroInCelsius
}
let roomTemperature = convertToKelvin(27) // 正常运行
let tooCold = convertToKelvin(-300) // Debug模式崩溃,Release模式继续执行
1.4.6 fatalError
重点说明
- 无论Debug/Release版本都会执行,执行后程序立即强制终止
- 用于处理绝对不应该发生的情况,如抽象方法未实现、不支持的初始化方法
- 可模拟Objective-C的抽象函数,强制子类重写父类方法
- 常用在
init(coder:)等必须实现但项目中不会用到的初始化方法
代码示例
// 1. 模拟抽象方法
class MyClass {
func methodMustBeImplementedInSubclass() {
fatalError("这个方法必须在子类中被重写")
}
}
class YourClass: MyClass {
override func methodMustBeImplementedInSubclass() {
print("YourClass 实现了该方法")
}
}
// 2. 不支持的初始化方法
class ViewController: UIViewController {
required init?(coder: NSCoder) {
fatalError("NSCoding not supported")
}
}
1.4.7 代码组织和Framework
重点说明
- iOS不允许第三方动态框架,自制
Cocoa Touch Framework本质是静态库,需嵌入app bundle - Swift框架推荐以源码依赖形式使用,避免Swift运行时版本不兼容
- 框架中需要对外暴露的类、方法、属性必须添加
public修饰符 - 需用
lipo命令合并模拟器和真机架构的二进制文件,生成通用Framework
代码示例
// 框架中的代码(HelloKit.framework)
public class Hello {
public class func sayHello() {
print("Hello Kit")
}
}
// app中调用
import HelloKit
Hello.sayHello() // 输出:Hello Kit
1.4.8 安全的资源组织方式
重点说明
- 直接用字符串指定资源名存在拼写错误、改名后漏改的运行时风险
- 用
rawValue为String的enum封装资源名,结合extension提供类型安全的API - 推荐使用
R.swift、SwiftGen等工具自动生成资源类型,避免手动维护
代码示例
// 定义资源枚举
enum ImageName: String {
case myImage = "my_image"
}
enum SegueName: String {
case mySegue = "my_segue"
}
// 扩展系统类提供类型安全的API
extension UIImage {
convenience init!(imageName: ImageName) {
self.init(named: imageName.rawValue)
}
}
extension UIViewController {
func performSegue(withName segueName: SegueName, sender: Any?) {
performSegue(withIdentifier: segueName.rawValue, sender: sender)
}
}
// 调用
let image = UIImage(imageName: .myImage)
performSegue(withName: .mySegue, sender: self)
1.4.9 Playground延时运行
重点说明
- Playground默认执行完所有顶层语句后立即退出,异步代码无法正常执行
- 导入
PlaygroundSupport框架,设置needsIndefiniteExecution = true开启延时运行 - 默认最长运行30秒,可在辅助编辑器右下角修改超时时间
- 适用于测试网络请求、定时器、动画等异步逻辑
代码示例
import UIKit
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
// 1. 定时器测试
class MyClass {
@objc func callMe() { print("Hi") }
}
let object = MyClass()
Timer.scheduledTimer(timeInterval: 1, target: object, selector: #selector(MyClass.callMe), userInfo: nil, repeats: true)
// 2. 网络请求测试
let url = URL(string: "http://httpbin.org/get")!
URLSession.shared.dataTask(with: url) { data, _, error in
if let data = data, let json = try? JSONSerialization.jsonObject(with: data) {
print(json)
}
}.resume()
1.4.10 Playground与项目协作
重点说明
- 单独的Playground无法使用项目代码,必须将Playground加入项目中
- 想要复用的项目代码必须组织为Cocoa Touch Framework的独立target
- 框架需针对64位模拟器编译,且Xcode的
Derived Data保持默认位置 - 在Playground中导入框架的module名,即可使用项目中的代码
代码示例
无单独代码,核心是项目配置步骤。
1.4.11 Playground可视化开发
重点说明
- 支持直接显示
UIView和UIViewController,通过PlaygroundPage.current.liveView赋值 - 打开
Assistant Editor(Alt+Shift+Cmd+Return)可查看实时UI并进行交互 - 适用于快速原型开发、UI组件调试,无需每次重启模拟器
代码示例
import UIKit
import PlaygroundSupport
// 显示UILabel
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 400, height: 200))
label.backgroundColor = .white
label.font = UIFont.systemFont(ofSize: 32)
label.textAlignment = .center
label.text = "Hello World"
PlaygroundPage.current.liveView = label
// 显示可交互的UITableViewController
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .cyan
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 30
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = String(indexPath.row)
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("Select: \(indexPath.row)")
}
}
PlaygroundPage.current.liveView = ViewController()
1.4.12 数学和数字
重点说明
- 导入
Darwin可使用所有C标准数学函数(sin、cos、M_PI等) Double.infinity表示无穷大,超过Double最大值的数会被判定为无穷NaN(Not a Number)表示未定义/错误运算结果,不能用==判断,需用isNaN属性
代码示例
import Darwin
// 圆周率计算
func circlePerimeter(radius: Double) -> Double {
return 2 * Double.pi * radius
}
// 无穷大判断
print(1.797693134862315e+308 < Double.infinity) // true
print(1.797693134862316e+308 < Double.infinity) // false
// NaN判断
let a = 0.0 / 0.0
print(a == a) // false
print(a.isNaN) // true
1.4.13 JSON和Codable
重点说明
- Swift 4引入
Codable协议,替代手动JSON解析,自动处理序列化/反序列化 - 所有成员都实现
Codable的类型,可自动满足Codable要求 - 用
CodingKeys枚举映射JSON键名与Swift属性名(如下划线转驼峰) JSONDecoder/JSONEncoder处理Data与对象的转换,错误时抛出异常
代码示例
// JSON结构
// {"menu": { "id": "file", "value": "File", "popup": { "menuitem": [{"value": "New", "onclick": "CreateNewDoc()"}] }}}
// 定义Codable类型
struct Obj: Codable {
let menu: Menu
struct Menu: Codable {
let id: String
let value: String
let popup: Popup
}
struct Popup: Codable {
let menuItem: [MenuItem]
enum CodingKeys: String, CodingKey {
case menuItem = "menuitem"
}
}
struct MenuItem: Codable {
let value: String
let onClick: String
enum CodingKeys: String, CodingKey {
case value
case onClick = "onclick"
}
}
}
// 解析JSON
let jsonString = "..." // 上述JSON字符串
let data = jsonString.data(using: .utf8)!
do {
let obj = try JSONDecoder().decode(Obj.self, from: data)
print(obj.menu.popup.menuItem[0].value) // 输出:New
} catch {
print("解析错误:\(error)")
}
1.4.14 NSNull
重点说明
- 用于表示集合中的
null值,因为Swift集合只能存储对象类型 - Swift的类型安全特性避免了向
NSNull发送消息导致的崩溃 - 通过
Optional Binding可自动将NSNull转换为nil,安全处理JSON中的null
代码示例
// 假设从JSON中取出的NSNull值
let jsonValue: AnyObject = NSNull()
if let string = jsonValue as? String {
print(string.hasPrefix("a"))
} else {
print("不能解析") // 输出:不能解析
}
1.4.15 文档注释
重点说明
- 使用
/**...*/块注释或///行注释编写文档,Xcode可自动提取为Quick Help - 用
- parameter说明参数,- returns说明返回值 - Xcode 8+支持快捷键
Alt+Cmd+/自动生成文档注释模板 jazzy是Swift官方推荐的文档生成工具,可生成HTML格式文档
代码示例
/// 一个演示方法
/// - parameter input: 一个整数输入
/// - returns: 表示输入数字的字符串
func method(input: Int) -> String {
return String(input)
}
/// 人的名字
struct Person {
/// 姓名
var name: String
}
1.4.16 性能考虑
重点说明
- Swift方法调用默认使用虚函数表派发,比Objective-C的动态派发快得多
final标记的方法/类可被编译器优化为inline调用,进一步提升性能- 性能敏感代码优先使用纯Swift类型,避免
NSObject子类和Objective-C运行时 - Swift基本算法性能接近C语言,远优于Objective-C
代码示例
无单独代码,核心是性能优化原则。
1.4.17 Log输出
重点说明
- 利用
#file、#line、#function等编译符号自动添加日志上下文(文件名、行号、方法名) - 封装统一的日志方法,通过条件编译在Release版本中禁用日志
- Release版本中空方法会被编译器完全优化,实现零成本日志
代码示例
func printLog<T>(_ message: T,
file: String = #file,
method: String = #function,
line: Int = #line) {
#if DEBUG
let fileName = (file as NSString).lastPathComponent
print("\(fileName)[\(line)], \(method): \(message)")
#endif
}
// 调用
func testLog() {
printLog("这是一条测试日志")
}
// 输出:Test.swift[12], testLog(): 这是一条测试日志
1.4.18 溢出
重点说明
- Swift默认数值溢出会导致程序崩溃,而非截断高位,提升了类型安全性
Int在32位平台是Int32,64位平台是Int64,跨平台开发需注意数值范围- 使用
&+、&-、&*、&/、&%溢出运算符,可实现高位截断的溢出行为
代码示例
// ❌ 默认溢出崩溃
var max = Int.max
max = max + 1 // 崩溃
// ✅ 溢出运算符:高位截断
var max2 = Int.max
max2 = max2 &+ 1
// 64位系统输出:-9223372036854775808
print(max2)
1.4.19 宏定义define
重点说明
- Swift彻底移除了宏定义,避免了宏的安全性和可维护性问题
- 常量宏用
let或计算属性替代,函数宏用全局方法替代 - 条件编译用
#if配合编译标记实现,替代#ifdef - 无法实现改变代码结构的宏(如类定义宏)
代码示例
// 替代常量宏
let M_PI = Double.pi
// 替代函数宏:NSLocalizedString
func NSLocalizedString(_ key: String, comment: String) -> String {
return Bundle.main.localizedString(forKey: key, value: "", table: nil)
}
// 条件编译
#if os(iOS)
print("iOS平台")
#elseif os(macOS)
print("macOS平台")
#endif
1.4.20 属性访问控制
重点说明
- Swift提供五级访问控制(权限从低到高):
private<fileprivate<internal(默认) <public<open private仅当前作用域/同文件同类型可访问,fileprivate仅当前文件可访问public允许外部module访问但不能继承/重写,open允许继承和重写private(set)可分离读写权限,实现只读公开、可写私有
代码示例
// 分离读写权限
class MyClass {
public private(set) var name: String?
}
// 外部module只能读取name,不能修改
let obj = MyClass()
print(obj.name) // 正常
obj.name = "test" // 编译错误
1.4.21 Swift中的测试
重点说明
- 使用XCTest框架进行测试,测试target与app target相互独立
- Swift 2.0引入
@testable import,可在测试中访问app的internal成员 - 无需为了测试将方法标记为
public,遵循最小权限原则 - 库开发时
public方法即为测试接口,app开发用@testable访问内部方法
代码示例
// app中的业务代码(internal权限)
func methodToTest() -> Int {
return 123
}
// 测试代码
@testable import MyApp
import XCTest
class MyAppTests: XCTestCase {
func testMethod() {
XCTAssertEqual(methodToTest(), 123)
}
}
1.4.22 Core Data
重点说明
- Swift中用
@NSManaged替代Objective-C的@dynamic,标记Core Data动态属性 @NSManaged告诉编译器属性的getter/setter由Core Data在运行时生成- 创建Entity时需指定module名,否则类型转换会失败
@NSManaged仅限NSManagedObject子类使用,不推荐在其他类中使用
代码示例
import CoreData
class MyModel: NSManagedObject {
@NSManaged var title: String
@NSManaged var createTime: Date
}
1.4.23 闭包歧义
重点说明
- 闭包省略参数类型时,编译器可能推断错误导致调用歧义
- Swift 1.2+会对歧义调用抛出编译错误,避免运行时异常
- 显式声明闭包参数类型可彻底解决歧义,同时提升代码可读性
Void本质是空元组(),无参数闭包实际是接受空元组的闭包
代码示例
// 存在歧义的扩展
extension Int {
func times(f: (Int) -> ()) {
for i in 1...self { f(i) }
}
func times(f: () -> ()) {
for _ in 1...self { f() }
}
}
// ❌ 歧义调用:编译器无法推断
3.times { print($0) }
// ✅ 显式指定参数类型解决歧义
3.times { (i: Int) in print(i) }
3.times { () in print("Hello") }
1.4.24 泛型扩展
重点说明
- 泛型类型的扩展可直接使用原类型定义的泛型符号,无需重复声明
- 扩展中不能添加整个类型可用的新泛型符号,但单个方法可定义自己的泛型
- 可通过扩展为泛型类型添加通用方法,无需修改原类型定义
代码示例
// 为Array添加随机取元素的属性
extension Array {
var random: Element? {
return self.count != 0 ? self[Int(arc4random_uniform(UInt32(self.count)))] : nil
}
// 方法级别的泛型
func appendRandomDescription<U: CustomStringConvertible>(_ input: U) -> String {
if let element = self.random {
return "\(element) " + input.description
} else {
return "empty array"
}
}
}
// 调用
let languages = ["Swift", "ObjC", "C++"]
print(languages.random!) // 随机输出一个语言
print(languages.appendRandomDescription(123)) // 输出类似:Swift 123
1.4.25 兼容性
重点说明
- Swift app会打包自带Swift运行时库,保证不同系统版本行为一致
- 支持向下兼容到iOS 7和OS X 10.9,但会增加app尺寸约4-5MB
- 第三方Swift代码推荐以源码形式依赖,避免二进制版本不兼容
- 主app未用Swift但扩展用了Swift时,需开启
Embedded Content Contains Swift Code
代码示例
无单独代码,核心是项目配置和依赖原则。
1.4.26 列举enum类型
重点说明
- Swift原生不支持直接枚举enum的所有case,因为enum可包含关联值
- 可通过协议+static属性手动实现
allValues,枚举无关联值的enum - 适用于固定case集合的enum,如扑克牌花色、等级等
代码示例
// 定义可枚举协议
protocol EnumeratableEnum {
static var allValues: [Self] { get }
}
// 实现协议
enum Suit: String, EnumeratableEnum {
case spades = "黑桃"
case hearts = "红桃"
case clubs = "草花"
case diamonds = "方片"
static var allValues: [Suit] {
return [.spades, .hearts, .clubs, .diamonds]
}
}
// 遍历所有case
for suit in Suit.allValues {
print(suit.rawValue)
}
1.4.27 尾递归
重点说明
- 普通递归会不断创建栈帧,深度过大时导致栈溢出
- 尾递归让函数最后一个动作是函数调用,编译器可优化为复用栈帧
- SwiftRelease模式下支持尾递归优化,Debug模式不支持
- 嵌套函数可用于实现尾递归的内部辅助方法
代码示例
// ❌ 普通递归:深度过大栈溢出
func sum(_ n: UInt) -> UInt {
if n == 0 { return 0 }
return n + sum(n - 1)
}
sum(1000000) // 崩溃
// ✅ 尾递归:Release模式下无栈溢出
func tailSum(_ n: UInt) -> UInt {
func sumInternal(_ n: UInt, current: UInt) -> UInt {
if n == 0 {
return current
} else {
return sumInternal(n - 1, current: current + n)
}
}
return sumInternal(n, current: 0)
}
tailSum(1000000) // Release模式正常运行