关闭

Swift 编程笔记

常规关键词

属性包装器

@Published, @ObservedObject, @State, 和 @Binding 是 SwiftUI 中的一些属性包装器,它们用于处理视图状态和数据流。这些属性包装器在 SwiftUI 的声明性 UI 编程范式中扮演着核心的角色。我们来一起详细了解它们:

@Published

用法: @Published 是用于在类(通常是 ObservableObject)中标记可观察的属性。当这些属性的值发生变化时,所有订阅这些更改的观察者都将收到通知。

注意事项:

  • @Published 只能用于类的属性。
  • 使用 @Published 时,必须让类遵守 ObservableObject 协议。
class ExampleModel: ObservableObject {
    @Published var count = 0
}

@ObservedObject

用法: @ObservedObject 用于在视图中订阅一个遵守 ObservableObject 协议的对象,并在对象的 @Published 属性发生变化时更新视图。

注意事项:

  • ObservedObject 发生改变时,视图将重新渲染。
  • 通常用于更复杂的数据模型或来自外部源的数据。
struct ExampleView: View {
    @ObservedObject var model: ExampleModel
    
    var body: some View {
        Text("\(model.count)")
    }
}

@State

用法: @State 用于在视图内部管理私有状态。当 @State 变量的值发生变化时,视图将更新。

注意事项:

  • @State 应该用于视图私有的简单状态管理。
  • 它在视图结构体之外创建一个存储,允许在值类型的视图中持有可变状态。
struct ExampleView: View {
    @State private var isToggled = false
    
    var body: some View {
        Toggle(isOn: $isToggled) {
            Text("Toggle me!")
        }
    }
}

@Binding

用法: @Binding 用于创建对其他视图中状态的引用。它允许一个视图修改另一个视图的状态。

注意事项:

  • 使用 $ 前缀来传递 @State 变量的绑定。
  • @Binding 不拥有其引用的状态的源。
struct ToggleSwitch: View {
    @Binding var isToggled: Bool
    
    var body: some View {
        Toggle(isOn: $isToggled) {
            Text("Toggle me!")
        }
    }
}

struct ContentView: View {
    @State private var isToggled = false
    
    var body: some View {
        ToggleSwitch(isToggled: $isToggled)
    }
}

在 SwiftUI 的设计中,@Published, @ObservedObject, @State, 和 @Binding 一起提供了一套强大的工具来在不同视图和数据模型之间管理和传递状态。了解并熟悉这些概念对于建立健壮的 SwiftUI 应用程序至关重要。

是的,SwiftUI 还提供了其他几种属性包装器来满足不同的使用场景。除了您已提到的 @Published, @ObservedObject, @State, 和 @Binding 以外,下面是一些其他常用的属性包装器:

@EnvironmentObject

用法: @EnvironmentObject 用于在视图层级中共享一个数据对象。它允许一个对象能够被多个视图访问,而不必显式地传递它。

注意事项:

  • 需要在视图层级的某个地方用 .environmentObject(_:) 修饰符来提供数据。
struct ExampleView: View {
    @EnvironmentObject var settings: SettingsModel
    
    var body: some View {
        Text("Using settings: \(settings.someSetting)")
    }
}

确保在视图层次结构的早期阶段注入@EnvironmentObject非常重要,因为这样做可以确保该对象在下游所有依赖它的视图中都是可访问的。下面的示例展示了如何正确地使用.environmentObject(_:)修饰符来注入一个对象实例。

// 定义一个 ObservableObject 类型的类,它将在多个视图间共享
class SharedUserSettings: ObservableObject {
    @Published var username: String = "User"
}

// 主视图
struct ContentView: View {
    // 使用 @EnvironmentObject 属性包装器来声明对共享对象的需求
    @EnvironmentObject var settings: SharedUserSettings
    
    var body: some View {
        VStack {
            // 该视图使用共享的 settings 对象
            Text("Username: \(settings.username)")
            
            // 下一个视图也需要访问共享的 settings 对象
            DetailView()
        }
        // 注意没有在这里使用 .environmentObject(_:) 修饰符
    }
}

// 详细视图
struct DetailView: View {
    // 同样使用 @EnvironmentObject 来声明对共享对象的需求
    @EnvironmentObject var settings: SharedUserSettings
    
