Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion SwiftDemos/SwiftDemos/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
import SwiftUI

struct ContentView: View {
private var demos = ["COW"]
private var demos = ["COW", "PropertyWrapper"]

var body: some View {
NavigationStack {
List {
ForEach(demos.enumerated(), id: \.element) { idx, item in
if idx == 0 {
NavigationLink(item, destination: COWView())
} else if idx == 1 {
NavigationLink(item, destination: PropertyWrapperView())
}
}
}
Expand Down
8 changes: 0 additions & 8 deletions SwiftDemos/SwiftDemos/Features/PropertyWrapper.swift

This file was deleted.

4 changes: 2 additions & 2 deletions SwiftDemos/SwiftDemos/Features/TypedThrows.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ enum FileError: Error {

// throws:声明函数可能抛出错误
func fetchData(from urlString: String) throws -> Data {
guard let url = URL(string: urlString) else {
guard let _ = URL(string: urlString) else {
throw NetworkError.invalidURL
}
// ...
Expand Down Expand Up @@ -55,7 +55,7 @@ func fetch() throws(NetworkError) -> Data {
func test2() {
// 调用方 catch 分支无需类型转换
do {
let data = try fetch()
let _ = try fetch()
} catch {
// error 的类型是 NetworkError(不是 any Error)
switch error {
Expand Down
57 changes: 57 additions & 0 deletions SwiftDemos/SwiftDemos/PropertyWrapper/PropertyWrapper.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// PropertyWrapper.swift
// SwiftDemos
//
// Created by Jinchao Lin on 2026/2/25.
//

import Foundation
import Combine

@propertyWrapper
struct UserDefault<T> {
let key: String
let defaultValue: T

var wrappedValue: T {
get {
UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
nonmutating set {
UserDefaults.standard.set(newValue, forKey: key)
}
}

// 通过 $ 访问
var projectedValue: AnyPublisher<T, Never> {
/// publisher(for:) 是 Combine 的 KVO Publisher,\.self 表示观察 UserDefaults 对象本身。
/// 但 UserDefaults.standard 是单例,对象引用永远不变,KVO 只在订阅时触发一次初始值,之后不再触发
// UserDefaults.standard
// .publisher(for: \.self) // 监听整个 UserDefaults
// .compactMap { $0.object(forKey: self.key) as? T }
// .eraseToAnyPublisher()

NotificationCenter.default
.publisher(for: UserDefaults.didChangeNotification)
.map { _ in UserDefaults.standard.object(forKey: key) as? T ?? defaultValue }
.prepend(wrappedValue) // 补一个初始值,行为与之前一致
.eraseToAnyPublisher()
}
}

/*
// 源码
struct Foo {
@UserDefault(key: "x", defaultValue: 0)
var x: Int
}

// 编译器生成(等价代码)
struct Foo {
private var _x = UserDefault<Int>(key: "x", defaultValue: 0)
var x: Int {
get { _x.wrappedValue }
set { _x.wrappedValue = newValue }
}
}
*/
35 changes: 35 additions & 0 deletions SwiftDemos/SwiftDemos/PropertyWrapper/PropertyWrapperView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// PropertyWrapperView.swift
// SwiftDemos
//
// Created by Jinchao Lin on 2026/2/26.
//

import SwiftUI

struct PropertyWrapperView: View {
@UserDefault(key: "isDarkMode", defaultValue: false)
var isDarkMode: Bool // _isDarkMode.wrappedValue

var body: some View {
VStack {
Button("Switch to Dark") {
isDarkMode = true
}
.padding()

Button("Switch to White") {
isDarkMode = false
}
.padding()
}
.navigationTitle("Property Wrapper")
.onReceive($isDarkMode) { newValue in
debugPrint("isDarkMode changed to \(newValue)")
}
}
}

#Preview {
PropertyWrapperView()
}