Swift基础知识(二)

一、Swift 属性观察器(Property Observers)

1. 核心概念

属性观察器用于监听存储属性值的变化,在值被修改前后执行自定义逻辑。包含两种观察器:

  • willSet :在值即将被设置前 调用,默认提供 newValue 参数(新值)。
  • didSet :在值已被设置后 调用,默认提供 oldValue 参数(旧值)。

2. 使用条件

  • 适用对象 :仅用于 lazyvar 存储属性

    swift 复制代码
    var score: Int = 0 {  // 正确:非 lazy 的存储属性
        willSet { print("新值:\(newValue)") }
        didSet { print("旧值:\(oldValue)") }
    }
    
    // lazy var data: [Int] = loadData() { willSet { } } // 错误:lazy 属性不支持
  • 不适用场景

    • 常量(let 属性)。
    • 计算属性(需通过 set 方法监听)。
    • 延迟存储属性(lazy var)。

3. 注意事项

  • 初始化不触发 :属性在初始化赋值时不会调用观察器。

    swift 复制代码
    struct User {
        var name: String = "Guest" { // 初始化赋值不会触发观察器
            didSet { print("名字被修改") }
        }
    }
  • 自定义参数名 :可显式命名参数(如 willSet(newScore))。

  • 避免循环修改 :在 didSet 中修改属性本身会再次触发观察器。


4. 使用场景

  • 数据验证 :确保值在合法范围内。

    swift 复制代码
    var age: Int = 0 {
        didSet {
            age = min(max(age, 0), 120)  // 限制年龄在 0~120
        }
    }
  • UI 同步更新 :值变化时刷新界面。

    swift 复制代码
    var isLoggedIn: Bool = false {
        didSet {
            updateLoginUI()
        }
    }
  • 日志记录 :跟踪属性变化历史。

    swift 复制代码
    var temperature: Double = 25.0 {
        willSet { print("温度将从 \(temperature) 变为 \(newValue)") }
        didSet { print("温度已从 \(oldValue) 更新为 \(temperature)") }
    }

5. 与计算属性的区别

