Slide 1

Slide 1 text

ݸਓΞϓϦΛ೥ͿΓʹ Ξϓσ͔ͨ͠Β๙Ίͯ f o r . J OP L B NP T X J G U

Slide 2

Slide 2 text

} 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 {

Slide 3

Slide 3 text

͜ͷΞϓϦ࡞Γ·ͨ͠

Slide 4

Slide 4 text

w"OESPJEͱڞ༗Ͱ͖ͳ͍ wҰ౓ʹෳ਺ͷJ1IPOFͱ ڞ༗͠ʹ͍͘ ͜ͷΞϓϦ࡞Γ·ͨ͠

Slide 5

Slide 5 text

ͦ ͏ ͩ 23 Ͱ ỏ ڞ ༗ ͠ Α ͏ Ố

Slide 6

Slide 6 text

ͱ͍͏Θ͚Ͱ೥ͿΓʹ ͜ͷΞϓϦΛΞϓσͨ͠

Slide 7

Slide 7 text

ओͳߋ৽఺ w ཤྺػೳΛ௥Ճͨ͠!" w ϓϩδΣΫτߏ੒Λ4XJGU1.ϕʔεʹ w σʔλϑϩʔΛ0CTFSWBUJPOʹ w ඇಉظॲཧΛ4XJGU$PODVSSFODZʹ w ݴޠόʔδϣϯΛ4XJGUʹ

Slide 8

Slide 8 text

ཤྺػೳɿηʔϒσʔλόʔδϣχϯά @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() } // ... }

Slide 9

Slide 9 text

ཤྺػೳɿཤྺͷఴ࡟ @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() } }

Slide 10

Slide 10 text

ཤྺػೳɿཤྺը໘ 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") } } } } }

Slide 11

Slide 11 text

4XJGU1.ߏ੒

Slide 12

Slide 12 text

͓·͚ɿ&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.") } } }

Slide 13

Slide 13 text

ৄ͘͠͸(JU)VCͰʂ IUUQTHJUIVCDPNFMIPTIJOP2VJDLTIB3F

Slide 14

Slide 14 text

ৄ͘͠͸(JU)VCͰʂ IUUQTHJUIVCDPNFMIPTIJOP2VJDLTIB3F