    var body: some View {
        Text("Detailed view for user: \(settings.username)")
        // 注意没有在这里使用 .environmentObject(_:) 修饰符
    }
}

// 预览视图
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        // 使用 .environmentObject(_:) 修饰符在预览中注入共享对象
        ContentView().environmentObject(SharedUserSettings())
    }
}

// 在 App 的主入口处注入共享对象
@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            // 在此处使用 .environmentObject(_:) 修饰符来注入共享对象
            ContentView().environmentObject(SharedUserSettings())
        }
    }
}

在上述代码中:

  • SharedUserSettings 是一个ObservableObject,用于在视图之间共享数据。
  • ContentViewDetailView 使用 @EnvironmentObject 属性包装器声明对 SharedUserSettings 对象的依赖关系。
  • YourAppWindowGroup 中,.environmentObject(_:) 修饰符被用于 ContentView 来注入 SharedUserSettings 实例。这样,ContentView 以及它的所有子视图(如 DetailView)都能访问这个共享对象。
  • 同样的,为了在预览中正确显示视图,也需要在 ContentView_Previews 中使用 .environmentObject(_:)

这样就确保了在视图层次结构的早期阶段就注入了共享的对象实例,使得所有子视图都可以无缝访问它。

@AppStorage

用法: @AppStorage 是一个便捷的存储简单数据(例如:Int, String, Bool 等)到用户的设置中的方式。

注意事项:

  • 使用时要提供一个键来关联存储的值。
struct ExampleView: View {
    @AppStorage("username") var username: String = "Guest"
    
    var body: some View {
        Text("Hello, \(username)!")
    }
}

@Environment

用法: @Environment 用来访问和使用环境中的值。例如,使用系统的大小类或布局方向等。

注意事项:

  • 不要尝试修改 @Environment 变量的值。
struct ExampleView: View {
    @Environment(\.sizeCategory) var sizeCategory
    
    var body: some View {
        if sizeCategory >= .accessibilityLarge {
            Text("Large Text")
        } else {
            Text("Regular Text")
        }
    }
}

在 SwiftUI 中,EnvironmentKeyEnvironmentValues 一起工作,允许您在视图层次结构中插入和提取环境值。环境值通常用于在视图树中多个视图之间共享值或状态,而不需要显式地将它们传递作为参数。环境值对于定义视图的外观和行为的系统设置特别有用,例如:字体大小、颜色方案或布局方向。

使用 .environment 修饰符可以设置特定视图及其所有子视图的环境值。当您在视图树中更深的位置设置环境值时,它会覆盖更高级别的值。

在您给出的示例中:

someview.environment(\.isHovered, isHovered)

这里,假设 isHovered 是一个自定义的 EnvironmentKey。首先,我们来看一下怎样可能定义这个环境键:

struct IsHoveredKey: EnvironmentKey {
    static var defaultValue: Bool = false
}

extension EnvironmentValues {
    var isHovered: Bool {
        get { self[IsHoveredKey.self] }
        set { self[IsHoveredKey.self] = newValue }
    }
}

在上述代码中:

  • IsHoveredKey 是一个结构体,实现了 EnvironmentKey 协议,并且定义了一个 defaultValue。这是如果环境中没有提供值时将要使用的值。
  • 通过扩展 EnvironmentValues 并使用键的类型 (IsHoveredKey.self) 作为下标,我们提供了一个获取和设置环境变量的便捷方法。

然后,我们就可以用 .environment(\.isHovered, isHovered) 来为特定视图及其子视图设置环境值了。在视图或其子视图中,您可以这样使用它:

struct MyView: View {
    @Environment(\.isHovered) var isHovered
    
    var body: some View {
        Text("Is hovered: \(isHovered ? "Yes" : "No")")
    }
}

在这里,@Environment(\.isHovered) var isHovered 会自动从环境中取得 isHovered 的值。

总的来说,使用 EnvironmentKey 可以使我们在视图层级中轻松共享状态和数据,而不需要通过复杂的传递链将数据从父视图传递到深层子视图。

@FetchRequest

用法: 在使用 Core Data 时,@FetchRequest 用于在您的视图中创建一个从您的 Core Data store 中检索数据的请求。

