Upgrade to Pro — share decks privately, control downloads, hide ads and more …

個人アプリを2年ぶりにアプデしたから褒めて / I just updated my perso...

Elvis Shi
January 25, 2025

個人アプリを2年ぶりにアプデしたから褒めて / I just updated my personal app, praise me!

Elvis Shi

January 25, 2025
Tweet

More Decks by Elvis Shi

Other Decks in Programming

Transcript

  1. } var employedBy = "YUMEMI Inc." var job = "iOS

    Developer" var organizerOf = "HAKATA.swift" var favoriteLanguage = "Swift" var twitter = "@lovee" var qiita = "lovee" var github = "el-hoshino" var additionalInfo = """ ߴࢁߦ͖ͬͯͨΒࡢ೔ͷωΧϑΣ νΣοΫΞ΢τ͢Δͷ๨ΕͪΌͬͯ ߴࢁ͔Βͷ໭Γʹ߄͓ͯͯళߦͬͨΒौ଺ʹר͖ࠐ·ΕͪΌͬͨ """ final class Me: Developable, Talkable {
  2. ཤྺػೳɿηʔϒσʔλόʔδϣχϯά @Observable public final class HistoryManager: Sendable { private enum

    SaveDataVersion: Codable { case v1 } @MainActor private let userDefaults: UserDefaults = .init(suiteName: defaultSuiteName) ?? { assertionFailure("Failed to initialize UserDefaults with suite name \(defaultSuiteName)") return .standard }() private let defaultsVersionKey = "HistoryManagerDefaultsVersion" @MainActor private var histories: [History]? { didSet { assert(canSetHistories(against: oldValue)) saveHistoriesIfAvailable() } } @MainActor public init() { // Load the history list from the user defaults. guard let historyDataVersion: SaveDataVersion = userDefaults.loadCodable(forKey: defaultsVersionKey) else { defer { initializationFinished = true } self.histories = [] return } Task { defer { initializationFinished = true } await migrateSaveDataVersionIfNeeded(from: historyDataVersion) await loadHistoriesFromUserDefaults() } } private func migrateSaveDataVersionIfNeeded(from existVersion: SaveDataVersion) async { // Currently only v1, so does nothing. assert(existVersion == .v1) } @MainActor private func saveHistoriesIfAvailable() { guard let histories else { return } userDefaults.saveCodable(SaveDataVersion.v1, forKey: defaultsVersionKey) userDefaults.saveCodable(histories, forKey: defaultsHistoryKey) userDefaults.synchronize() } // ... }
  3. ཤྺػೳɿཤྺͷఴ࡟ @Observable public final class HistoryManager: Sendable { private let

    maxHistoryCount = 50 @discardableResult @MainActor private func waitForHistories() async -> [History] { while true { if let histories { return histories } await Task.yield() } } //... } extension HistoryManager: HistoryManagerProtocol { public func addHistory(_ history: History) async { await waitForHistories() await MainActor.run { // If history already exist, remove it before adding it so it'll be listed at last. while let existHistoryIndex = histories?.firstIndex(of: history) { histories?.remove(at: existHistoryIndex) } histories?.append(history) // If the list is too long, remove the oldest history. while (histories?.count ?? 0) >= maxHistoryCount { self.histories?.removeFirst() } } } public func deleteHistory(_ history: History) async { await waitForHistories() // If the history is not in the list, do nothing. await MainActor.run { [self] in while let index = histories?.firstIndex(of: history) { self.histories?.remove(at: index) } } } @MainActor public func getHistories() -> [History] { // Return the list in the order of the most recent history. guard let histories else { return [] } return histories.reversed() } }
  4. ཤྺػೳɿཤྺը໘ public struct TextHistoryView: View { @Environment(\.histories) var histories: [History]?

    @Environment(\.deleteHistory) var deleteHistory: DeleteHistoryAction public var body: some View { if let histories { contents(of: histories) } else { Text("Loading...") } } @ViewBuilder private func contents(of histories: [History]) -> some View { if histories.isEmpty { Text("No history available.") } else { NavigationSplitView { List { historyList(of: histories) } .navigationBarTitle("History") .animation(.default, value: histories) } detail: { Text("Select a history.") } } } private func historyList(of histories: [History]) -> some View { ForEach(histories, id: \.self) { text in NavigationLink(text) { QRCodeImageView( content: text, shouldAddContentToHistory: false ) } .swipeActions(edge: .trailing) { Button(role: .destructive) { Task { await deleteHistory(of: text) } } label: { Label("Delete", systemImage: "trash") } } } } }
  5. ͓·͚ɿ&OWJSPONFOU"DUJPO public struct DeleteHistoryAction: Sendable { private var execution: @Sendable

    (_ history: History) async -> Void fileprivate init(execution: @Sendable @escaping (History) async -> Void) { self.execution = execution } public static func preview(with execution: @Sendable @escaping (History) async -> Void = { _ in }) -> Self { return self.init(execution: { print(#file, #line, #function) await execution($0) }) } public func callAsFunction(of history: History) async { await execution(history) } } private extension HistoryManagerProtocol { var deleteHistoryAction: DeleteHistoryAction { .init(execution: { history in await deleteHistory(history) }) } } public extension EnvironmentValues { @Entry var historyManager: HistoryManagerProtocol? var deleteHistory: DeleteHistoryAction { historyManager?.deleteHistoryAction ?? .preview { assertionFailure("Failed to delete \($0): HistoryManager not exist.") } } }