diff --git a/App/MutaroDev/MutaroDev.xcodeproj/project.pbxproj b/App/MutaroDev/MutaroDev.xcodeproj/project.pbxproj index 1843ea85..2cdd8c8a 100644 --- a/App/MutaroDev/MutaroDev.xcodeproj/project.pbxproj +++ b/App/MutaroDev/MutaroDev.xcodeproj/project.pbxproj @@ -195,7 +195,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif [ $CI ]; then\n export PATH=\"$PATH:/usr/local/bin\"\nelse\n export PATH=\"$PATH:/opt/homebrew/bin\" \nfi\n\nif which needle; then\n echo \"Mins: Create Needle\"\n if [ $CI ]; then\n NEEDLE_SWIFT_FILE_PATH=${CI_PRIMARY_REPOSITORY_PATH}/MutaroModule/Sources/App/Development/DI/NeedleGenerated.swift\n COMPONENT_PATH=${CI_PRIMARY_REPOSITORY_PATH}/MutaroModule/Sources/\n else\n NEEDLE_SWIFT_FILE_PATH=../../MutaroModule/Sources/App/Development/DI/NeedleGenerated.swift\n COMPONENT_PATH=../../MutaroModule/Sources/\n fi\n \n needle generate $NEEDLE_SWIFT_FILE_PATH $COMPONENT_PATH --exclude-paths /Production\nelse\n echo \"warning: Needle not installed\"\nfi\n"; + shellScript = "# Type a script or drag a script file from your workspace to insert its path.\nif [ $CI ]; then\n export PATH=\"$PATH:/usr/local/bin\"\nelse\n export PATH=\"$PATH:/opt/homebrew/bin\" \nfi\n\nif which needle; then\n echo \"Mins: Create Needle\"\n if [ $CI ]; then\n NEEDLE_SWIFT_FILE_PATH=${CI_PRIMARY_REPOSITORY_PATH}/MutaroModule/Sources/App/Development/DI/NeedleGenerated.swift\n COMPONENT_PATH=${CI_PRIMARY_REPOSITORY_PATH}/MutaroModule/Sources/\n else\n NEEDLE_SWIFT_FILE_PATH=../../MutaroModule/Sources/App/Development/DI/NeedleGenerated.swift\n COMPONENT_PATH=../../MutaroModule/Sources/\n fi\n rm -rf $NEEDLE_SWIFT_FILE_PATH\n needle generate $NEEDLE_SWIFT_FILE_PATH $COMPONENT_PATH --exclude-paths /Production\nelse\n echo \"warning: Needle not installed\"\nfi\n"; }; 71D24CCC2A318EC300092101 /* [NEED FIRST RUN] register env */ = { isa = PBXShellScriptBuildPhase; diff --git a/BuildTools/Package.swift b/BuildTools/Package.swift index 84dd64c6..e03d730a 100644 --- a/BuildTools/Package.swift +++ b/BuildTools/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.7 +// swift-tools-version: 5.10 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -21,8 +21,8 @@ let package = Package( ), .binaryTarget( name: "LicensePlistBinary", - url: "https://github.com/mono0926/LicensePlist/releases/download/3.24.9/LicensePlistBinary-macos.artifactbundle.zip", - checksum: "57bc9aad97510ee73af979bb3a8dc63653e522dce71a505c13059ab54d6fc990" + url: "https://github.com/mono0926/LicensePlist/releases/download/3.25.1/LicensePlistBinary-macos.artifactbundle.zip", + checksum: "a80181eeed49396dae5d3ce6fc339f33a510299b068fd6b4f507483db78f7f30" ) ] ) diff --git a/MutaroModule/Package.swift b/MutaroModule/Package.swift index 275d87bd..cf6fb58f 100644 --- a/MutaroModule/Package.swift +++ b/MutaroModule/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.8 +// swift-tools-version: 5.10 // The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription @@ -36,7 +36,7 @@ let firebaseAnalyticsDependencies: [Target.Dependency] = [ let debugSwiftSettings: [PackageDescription.SwiftSetting] = [ .define("DEV", .when(configuration: .debug)), - .unsafeFlags(["-strict-concurrency=complete"]), + .unsafeFlags(["-strict-concurrency=complete", "-warn-concurrency", "-enable-actor-data-race-checks"]), .forwardTrailingClosures, .existentialAny, .bareSlashRegexLiterals, @@ -244,7 +244,9 @@ let package = Package( ), .target( name: "KeychainStore", - dependencies: [], + dependencies: [ + .core + ], path: "./Sources/Modules/KeychainStore" ), .target( diff --git a/MutaroModule/Sources/App/Development/DI/DevRootComponent.swift b/MutaroModule/Sources/App/Development/DI/DevRootComponent.swift index 499aee8d..a4a5dc3e 100644 --- a/MutaroModule/Sources/App/Development/DI/DevRootComponent.swift +++ b/MutaroModule/Sources/App/Development/DI/DevRootComponent.swift @@ -8,6 +8,7 @@ import Client import Core import ImageLoader +import KeychainStore import NeedleFoundation import AppIntroductionFeature @@ -71,4 +72,10 @@ public final class DevRootComponent: BootstrapComponent { ImageDownloadServiceImp() } } + + public var keychainDataStore: any KeychainDataStoreProtocol { + shared { + KeychainDataStore() + } + } } diff --git a/MutaroModule/Sources/App/Development/DI/NeedleGenerated.swift b/MutaroModule/Sources/App/Development/DI/NeedleGenerated.swift index 15a50735..6501c2ef 100644 --- a/MutaroModule/Sources/App/Development/DI/NeedleGenerated.swift +++ b/MutaroModule/Sources/App/Development/DI/NeedleGenerated.swift @@ -6,6 +6,7 @@ import Core import Foundation import HomeFeature import ImageLoader +import KeychainStore import MyAppToolsFeature import MyAppsFeature import NeedleFoundation @@ -41,6 +42,9 @@ private class MyAppToolsFeatureDependency8819cdcb20ff8ed502dcProvider: MyAppTool var client: any Providable { return devRootComponent.client } + var keychainDataStore: any KeychainDataStoreProtocol { + return devRootComponent.keychainDataStore + } private let devRootComponent: DevRootComponent init(devRootComponent: DevRootComponent) { self.devRootComponent = devRootComponent @@ -51,15 +55,17 @@ private func factory1cd9013174eed344cbd4295202051d8ff8d8a13a(_ component: Needle return MyAppToolsFeatureDependency8819cdcb20ff8ed502dcProvider(devRootComponent: parent1(component) as! DevRootComponent) } private class RegisterJWTFeatureDependencyb8fd3e5e6507e3cea0d9Provider: RegisterJWTFeatureDependency { - - - init() { - + var keychainDataStore: any KeychainDataStoreProtocol { + return devRootComponent.keychainDataStore + } + private let devRootComponent: DevRootComponent + init(devRootComponent: DevRootComponent) { + self.devRootComponent = devRootComponent } } /// ^->DevRootComponent->RegisterJWTFeatureBuilderComponent -private func factory3027719ba7197de4dae7e3b0c44298fc1c149afb(_ component: NeedleFoundation.Scope) -> AnyObject { - return RegisterJWTFeatureDependencyb8fd3e5e6507e3cea0d9Provider() +private func factory3027719ba7197de4dae7295202051d8ff8d8a13a(_ component: NeedleFoundation.Scope) -> AnyObject { + return RegisterJWTFeatureDependencyb8fd3e5e6507e3cea0d9Provider(devRootComponent: parent1(component) as! DevRootComponent) } private class MyAppsFeatureDependency6e3d99de62d490ee639fProvider: MyAppsFeatureDependency { var client: any Providable { @@ -68,6 +74,9 @@ private class MyAppsFeatureDependency6e3d99de62d490ee639fProvider: MyAppsFeature var imageDownloadService: any ImageDownloadService { return devRootComponent.imageDownloadService } + var keychainDataStore: any KeychainDataStoreProtocol { + return devRootComponent.keychainDataStore + } var registerJWTFeatureBuilder: any RegisterJWTFeatureBuildable { return devRootComponent.registerJWTFeatureBuilder } @@ -128,6 +137,7 @@ extension DevRootComponent: Registration { localTable["myAppToolsFeatureBuilder-any MyAppToolsFeatureBuildable"] = { [unowned self] in self.myAppToolsFeatureBuilder as Any } localTable["client-any Providable"] = { [unowned self] in self.client as Any } localTable["imageDownloadService-any ImageDownloadService"] = { [unowned self] in self.imageDownloadService as Any } + localTable["keychainDataStore-any KeychainDataStoreProtocol"] = { [unowned self] in self.keychainDataStore as Any } } } extension HomeFeatureBuilderComponent: Registration { @@ -138,17 +148,19 @@ extension HomeFeatureBuilderComponent: Registration { extension MyAppToolsFeatureComponent: Registration { public func registerItems() { keyPathToName[\MyAppToolsFeatureDependency.client] = "client-any Providable" + keyPathToName[\MyAppToolsFeatureDependency.keychainDataStore] = "keychainDataStore-any KeychainDataStoreProtocol" } } extension RegisterJWTFeatureBuilderComponent: Registration { public func registerItems() { - + keyPathToName[\RegisterJWTFeatureDependency.keychainDataStore] = "keychainDataStore-any KeychainDataStoreProtocol" } } extension MyAppsFeatureBuilderComponent: Registration { public func registerItems() { keyPathToName[\MyAppsFeatureDependency.client] = "client-any Providable" keyPathToName[\MyAppsFeatureDependency.imageDownloadService] = "imageDownloadService-any ImageDownloadService" + keyPathToName[\MyAppsFeatureDependency.keychainDataStore] = "keychainDataStore-any KeychainDataStoreProtocol" keyPathToName[\MyAppsFeatureDependency.registerJWTFeatureBuilder] = "registerJWTFeatureBuilder-any RegisterJWTFeatureBuildable" keyPathToName[\MyAppsFeatureDependency.myAppToolsFeatureBuilder] = "myAppToolsFeatureBuilder-any MyAppToolsFeatureBuildable" } @@ -184,7 +196,7 @@ private func registerProviderFactory(_ componentPath: String, _ factory: @escapi registerProviderFactory("^->DevRootComponent", factoryEmptyDependencyProvider) registerProviderFactory("^->DevRootComponent->HomeFeatureBuilderComponent", factory61c9a65d91c54126627be3b0c44298fc1c149afb) registerProviderFactory("^->DevRootComponent->MyAppToolsFeatureComponent", factory1cd9013174eed344cbd4295202051d8ff8d8a13a) - registerProviderFactory("^->DevRootComponent->RegisterJWTFeatureBuilderComponent", factory3027719ba7197de4dae7e3b0c44298fc1c149afb) + registerProviderFactory("^->DevRootComponent->RegisterJWTFeatureBuilderComponent", factory3027719ba7197de4dae7295202051d8ff8d8a13a) registerProviderFactory("^->DevRootComponent->MyAppsFeatureBuilderComponent", factory7c6a8079ee013eb6ac6c295202051d8ff8d8a13a) registerProviderFactory("^->DevRootComponent->SettingFeatureBuilderComponent", factory5d4e408a43798995182c295202051d8ff8d8a13a) registerProviderFactory("^->DevRootComponent->AppIntroductionFeatureBuilderComponent", factorya635079052b624173c15295202051d8ff8d8a13a) diff --git a/MutaroModule/Sources/App/Production/DI/RootComponent.swift b/MutaroModule/Sources/App/Production/DI/RootComponent.swift index 3c51405c..b46b7055 100644 --- a/MutaroModule/Sources/App/Production/DI/RootComponent.swift +++ b/MutaroModule/Sources/App/Production/DI/RootComponent.swift @@ -8,6 +8,7 @@ import Client import Core import ImageLoader +import KeychainStore import NeedleFoundation import AppIntroductionFeature @@ -18,57 +19,63 @@ import RegisterJWTFeature import SettingFeature final class RootComponent: BootstrapComponent { - var appIntroductionFeatureBuilder: AppIntroductionFeatureBuildable { + public var appIntroductionFeatureBuilder: any AppIntroductionFeatureBuildable { shared { AppIntroductionFeatureBuilderComponent(parent: self) .appIntroductionBuilder() } } - var settingFeatureBuilder: SettingFeatureBuildable { + public var settingFeatureBuilder: any SettingFeatureBuildable { shared { SettingFeatureBuilderComponent(parent: self) .settingFeatureBuilder() } } - var homeFeatureBuilder: HomeFeatureBuildable { + public var homeFeatureBuilder: any HomeFeatureBuildable { shared { HomeFeatureBuilderComponent(parent: self) .homeFeatureBuilder() } } - var myAppsFeatureBuilder: MyAppsFeatureBuildable { + public var myAppsFeatureBuilder: any MyAppsFeatureBuildable { shared { MyAppsFeatureBuilderComponent(parent: self) .myAppsFeatureBuilder() } } - var registerJWTFeatureBuilder: RegisterJWTFeatureBuildable { + public var registerJWTFeatureBuilder: any RegisterJWTFeatureBuildable { shared { RegisterJWTFeatureBuilderComponent(parent: self) .registerJWTFeatureBuilder() } } - var myAppToolsFeatureBuilder: MyAppToolsFeatureBuildable { + public var myAppToolsFeatureBuilder: any MyAppToolsFeatureBuildable { shared { MyAppToolsFeatureComponent(parent: self) .myAppToolsFeatureBuilder() } } - var client: Providable { + public var client: any Providable { shared { Provider() } } - var imageDownloadService: ImageDownloadService { + public var imageDownloadService: any ImageDownloadService { shared { ImageDownloadServiceImp() } } + + public var keychainDataStore: any KeychainDataStoreProtocol { + shared { + KeychainDataStore() + } + } } diff --git a/MutaroModule/Sources/Core/Client/Common/JSONNull.swift b/MutaroModule/Sources/Core/Client/Common/JSONNull.swift index 39c359fe..374aae64 100644 --- a/MutaroModule/Sources/Core/Client/Common/JSONNull.swift +++ b/MutaroModule/Sources/Core/Client/Common/JSONNull.swift @@ -7,13 +7,13 @@ import Foundation -public class JSONNull: Codable, Hashable { +public final class JSONNull: Codable, Hashable, Sendable { public static func == (_: JSONNull, _: JSONNull) -> Bool { true } - public var hashValue: Int { - 0 + public func hash(into hasher: inout Hasher) { + hasher.combine(0) } public init() {} diff --git a/MutaroModule/Sources/Core/Client/Entity/BuildsEntity.swift b/MutaroModule/Sources/Core/Client/Entity/BuildsEntity.swift index 892602b3..280e4fd1 100644 --- a/MutaroModule/Sources/Core/Client/Entity/BuildsEntity.swift +++ b/MutaroModule/Sources/Core/Client/Entity/BuildsEntity.swift @@ -9,7 +9,7 @@ import Foundation // MARK: - BuildsEntity -public struct BuildsEntity { +public struct BuildsEntity: Sendable { public let links: BuildsLinks? public let data: [BuildsDatum]? public let meta: BuildsMeta? @@ -22,7 +22,7 @@ public struct BuildsEntity { // MARK: - BuildsDatum - public struct BuildsDatum { + public struct BuildsDatum: Sendable { public let id: String? public let relationships: BuildsRelationships? public let links: BuildsLinks? @@ -40,7 +40,7 @@ public struct BuildsEntity { // MARK: - BuildsAttributes - public struct BuildsAttributes { + public struct BuildsAttributes: Sendable { public let expirationDate: Date? public let expired: Bool? public let processingState, buildAudienceType, minOSVersion: String? @@ -68,7 +68,7 @@ public struct BuildsEntity { // MARK: - BuildsIconAssetToken - public struct BuildsIconAssetToken { + public struct BuildsIconAssetToken: Sendable { public let width: Int? public let templateURL: String? public let height: Int? @@ -82,7 +82,7 @@ public struct BuildsEntity { // MARK: - BuildsLinks - public struct BuildsLinks { + public struct BuildsLinks: Sendable { public let linksSelf: String? public init(linksSelf: String?) { @@ -92,7 +92,7 @@ public struct BuildsEntity { // MARK: - BuildsRelationships - public struct BuildsRelationships { + public struct BuildsRelationships: Sendable { public let betaAppReviewSubmission, appStoreVersion, appEncryptionDeclaration, individualTesters: BuildsLinksElement? public let perfPowerMetrics: BuildsPerfPowerMetrics? public let betaBuildLocalizations: BuildsLinksElement? @@ -118,7 +118,7 @@ public struct BuildsEntity { // MARK: - BuildsLinksElement - public struct BuildsLinksElement { + public struct BuildsLinksElement: Sendable { public let links: BuildsLinksDetails? public init(links: BuildsLinksDetails?) { @@ -128,7 +128,7 @@ public struct BuildsEntity { // MARK: - BuildsLinksDetails - public struct BuildsLinksDetails { + public struct BuildsLinksDetails: Sendable { public let related, linksSelf: String? public init(related: String?, linksSelf: String?) { @@ -139,7 +139,7 @@ public struct BuildsEntity { // MARK: - BuildsBetaGroups - public struct BuildsBetaGroups { + public struct BuildsBetaGroups: Sendable { public let links: BuildsLinks? public init(links: BuildsLinks?) { @@ -149,7 +149,7 @@ public struct BuildsEntity { // MARK: - BuildsPerfPowerMetrics - public struct BuildsPerfPowerMetrics { + public struct BuildsPerfPowerMetrics: Sendable { public let links: BuildsPerfPowerMetricsLinks? public init(links: BuildsPerfPowerMetricsLinks?) { @@ -159,7 +159,7 @@ public struct BuildsEntity { // MARK: - BuildsPerfPowerMetricsLinks - public struct BuildsPerfPowerMetricsLinks { + public struct BuildsPerfPowerMetricsLinks: Sendable { public let related: String? public init(related: String?) { @@ -169,7 +169,7 @@ public struct BuildsEntity { // MARK: - BuildsMeta - public struct BuildsMeta { + public struct BuildsMeta: Sendable { public let paging: BuildsPaging? public init(paging: BuildsPaging?) { @@ -179,7 +179,7 @@ public struct BuildsEntity { // MARK: - BuildsPaging - public struct BuildsPaging { + public struct BuildsPaging: Sendable { public let total, limit: Int? public init(total: Int?, limit: Int?) { diff --git a/MutaroModule/Sources/Core/Client/Entity/CIProductsEntity.swift b/MutaroModule/Sources/Core/Client/Entity/CIProductsEntity.swift index 9e9302bd..e884dcbc 100644 --- a/MutaroModule/Sources/Core/Client/Entity/CIProductsEntity.swift +++ b/MutaroModule/Sources/Core/Client/Entity/CIProductsEntity.swift @@ -7,7 +7,7 @@ import Foundation -public struct CIProductsEntity { +public struct CIProductsEntity: Sendable { public let links: CIProductsLinks? public let data: CIProductsData? @@ -16,7 +16,7 @@ public struct CIProductsEntity { self.data = data } - public struct CIProductsData { + public struct CIProductsData: Sendable { public let id: String? public let type: String? public let attributes: CIProductsAttributes? @@ -32,7 +32,7 @@ public struct CIProductsEntity { } } - public struct CIProductsAttributes { + public struct CIProductsAttributes: Sendable { public let name: String? public let createdDate: String? public let productType: String? @@ -44,7 +44,7 @@ public struct CIProductsEntity { } } - public struct CIProductsRelationships { + public struct CIProductsRelationships: Sendable { public let app, workflows, primaryRepositories, additionalRepositories, buildRuns: CIProductsLinksElement? public init(app: CIProductsLinksElement?, workflows: CIProductsLinksElement?, primaryRepositories: CIProductsLinksElement?, additionalRepositories: CIProductsLinksElement?, buildRuns: CIProductsLinksElement?) { @@ -58,7 +58,7 @@ public struct CIProductsEntity { // MARK: - CIProductsLinksElement - public struct CIProductsLinksElement { + public struct CIProductsLinksElement: Sendable { public let links: CIProductsLinksDetails? public init(links: CIProductsLinksDetails?) { @@ -68,7 +68,7 @@ public struct CIProductsEntity { // MARK: - CIProductsLinksDetails - public struct CIProductsLinksDetails { + public struct CIProductsLinksDetails: Sendable { public let related, linksSelf: String? public init(related: String?, linksSelf: String?) { @@ -79,7 +79,7 @@ public struct CIProductsEntity { // MARK: - CIProductsLinks - public struct CIProductsLinks { + public struct CIProductsLinks: Sendable { public let linksSelf: String? public init(linksSelf: String?) { diff --git a/MutaroModule/Sources/Core/Client/Entity/MyAppsEntity.swift b/MutaroModule/Sources/Core/Client/Entity/MyAppsEntity.swift index 2d20c146..0c9e6d91 100644 --- a/MutaroModule/Sources/Core/Client/Entity/MyAppsEntity.swift +++ b/MutaroModule/Sources/Core/Client/Entity/MyAppsEntity.swift @@ -9,7 +9,7 @@ import Foundation // MARK: - MyAppsEntity -public struct MyAppsEntity { +public struct MyAppsEntity: Sendable { public let links: MyAppsLinks? public let data: [MyAppsData]? public let meta: MyAppsMeta? @@ -22,7 +22,7 @@ public struct MyAppsEntity { // MARK: - MyAppsDatum - public struct MyAppsData { + public struct MyAppsData: Sendable { public let id: String? public let relationships: MyAppsRelationships? public let links: MyAppsLinks? @@ -40,7 +40,7 @@ public struct MyAppsEntity { // MARK: - MyAppsAttributes - public struct MyAppsAttributes { + public struct MyAppsAttributes: Sendable { public let subscriptionStatusURLVersionForSandbox, subscriptionStatusURLVersion: JSONNull? public let availableInNewTerritories: Bool? public let sku: String? @@ -67,7 +67,7 @@ public struct MyAppsEntity { // MARK: - MyAppsRelationships - public struct MyAppsRelationships { + public struct MyAppsRelationships: Sendable { public let reviewSubmissions, betaAppLocalizations, promotedPurchases, ciProduct: MyAppsLinksElement? public let appClips: MyAppsLinksElement? public let betaTesters: MyAppsBetaTesters? @@ -115,7 +115,7 @@ public struct MyAppsEntity { // MARK: - MyAppsAppAvailability - public struct MyAppsLinksElement { + public struct MyAppsLinksElement: Sendable { public let links: MyAppsLinksDetails? public init(links: MyAppsLinksDetails?) { @@ -125,7 +125,7 @@ public struct MyAppsEntity { // MARK: - MyAppsAppAvailabilityLinks - public struct MyAppsLinksDetails { + public struct MyAppsLinksDetails: Sendable { public let related, linksSelf: String? public init(related: String?, linksSelf: String?) { @@ -136,7 +136,7 @@ public struct MyAppsEntity { // MARK: - MyAppsLinks - public struct MyAppsLinks { + public struct MyAppsLinks: Sendable { public let linksSelf: String? public init(linksSelf: String?) { @@ -146,7 +146,7 @@ public struct MyAppsEntity { // MARK: - MyAppsBetaTesters - public struct MyAppsBetaTesters { + public struct MyAppsBetaTesters: Sendable { public let links: MyAppsLinks? public init(links: MyAppsLinks?) { @@ -156,7 +156,7 @@ public struct MyAppsEntity { // MARK: - MyAppsPerfPowerMetrics - public struct MyAppsPerfPowerMetrics { + public struct MyAppsPerfPowerMetrics: Sendable { public let links: MyAppsPerfPowerMetricsLinks? public init(links: MyAppsPerfPowerMetricsLinks?) { @@ -166,7 +166,7 @@ public struct MyAppsEntity { // MARK: - MyAppsPerfPowerMetricsLinks - public struct MyAppsPerfPowerMetricsLinks { + public struct MyAppsPerfPowerMetricsLinks: Sendable { public let related: String? public init(related: String?) { @@ -176,7 +176,7 @@ public struct MyAppsEntity { // MARK: - MyAppsMeta - public struct MyAppsMeta { + public struct MyAppsMeta: Sendable { public let paging: MyAppsPaging? public init(paging: MyAppsPaging?) { @@ -186,7 +186,7 @@ public struct MyAppsEntity { // MARK: - MyAppsPaging - public struct MyAppsPaging { + public struct MyAppsPaging: Sendable { public let total, limit: Int? public init(total: Int?, limit: Int?) { diff --git a/MutaroModule/Sources/Core/Client/Providable.swift b/MutaroModule/Sources/Core/Client/Providable.swift index df84989d..5b8c386e 100644 --- a/MutaroModule/Sources/Core/Client/Providable.swift +++ b/MutaroModule/Sources/Core/Client/Providable.swift @@ -7,7 +7,7 @@ import Foundation -public protocol Providable { +public protocol Providable: Sendable { func request(endpoint: Endpoint, responseModel: T.Type) async -> Result< T, RequestError > diff --git a/MutaroModule/Sources/Core/DI/AppIntroductionFeatureBuilder.swift b/MutaroModule/Sources/Core/DI/AppIntroductionFeatureBuilder.swift index 9bb69dee..947af6b6 100644 --- a/MutaroModule/Sources/Core/DI/AppIntroductionFeatureBuilder.swift +++ b/MutaroModule/Sources/Core/DI/AppIntroductionFeatureBuilder.swift @@ -8,7 +8,7 @@ import NeedleFoundation import UIKit -public protocol AppIntroductionFeatureBuildable: Buildable { +public protocol AppIntroductionFeatureBuildable: Buildable, Sendable { @MainActor func build() -> UIViewController } diff --git a/MutaroModule/Sources/Core/DI/HomeViewFeatureBuilder.swift b/MutaroModule/Sources/Core/DI/HomeViewFeatureBuilder.swift index bd4b89a6..3ffef8aa 100644 --- a/MutaroModule/Sources/Core/DI/HomeViewFeatureBuilder.swift +++ b/MutaroModule/Sources/Core/DI/HomeViewFeatureBuilder.swift @@ -8,7 +8,7 @@ import NeedleFoundation import UIKit -public protocol HomeFeatureBuildable: Buildable { +public protocol HomeFeatureBuildable: Buildable, Sendable { @MainActor func build(viewControllers: [UIViewController]) -> UIViewController } diff --git a/MutaroModule/Sources/Core/DI/MyAppToolsFeatureBuilder.swift b/MutaroModule/Sources/Core/DI/MyAppToolsFeatureBuilder.swift index 473400cc..325fa829 100644 --- a/MutaroModule/Sources/Core/DI/MyAppToolsFeatureBuilder.swift +++ b/MutaroModule/Sources/Core/DI/MyAppToolsFeatureBuilder.swift @@ -8,7 +8,7 @@ import NeedleFoundation import UIKit -public protocol MyAppToolsFeatureBuildable: Buildable { +public protocol MyAppToolsFeatureBuildable: Buildable, Sendable { @MainActor func build(appId: String) -> UIViewController } diff --git a/MutaroModule/Sources/Core/DI/MyAppsFeatureBuilder.swift b/MutaroModule/Sources/Core/DI/MyAppsFeatureBuilder.swift index e0836f40..d3991c92 100644 --- a/MutaroModule/Sources/Core/DI/MyAppsFeatureBuilder.swift +++ b/MutaroModule/Sources/Core/DI/MyAppsFeatureBuilder.swift @@ -8,7 +8,7 @@ import NeedleFoundation import UIKit -public protocol MyAppsFeatureBuildable: Buildable { +public protocol MyAppsFeatureBuildable: Buildable, Sendable { @MainActor func build() -> UIViewController } diff --git a/MutaroModule/Sources/Core/DI/RegisterJWTFeatureBuilder.swift b/MutaroModule/Sources/Core/DI/RegisterJWTFeatureBuilder.swift index 0ce4bbe3..07f14249 100644 --- a/MutaroModule/Sources/Core/DI/RegisterJWTFeatureBuilder.swift +++ b/MutaroModule/Sources/Core/DI/RegisterJWTFeatureBuilder.swift @@ -8,7 +8,7 @@ import NeedleFoundation import UIKit -public protocol RegisterJWTFeatureBuildable: Buildable { +public protocol RegisterJWTFeatureBuildable: Buildable, Sendable { @MainActor func build() -> UIViewController } diff --git a/MutaroModule/Sources/Core/DI/SettingFeatureBuilder.swift b/MutaroModule/Sources/Core/DI/SettingFeatureBuilder.swift index a8d8d69f..728f7c57 100644 --- a/MutaroModule/Sources/Core/DI/SettingFeatureBuilder.swift +++ b/MutaroModule/Sources/Core/DI/SettingFeatureBuilder.swift @@ -8,7 +8,7 @@ import NeedleFoundation import UIKit -public protocol SettingFeatureBuildable: Buildable { +public protocol SettingFeatureBuildable: Buildable, Sendable { @MainActor func build() -> UIViewController } diff --git a/MutaroModule/Sources/Core/DI/ViewBuilder.swift b/MutaroModule/Sources/Core/DI/ViewBuilder.swift index 2afd26c1..8eda1991 100644 --- a/MutaroModule/Sources/Core/DI/ViewBuilder.swift +++ b/MutaroModule/Sources/Core/DI/ViewBuilder.swift @@ -8,9 +8,9 @@ import Foundation import NeedleFoundation -public protocol Buildable: AnyObject {} +public protocol Buildable {} -open class Builder: Buildable { +open class Builder: Buildable, @unchecked Sendable { public let dependency: Dependency public init(dependency: Dependency) { diff --git a/MutaroModule/Sources/Core/DataStore/Keychain/KeychainDataStoreError.swift b/MutaroModule/Sources/Core/DataStore/Keychain/KeychainDataStoreError.swift new file mode 100644 index 00000000..a3ffb447 --- /dev/null +++ b/MutaroModule/Sources/Core/DataStore/Keychain/KeychainDataStoreError.swift @@ -0,0 +1,15 @@ +// +// KeychainDataStoreError.swift +// +// +// Created by minguk-kim on 2024/03/17. +// + +import Foundation +import Security + +public enum KeychainError: Error { + case saveError(OSStatus) + case loadError(OSStatus) + case deleteError(OSStatus) +} diff --git a/MutaroModule/Sources/Core/DataStore/Keychain/KeychainDataStoreProtocol.swift b/MutaroModule/Sources/Core/DataStore/Keychain/KeychainDataStoreProtocol.swift new file mode 100644 index 00000000..d6bef6ca --- /dev/null +++ b/MutaroModule/Sources/Core/DataStore/Keychain/KeychainDataStoreProtocol.swift @@ -0,0 +1,15 @@ +// +// KeychainDataStoreProtocol.swift +// +// +// Created by minguk-kim on 2024/03/17. +// + +import Foundation +import Security + +public protocol KeychainDataStoreProtocol: Sendable { + func saveValue(_ value: some Codable & Sendable, forKey key: KeychainStoreKey) throws + func loadValue(forKey key: KeychainStoreKey) throws -> T + func deleteValue(forKey key: KeychainStoreKey) throws +} diff --git a/MutaroModule/Sources/Modules/KeychainStore/KeychainStoreKey.swift b/MutaroModule/Sources/Core/DataStore/Keychain/KeychainStoreKey.swift similarity index 100% rename from MutaroModule/Sources/Modules/KeychainStore/KeychainStoreKey.swift rename to MutaroModule/Sources/Core/DataStore/Keychain/KeychainStoreKey.swift diff --git a/MutaroModule/Sources/Core/Extensions/Concurrency/Publisher+Extensions.swift b/MutaroModule/Sources/Core/Extensions/Concurrency/Publisher+Extensions.swift deleted file mode 100644 index fc1bcd5a..00000000 --- a/MutaroModule/Sources/Core/Extensions/Concurrency/Publisher+Extensions.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// Publisher+Extensions.swift -// -// -// Created by minguk-kim on 2023/06/12. -// - -import Combine - -public extension Publisher { - func asyncMap( - _ transform: @escaping (Output) async -> T - ) -> Publishers.FlatMap, Self> { - flatMap { value in - Future { promise in - Task { - let output = await transform(value) - promise(.success(output)) - } - } - } - } - - func asyncMapThrows( - _ transform: @escaping (Output) async throws -> T - ) -> Publishers.FlatMap, Publishers.SetFailureType> { - flatMap { value in - Future { promise in - Task { - do { - let output = try await transform(value) - promise(.success(output)) - } catch { - promise(.failure(error)) - } - } - } - } - } -} - -public extension Publisher where Self.Failure == Never { - func asyncSink( - taskCancellable: TaskCancellable, - receiveValue: @escaping ((Self.Output) async -> Void) - ) -> AnyCancellable { - sink { value in - Task { - await receiveValue(value) - } - .store(in: taskCancellable) - } - } -} diff --git a/MutaroModule/Sources/Core/ImageLoader/ImageDownloadService.swift b/MutaroModule/Sources/Core/ImageLoader/ImageDownloadService.swift index 9fdfd3ae..af620fed 100644 --- a/MutaroModule/Sources/Core/ImageLoader/ImageDownloadService.swift +++ b/MutaroModule/Sources/Core/ImageLoader/ImageDownloadService.swift @@ -8,7 +8,7 @@ import Foundation import Kingfisher -public protocol ImageDownloadService { +public protocol ImageDownloadService: Sendable { func downloadImage(with urlString: String, cache targetCache: ImageCache) func cancelDownloadImage(with urlString: String) } diff --git a/MutaroModule/Sources/Features/AppIntroduction/DI/AppIntroductionFeatureComponent.swift b/MutaroModule/Sources/Features/AppIntroduction/DI/AppIntroductionFeatureComponent.swift index 503b1816..c40fa254 100644 --- a/MutaroModule/Sources/Features/AppIntroduction/DI/AppIntroductionFeatureComponent.swift +++ b/MutaroModule/Sources/Features/AppIntroduction/DI/AppIntroductionFeatureComponent.swift @@ -10,7 +10,7 @@ import Foundation import NeedleFoundation import UIKit -public protocol AppIntroductionFeatureDependency: Dependency { +public protocol AppIntroductionFeatureDependency: Dependency, Sendable { // TODO: - var 遷移するB FeatureのBuilder: BModuleBuildable { get } var myAppsFeatureBuilder: any MyAppsFeatureBuildable { get } var settingFeatureBuilder: any SettingFeatureBuildable { get } @@ -29,7 +29,10 @@ public class AppIntroductionFeatureBuilder: Builder, FeatureAppIntroduction { diff --git a/MutaroModule/Sources/Features/AppIntroduction/DI/AppIntroductionFeatureEnvironment.swift b/MutaroModule/Sources/Features/AppIntroduction/DI/AppIntroductionFeatureEnvironment.swift index 434f2734..2d7d375d 100644 --- a/MutaroModule/Sources/Features/AppIntroduction/DI/AppIntroductionFeatureEnvironment.swift +++ b/MutaroModule/Sources/Features/AppIntroduction/DI/AppIntroductionFeatureEnvironment.swift @@ -7,10 +7,15 @@ import Foundation -public struct AppIntroductionFeatureEnvironment { +public struct AppIntroductionFeatureEnvironment: Sendable { public let router: any AppIntroductionRoutable + public let dataStore: any AppIntroductionDataStoreProtocol - public init(router: any AppIntroductionRoutable) { + public init( + router: any AppIntroductionRoutable, + dataStore: any AppIntroductionDataStoreProtocol + ) { self.router = router + self.dataStore = dataStore } } diff --git a/MutaroModule/Sources/Features/AppIntroduction/DataStore/AppIntroductionDataStore.swift b/MutaroModule/Sources/Features/AppIntroduction/DataStore/AppIntroductionDataStore.swift new file mode 100644 index 00000000..95a5b620 --- /dev/null +++ b/MutaroModule/Sources/Features/AppIntroduction/DataStore/AppIntroductionDataStore.swift @@ -0,0 +1,19 @@ +// +// AppIntroductionDataStore.swift +// +// +// Created by minguk-kim on 2024/03/17. +// + +import Core +import Foundation + +public protocol AppIntroductionDataStoreProtocol: Sendable { + func storeFirstLaunchStatus(with status: Bool) +} + +public final class AppIntroductionDataStore: AppIntroductionDataStoreProtocol, Sendable { + public func storeFirstLaunchStatus(with status: Bool) { + UserDefaults.standard.set(status, forKey: UserDefaultsKey.notFirstAppLaunching.rawValue) + } +} diff --git a/MutaroModule/Sources/Features/AppIntroduction/Router/AppIntroductionRouter.swift b/MutaroModule/Sources/Features/AppIntroduction/Router/AppIntroductionRouter.swift index 45f9dbb0..3977e31b 100644 --- a/MutaroModule/Sources/Features/AppIntroduction/Router/AppIntroductionRouter.swift +++ b/MutaroModule/Sources/Features/AppIntroduction/Router/AppIntroductionRouter.swift @@ -8,13 +8,13 @@ import Core import UIKit -public protocol AppIntroductionRoutable { +public protocol AppIntroductionRoutable: Sendable { @MainActor func showHomeAsRoot() } -public class AppIntroductionRouter: AppIntroductionRoutable { - public struct Dependency { +public final class AppIntroductionRouter: AppIntroductionRoutable { + public struct Dependency: Sendable { // TODO: - ここから遷移するfeatureのbuilder public let homeFeatureBuilder: any HomeFeatureBuildable public let myAppsFeatureBuilder: any MyAppsFeatureBuildable diff --git a/MutaroModule/Sources/Features/AppIntroduction/ViewController/AppIntroductionViewController.swift b/MutaroModule/Sources/Features/AppIntroduction/ViewController/AppIntroductionViewController.swift index 7a95eb79..175c2ce8 100644 --- a/MutaroModule/Sources/Features/AppIntroduction/ViewController/AppIntroductionViewController.swift +++ b/MutaroModule/Sources/Features/AppIntroduction/ViewController/AppIntroductionViewController.swift @@ -54,9 +54,7 @@ public final class AppIntroductionViewController: UIViewController { $0.configuration = configuration $0.updateAction(.touchUpInside) { [weak self] in - Task { - await self?.viewModel.onTapAgree() - } + self?.viewModel.onTapAgree() } view.addSubview($0) diff --git a/MutaroModule/Sources/Features/AppIntroduction/ViewModel/AppIntroductionViewModel.swift b/MutaroModule/Sources/Features/AppIntroduction/ViewModel/AppIntroductionViewModel.swift index f00fe126..462f2d86 100644 --- a/MutaroModule/Sources/Features/AppIntroduction/ViewModel/AppIntroductionViewModel.swift +++ b/MutaroModule/Sources/Features/AppIntroduction/ViewModel/AppIntroductionViewModel.swift @@ -8,15 +8,16 @@ import Core import UIKit -public final class AppIntroductionViewModel: NSObject { +@MainActor +public final class AppIntroductionViewModel: NSObject, Sendable { private let environment: AppIntroductionFeatureEnvironment public init(environment: AppIntroductionFeatureEnvironment) { self.environment = environment } - func onTapAgree() async { - UserDefaults.standard.set(true, forKey: UserDefaultsKey.notFirstAppLaunching.rawValue) - await environment.router.showHomeAsRoot() + func onTapAgree() { + environment.dataStore.storeFirstLaunchStatus(with: true) + environment.router.showHomeAsRoot() } } diff --git a/MutaroModule/Sources/Features/Home/DI/HomeFeatureComponent.swift b/MutaroModule/Sources/Features/Home/DI/HomeFeatureComponent.swift index 7ec73fdf..0b4b33ff 100644 --- a/MutaroModule/Sources/Features/Home/DI/HomeFeatureComponent.swift +++ b/MutaroModule/Sources/Features/Home/DI/HomeFeatureComponent.swift @@ -10,11 +10,11 @@ import Foundation import NeedleFoundation import UIKit -public protocol HomeFeatureDependency: Dependency { +public protocol HomeFeatureDependency: Dependency, Sendable { // TODO: - var 遷移するB FeatureのBuilder: BModuleBuildable { get } } -class HomeFeatureBuilder: Builder, HomeFeatureBuildable { +public class HomeFeatureBuilder: Builder, HomeFeatureBuildable { @MainActor public func build(viewControllers: [UIViewController]) -> UIViewController { HomeTabViewController(viewControllers: viewControllers) diff --git a/MutaroModule/Sources/Features/MyAppTools/DI/MyAppToolsFeatureComponent.swift b/MutaroModule/Sources/Features/MyAppTools/DI/MyAppToolsFeatureComponent.swift index 16051e70..ea849454 100644 --- a/MutaroModule/Sources/Features/MyAppTools/DI/MyAppToolsFeatureComponent.swift +++ b/MutaroModule/Sources/Features/MyAppTools/DI/MyAppToolsFeatureComponent.swift @@ -10,13 +10,14 @@ import Foundation import NeedleFoundation import UIKit -public protocol MyAppToolsFeatureDependency: Dependency { +public protocol MyAppToolsFeatureDependency: Dependency, Sendable { var client: any Providable { get } + var keychainDataStore: any KeychainDataStoreProtocol { get } } -class MyAppToolsFeatureBuilder: Builder, MyAppToolsFeatureBuildable { +public class MyAppToolsFeatureBuilder: Builder, MyAppToolsFeatureBuildable { @MainActor - func build(appId: String) -> UIViewController { + public func build(appId: String) -> UIViewController { let myAppTools = MyAppToolsViewController( dependency: .init( viewModel: .init( @@ -31,7 +32,8 @@ class MyAppToolsFeatureBuilder: Builder, MyAppTools private var environment: MyAppToolsFeatureEnvironment { .init( ciProductUseCase: ciProductUseCase, - router: router + router: router, + keychainDataStore: dependency.keychainDataStore ) } diff --git a/MutaroModule/Sources/Features/MyAppTools/DI/MyAppToolsFeatureEnvironment.swift b/MutaroModule/Sources/Features/MyAppTools/DI/MyAppToolsFeatureEnvironment.swift index 113ea97f..e8aaff2a 100644 --- a/MutaroModule/Sources/Features/MyAppTools/DI/MyAppToolsFeatureEnvironment.swift +++ b/MutaroModule/Sources/Features/MyAppTools/DI/MyAppToolsFeatureEnvironment.swift @@ -8,15 +8,18 @@ import Core import Foundation -public struct MyAppToolsFeatureEnvironment { +public struct MyAppToolsFeatureEnvironment: Sendable { public let ciProductUseCase: any CIProductUseCase public let router: any MyAppToolsRoutable + public let keychainDataStore: any KeychainDataStoreProtocol public init( ciProductUseCase: any CIProductUseCase, - router: any MyAppToolsRoutable + router: any MyAppToolsRoutable, + keychainDataStore: any KeychainDataStoreProtocol ) { self.ciProductUseCase = ciProductUseCase self.router = router + self.keychainDataStore = keychainDataStore } } diff --git a/MutaroModule/Sources/Features/MyAppTools/Model/MyAppToolsModel.swift b/MutaroModule/Sources/Features/MyAppTools/Model/MyAppToolsModel.swift index 8c669b06..cd266b06 100644 --- a/MutaroModule/Sources/Features/MyAppTools/Model/MyAppToolsModel.swift +++ b/MutaroModule/Sources/Features/MyAppTools/Model/MyAppToolsModel.swift @@ -15,10 +15,16 @@ import UIKit struct MyAppToolsModel { private let appId: String private let ciProductUseCase: any CIProductUseCase + private let keychainDataStore: any KeychainDataStoreProtocol - init(appId: String, ciProductUseCase: any CIProductUseCase) { + init( + appId: String, + ciProductUseCase: any CIProductUseCase, + keychainDataStore: any KeychainDataStoreProtocol + ) { self.appId = appId self.ciProductUseCase = ciProductUseCase + self.keychainDataStore = keychainDataStore } func checkAvailableItems( @@ -36,7 +42,7 @@ struct MyAppToolsModel { func getCIProducts() async -> CIProductsEntity.CIProductsData? { do { - let storedJWTInfo: MutaroJWT.JWTRequestInfo = try KeychainStore.shared.loadValue(forKey: .jwt) + let storedJWTInfo: MutaroJWT.JWTRequestInfo = try keychainDataStore.loadValue(forKey: .jwt) let ciProducts = try await ciProductUseCase.fetchCIProducts( storedJWTInfo: storedJWTInfo, appId: appId diff --git a/MutaroModule/Sources/Features/MyAppTools/Router/MyAppToolsRouter.swift b/MutaroModule/Sources/Features/MyAppTools/Router/MyAppToolsRouter.swift index 9d430a0e..9239c82d 100644 --- a/MutaroModule/Sources/Features/MyAppTools/Router/MyAppToolsRouter.swift +++ b/MutaroModule/Sources/Features/MyAppTools/Router/MyAppToolsRouter.swift @@ -8,10 +8,10 @@ import Core import Foundation -public protocol MyAppToolsRoutable {} +public protocol MyAppToolsRoutable: Sendable {} -public class MyAppToolsRouter: MyAppToolsRoutable { - public struct Dependency {} +public final class MyAppToolsRouter: MyAppToolsRoutable { + public struct Dependency: Sendable {} private let dependency: Dependency diff --git a/MutaroModule/Sources/Features/MyAppTools/UseCase/CIProductUseCase.swift b/MutaroModule/Sources/Features/MyAppTools/UseCase/CIProductUseCase.swift index c28535fa..f3ea0bc7 100644 --- a/MutaroModule/Sources/Features/MyAppTools/UseCase/CIProductUseCase.swift +++ b/MutaroModule/Sources/Features/MyAppTools/UseCase/CIProductUseCase.swift @@ -10,7 +10,7 @@ import Core import Foundation import JWTGenerator -public protocol CIProductUseCase { +public protocol CIProductUseCase: Sendable { func fetchCIProducts(storedJWTInfo: JWTGenerator.MutaroJWT.JWTRequestInfo, appId: String) async throws -> CIProductsEntity.CIProductsData? } diff --git a/MutaroModule/Sources/Features/MyAppTools/ViewModel/MyAppToolsViewModel.swift b/MutaroModule/Sources/Features/MyAppTools/ViewModel/MyAppToolsViewModel.swift index 22d8e98c..6afa1405 100644 --- a/MutaroModule/Sources/Features/MyAppTools/ViewModel/MyAppToolsViewModel.swift +++ b/MutaroModule/Sources/Features/MyAppTools/ViewModel/MyAppToolsViewModel.swift @@ -9,6 +9,7 @@ import Combine import Core import Foundation +@MainActor protocol MyAppToolsViewModelProtocol { func transform(input: MyAppToolsViewModel.Input) -> MyAppToolsViewModel.Output } @@ -24,7 +25,8 @@ extension MyAppToolsViewModel { } } -public class MyAppToolsViewModel: MyAppToolsViewModelProtocol { +@MainActor +public final class MyAppToolsViewModel: MyAppToolsViewModelProtocol, Sendable { private let appId: String private let environment: MyAppToolsFeatureEnvironment var cancellables: Set = [] @@ -32,7 +34,8 @@ public class MyAppToolsViewModel: MyAppToolsViewModelProtocol { private lazy var model: MyAppToolsModel = .init( appId: appId, - ciProductUseCase: environment.ciProductUseCase + ciProductUseCase: environment.ciProductUseCase, + keychainDataStore: environment.keychainDataStore ) private let items: CurrentValueSubject<[MyAppToolsModel.ItemType], Never> = .init([]) @@ -46,37 +49,33 @@ public class MyAppToolsViewModel: MyAppToolsViewModelProtocol { self.environment = environment } - func transform(input: Input) -> Output { - let viewDidLoad = input - .viewDidLoad - .share() - - let ciProducts = viewDidLoad - .asyncMap { await self.model.getCIProducts() } - .eraseToAnyPublisher() - .share() + private func fetch() async { + let ciProducts = await model.getCIProducts() + ciProductsItem.send(ciProducts) - ciProducts - .assign(to: \.value, on: ciProductsItem) - .store(in: &cancellables) + if !items.value.contains(.xcodeCloud) { + var results = items.value + results.append(.xcodeCloud) + items.send(results) + } + } - ciProducts - .compactMap { _ in MyAppToolsModel.ItemType.xcodeCloud } - .sink { [weak self] type in - guard let self, - !self.items.value.contains(type) else { - return + func transform(input: Input) -> Output { + input + .viewDidLoad + .sink { [weak self] in + Task { + await self?.fetch() } - var results = self.items.value - results.append(type) - self.items.send(results) } .store(in: &cancellables) input .didTapItem - .asyncSink(taskCancellable: taskCancellable) { [weak self] in - await self?.onTapItem($0) + .sink { [weak self] item in + Task { + await self?.onTapItem(item) + } } .store(in: &cancellables) diff --git a/MutaroModule/Sources/Features/MyApps/DI/MyAppsFeatureComponent.swift b/MutaroModule/Sources/Features/MyApps/DI/MyAppsFeatureComponent.swift index 18f458ad..523bd1b0 100644 --- a/MutaroModule/Sources/Features/MyApps/DI/MyAppsFeatureComponent.swift +++ b/MutaroModule/Sources/Features/MyApps/DI/MyAppsFeatureComponent.swift @@ -11,14 +11,15 @@ import ImageLoader import NeedleFoundation import UIKit -public protocol MyAppsFeatureDependency: Dependency { +public protocol MyAppsFeatureDependency: Dependency, Sendable { var client: any Providable { get } var imageDownloadService: any ImageDownloadService { get } + var keychainDataStore: any KeychainDataStoreProtocol { get } var registerJWTFeatureBuilder: any RegisterJWTFeatureBuildable { get } var myAppToolsFeatureBuilder: any MyAppToolsFeatureBuildable { get } } -class MyAppsFeatureBuilder: Builder, MyAppsFeatureBuildable { +public class MyAppsFeatureBuilder: Builder, MyAppsFeatureBuildable { @MainActor public func build() -> UIViewController { let myAppsVC = MyAppsViewController( @@ -50,6 +51,7 @@ class MyAppsFeatureBuilder: Builder, MyAppsFeatureBuild .init( appInfoUseCase: appInfoUseCase, imageDownloadService: dependency.imageDownloadService, + keychainDataStore: dependency.keychainDataStore, router: router ) } @@ -64,7 +66,10 @@ class MyAppsFeatureBuilder: Builder, MyAppsFeatureBuild } private var appInfoUseCase: any AppInfoUseCase { - AppInfoUseCaseImpl(client: dependency.client) + AppInfoUseCaseImpl( + client: dependency.client, + keychainDataStore: dependency.keychainDataStore + ) } } diff --git a/MutaroModule/Sources/Features/MyApps/DI/MyAppsFeatureEnvironment.swift b/MutaroModule/Sources/Features/MyApps/DI/MyAppsFeatureEnvironment.swift index 24fe21b0..3bb89d32 100644 --- a/MutaroModule/Sources/Features/MyApps/DI/MyAppsFeatureEnvironment.swift +++ b/MutaroModule/Sources/Features/MyApps/DI/MyAppsFeatureEnvironment.swift @@ -8,18 +8,21 @@ import Core import Foundation -public struct MyAppsFeatureEnvironment { +public struct MyAppsFeatureEnvironment: Sendable { public let appInfoUseCase: any AppInfoUseCase public let imageDownloadService: any ImageDownloadService + public let keychainDataStore: any KeychainDataStoreProtocol public let router: any MyAppsRoutable public init( appInfoUseCase: any AppInfoUseCase, imageDownloadService: any ImageDownloadService, + keychainDataStore: any KeychainDataStoreProtocol, router: any MyAppsRoutable ) { self.appInfoUseCase = appInfoUseCase self.imageDownloadService = imageDownloadService + self.keychainDataStore = keychainDataStore self.router = router } } diff --git a/MutaroModule/Sources/Features/MyApps/Model/AppInfo.swift b/MutaroModule/Sources/Features/MyApps/Model/AppInfo.swift index 0fdc9ca1..3893d465 100644 --- a/MutaroModule/Sources/Features/MyApps/Model/AppInfo.swift +++ b/MutaroModule/Sources/Features/MyApps/Model/AppInfo.swift @@ -7,7 +7,7 @@ import Foundation -public struct AppInfo: Equatable { +public struct AppInfo: Equatable, Sendable { let id: String let name: String let iconUrl: String? diff --git a/MutaroModule/Sources/Features/MyApps/Router/MyAppsRouter.swift b/MutaroModule/Sources/Features/MyApps/Router/MyAppsRouter.swift index 8f9ead02..ca3fe5ad 100644 --- a/MutaroModule/Sources/Features/MyApps/Router/MyAppsRouter.swift +++ b/MutaroModule/Sources/Features/MyApps/Router/MyAppsRouter.swift @@ -8,15 +8,15 @@ import Core import UIKit -public protocol MyAppsRoutable { +public protocol MyAppsRoutable: Sendable { @MainActor func showRegisterJWT(from viewController: UIViewController) @MainActor func showMyAppTools(from viewController: UIViewController, appId: String) } -public class MyAppsRouter: MyAppsRoutable { - public struct Dependency { +public final class MyAppsRouter: MyAppsRoutable { + public struct Dependency: Sendable { public let registerJWTFeatureBuilder: any RegisterJWTFeatureBuildable public let myAppToolsFeatureBuilder: any MyAppToolsFeatureBuildable diff --git a/MutaroModule/Sources/Features/MyApps/UseCase/AppInfoUseCase.swift b/MutaroModule/Sources/Features/MyApps/UseCase/AppInfoUseCase.swift index 16f47025..50c75efe 100644 --- a/MutaroModule/Sources/Features/MyApps/UseCase/AppInfoUseCase.swift +++ b/MutaroModule/Sources/Features/MyApps/UseCase/AppInfoUseCase.swift @@ -10,19 +10,25 @@ import Core import Foundation import JWTGenerator -public protocol AppInfoUseCase { - func fetchAppInfos(storedJWTInfo: JWTGenerator.MutaroJWT.JWTRequestInfo, myApps: [(id: String, name: String)]) async throws -> [AppInfo] - func fetchMyApps(storedJWTInfo: JWTGenerator.MutaroJWT.JWTRequestInfo) async throws -> [MyAppsEntity.MyAppsData] +public protocol AppInfoUseCase: Sendable { + func fetchAppInfos(myApps: [MyAppsEntity.MyAppsData]) async throws -> [AppInfo] + func fetchMyApps() async throws -> [MyAppsEntity.MyAppsData] } public final class AppInfoUseCaseImpl: AppInfoUseCase { private let client: any Providable + private let keychainDataStore: any KeychainDataStoreProtocol - public init(client: any Providable) { + public init( + client: any Providable, + keychainDataStore: any KeychainDataStoreProtocol + ) { self.client = client + self.keychainDataStore = keychainDataStore } - public func fetchMyApps(storedJWTInfo: JWTGenerator.MutaroJWT.JWTRequestInfo) async throws -> [MyAppsEntity.MyAppsData] { + public func fetchMyApps() async throws -> [MyAppsEntity.MyAppsData] { + let storedJWTInfo: MutaroJWT.JWTRequestInfo = try keychainDataStore.loadValue(forKey: .jwt) let builder = MutaroJWT.AppstoreConnectJWTBuilder( keyId: storedJWTInfo.keyID, issuerId: storedJWTInfo.issuerID, @@ -33,14 +39,23 @@ public final class AppInfoUseCaseImpl: AppInfoUseCase { return myApps } - public func fetchAppInfos(storedJWTInfo: JWTGenerator.MutaroJWT.JWTRequestInfo, myApps: [(id: String, name: String)]) async throws -> [AppInfo] { + public func fetchAppInfos(myApps: [MyAppsEntity.MyAppsData]) async throws -> [AppInfo] { + let myAppsInfo = myApps + .compactMap { data -> (String, String)? in + guard let id = data.id, + let name = data.attributes?.name else { + return nil + } + return (id, name) + } + let storedJWTInfo: MutaroJWT.JWTRequestInfo = try keychainDataStore.loadValue(forKey: .jwt) let builder = MutaroJWT.AppstoreConnectJWTBuilder( keyId: storedJWTInfo.keyID, issuerId: storedJWTInfo.issuerID, pemString: storedJWTInfo.privateKey ) let token = try builder.generateJWT() - let appInfos = try await getAppInfos(token: token, myApps: myApps) + let appInfos = try await getAppInfos(token: token, myApps: myAppsInfo) return appInfos } diff --git a/MutaroModule/Sources/Features/MyApps/ViewModel/MyAppsViewModel.swift b/MutaroModule/Sources/Features/MyApps/ViewModel/MyAppsViewModel.swift index 18b911c1..d76c8124 100644 --- a/MutaroModule/Sources/Features/MyApps/ViewModel/MyAppsViewModel.swift +++ b/MutaroModule/Sources/Features/MyApps/ViewModel/MyAppsViewModel.swift @@ -13,6 +13,7 @@ import JWTGenerator import KeychainStore import UIKit +@MainActor protocol MyAppsViewModelProtocol { func transform(input: MyAppsViewModel.Input) -> MyAppsViewModel.Output func getAppInfos(_ index: Int) -> AppInfo? @@ -34,7 +35,8 @@ extension MyAppsViewModel { } } -public final class MyAppsViewModel: NSObject, MyAppsViewModelProtocol { +@MainActor +public final class MyAppsViewModel: NSObject, MyAppsViewModelProtocol, Sendable { private let environment: MyAppsFeatureEnvironment private let appInfosSubject: CurrentValueSubject<[AppInfo], Never> = .init([]) @@ -51,63 +53,29 @@ public final class MyAppsViewModel: NSObject, MyAppsViewModelProtocol { } func transform(input: Input) -> Output { - let viewWillAppear = input + input .viewWillAppear - .share() - - let myApps = viewWillAppear - .asyncMapThrows { try await self.fetchMyApps() } - .eraseToAnyPublisher() - .share() - - myApps - .sink { [weak self] completion in - switch completion { - case .finished: - self?.showRegisterJWT.send(false) - case .failure: - self?.showRegisterJWT.send(true) + .sink { [weak self] in + Task { + await self?.fetch() } - } receiveValue: { _ in } - .store(in: &cancellables) - - myApps - .replaceError(with: []) - .assign(to: \.value, on: myAppsSubject) - .store(in: &cancellables) - - let appInfos = myApps - .replaceError(with: []) - .eraseToAnyPublisher() - .asyncMapThrows { try await self.fetchAppInfos(myApps: $0) } - .replaceError(with: []) - .eraseToAnyPublisher() - .share() - - appInfos - .assign(to: \.value, on: appInfosSubject) + } .store(in: &cancellables) input .didTapMyApp .receive(on: DispatchQueue.main) - .asyncSink( - taskCancellable: taskCancellables, - receiveValue: { [weak self] in - await self?.onTapMyApp(from: $0.from, index: $0.index) - } - ) + .sink { [weak self] in + self?.onTapMyApp(from: $0.from, index: $0.index) + } .store(in: &cancellables) input .didTapRegisterJWT .receive(on: DispatchQueue.main) - .asyncSink( - taskCancellable: taskCancellables, - receiveValue: { [weak self] in - await self?.onTapRegisterJWT(from: $0) - } - ) + .sink { [weak self] in + self?.onTapRegisterJWT(from: $0) + } .store(in: &cancellables) input @@ -130,39 +98,38 @@ public final class MyAppsViewModel: NSObject, MyAppsViewModelProtocol { ) } - private func fetchMyApps() async throws -> [MyAppsEntity.MyAppsData] { + private func fetch() async { do { - let storedJWTInfo: MutaroJWT.JWTRequestInfo = try KeychainStore.shared.loadValue(forKey: .jwt) - let myApps = try? await environment.appInfoUseCase.fetchMyApps(storedJWTInfo: storedJWTInfo) - return myApps ?? [] + let myApps = try await fetchMyApps() + showRegisterJWT.send(false) + myAppsSubject.send(myApps) + let appInfos = try await fetchAppInfos(myApps: myApps) + appInfosSubject.send(appInfos) } catch { - throw JWTError.loadError + showRegisterJWT.send(true) + myAppsSubject.send([]) } } + private func fetchMyApps() async throws -> [MyAppsEntity.MyAppsData] { + let myApps = try await environment.appInfoUseCase.fetchMyApps() + return myApps + } + private func fetchAppInfos(myApps: [MyAppsEntity.MyAppsData]) async throws -> [AppInfo] { - let storedJWTInfo: MutaroJWT.JWTRequestInfo = try KeychainStore.shared.loadValue(forKey: .jwt) - let myAppsInfo = myApps - .compactMap { data -> (String, String)? in - guard let id = data.id, - let name = data.attributes?.name else { - return nil - } - return (id, name) - } - let appInfos = try await environment.appInfoUseCase.fetchAppInfos(storedJWTInfo: storedJWTInfo, myApps: myAppsInfo) + let appInfos = try await environment.appInfoUseCase.fetchAppInfos(myApps: myApps) return appInfos } - private func onTapRegisterJWT(from viewController: UIViewController) async { - await environment.router.showRegisterJWT(from: viewController) + private func onTapRegisterJWT(from viewController: UIViewController) { + environment.router.showRegisterJWT(from: viewController) } - private func onTapMyApp(from viewController: UIViewController, index: Int) async { + private func onTapMyApp(from viewController: UIViewController, index: Int) { guard let appInfo = appInfosSubject.value[getOrNil: index] else { return } - await environment.router.showMyAppTools(from: viewController, appId: appInfo.id) + environment.router.showMyAppTools(from: viewController, appId: appInfo.id) } private func prefetchItem( diff --git a/MutaroModule/Sources/Features/RegisterJWT/DI/RegisterJWTFeatureComponent.swift b/MutaroModule/Sources/Features/RegisterJWT/DI/RegisterJWTFeatureComponent.swift index 2c049dbb..8dd93f1a 100644 --- a/MutaroModule/Sources/Features/RegisterJWT/DI/RegisterJWTFeatureComponent.swift +++ b/MutaroModule/Sources/Features/RegisterJWT/DI/RegisterJWTFeatureComponent.swift @@ -10,11 +10,11 @@ import Foundation import NeedleFoundation import UIKit -public protocol RegisterJWTFeatureDependency: Dependency { - // TODO: - var 遷移するB FeatureのBuilder: BModuleBuildable { get } +public protocol RegisterJWTFeatureDependency: Dependency, Sendable { + var keychainDataStore: any KeychainDataStoreProtocol { get } } -class RegisterJWTFeatureBuilder: Builder, RegisterJWTFeatureBuildable { +public class RegisterJWTFeatureBuilder: Builder, RegisterJWTFeatureBuildable { @MainActor public func build() -> UIViewController { RegisterJWTViewController( @@ -25,7 +25,10 @@ class RegisterJWTFeatureBuilder: Builder, Register } private var environment: RegisterJWTFeatureEnvironment { - .init(router: router) + .init( + keychainDataStore: dependency.keychainDataStore, + router: router + ) } private var router: any RegisterJWTFeatureRoutable { diff --git a/MutaroModule/Sources/Features/RegisterJWT/DI/RegisterJWTFeatureEnvironment.swift b/MutaroModule/Sources/Features/RegisterJWT/DI/RegisterJWTFeatureEnvironment.swift index 92475dc3..34d0a1d8 100644 --- a/MutaroModule/Sources/Features/RegisterJWT/DI/RegisterJWTFeatureEnvironment.swift +++ b/MutaroModule/Sources/Features/RegisterJWT/DI/RegisterJWTFeatureEnvironment.swift @@ -5,12 +5,18 @@ // Created by minguk-kim on 2023/05/07. // +import Core import Foundation -public struct RegisterJWTFeatureEnvironment { +public struct RegisterJWTFeatureEnvironment: Sendable { + public let keychainDataStore: any KeychainDataStoreProtocol public let router: any RegisterJWTFeatureRoutable - public init(router: any RegisterJWTFeatureRoutable) { + public init( + keychainDataStore: any KeychainDataStoreProtocol, + router: any RegisterJWTFeatureRoutable + ) { + self.keychainDataStore = keychainDataStore self.router = router } } diff --git a/MutaroModule/Sources/Features/RegisterJWT/Router/RegisterJWTRouter.swift b/MutaroModule/Sources/Features/RegisterJWT/Router/RegisterJWTRouter.swift index 6ac2aa16..bd8cc289 100644 --- a/MutaroModule/Sources/Features/RegisterJWT/Router/RegisterJWTRouter.swift +++ b/MutaroModule/Sources/Features/RegisterJWT/Router/RegisterJWTRouter.swift @@ -7,13 +7,13 @@ import UIKit -public protocol RegisterJWTFeatureRoutable { +public protocol RegisterJWTFeatureRoutable: Sendable { @MainActor func close(from viewController: UIViewController) } -public class RegisterJWTFeatureRouter: RegisterJWTFeatureRoutable { - public struct Dependency {} +public final class RegisterJWTFeatureRouter: RegisterJWTFeatureRoutable { + public struct Dependency: Sendable {} private let dependency: Dependency diff --git a/MutaroModule/Sources/Features/RegisterJWT/ViewModel/RegisterJWTViewModel.swift b/MutaroModule/Sources/Features/RegisterJWT/ViewModel/RegisterJWTViewModel.swift index a36a3893..5a9cfe88 100644 --- a/MutaroModule/Sources/Features/RegisterJWT/ViewModel/RegisterJWTViewModel.swift +++ b/MutaroModule/Sources/Features/RegisterJWT/ViewModel/RegisterJWTViewModel.swift @@ -12,10 +12,12 @@ import JWTGenerator import KeychainStore import UIKit +@MainActor public protocol RegisterJWTRoute { func openRegisterJWTRoute() } +@MainActor protocol RegisterJWTViewModelProtocol { func transform(input: RegisterJWTViewModel.Input) -> RegisterJWTViewModel.Output } @@ -34,7 +36,8 @@ extension RegisterJWTViewModel { } } -public final class RegisterJWTViewModel: RegisterJWTViewModelProtocol { +@MainActor +public final class RegisterJWTViewModel: RegisterJWTViewModelProtocol, Sendable { private let showAlertSubject = PassthroughSubject() private var cancellables: Set = [] private let taskCancellables: TaskCancellable = .init() @@ -57,12 +60,9 @@ public final class RegisterJWTViewModel: RegisterJWTViewModelProtocol { input .didTapRegister .receive(on: DispatchQueue.main) - .asyncSink( - taskCancellable: taskCancellables, - receiveValue: { [weak self] in - await self?.onTapRegister(item: $0) - } - ) + .sink { [weak self] in + self?.onTapRegister(item: $0) + } .store(in: &cancellables) return .init( @@ -74,7 +74,7 @@ public final class RegisterJWTViewModel: RegisterJWTViewModelProtocol { private func onTapRegister( item: RegisterItem - ) async { + ) { guard let issuerID = item.issuerID, !issuerID.isEmpty else { showAlertSubject.send(.invalidIssuerID) @@ -111,17 +111,17 @@ public final class RegisterJWTViewModel: RegisterJWTViewModelProtocol { ) do { - try KeychainStore.shared.deleteValue(forKey: .jwt) - try KeychainStore.shared.saveValue(info, forKey: .jwt) + try environment.keychainDataStore.deleteValue(forKey: .jwt) + try environment.keychainDataStore.saveValue(info, forKey: .jwt) showAlertSubject.send(.successedSavingJWTReuqestInfo) - await environment.router.close(from: item.viewController) + environment.router.close(from: item.viewController) } catch { showAlertSubject.send(.failedSavingJWTRequestInfo) } } private func loadRegisteredInfo() -> MutaroJWT.JWTRequestInfo? { - try? KeychainStore.shared.loadValue(forKey: .jwt) + try? environment.keychainDataStore.loadValue(forKey: .jwt) } private func didPickDocuments(urls: [URL]) -> String? { diff --git a/MutaroModule/Sources/Features/Setting/DI/SettingFeatureComponent.swift b/MutaroModule/Sources/Features/Setting/DI/SettingFeatureComponent.swift index b482339c..2acef906 100644 --- a/MutaroModule/Sources/Features/Setting/DI/SettingFeatureComponent.swift +++ b/MutaroModule/Sources/Features/Setting/DI/SettingFeatureComponent.swift @@ -10,7 +10,7 @@ import Foundation import NeedleFoundation import UIKit -public protocol SettingFeatureDependency: Dependency { +public protocol SettingFeatureDependency: Dependency, Sendable { // TODO: - var 遷移するB FeatureのBuilder: BModuleBuildable { get } var registerJWTFeatureBuilder: any RegisterJWTFeatureBuildable { get } } diff --git a/MutaroModule/Sources/Features/Setting/DI/SettingFeatureEnvironment.swift b/MutaroModule/Sources/Features/Setting/DI/SettingFeatureEnvironment.swift index 659c0f78..3da731c5 100644 --- a/MutaroModule/Sources/Features/Setting/DI/SettingFeatureEnvironment.swift +++ b/MutaroModule/Sources/Features/Setting/DI/SettingFeatureEnvironment.swift @@ -7,7 +7,7 @@ import Foundation -public struct SettingFeatureEnvironment { +public struct SettingFeatureEnvironment: Sendable { public let router: any SettingRoutable public init(router: any SettingRoutable) { diff --git a/MutaroModule/Sources/Features/Setting/Router/SettingRouter.swift b/MutaroModule/Sources/Features/Setting/Router/SettingRouter.swift index 84c5fd12..ffcb56fe 100644 --- a/MutaroModule/Sources/Features/Setting/Router/SettingRouter.swift +++ b/MutaroModule/Sources/Features/Setting/Router/SettingRouter.swift @@ -8,13 +8,13 @@ import Core import UIKit -public protocol SettingRoutable { +public protocol SettingRoutable: Sendable { @MainActor func showRegisterJWT(from viewController: UIViewController) } -public class SettingRouter: SettingRoutable { - public struct Dependency { +public final class SettingRouter: SettingRoutable { + public struct Dependency: Sendable { public let registerJWTFeatureBuilder: any RegisterJWTFeatureBuildable init(registerJWTFeatureBuilder: any RegisterJWTFeatureBuildable) { diff --git a/MutaroModule/Sources/Features/Setting/ViewController/SettingViewController.swift b/MutaroModule/Sources/Features/Setting/ViewController/SettingViewController.swift index 44a9ee6b..9885c487 100644 --- a/MutaroModule/Sources/Features/Setting/ViewController/SettingViewController.swift +++ b/MutaroModule/Sources/Features/Setting/ViewController/SettingViewController.swift @@ -166,9 +166,7 @@ extension SettingViewController: UICollectionViewDelegate { case .defaultSetting: break case .registerJWT: - Task { - await viewModel.routeToRegisterJWT(from: self) - } + viewModel.routeToRegisterJWT(from: self) case .none: break } diff --git a/MutaroModule/Sources/Features/Setting/ViewModel/SettingViewModel.swift b/MutaroModule/Sources/Features/Setting/ViewModel/SettingViewModel.swift index 3d0147a3..1b66f3a0 100644 --- a/MutaroModule/Sources/Features/Setting/ViewModel/SettingViewModel.swift +++ b/MutaroModule/Sources/Features/Setting/ViewModel/SettingViewModel.swift @@ -9,7 +9,8 @@ import Combine import Core import UIKit -public final class SettingViewModel: NSObject { +@MainActor +public final class SettingViewModel: NSObject, Sendable { private let environment: SettingFeatureEnvironment var cancellables: Set = [] @@ -18,7 +19,7 @@ public final class SettingViewModel: NSObject { self.environment = environment } - public func routeToRegisterJWT(from viewController: UIViewController) async { - await environment.router.showRegisterJWT(from: viewController) + public func routeToRegisterJWT(from viewController: UIViewController) { + environment.router.showRegisterJWT(from: viewController) } } diff --git a/MutaroModule/Sources/Modules/JWTGenerator/Common/JWT.swift b/MutaroModule/Sources/Modules/JWTGenerator/Common/JWT.swift index eccc4075..ea3e32d4 100644 --- a/MutaroModule/Sources/Modules/JWTGenerator/Common/JWT.swift +++ b/MutaroModule/Sources/Modules/JWTGenerator/Common/JWT.swift @@ -8,7 +8,7 @@ import Foundation public enum MutaroJWT { - public struct JWTRequestInfo: Codable, Equatable { + public struct JWTRequestInfo: Codable, Equatable, Sendable { public let issuerID: String public let keyID: String public let privateKey: String diff --git a/MutaroModule/Sources/Modules/KeychainStore/KeychainStore.swift b/MutaroModule/Sources/Modules/KeychainStore/KeychainDataStore.swift similarity index 91% rename from MutaroModule/Sources/Modules/KeychainStore/KeychainStore.swift rename to MutaroModule/Sources/Modules/KeychainStore/KeychainDataStore.swift index fb7e74c3..a15ba13a 100644 --- a/MutaroModule/Sources/Modules/KeychainStore/KeychainStore.swift +++ b/MutaroModule/Sources/Modules/KeychainStore/KeychainDataStore.swift @@ -1,18 +1,19 @@ // -// KeychainStore.swift +// KeychainDataStore.swift // // // Created by minguk-kim on 2023/05/04. // +import Core import Foundation import Security -public final class KeychainStore { - public static let shared = KeychainStore() - +public struct KeychainDataStore: KeychainDataStoreProtocol, Sendable { private let service = Bundle.main.bundleIdentifier ?? "Mutaro.com" + public init() {} + public func saveValue(_ value: some Codable, forKey key: KeychainStoreKey) throws { let data = try JSONEncoder().encode(value) @@ -80,9 +81,3 @@ public final class KeychainStore { } } } - -enum KeychainError: Error { - case saveError(OSStatus) - case loadError(OSStatus) - case deleteError(OSStatus) -}