注意事项:

  • 配置时要提供排序描述符和获取请求。
struct ExampleView: View {
    @FetchRequest(
        entity: Item.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)]
    ) private var items: FetchedResults<Item>
    
    var body: some View {
        List(items, id: \.self) { item in
            Text("Item at \(item.timestamp!, formatter: itemFormatter)")
        }
    }
}

每个属性包装器都针对特定的使用案例设计,并在 SwiftUI 应用的数据流管理中起着不同的作用。理解它们的功能和适用场景有助于更高效地使用 SwiftUI 构建用户界面。

guard

guard 语句在 Swift 中用于提前退出一个代码块,如果一个或多个条件不满足。它常用于减少嵌套代码的复杂性,并在进一步的代码执行之前,检查必要条件是否满足。

基本用法:

guard someCondition else {
    // 这个代码块在 someCondition == false 时执行。
    // 通常你在这里处理不满足条件时的情况,比如返回一个函数或抛出一个错误。
    return // 或 throw 或 break 等
}

// 在这里,你可以确定 someCondition == true,所以你安全地进行其它的操作

使用时的几点注意:

  1. 提前退出guard 语句允许我们在不满足特定条件的情况下提前退出函数、方法或循环。

  2. 减少嵌套: 与 if 语句不同的是,如果 guard 的条件不满足,else 代码块中的代码将执行,并且整个作用域将被退出(使用 returnbreakcontinuethrow)。这减少了代码的嵌套,使其更加易读。

  3. 保持变量和常量的作用域guard 语句允许在其之后的代码中使用在条件语句中定义的常量和变量。换句话说,你检查的常量和变量在 guard 语句后仍然是可用的。

使用示例:

func someFunction(input: String?) {
    // 检查 input 是否有值
    guard let nonOptionalInput = input else {
        print("Input is nil")
        return
    }

    // 由于 guard 语句确保了 input 不是 nil,所以 nonOptionalInput 现在是非可选的,
    // 我们可以安全地使用它,没有需要检查它是否为 nil。
    print("Input is: \(nonOptionalInput)")
}

在这个示例中:

  • 如果 inputnilelse 代码块将会执行,打印一条消息,并退出函数。
  • 如果 input 不是 nil,它的值会被赋给 nonOptionalInput,并且函数会继续执行 guard 之后的代码。

在 Swift 中,guard 是一种强大的工具,它可以帮助你更清晰、更安全地管理你的代码的控制流程和可选值的解包。

URLSession

当然可以。下面是一个基本的 URLSession 使用示范,通过一个简单的 GET 请求来获取数据。我们将获取一个 JSON 数据,并在控制台打印出来:

import Foundation

// 创建一个 URL 对象
guard let url = URL(string: "https://api.example.com/data") else {
    fatalError("Invalid URL")
}

// 创建一个 URLSession 对象
let session = URLSession.shared

// 创建一个 data task 对象来执行 GET 请求
let task = session.dataTask(with: url) { data, response, error in
    // 确保没有错误发生
    guard error == nil else {
        print("Error: \(error!.localizedDescription)")
        return
    }
    
    // 确保我们收到了数据
    guard let data = data else {
        print("No data received.")
        return
    }
    
    // 尝试解析 JSON
    do {
        let json = try JSONSerialization.jsonObject(with: data, options: [])
        print("JSON: \(json)")
        DispatchQueue.main.async {
            // 在主线程中更新 UI,或者更新影响 UI 的数据结构
        }
    } catch {
        print("Unable to parse JSON: \(error.localizedDescription)")
    }
}

// 开始执行任务
task.resume()

这个示例演示了如何使用 URLSession 来进行网络请求并处理返回的数据。我们创建一个 URL 对象、一个 URLSession 任务,并在任务的回调中处理可能发生的错误以及接收到的数据。在这个回调中,我们也尝试解析 JSON 数据并打印它。最后,我们使用 resume() 开始任务。

注意:在实际的应用中,你可能需要在 UI 更新中处理解析到的数据,这时确保你的 UI 更新在主线程上执行,以避免不可预测的行为。你可以使用 DispatchQueue.main.async {} 来在主线程中更新 UI。