特性 属性观察器 计算属性
用途 监听存储属性变化 通过计算动态获取值
触发时机 属性值被修改前后 每次访问时计算
存储能力 需有存储空间(必须初始化) 无存储空间(依赖其他属性)
语法 willSet/didSet get + set(或只读 get

6. 总结

  • 核心作用:在属性值变化前后注入逻辑,提升代码灵活性和可维护性。
  • 适用场景:数据验证、日志记录、UI 同步等。
  • 限制 :仅适用于非 lazyvar 存储属性,初始化不触发。

二、Swift 中的异常捕获

Swift 通过 Error 协议和 throws 关键字实现异常处理,提供多种方式捕获和处理错误。以下是主要的异常捕获方法:

1. do-catch 语句(详细错误处理)

  • 用法:捕获特定错误类型并处理。

  • 适用场景:需要根据不同错误类型执行不同逻辑。

  • 示例

    swift 复制代码
    enum NetworkError: Error {
        case invalidURL
        case timeout(seconds: Int)
    }
    
    func fetchData() throws {
        throw NetworkError.timeout(seconds: 30)
    }
    
    do {
        try fetchData()
    } catch NetworkError.invalidURL {
        print("URL 无效")
    } catch NetworkError.timeout(let seconds) {
        print("请求超时:\(seconds) 秒")
    } catch {
        print("未知错误:\(error)")
    }

2. try?(转换为可选类型)

  • 用法 :忽略具体错误,返回 nil

  • 适用场景:不关心错误细节,只需判断是否成功。

  • 示例

    swift 复制代码
    let result = try? someThrowingFunction()
    if let data = result {
        // 成功
    } else {
        // 失败
    }

3. try!(强制解包,慎用)

  • 用法:假设函数不会抛出错误,若出错则触发运行时崩溃。

  • 适用场景:确信代码不会失败(如本地静态数据加载)。

  • 示例

    swift 复制代码
    let data = try! loadLocalConfig() // 确定本地文件存在时使用

4. 向上传递错误(throws 关键字)

  • 用法 :函数声明 throws,错误由调用者处理。

  • 适用场景:将错误传递给上层调用链。

  • 示例

    swift 复制代码
    func processFile() throws {
        let content = try String(contentsOfFile: "path/to/file")
        // 处理内容
    }
    
    // 调用处需处理或继续传递
    do {
        try processFile()
    } catch {
        print("文件处理失败:\(error)")
    }

5. rethrows(传递闭包中的错误)

  • 用法:函数本身不产生错误,但可能传递闭包的异常。

  • 适用场景 :高阶函数(如 mapfilter)中处理闭包可能抛出的错误。

  • 示例

    swift 复制代码
    func customMap<T>(_ array: [T], _ transform: (T) throws -> T) rethrows -> [T] {
        var result = [T]()
        for item in array {
            try result.append(transform(item))
        }
        return result
    }
    
    let numbers = [1, 2, 3]
    let doubled = try? customMap(numbers) { num in
        if num == 2 { throw NetworkError.invalidURL }
        return num * 2
    }

6. 异步错误处理(async/await

  • 用法 :在异步函数中使用 throwstry

  • 适用场景:异步操作中的错误处理。

  • 示例

    swift 复制代码
    func downloadData() async throws -> Data {
        let url = URL(string: "https://example.com")!
        let (data, _) = try await URLSession.shared.data(from: url)
        return data
    }
    
    Task {
        do {
            let data = try await downloadData()
        } catch {
            print("下载失败:\(error)")
        }
    }

总结

方法 关键字 特点 适用场景
详细错误处理 do-catch 精准捕获错误类型 需要区分不同错误逻辑
可选值简化 try? 忽略错误,返回 nil 不关心错误细节
强制解包(高危) try! 假设成功,崩溃风险高 确定不会失败的场景
向上传递错误 throws 将错误传递给调用者 多层调用链中的错误处理
闭包错误传递 rethrows 传递闭包中的错误 高阶函数中的闭包操作
异步错误处理 async throws 结合 async/await 处理异步错误 网络请求、文件读写等异步操作

最佳实践

  • 优先使用 do-catch 处理可预见的错误。
  • 谨慎使用 try!,确保代码绝对安全。
  • 异步操作中结合 async/await 提升可读性。

三、Swift 中 defer 关键字的详解

1. 基本概念

defer 用于定义一个代码块(延迟执行块 ),该代码块会在 当前作用域结束前 执行,无论作用域是通过正常返回、抛出错误还是其他控制流(如 returnbreak)结束的。
核心作用:确保清理或收尾逻辑(如资源释放)一定会执行,避免资源泄漏。


2. 使用场景

  • 资源管理:文件操作、网络请求、锁的获取与释放等。
  • 错误处理:在抛出错误前确保清理逻辑执行。
  • 代码可读性:将清理代码紧跟在初始化代码后,提高可维护性。

3. 基本用法

swift 复制代码
func readFile() {
    let file = openFile()
    defer {
        closeFile(file) // 作用域结束前执行
    }
    // 处理文件...
    if someCondition {
        return // 触发 defer
    }
    // 更多操作...
} // 函数结束,触发 defer

4. 执行规则

  • 逆序执行 :同一作用域内的多个 defer定义顺序的逆序 执行(类似栈结构)。

    swift 复制代码
    defer { print("1") }
    defer { print("2") }
    // 输出:2 → 1
  • 作用域限制defer 仅在 当前作用域 有效(如函数、循环、条件语句)。

    swift 复制代码
    func example() {
        if condition {
            defer { print("if 块结束") }
            // ...
        } // 此处执行 defer
        // 函数后续代码...
    }

5. 常见应用示例

(1) 文件操作
swift 复制代码
func readFile(path: String) throws -> String {
    let file = try openFile(path)
    defer {
        closeFile(file) // 确保文件关闭
    }
    return try parseFile(file)
}
(2) 加锁与解锁
swift 复制代码
let lock = NSLock()
func criticalSection() {
    lock.lock()
    defer { lock.unlock() } // 确保锁释放
    // 执行关键代码...
}
(3) 循环中的资源清理
swift 复制代码
for url in urls {
    let data = try downloadData(from: url)
    defer { 
        cleanupTemporaryFiles() // 每次循环结束清理
    }
    process(data)
}

6. 注意事项

  • 禁止控制流操作defer 块中不能使用 breakreturn 或抛出错误。

  • 避免副作用 :修改外部变量可能导致逻辑混乱。

    swift 复制代码
    var value = 0
    func example() {
        defer { value += 1 } // 不推荐
        // ...
    }
  • 性能影响 :频繁使用 defer 可能影响性能(极少数情况)。


7. 错误处理中的 defer

即使函数抛出错误,defer 仍会执行:

swift 复制代码
func riskyOperation() throws {
    let resource = allocateResource()
    defer { releaseResource(resource) } // 错误抛出前执行
    try mayThrowError()
}

8. 总结

场景 示例 作用
文件/网络资源释放 defer { file.close() } 防止资源泄漏
锁的获取与释放 defer { lock.unlock() } 避免死锁
错误处理中的清理 defer { cleanup() } 确保异常时资源释放
临时数据清理 defer { removeTempFiles() } 提升代码健壮性

最佳实践

  • defer 紧跟在资源获取代码后,提高可读性。
  • 避免在 defer 中执行耗时操作或修改外部状态。
  • 优先用于必须执行的清理逻辑,而非复杂业务代码。

通过合理使用 defer,可以显著提升代码的健壮性和可维护性,确保资源安全和逻辑清晰。


四、Swift 与 Objective-C 中的 Protocol 区别

1. 基本概念

  • Objective-C :Protocol 是一种定义方法列表的方式,用于声明接口,实现类必须遵守这些接口(除非标记为 @optional)。
  • Swift:Protocol 不仅定义接口,还支持属性、方法、关联类型、默认实现等,是面向协议编程(POP)的核心工具。

2. 核心区别

特性 Objective-C Protocol Swift Protocol
可选方法 使用 @optional 标记可选方法 默认无可选方法,可通过 @objc optional 兼容 OC,或通过协议扩展提供默认实现
默认实现 不支持 支持通过 协议扩展 提供默认实现
关联类型 不支持 支持 associatedtype,实现泛型协议
值类型支持 仅适用于类(Class) 适用于类、结构体(Struct)、枚举(Enum)
协议继承与组合 单继承(只能继承一个协议) 支持多继承(ProtocolA & ProtocolB
泛型支持 支持泛型约束(where 子句)
属性定义 只能定义方法 可定义属性(需指定 { get }{ get set }
类型检查 运行时检查(conformsToProtocol: 编译时检查 + 运行时检查(isas?

3. 使用场景与示例

Objective-C Protocol
objc 复制代码
@protocol DataSource <NSObject>
@required
- (NSInteger)numberOfItems;
@optional
- (NSString *)titleForItemAtIndex:(NSInteger)index;
@end

// 类遵循协议
@interface ViewController : UIViewController <DataSource>
@property(nonatomic,weak)id <DataSource> delegate;
@end
  • 特点:主要用于委托模式(Delegate)、数据源模式(DataSource)。
Swift Protocol
swift 复制代码
protocol Drawable {
    func draw()
}

// 协议扩展提供默认实现
extension Drawable {
    func draw() { print("默认绘制") }
}

struct Circle: Drawable {} // 结构体遵循协议
class Square: Drawable {}  // 类遵循协议

// 关联类型
protocol Container {
    associatedtype Item
    var items: [Item] { get }
    mutating func add(_ item: Item)
}

// 泛型约束
func process<T: Container>(container: T) where T.Item: Equatable {
    // ...
}
  • 特点:支持面向协议编程,适用于值类型和引用类型,灵活扩展功能。

4. 协议扩展(Swift 独有)

Swift 允许通过扩展为协议添加默认实现,而 Objective-C 无法实现:

swift 复制代码
protocol Loggable {
    func log()
}

extension Loggable {
    func log() { print("日志记录") }
}

struct User: Loggable {} // 自动获得默认 log() 实现

5. 可选方法的实现方式

  • Objective-C :显式标记 @optional,调用时需检查 respondsToSelector:

    objc 复制代码
    @protocol NetworkDelegate <NSObject>
    @optional
    - (void)didReceiveData:(NSData *)data;
    @end
    
    // 调用前检查
    if ([delegate respondsToSelector:@selector(didReceiveData:)]) {
        [delegate didReceiveData:data];
    }
  • Swift :通过协议扩展或 @objc optional(仅限类)实现可选方法。

    swift 复制代码
    @objc protocol NetworkDelegate {
        @objc optional func didReceiveData(_ data: Data)
    }
    
    class Handler: NetworkDelegate {} // 可选实现方法
    
    // 调用时检查
    delegate?.didReceiveData?(data)

6. 总结

场景 Objective-C Swift
接口定义 委托、数据源等简单场景 面向协议编程、泛型抽象、功能扩展
类型支持 仅类 类、结构体、枚举
灵活性 基础功能,依赖运行时检查 编译时安全,支持默认实现和关联类型
跨平台设计 主要用于 iOS/macOS 开发 跨平台(iOS/macOS/服务器/开源项目)

选择建议

  • Objective-C:在传统项目或需要与 OC 代码交互时使用,适合简单接口定义。
  • Swift:在新项目或需要高度抽象、复用和类型安全时使用,充分发挥面向协议编程的优势。

五、Swift 与 Objective-C 初始化方法(init)有什么不一样?

1. 核心设计理念

  • Swift :强调 安全性严格性 ,通过两段式初始化编译时检查确保对象完整初始化。
  • Objective-C灵活性优先,依赖开发者自觉管理初始化过程,缺少编译时强制约束。

2. 主要区别

特性 Swift Objective-C
初始化阶段 两段式初始化(属性初始化 → 自定义操作) 无明确阶段划分
安全检查 强制所有非可选(non-optional)存储属性初始化 无强制检查,未初始化的属性可能为 nil 或默认值
可选属性 必须显式初始化或声明为 Optional 默认允许 nil(引用类型)
初始化器类型 支持 指定初始化器 (Designated)和 便利初始化器(Convenience) 无明确分类,但可通过 NS_DESIGNATED_INITIALIZER 标记指定初始化器
修饰符 convenience(便利初始化器)、required(强制子类实现)、override(重写父类初始化器) 无类似关键字
可失败初始化器 支持 init?init! 返回 nil 表示失败
继承与重写规则 子类必须重写父类的指定初始化器或自动继承,规则严格 子类可自由重写初始化器,无强制要求
值类型支持 结构体(struct)、枚举(enum)可定义初始化器 仅类(class)支持初始化器

3. 关键机制详解

(1) 两段式初始化(Swift 独有)
  • 阶段 1:确保所有存储属性被初始化。

  • 阶段 2 :在属性初始化完成后,进一步自定义实例(如调用方法、访问 self)。

  • 优势:避免属性未初始化就被使用,提升安全性。

  • 示例

    swift 复制代码
    class Person {
        var name: String
        var age: Int
        
        // 指定初始化器
        init(name: String, age: Int) {
            self.name = name // 阶段1:初始化属性
            self.age = age
            // 阶段2:可调用方法
            setup()
        }
        
        convenience init() {
            self.init(name: "Unknown", age: 0) // 必须调用指定初始化器
        }
        
        private func setup() { /*...*/ }
    }
(2) 强制属性初始化(Swift)
  • 所有非可选存储属性必须在初始化完成前赋值,否则编译器报错。

  • 示例

    swift 复制代码
    class User {
        var id: Int    // 错误:未初始化
        var name: String?
        
        init(id: Int) {
            self.id = 1234 // 正确:非可选属性必须赋值
        }
    }
(3) 初始化器修饰符(Swift)
  • convenience :定义便利初始化器,必须调用同类中的指定初始化器。

    swift 复制代码
    class Rectangle {
        var width: Double
        var height: Double
        
        init(width: Double, height: Double) {
            self.width = width
            self.height = height
        }
        
        convenience init(side: Double) {
            self.init(width: side, height: side) // 调用指定初始化器
        }
    }
  • required :强制子类实现该初始化器。

    swift 复制代码
    class Vehicle {
        required init() { /*...*/ }
    }
    
    class Car: Vehicle {
        required init() { // 必须实现
            super.init()
        }
    }
(4) 可失败初始化器
  • Swift :通过 init? 返回可选实例,init! 返回隐式解包实例。

    swift 复制代码
    struct Temperature {
        let celsius: Double
        init?(celsius: Double) {
            guard celsius >= -273.15 else { return nil }
            self.celsius = celsius
        }
    }
  • Objective-C :返回 nil 表示失败。

    objc 复制代码
    @interface MyClass : NSObject
    - (instancetype)initWithValue:(NSInteger)value;
    @end
    
    @implementation MyClass
    - (instancetype)initWithValue:(NSInteger)value {
        if (value < 0) return nil;
        self = [super init];
        return self;
    }
    @end

4. 初始化器继承规则

  • Swift
    • 子类默认不继承父类初始化器。
    • 若子类未定义任何指定初始化器,则自动继承父类所有指定初始化器。
    • 若子类实现了父类所有指定初始化器,则自动继承父类便利初始化器。
  • Objective-C:子类自动继承父类所有初始化器,除非显式重写。

5. 总结

场景 Swift Objective-C
安全性 高(编译时强制检查) 低(依赖开发者自觉)
灵活性 较低(严格规则限制) 高(自由定义初始化逻辑)
代码复杂度 高(需遵循两段式、修饰符规则) 低(简单直接)
适用类型 类、结构体、枚举 仅类
错误处理 可失败初始化器、异常抛出 返回 nilNSError

选择建议

  • Swift:在需要高安全性、复杂类型设计的场景下使用,遵循严格初始化规则。
  • Objective-C:在维护旧项目或需要快速灵活初始化时使用,但需注意潜在风险。
相关推荐
lilili啊啊啊10 小时前
IOS奔溃日志分析-克魔ios开发助手实战-以支付宝奔溃日志为例
ios
lichao89042712 小时前
JBDev - Theos下一代越狱开发工具
ios
二流小码农15 小时前
鸿蒙开发:使用Ellipse绘制椭圆
android·ios·harmonyos
二流小码农16 小时前
鸿蒙开发:使用Circle绘制圆形
android·ios·harmonyos
Macle_Chen16 小时前
XCode中使用MonkeyDev开发iOS版的Comand-line Tool的daemon程序
macos·ios·xcode·逆向·comand-line
二流小码农17 小时前
鸿蒙开发:使用Rect绘制矩形
android·ios·harmonyos
丁乾坤的博客19 小时前
iOS审核被拒:Missing privacy manifest 第三方库添加隐私声明文件
ios·manifest·第三方库隐私文件
Unlimitedz20 小时前
iOS GCD
macos·ios·cocoa
布多21 小时前
AutoreleasePool:iOS 内存管理乐章中的隐秘旋律
ios·源码阅读
YungFan21 小时前
SwiftUI-国际化
ios·swiftui·swift