URLSession 是一个相当强大的工具,用于在 iOS, macOS, watchOS, 和 tvOS app 中管理 HTTP 和 HTTPS 网络请求。在涉及到身份验证和会话管理时,URLSession 提供了几种不同的行为模式,具体取决于如何创建和配置它。

这里有几个关键点,可能有助于你理解你所描述的行为:

URLSession Configuration

URLSession 对象被创建时,它使用了一个 URLSessionConfiguration 对象。这个配置对象决定了 session 的行为。它有几种类型:

  • .default: 使用磁盘存储缓存、Cookie 和证书存储。如果你不做任何配置,session 将会保留所有的 cookies 和缓存数据,直到它们到期或者被清除。

  • .ephemeral: 不会在磁盘上存储任何东西。所有的缓存、证书存储和 cookies 都保存在 RAM 中,并且在 session 任务结束时被清除。

  • .background: 用于在后台执行上传和下载任务。

当你使用 URLSession 并成功进行身份验证(例如,登录到 GitHub)时,任何从服务器返回的 cookies(包括 session cookies 用于身份验证)通常都会自动存储在 session 中。如果你使用 .default 配置,这些 cookies 将被持久化,因此,你的应用将“记住”登录状态,即使在不同的 URLSession 实例和 app 重启之间也是如此。

如果你不希望这种行为,可以使用 .ephemeral 配置或者定制你自己的 URLSessionConfiguration 来改变 cookie 的存储行为。

例如,创建一个不保存 cookies 的 session 可以这样做:

let configuration = URLSessionConfiguration.ephemeral
let session = URLSession(configuration: configuration)

或者,你可以细粒度控制 cookies 的行为:

let configuration = URLSessionConfiguration.default
configuration.httpShouldSetCookies = false
configuration.httpCookieAcceptPolicy = .never
configuration.httpCookieStorage = nil
let session = URLSession(configuration: configuration)

请注意,这样的设置可能会影响你的网络请求(例如,不再保持登录状态),确保这是你想要的行为。

在 Swift 中,你可以使用 HTTPCookieStorage 类来清除或管理存储在你的 app 中的 cookies。这个类提供了几个方法来获取和删除存储在用户的设备上的 cookies。

退出登录

由于 URLSession 很方便,但是退出的时候就需要考虑清除一些方便,比如自测切换账号的时候,你就需要清除一些 cookie。

以下是一些用来清除 cookies 的基本方法:

你可以通过遍历 HTTPCookieStorage 中的所有 cookies,然后通过 URL 或者名称找到并移除特定的 cookie。例如:

if let cookies = HTTPCookieStorage.shared.cookies {
    for cookie in cookies {
        if cookie.name == "yourCookieName" {
            HTTPCookieStorage.shared.deleteCookie(cookie)
        }
    }
}

要删除所有的 cookies,你可以遍历 HTTPCookieStorage 中的所有 cookies 并删除它们:

if let cookies = HTTPCookieStorage.shared.cookies {
    for cookie in cookies {
        HTTPCookieStorage.shared.deleteCookie(cookie)
    }
}

或者在 macOS 10.11+, iOS 9.0+ 等系统中,你可以直接调用:

HTTPCookieStorage.shared.removeCookies(since: Date.distantPast)

如果你想要清除一个特定域名的所有 cookies,你可以这样操作:

if let cookies = HTTPCookieStorage.shared.cookies(for: URL(string: "https://www.yourdomain.com")!) {
    for cookie in cookies {
        HTTPCookieStorage.shared.deleteCookie(cookie)
    }
}

清除所有会话 Cookies

你也可以设置 session cookies 来在 app 退出后自动清除它们:

var cookieProperties = [HTTPCookiePropertyKey: Any]()
cookieProperties[.name] = "yourCookieName"
cookieProperties[.value] = "yourCookieValue"
cookieProperties[.domain] = "yourdomain.com"
cookieProperties[.path] = "/"
cookieProperties[.expires] = Date(timeIntervalSinceNow: -3600)

let cookie = HTTPCookie(properties: cookieProperties)
HTTPCookieStorage.shared.setCookie(cookie!)

这些方法通常能够满足你清除 cookies 的需求。

邀请标记你的阅读体验😉 | →