From 31c47333d759f689a7750ce13ec57c9aa3ad4dcd Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Fri, 16 May 2025 00:15:17 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[#40]=20ReducerKit=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReducerKit.xcodeproj/project.pbxproj | 369 ++++++++++++++++++ Core/ReducerKit/Sources/ReducerKit.swift | 9 + Mark-In.xcodeproj/project.pbxproj | 13 + 3 files changed, 391 insertions(+) create mode 100644 Core/ReducerKit/ReducerKit.xcodeproj/project.pbxproj create mode 100644 Core/ReducerKit/Sources/ReducerKit.swift diff --git a/Core/ReducerKit/ReducerKit.xcodeproj/project.pbxproj b/Core/ReducerKit/ReducerKit.xcodeproj/project.pbxproj new file mode 100644 index 0000000..60bd215 --- /dev/null +++ b/Core/ReducerKit/ReducerKit.xcodeproj/project.pbxproj @@ -0,0 +1,369 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXFileReference section */ + 57606D5B2DD63B14005EBE3D /* ReducerKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReducerKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 57606D5D2DD63B14005EBE3D /* Sources */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Sources; + sourceTree = ""; + }; +/* End PBXFileSystemSynchronizedRootGroup section */ + +/* Begin PBXFrameworksBuildPhase section */ + 57606D582DD63B14005EBE3D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 57606D512DD63B14005EBE3D = { + isa = PBXGroup; + children = ( + 57606D5D2DD63B14005EBE3D /* Sources */, + 57606D5C2DD63B14005EBE3D /* Products */, + ); + sourceTree = ""; + }; + 57606D5C2DD63B14005EBE3D /* Products */ = { + isa = PBXGroup; + children = ( + 57606D5B2DD63B14005EBE3D /* ReducerKit.framework */, + ); + name = Products; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 57606D562DD63B14005EBE3D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 57606D5A2DD63B14005EBE3D /* ReducerKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = 57606D622DD63B14005EBE3D /* Build configuration list for PBXNativeTarget "ReducerKit" */; + buildPhases = ( + 57606D562DD63B14005EBE3D /* Headers */, + 57606D572DD63B14005EBE3D /* Sources */, + 57606D582DD63B14005EBE3D /* Frameworks */, + 57606D592DD63B14005EBE3D /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + fileSystemSynchronizedGroups = ( + 57606D5D2DD63B14005EBE3D /* Sources */, + ); + name = ReducerKit; + packageProductDependencies = ( + ); + productName = ReducerKit; + productReference = 57606D5B2DD63B14005EBE3D /* ReducerKit.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 57606D522DD63B14005EBE3D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = 1; + LastSwiftUpdateCheck = 1630; + LastUpgradeCheck = 1630; + TargetAttributes = { + 57606D5A2DD63B14005EBE3D = { + CreatedOnToolsVersion = 16.3; + }; + }; + }; + buildConfigurationList = 57606D552DD63B14005EBE3D /* Build configuration list for PBXProject "ReducerKit" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 57606D512DD63B14005EBE3D; + minimizedProjectReferenceProxies = 1; + preferredProjectObjectVersion = 77; + productRefGroup = 57606D5C2DD63B14005EBE3D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 57606D5A2DD63B14005EBE3D /* ReducerKit */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 57606D592DD63B14005EBE3D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 57606D572DD63B14005EBE3D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 57606D602DD63B14005EBE3D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEVELOPMENT_TEAM = 5DFZR8RCQR; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 57606D612DD63B14005EBE3D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEVELOPMENT_TEAM = 5DFZR8RCQR; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SWIFT_COMPILATION_MODE = wholemodule; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 57606D632DD63B14005EBE3D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 5DFZR8RCQR; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = kr.co.ios.swift.apple.ReducerKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = auto; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_MODULE = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 2.0; + }; + name = Debug; + }; + 57606D642DD63B14005EBE3D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALLOW_TARGET_PLATFORM_SPECIALIZATION = YES; + BUILD_LIBRARY_FOR_DISTRIBUTION = YES; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 5DFZR8RCQR; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_MODULE_VERIFIER = YES; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; + LD_RUNPATH_SEARCH_PATHS = ( + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + MACOSX_DEPLOYMENT_TARGET = 14.0; + MARKETING_VERSION = 1.0; + MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; + MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; + PRODUCT_BUNDLE_IDENTIFIER = kr.co.ios.swift.apple.ReducerKit; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SDKROOT = auto; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx xros xrsimulator"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_INSTALL_MODULE = YES; + SWIFT_INSTALL_OBJC_HEADER = NO; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2,7"; + XROS_DEPLOYMENT_TARGET = 2.0; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 57606D552DD63B14005EBE3D /* Build configuration list for PBXProject "ReducerKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 57606D602DD63B14005EBE3D /* Debug */, + 57606D612DD63B14005EBE3D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 57606D622DD63B14005EBE3D /* Build configuration list for PBXNativeTarget "ReducerKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 57606D632DD63B14005EBE3D /* Debug */, + 57606D642DD63B14005EBE3D /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 57606D522DD63B14005EBE3D /* Project object */; +} diff --git a/Core/ReducerKit/Sources/ReducerKit.swift b/Core/ReducerKit/Sources/ReducerKit.swift new file mode 100644 index 0000000..3f1d9f4 --- /dev/null +++ b/Core/ReducerKit/Sources/ReducerKit.swift @@ -0,0 +1,9 @@ +// +// ReducerKit.swift +// ReducerKit +// +// Created by 이정동 on 5/16/25. +// + +import Foundation + diff --git a/Mark-In.xcodeproj/project.pbxproj b/Mark-In.xcodeproj/project.pbxproj index e1171a9..af3cc96 100644 --- a/Mark-In.xcodeproj/project.pbxproj +++ b/Mark-In.xcodeproj/project.pbxproj @@ -71,6 +71,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 57606D652DD63B14005EBE3D /* ReducerKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ReducerKit.xcodeproj; path = ReducerKit/ReducerKit.xcodeproj; sourceTree = ""; }; 57AC52EF2DA4FBC900BA84BD /* DesignSystem.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = DesignSystem.xcodeproj; path = DesignSystem/DesignSystem.xcodeproj; sourceTree = ""; }; 57AC53092DA4FC1800BA84BD /* Util.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Util.xcodeproj; path = Util/Util.xcodeproj; sourceTree = ""; }; 57AC53232DA4FC4900BA84BD /* LinkMetadataKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = LinkMetadataKit.xcodeproj; path = LinkMetadataKit/LinkMetadataKit.xcodeproj; sourceTree = ""; }; @@ -118,6 +119,13 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 57588A542DD63B5900DBD9A7 /* Products */ = { + isa = PBXGroup; + children = ( + ); + name = Products; + sourceTree = ""; + }; 57AC55422DA507EC00BA84BD /* Frameworks */ = { isa = PBXGroup; children = ( @@ -128,6 +136,7 @@ 57AC565F2DA511A900BA84BD /* Core */ = { isa = PBXGroup; children = ( + 57606D652DD63B14005EBE3D /* ReducerKit.xcodeproj */, 57AC53232DA4FC4900BA84BD /* LinkMetadataKit.xcodeproj */, 57AC52EF2DA4FBC900BA84BD /* DesignSystem.xcodeproj */, ); @@ -259,6 +268,10 @@ ProductGroup = 57AC56782DA511F200BA84BD /* Products */; ProjectRef = 57AC53232DA4FC4900BA84BD /* LinkMetadataKit.xcodeproj */; }, + { + ProductGroup = 57588A542DD63B5900DBD9A7 /* Products */; + ProjectRef = 57606D652DD63B14005EBE3D /* ReducerKit.xcodeproj */; + }, { ProductGroup = 57AC566F2DA511E900BA84BD /* Products */; ProjectRef = 57AC53092DA4FC1800BA84BD /* Util.xcodeproj */; From 62435406e1064f3f168a7f0c34668ce71c25e3de Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Fri, 16 May 2025 14:41:03 +0900 Subject: [PATCH 2/5] =?UTF-8?q?[#40]=20ReducerKit=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ReducerKit.xcodeproj/project.pbxproj | 2 + Core/ReducerKit/Sources/Effect.swift | 17 +++++++ Core/ReducerKit/Sources/Reducer.swift | 21 +++++++++ Core/ReducerKit/Sources/ReducerKit.swift | 9 ---- Core/ReducerKit/Sources/Store.swift | 47 +++++++++++++++++++ Mark-In.xcodeproj/project.pbxproj | 19 ++++++++ 6 files changed, 106 insertions(+), 9 deletions(-) create mode 100644 Core/ReducerKit/Sources/Effect.swift create mode 100644 Core/ReducerKit/Sources/Reducer.swift delete mode 100644 Core/ReducerKit/Sources/ReducerKit.swift create mode 100644 Core/ReducerKit/Sources/Store.swift diff --git a/Core/ReducerKit/ReducerKit.xcodeproj/project.pbxproj b/Core/ReducerKit/ReducerKit.xcodeproj/project.pbxproj index 60bd215..64d4434 100644 --- a/Core/ReducerKit/ReducerKit.xcodeproj/project.pbxproj +++ b/Core/ReducerKit/ReducerKit.xcodeproj/project.pbxproj @@ -35,7 +35,9 @@ 57606D5D2DD63B14005EBE3D /* Sources */, 57606D5C2DD63B14005EBE3D /* Products */, ); + indentWidth = 2; sourceTree = ""; + tabWidth = 2; }; 57606D5C2DD63B14005EBE3D /* Products */ = { isa = PBXGroup; diff --git a/Core/ReducerKit/Sources/Effect.swift b/Core/ReducerKit/Sources/Effect.swift new file mode 100644 index 0000000..05f3a5c --- /dev/null +++ b/Core/ReducerKit/Sources/Effect.swift @@ -0,0 +1,17 @@ +// +// Effect.swift +// ReducerKit +// +// Created by 이정동 on 5/16/25. +// + +import Foundation + +/// Reducer가 처리한 결과로 발생시킬 수 있는 추가적인 작업 +public enum Effect { + /// 추가적인 작업을 실행하지 않음 + case none + + /// Side Effect를 처리하고, 새로운 Action을 호출 + case run(() async -> Action) +} diff --git a/Core/ReducerKit/Sources/Reducer.swift b/Core/ReducerKit/Sources/Reducer.swift new file mode 100644 index 0000000..48402d4 --- /dev/null +++ b/Core/ReducerKit/Sources/Reducer.swift @@ -0,0 +1,21 @@ +// +// ReducerKit.swift +// ReducerKit +// +// Created by 이정동 on 5/16/25. +// + +import Foundation + +/// 액션에 따른 상태 변화 로직의 역할을 담당하는 프로토콜 +public protocol Reducer { + /// View에 표시할 데이터를 정의 + associatedtype State + + /// View에서 발생할 수 있는 동작을 정의 + associatedtype Action + + /// 액션에 따라 기존 상태를 새로운 상태로 변경 + func reduce(into state: inout State, action: Action) -> Effect +} + diff --git a/Core/ReducerKit/Sources/ReducerKit.swift b/Core/ReducerKit/Sources/ReducerKit.swift deleted file mode 100644 index 3f1d9f4..0000000 --- a/Core/ReducerKit/Sources/ReducerKit.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// ReducerKit.swift -// ReducerKit -// -// Created by 이정동 on 5/16/25. -// - -import Foundation - diff --git a/Core/ReducerKit/Sources/Store.swift b/Core/ReducerKit/Sources/Store.swift new file mode 100644 index 0000000..1956413 --- /dev/null +++ b/Core/ReducerKit/Sources/Store.swift @@ -0,0 +1,47 @@ +// +// Store.swift +// ReducerKit +// +// Created by 이정동 on 5/16/25. +// + +import Foundation + +/// Store 타입을 좀 더 간편하게 정의(표현). +public typealias StoreOf = Store + +/// Reducer를 실행하고 UI를 업데이트하는 상태 관리 라이프 사이클 관리자 +@MainActor @Observable +public final class Store { + + private(set) public var state: State + private let reduce: (inout State, Action) -> Effect + + // Reducer의 연관 타입 State, Action과 Store의 타입 파라미터 State, Action이 동일함을 표현 + public init>( + initialState: State, + reducer: R + ) { + self.state = initialState + self.reduce = reducer.reduce(into:action:) + } + + // View로부터 Action을 전달 받아서 상태 업데이트 + public func send(_ action: Action) { + let effect = reduce(&state, action) + handleEffect(effect) + } + + // reduce 작업 이후 추가적인 작업 실행 + private func handleEffect(_ effect: Effect) { + switch effect { + case .none: + break + case let .run(action): + Task.detached { [weak self] in + let newAction = await action() + await self?.send(newAction) + } + } + } +} diff --git a/Mark-In.xcodeproj/project.pbxproj b/Mark-In.xcodeproj/project.pbxproj index af3cc96..873b9e7 100644 --- a/Mark-In.xcodeproj/project.pbxproj +++ b/Mark-In.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 57588B452DD640C100DBD9A7 /* ReducerKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57588B412DD63D9700DBD9A7 /* ReducerKit.framework */; }; + 57588B462DD640C100DBD9A7 /* ReducerKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 57588B412DD63D9700DBD9A7 /* ReducerKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 57944D0C2DC52AEF00EF3D9A /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = 57944D0B2DC52AEF00EF3D9A /* GoogleSignIn */; }; 57AC56A22DA5120600BA84BD /* DesignSystem.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57AC56A12DA511F800BA84BD /* DesignSystem.framework */; }; 57AC56A32DA5120600BA84BD /* DesignSystem.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 57AC56A12DA511F800BA84BD /* DesignSystem.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; @@ -23,6 +25,13 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + 57588B402DD63D9700DBD9A7 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 57606D652DD63B14005EBE3D /* ReducerKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 57606D5B2DD63B14005EBE3D; + remoteInfo = ReducerKit; + }; 57AC56722DA511E900BA84BD /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 57AC53092DA4FC1800BA84BD /* Util.xcodeproj */; @@ -61,6 +70,7 @@ dstSubfolderSpec = 10; files = ( 57AC56A92DA5120600BA84BD /* Util.framework in Embed Frameworks */, + 57588B462DD640C100DBD9A7 /* ReducerKit.framework in Embed Frameworks */, 57AC56A72DA5120600BA84BD /* LinkMetadataKitInterface.framework in Embed Frameworks */, 57AC56A52DA5120600BA84BD /* LinkMetadataKit.framework in Embed Frameworks */, 57AC56A32DA5120600BA84BD /* DesignSystem.framework in Embed Frameworks */, @@ -110,6 +120,7 @@ 57AC56A62DA5120600BA84BD /* LinkMetadataKitInterface.framework in Frameworks */, 57AC56A42DA5120600BA84BD /* LinkMetadataKit.framework in Frameworks */, 57E1F9872DA8F9B40033F30C /* FirebaseCore in Frameworks */, + 57588B452DD640C100DBD9A7 /* ReducerKit.framework in Frameworks */, 57B5F82E2DC08B400061F0DE /* FirebaseStorage in Frameworks */, 57AC56A22DA5120600BA84BD /* DesignSystem.framework in Frameworks */, 57E1F9892DA8F9B40033F30C /* FirebaseFirestore in Frameworks */, @@ -122,6 +133,7 @@ 57588A542DD63B5900DBD9A7 /* Products */ = { isa = PBXGroup; children = ( + 57588B412DD63D9700DBD9A7 /* ReducerKit.framework */, ); name = Products; sourceTree = ""; @@ -285,6 +297,13 @@ /* End PBXProject section */ /* Begin PBXReferenceProxy section */ + 57588B412DD63D9700DBD9A7 /* ReducerKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = ReducerKit.framework; + remoteRef = 57588B402DD63D9700DBD9A7 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 57AC56732DA511E900BA84BD /* Util.framework */ = { isa = PBXReferenceProxy; fileType = wrapper.framework; From 5415f78b890d3e2e5797cb9961c10e7602a3665b Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Fri, 16 May 2025 15:01:53 +0900 Subject: [PATCH 3/5] =?UTF-8?q?[#40]=20ReducerKit=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ViewModel.swift => AddFolderReducer.swift} | 26 +++------------- .../Feature/AddFolder/AddFolderView.swift | 16 ++++++---- ...nkViewModel.swift => AddLinkReducer.swift} | 26 +++------------- .../Sources/Feature/AddLink/AddLinkView.swift | 16 ++++++---- Mark-In/Sources/Feature/Common/Reducer.swift | 23 -------------- ...oginViewModel.swift => LoginReducer.swift} | 27 +++-------------- Mark-In/Sources/Feature/Login/LoginView.swift | 28 +++++++++-------- .../Sources/Feature/Main/LinkListView.swift | 7 +++-- ...{MainViewModel.swift => MainReducer.swift} | 28 ++++------------- Mark-In/Sources/Feature/Main/MainView.swift | 30 +++++++++++-------- Mark-In/Sources/Feature/Main/SideBar.swift | 18 ++++++----- ...ageViewModel.swift => MyPageReducer.swift} | 28 ++++------------- .../Sources/Feature/MyPage/MyPageView.swift | 10 +++++-- 13 files changed, 97 insertions(+), 186 deletions(-) rename Mark-In/Sources/Feature/AddFolder/{AddFolderViewModel.swift => AddFolderReducer.swift} (67%) rename Mark-In/Sources/Feature/AddLink/{AddLinkViewModel.swift => AddLinkReducer.swift} (64%) delete mode 100644 Mark-In/Sources/Feature/Common/Reducer.swift rename Mark-In/Sources/Feature/Login/{LoginViewModel.swift => LoginReducer.swift} (80%) rename Mark-In/Sources/Feature/Main/{MainViewModel.swift => MainReducer.swift} (80%) rename Mark-In/Sources/Feature/MyPage/{MyPageViewModel.swift => MyPageReducer.swift} (68%) diff --git a/Mark-In/Sources/Feature/AddFolder/AddFolderViewModel.swift b/Mark-In/Sources/Feature/AddFolder/AddFolderReducer.swift similarity index 67% rename from Mark-In/Sources/Feature/AddFolder/AddFolderViewModel.swift rename to Mark-In/Sources/Feature/AddFolder/AddFolderReducer.swift index aa4e795..ada7577 100644 --- a/Mark-In/Sources/Feature/AddFolder/AddFolderViewModel.swift +++ b/Mark-In/Sources/Feature/AddFolder/AddFolderReducer.swift @@ -7,8 +7,9 @@ import Foundation -@Observable @MainActor -final class AddFolderViewModel: Reducer { +import ReducerKit + +struct AddFolderReducer: Reducer { struct State { var createdFolder: Folder? var isLoading: Bool = false @@ -23,18 +24,11 @@ final class AddFolderViewModel: Reducer { private let generateFolderUseCase: GenerateFolderUseCase - private(set) var state: State = .init() - init() { self.generateFolderUseCase = DIContainer.shared.resolve() } - func send(_ action: Action) { - let effect = reduce(state: &state, action: action) - handleEffect(effect) - } - - func reduce(state: inout State, action: Action) -> Effect { + func reduce(into state: inout State, action: Action) -> Effect { switch action { case .didTapAddFolderButton(let name): state.isLoading = true @@ -60,17 +54,5 @@ final class AddFolderViewModel: Reducer { return .none } } - - private func handleEffect(_ effect: Effect) { - switch effect { - case .none: - break - case .run(let action): - Task.detached { [weak self] in - let newAction = await action() - await self?.send(newAction) - } - } - } } diff --git a/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift b/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift index e2147b5..77a7ca1 100644 --- a/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift +++ b/Mark-In/Sources/Feature/AddFolder/AddFolderView.swift @@ -8,14 +8,18 @@ import SwiftUI import DesignSystem +import ReducerKit struct AddFolderView: View { @Environment(\.dismiss) private var dismiss - @State private var viewModel = AddFolderViewModel() + @State private var store: StoreOf = .init( + initialState: AddFolderReducer.State(), + reducer: AddFolderReducer() + ) @State private var title: String = "" private var isSaving: Bool { - viewModel.state.isLoading + store.state.isLoading } let completion: (Folder) -> () @@ -54,7 +58,7 @@ struct AddFolderView: View { .disabled(title.isEmpty || isSaving) Button { - viewModel.send(.didTapAddFolderButton(name: title)) + store.send(.didTapAddFolderButton(name: title)) } label: { Text("추가") .padding(.vertical, 4) @@ -72,7 +76,7 @@ struct AddFolderView: View { } .padding(20) .frame(width: 400) - .onChange(of: viewModel.state.createdFolder) { + .onChange(of: store.state.createdFolder) { guard let folder = $1 else { return } completion(folder) dismiss() @@ -80,8 +84,8 @@ struct AddFolderView: View { .alert( "폴더 생성에 실패했습니다.", isPresented: .init( - get: { viewModel.state.isError }, - set: { viewModel.send(.updateErrorState($0)) } + get: { store.state.isError }, + set: { store.send(.updateErrorState($0)) } ) ) { Button(role: .cancel) { diff --git a/Mark-In/Sources/Feature/AddLink/AddLinkViewModel.swift b/Mark-In/Sources/Feature/AddLink/AddLinkReducer.swift similarity index 64% rename from Mark-In/Sources/Feature/AddLink/AddLinkViewModel.swift rename to Mark-In/Sources/Feature/AddLink/AddLinkReducer.swift index 06d25c3..3389236 100644 --- a/Mark-In/Sources/Feature/AddLink/AddLinkViewModel.swift +++ b/Mark-In/Sources/Feature/AddLink/AddLinkReducer.swift @@ -7,8 +7,9 @@ import Foundation -@Observable @MainActor -final class AddLinkViewModel: Reducer { +import ReducerKit + +struct AddLinkReducer: Reducer { struct State { var createdLink: Link? var isSaving: Bool = false @@ -23,18 +24,11 @@ final class AddLinkViewModel: Reducer { private let generateLinkUseCase: GenerateLinkUseCase - private(set) var state: State = .init() - init() { self.generateLinkUseCase = DIContainer.shared.resolve() } - func send(_ action: Action) { - let effect = reduce(state: &state, action: action) - handleEffect(effect) - } - - func reduce(state: inout State, action: Action) -> Effect { + func reduce(into state: inout State, action: Action) -> Effect { switch action { case .addLinkButtonTapped(let writeLink): state.isSaving = true @@ -56,16 +50,4 @@ final class AddLinkViewModel: Reducer { return .none } } - - private func handleEffect(_ effect: Effect) { - switch effect { - case .none: - break - case .run(let action): - Task.detached { [weak self] in - let newAction = await action() - await self?.send(newAction) - } - } - } } diff --git a/Mark-In/Sources/Feature/AddLink/AddLinkView.swift b/Mark-In/Sources/Feature/AddLink/AddLinkView.swift index 36e3027..8ea8029 100644 --- a/Mark-In/Sources/Feature/AddLink/AddLinkView.swift +++ b/Mark-In/Sources/Feature/AddLink/AddLinkView.swift @@ -8,10 +8,14 @@ import SwiftUI import DesignSystem +import ReducerKit struct AddLinkView: View { @Environment(\.dismiss) var dismiss - @State private var viewModel = AddLinkViewModel() + @State private var store: StoreOf = .init( + initialState: AddLinkReducer.State(), + reducer: AddLinkReducer() + ) @State private var title: String = "" @State private var url: String = "" @@ -21,7 +25,7 @@ struct AddLinkView: View { private let completion: (Link) -> Void private var isSaving: Bool { - viewModel.state.isSaving + store.state.isSaving } init( @@ -91,7 +95,7 @@ struct AddLinkView: View { title: title, folderID: currentFolder.id ) - viewModel.send(.addLinkButtonTapped(link: link)) + store.send(.addLinkButtonTapped(link: link)) } label: { Text("추가") .padding(.vertical, 4) @@ -109,7 +113,7 @@ struct AddLinkView: View { } .padding(20) .frame(width: 400) - .onChange(of: viewModel.state.createdLink) { + .onChange(of: store.state.createdLink) { guard let link = $1 else { return } completion(link) dismiss() @@ -117,8 +121,8 @@ struct AddLinkView: View { .alert( "링크 생성에 실패했습니다.", isPresented: .init( - get: { viewModel.state.isError }, - set: { viewModel.send(.occurError($0)) } + get: { store.state.isError }, + set: { store.send(.occurError($0)) } ) ) { Button(role: .cancel) { diff --git a/Mark-In/Sources/Feature/Common/Reducer.swift b/Mark-In/Sources/Feature/Common/Reducer.swift deleted file mode 100644 index 0f30140..0000000 --- a/Mark-In/Sources/Feature/Common/Reducer.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Reducer.swift -// Mark-In -// -// Created by 이정동 on 4/25/25. -// - -import Foundation - -@MainActor -protocol Reducer { - associatedtype State - associatedtype Action - - func send(_ action: Action) - - func reduce(state: inout State, action: Action) -> Effect -} - -enum Effect { - case none - case run(() async -> Action) -} diff --git a/Mark-In/Sources/Feature/Login/LoginViewModel.swift b/Mark-In/Sources/Feature/Login/LoginReducer.swift similarity index 80% rename from Mark-In/Sources/Feature/Login/LoginViewModel.swift rename to Mark-In/Sources/Feature/Login/LoginReducer.swift index d913d4e..e7aed64 100644 --- a/Mark-In/Sources/Feature/Login/LoginViewModel.swift +++ b/Mark-In/Sources/Feature/Login/LoginReducer.swift @@ -5,17 +5,17 @@ // Created by 이정동 on 5/1/25. // -import Foundation import AuthenticationServices +import Foundation import SwiftUI import FirebaseAuth import GoogleSignIn +import ReducerKit import Util -@Observable -final class LoginViewModel: Reducer { +struct LoginReducer: Reducer { struct State { // TODO: 로그인 실패 시 알림 화면을 보여줄 상태 구현 생각 중 } @@ -31,18 +31,11 @@ final class LoginViewModel: Reducer { private let signInUseCase: SignInUseCase - private(set) var state: State = .init() - init() { self.signInUseCase = DIContainer.shared.resolve() } - func send(_ action: Action) { - let effect = reduce(state: &state, action: action) - handleEffect(effect) - } - - func reduce(state: inout State, action: Action) -> Effect { + func reduce(into state: inout State, action: Action) -> Effect { switch action { case .appleLoginButtonTapped(let authController): /// 1. 애플 로그인 요청을 위한 객체 생성 @@ -95,16 +88,4 @@ final class LoginViewModel: Reducer { return .none } } - - private func handleEffect(_ effect: Effect) { - switch effect { - case .none: - break - case .run(let action): - Task.detached { [weak self] in - let newAction = await action() - await self?.send(newAction) - } - } - } } diff --git a/Mark-In/Sources/Feature/Login/LoginView.swift b/Mark-In/Sources/Feature/Login/LoginView.swift index 7c6b5e7..c7fd2e4 100644 --- a/Mark-In/Sources/Feature/Login/LoginView.swift +++ b/Mark-In/Sources/Feature/Login/LoginView.swift @@ -5,14 +5,18 @@ // Created by 이정동 on 4/30/25. // -import SwiftUI import AuthenticationServices +import SwiftUI import DesignSystem +import ReducerKit import Util struct LoginView: View { - @State private var loginViewModel = LoginViewModel() + @State private var store: StoreOf = .init( + initialState: LoginReducer.State(), + reducer: LoginReducer() + ) var body: some View { ZStack { @@ -26,7 +30,7 @@ struct LoginView: View { VStack(spacing: 28) { headerView - BodyView(loginViewModel: loginViewModel) + BodyView(store: store) } } } @@ -46,10 +50,10 @@ struct LoginView: View { } private struct BodyView: View { - private let loginViewModel: LoginViewModel + private let store: StoreOf - init(loginViewModel: LoginViewModel) { - self.loginViewModel = loginViewModel + init(store: StoreOf) { + self.store = store } var body: some View { @@ -59,7 +63,7 @@ private struct BodyView: View { divider - SignInButtonList(loginViewModel: loginViewModel) + SignInButtonList(store: store) } .frame(maxWidth: .infinity, alignment: .leading) } @@ -96,20 +100,20 @@ private struct BodyView: View { private struct SignInButtonList: View { @Environment(\.authorizationController) private var authorizationController - private let loginViewModel: LoginViewModel + private let store: StoreOf - init(loginViewModel: LoginViewModel) { - self.loginViewModel = loginViewModel + init(store: StoreOf) { + self.store = store } var body: some View { VStack(spacing: 12) { SignInButton(provider: .apple) { - loginViewModel.send(.appleLoginButtonTapped(authorizationController)) + store.send(.appleLoginButtonTapped(authorizationController)) } SignInButton(provider: .google) { - loginViewModel.send(.googleLoginButtonTapped) + store.send(.googleLoginButtonTapped) } } } diff --git a/Mark-In/Sources/Feature/Main/LinkListView.swift b/Mark-In/Sources/Feature/Main/LinkListView.swift index 15a316d..72b76d1 100644 --- a/Mark-In/Sources/Feature/Main/LinkListView.swift +++ b/Mark-In/Sources/Feature/Main/LinkListView.swift @@ -8,6 +8,7 @@ import SwiftUI import DesignSystem +import ReducerKit private enum ViewConstants { static let cellWidth: CGFloat = 210 @@ -17,11 +18,11 @@ private enum ViewConstants { struct LinkListView: View { - let viewModel: MainViewModel + let store: StoreOf private var links: [Link] { - let totalLinks = viewModel.state.links - let tab = viewModel.state.selectedTab ?? .total + let totalLinks = store.state.links + let tab = store.state.selectedTab ?? .total switch tab { case .total: diff --git a/Mark-In/Sources/Feature/Main/MainViewModel.swift b/Mark-In/Sources/Feature/Main/MainReducer.swift similarity index 80% rename from Mark-In/Sources/Feature/Main/MainViewModel.swift rename to Mark-In/Sources/Feature/Main/MainReducer.swift index 8cf7cf4..526342c 100644 --- a/Mark-In/Sources/Feature/Main/MainViewModel.swift +++ b/Mark-In/Sources/Feature/Main/MainReducer.swift @@ -7,8 +7,9 @@ import Foundation -@MainActor @Observable -final class MainViewModel: Reducer { +import ReducerKit + +struct MainReducer: Reducer { struct State { var isLoading: Bool = true @@ -39,19 +40,12 @@ final class MainViewModel: Reducer { private let fetchLinkListUseCase: FetchLinkListUseCase private let fetchFolderListUseCase: FetchFolderListUseCase - private(set) var state: State = .init() - init() { self.fetchLinkListUseCase = DIContainer.shared.resolve() self.fetchFolderListUseCase = DIContainer.shared.resolve() } - func send(_ action: Action) { - let effect = reduce(state: &state, action: action) - handleEffect(effect) - } - - func reduce(state: inout State, action: Action) -> Effect { + func reduce(into state: inout State, action: Action) -> Effect { switch action { case .onAppear: return .run { @@ -104,21 +98,9 @@ final class MainViewModel: Reducer { return .none } } - - private func handleEffect(_ effect: Effect) { - switch effect { - case .none: - break - case .run(let action): - Task.detached { [weak self] in - let newAction = await action() - await self?.send(newAction) - } - } - } } -extension MainViewModel { +extension MainReducer { enum SheetType: Identifiable { case addLink case addFolder diff --git a/Mark-In/Sources/Feature/Main/MainView.swift b/Mark-In/Sources/Feature/Main/MainView.swift index 38fd640..e6db047 100644 --- a/Mark-In/Sources/Feature/Main/MainView.swift +++ b/Mark-In/Sources/Feature/Main/MainView.swift @@ -8,9 +8,13 @@ import SwiftUI import DesignSystem +import ReducerKit struct MainView: View { - @State private var viewModel = MainViewModel() + @State private var store: StoreOf = .init( + initialState: MainReducer.State(), + reducer: MainReducer() + ) @State private var searchText: String = "" @State private var isPresentedMyPage: Bool = false @@ -18,12 +22,12 @@ struct MainView: View { var body: some View { ZStack { NavigationSplitView { - SideBar(viewModel: viewModel) + SideBar(store: store) .navigationSplitViewColumnWidth( min: 200, ideal: 200, max: 300 ) } detail: { - LinkListView(viewModel: viewModel) + LinkListView(store: store) } .navigationTitle("") .searchable(text: $searchText, placement: .toolbar) @@ -32,9 +36,9 @@ struct MainView: View { toolBarButtons } } - .disabled(viewModel.state.isLoading) + .disabled(store.state.isLoading) - if viewModel.state.isLoading { + if store.state.isLoading { ProgressView() .frame(maxWidth: .infinity, maxHeight: .infinity) .contentShape(Rectangle()) @@ -42,11 +46,11 @@ struct MainView: View { } } .onAppear { - viewModel.send(.onAppear) + store.send(.onAppear) } .sheet(item: .init( - get: { viewModel.state.isPresentedSheet }, - set: { viewModel.send(.presentSheet($0)) + get: { store.state.isPresentedSheet }, + set: { store.send(.presentSheet($0)) } )) { type in buildSheet(type) @@ -83,7 +87,7 @@ struct MainView: View { Spacer() Button { - viewModel.send(.presentSheet(.addLink)) + store.send(.presentSheet(.addLink)) } label: { Image(systemName: "plus") } @@ -111,21 +115,21 @@ struct MainView: View { } @ViewBuilder - private func buildSheet(_ type: MainViewModel.SheetType) -> some View { + private func buildSheet(_ type: MainReducer.SheetType) -> some View { switch type { case .addLink: - let folderTabs = viewModel.state.folderTabs + let folderTabs = store.state.folderTabs let folders = folderTabs .compactMap { if case let .folder(folder) = $0 { folder } else { nil } } AddLinkView(folders: folders) { - viewModel.send(.didCreateLink($0)) + store.send(.didCreateLink($0)) } case .addFolder: AddFolderView() { - viewModel.send(.didCreateFolder($0)) + store.send(.didCreateFolder($0)) } } } diff --git a/Mark-In/Sources/Feature/Main/SideBar.swift b/Mark-In/Sources/Feature/Main/SideBar.swift index ce2b051..121f225 100644 --- a/Mark-In/Sources/Feature/Main/SideBar.swift +++ b/Mark-In/Sources/Feature/Main/SideBar.swift @@ -8,20 +8,21 @@ import SwiftUI import DesignSystem +import ReducerKit struct SideBar: View { - let viewModel: MainViewModel + let store: StoreOf var body: some View { VStack(alignment: .leading) { List(selection: .init( - get: { viewModel.state.selectedTab }, - set: { viewModel.send(.changeTab($0)) }) + get: { store.state.selectedTab }, + set: { store.send(.changeTab($0)) }) ) { Section("기본") { ForEach( - viewModel.state.defaultTabs, + store.state.defaultTabs, id: \.self ) { tab in NavigationLink(value: tab) { @@ -32,7 +33,7 @@ struct SideBar: View { Section("저장된 폴더") { ForEach( - viewModel.state.folderTabs, + store.state.folderTabs, id: \.self ) { tab in NavigationLink(value: tab) { @@ -50,7 +51,7 @@ struct SideBar: View { .foregroundStyle(.markBlack20) Button(action: { - viewModel.send(.presentSheet(.addFolder)) + store.send(.presentSheet(.addFolder)) }, label: { Label("새로운 폴더 만들기", systemImage: "plus") .lineLimit(1) @@ -63,5 +64,8 @@ struct SideBar: View { } #Preview { - SideBar(viewModel: MainViewModel()) + SideBar(store: .init( + initialState: MainReducer.State(), + reducer: MainReducer() + )) } diff --git a/Mark-In/Sources/Feature/MyPage/MyPageViewModel.swift b/Mark-In/Sources/Feature/MyPage/MyPageReducer.swift similarity index 68% rename from Mark-In/Sources/Feature/MyPage/MyPageViewModel.swift rename to Mark-In/Sources/Feature/MyPage/MyPageReducer.swift index 5d2fd0f..9bf6e3b 100644 --- a/Mark-In/Sources/Feature/MyPage/MyPageViewModel.swift +++ b/Mark-In/Sources/Feature/MyPage/MyPageReducer.swift @@ -10,8 +10,9 @@ import Foundation import FirebaseAuth import GoogleSignIn -@Observable -final class MyPageViewModel: Reducer { +import ReducerKit + +struct MyPageReducer: Reducer { struct State { var userState: AuthUserState = .init() } @@ -28,19 +29,12 @@ final class MyPageViewModel: Reducer { private let signOutUseCase: SignOutUseCase private let withdrawalUseCase: WithdrawalUseCase - private(set) var state: State = .init() - init() { self.signOutUseCase = DIContainer.shared.resolve() self.withdrawalUseCase = DIContainer.shared.resolve() } - func send(_ action: Action) { - let effect = reduce(state: &state, action: action) - handleEffect(effect) - } - - func reduce(state: inout State, action: Action) -> Effect { + func reduce(into state: inout State, action: Action) -> Effect { switch action { case .logoutButtonTapped: signOutUseCase.execute() @@ -65,21 +59,9 @@ final class MyPageViewModel: Reducer { return .none } } - - private func handleEffect(_ effect: Effect) { - switch effect { - case .none: - break - case .run(let action): - Task.detached { [weak self] in - let newAction = await action() - await self?.send(newAction) - } - } - } } -extension MyPageViewModel { +extension MyPageReducer { struct AuthUserState { var name: String = "" var email: String = "" diff --git a/Mark-In/Sources/Feature/MyPage/MyPageView.swift b/Mark-In/Sources/Feature/MyPage/MyPageView.swift index d633930..68d46f0 100644 --- a/Mark-In/Sources/Feature/MyPage/MyPageView.swift +++ b/Mark-In/Sources/Feature/MyPage/MyPageView.swift @@ -8,13 +8,17 @@ import SwiftUI import DesignSystem +import ReducerKit private enum ViewConstants { static let minContentWidth: CGFloat = 164 } struct MyPageView: View { - @State private var viewModel = MyPageViewModel() + @State private var store: StoreOf = .init( + initialState: MyPageReducer.State(), + reducer: MyPageReducer() + ) @State private var contentWidth = ViewConstants.minContentWidth var body: some View { @@ -54,7 +58,7 @@ struct MyPageView: View { private var footerButtons: some View { VStack(spacing: 8) { Button { - viewModel.send(.logoutButtonTapped) + store.send(.logoutButtonTapped) } label: { Text("로그아웃") .font(.pretendard(size: 12, weight: .regular)) @@ -65,7 +69,7 @@ struct MyPageView: View { .padding(.horizontal, 8) Button { - viewModel.send(.withdrawalButtonTapped) + store.send(.withdrawalButtonTapped) } label: { Text("회원 탈퇴") .font(.pretendard(size: 12, weight: .regular)) From 4df22cf91add43162fa8dda71bb888d1942ee2ed Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Fri, 16 May 2025 15:05:04 +0900 Subject: [PATCH 4/5] =?UTF-8?q?[#40]=20=EA=B8=B0=ED=83=80=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../LinkMetadataKit.xcodeproj/project.pbxproj | 12 +-- Mark-In.xcodeproj/project.pbxproj | 83 +------------------ .../SignInWithAppleRequestProvider.swift | 4 +- 3 files changed, 10 insertions(+), 89 deletions(-) diff --git a/Core/LinkMetadataKit/LinkMetadataKit.xcodeproj/project.pbxproj b/Core/LinkMetadataKit/LinkMetadataKit.xcodeproj/project.pbxproj index feca28b..4b0048b 100644 --- a/Core/LinkMetadataKit/LinkMetadataKit.xcodeproj/project.pbxproj +++ b/Core/LinkMetadataKit/LinkMetadataKit.xcodeproj/project.pbxproj @@ -470,7 +470,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 18.4; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "@executable_path/Frameworks", "@loader_path/Frameworks", @@ -479,7 +479,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 15.4; + MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; @@ -493,7 +493,7 @@ SWIFT_INSTALL_OBJC_HEADER = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,7"; - XROS_DEPLOYMENT_TARGET = 2.4; + XROS_DEPLOYMENT_TARGET = 2.0; }; name = Debug; }; @@ -512,7 +512,7 @@ GENERATE_INFOPLIST_FILE = YES; INFOPLIST_KEY_NSHumanReadableCopyright = ""; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 18.4; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; LD_RUNPATH_SEARCH_PATHS = ( "@executable_path/Frameworks", "@loader_path/Frameworks", @@ -521,7 +521,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 15.4; + MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu17 gnu++20"; @@ -535,7 +535,7 @@ SWIFT_INSTALL_OBJC_HEADER = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,7"; - XROS_DEPLOYMENT_TARGET = 2.4; + XROS_DEPLOYMENT_TARGET = 2.0; }; name = Release; }; diff --git a/Mark-In.xcodeproj/project.pbxproj b/Mark-In.xcodeproj/project.pbxproj index 873b9e7..bd57b7b 100644 --- a/Mark-In.xcodeproj/project.pbxproj +++ b/Mark-In.xcodeproj/project.pbxproj @@ -10,12 +10,6 @@ 57588B452DD640C100DBD9A7 /* ReducerKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57588B412DD63D9700DBD9A7 /* ReducerKit.framework */; }; 57588B462DD640C100DBD9A7 /* ReducerKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 57588B412DD63D9700DBD9A7 /* ReducerKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 57944D0C2DC52AEF00EF3D9A /* GoogleSignIn in Frameworks */ = {isa = PBXBuildFile; productRef = 57944D0B2DC52AEF00EF3D9A /* GoogleSignIn */; }; - 57AC56A22DA5120600BA84BD /* DesignSystem.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57AC56A12DA511F800BA84BD /* DesignSystem.framework */; }; - 57AC56A32DA5120600BA84BD /* DesignSystem.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 57AC56A12DA511F800BA84BD /* DesignSystem.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 57AC56A42DA5120600BA84BD /* LinkMetadataKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57AC567D2DA511F200BA84BD /* LinkMetadataKit.framework */; }; - 57AC56A52DA5120600BA84BD /* LinkMetadataKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 57AC567D2DA511F200BA84BD /* LinkMetadataKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - 57AC56A62DA5120600BA84BD /* LinkMetadataKitInterface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57AC567F2DA511F200BA84BD /* LinkMetadataKitInterface.framework */; }; - 57AC56A72DA5120600BA84BD /* LinkMetadataKitInterface.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 57AC567F2DA511F200BA84BD /* LinkMetadataKitInterface.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 57AC56A82DA5120600BA84BD /* Util.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 57AC56732DA511E900BA84BD /* Util.framework */; }; 57AC56A92DA5120600BA84BD /* Util.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 57AC56732DA511E900BA84BD /* Util.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 57B5F82E2DC08B400061F0DE /* FirebaseStorage in Frameworks */ = {isa = PBXBuildFile; productRef = 57B5F82D2DC08B400061F0DE /* FirebaseStorage */; }; @@ -39,27 +33,6 @@ remoteGlobalIDString = 57AC52FF2DA4FC1800BA84BD; remoteInfo = Util; }; - 57AC567C2DA511F200BA84BD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 57AC53232DA4FC4900BA84BD /* LinkMetadataKit.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 57AC53192DA4FC4800BA84BD; - remoteInfo = LinkMetadataKit; - }; - 57AC567E2DA511F200BA84BD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 57AC53232DA4FC4900BA84BD /* LinkMetadataKit.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 57AC54A62DA503DE00BA84BD; - remoteInfo = LinkMetadataKitInterface; - }; - 57AC56A02DA511F800BA84BD /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 57AC52EF2DA4FBC900BA84BD /* DesignSystem.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 57AC52E52DA4FBC800BA84BD; - remoteInfo = DesignSystem; - }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -71,9 +44,6 @@ files = ( 57AC56A92DA5120600BA84BD /* Util.framework in Embed Frameworks */, 57588B462DD640C100DBD9A7 /* ReducerKit.framework in Embed Frameworks */, - 57AC56A72DA5120600BA84BD /* LinkMetadataKitInterface.framework in Embed Frameworks */, - 57AC56A52DA5120600BA84BD /* LinkMetadataKit.framework in Embed Frameworks */, - 57AC56A32DA5120600BA84BD /* DesignSystem.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -117,12 +87,9 @@ 57AC56A82DA5120600BA84BD /* Util.framework in Frameworks */, 57944D0C2DC52AEF00EF3D9A /* GoogleSignIn in Frameworks */, 57E1F9852DA8F9B40033F30C /* FirebaseAuth in Frameworks */, - 57AC56A62DA5120600BA84BD /* LinkMetadataKitInterface.framework in Frameworks */, - 57AC56A42DA5120600BA84BD /* LinkMetadataKit.framework in Frameworks */, 57E1F9872DA8F9B40033F30C /* FirebaseCore in Frameworks */, 57588B452DD640C100DBD9A7 /* ReducerKit.framework in Frameworks */, 57B5F82E2DC08B400061F0DE /* FirebaseStorage in Frameworks */, - 57AC56A22DA5120600BA84BD /* DesignSystem.framework in Frameworks */, 57E1F9892DA8F9B40033F30C /* FirebaseFirestore in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -148,9 +115,9 @@ 57AC565F2DA511A900BA84BD /* Core */ = { isa = PBXGroup; children = ( - 57606D652DD63B14005EBE3D /* ReducerKit.xcodeproj */, - 57AC53232DA4FC4900BA84BD /* LinkMetadataKit.xcodeproj */, 57AC52EF2DA4FBC900BA84BD /* DesignSystem.xcodeproj */, + 57AC53232DA4FC4900BA84BD /* LinkMetadataKit.xcodeproj */, + 57606D652DD63B14005EBE3D /* ReducerKit.xcodeproj */, ); path = Core; sourceTree = ""; @@ -171,23 +138,6 @@ name = Products; sourceTree = ""; }; - 57AC56782DA511F200BA84BD /* Products */ = { - isa = PBXGroup; - children = ( - 57AC567D2DA511F200BA84BD /* LinkMetadataKit.framework */, - 57AC567F2DA511F200BA84BD /* LinkMetadataKitInterface.framework */, - ); - name = Products; - sourceTree = ""; - }; - 57AC569D2DA511F800BA84BD /* Products */ = { - isa = PBXGroup; - children = ( - 57AC56A12DA511F800BA84BD /* DesignSystem.framework */, - ); - name = Products; - sourceTree = ""; - }; 57BFD8BA2DA4E19600648AD4 = { isa = PBXGroup; children = ( @@ -272,14 +222,6 @@ productRefGroup = 57BFD8C42DA4E19600648AD4 /* Products */; projectDirPath = ""; projectReferences = ( - { - ProductGroup = 57AC569D2DA511F800BA84BD /* Products */; - ProjectRef = 57AC52EF2DA4FBC900BA84BD /* DesignSystem.xcodeproj */; - }, - { - ProductGroup = 57AC56782DA511F200BA84BD /* Products */; - ProjectRef = 57AC53232DA4FC4900BA84BD /* LinkMetadataKit.xcodeproj */; - }, { ProductGroup = 57588A542DD63B5900DBD9A7 /* Products */; ProjectRef = 57606D652DD63B14005EBE3D /* ReducerKit.xcodeproj */; @@ -311,27 +253,6 @@ remoteRef = 57AC56722DA511E900BA84BD /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; - 57AC567D2DA511F200BA84BD /* LinkMetadataKit.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = LinkMetadataKit.framework; - remoteRef = 57AC567C2DA511F200BA84BD /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 57AC567F2DA511F200BA84BD /* LinkMetadataKitInterface.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = LinkMetadataKitInterface.framework; - remoteRef = 57AC567E2DA511F200BA84BD /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - 57AC56A12DA511F800BA84BD /* DesignSystem.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = DesignSystem.framework; - remoteRef = 57AC56A02DA511F800BA84BD /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ diff --git a/Mark-In/Sources/Feature/Login/SignInWithAppleRequestProvider.swift b/Mark-In/Sources/Feature/Login/SignInWithAppleRequestProvider.swift index 13d0bc6..f3d627c 100644 --- a/Mark-In/Sources/Feature/Login/SignInWithAppleRequestProvider.swift +++ b/Mark-In/Sources/Feature/Login/SignInWithAppleRequestProvider.swift @@ -5,9 +5,9 @@ // Created by 이정동 on 5/2/25. // -import Foundation -import CryptoKit import AuthenticationServices +import CryptoKit +import Foundation final class SignInWithAppleRequestProvider { From eec17ddb9b64f94abd12c8975f305cab3468779f Mon Sep 17 00:00:00 2001 From: Jeong Dong Date: Sat, 17 May 2025 00:41:28 +0900 Subject: [PATCH 5/5] =?UTF-8?q?[40]=20=EC=A3=BC=EC=84=9D=20=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Core/ReducerKit/Sources/Effect.swift | 2 +- Core/ReducerKit/Sources/Reducer.swift | 6 +++--- Core/ReducerKit/Sources/Store.swift | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Core/ReducerKit/Sources/Effect.swift b/Core/ReducerKit/Sources/Effect.swift index 05f3a5c..42ae5cb 100644 --- a/Core/ReducerKit/Sources/Effect.swift +++ b/Core/ReducerKit/Sources/Effect.swift @@ -12,6 +12,6 @@ public enum Effect { /// 추가적인 작업을 실행하지 않음 case none - /// Side Effect를 처리하고, 새로운 Action을 호출 + /// Side Effect를 처리하고, 새로운 Action을 반환 case run(() async -> Action) } diff --git a/Core/ReducerKit/Sources/Reducer.swift b/Core/ReducerKit/Sources/Reducer.swift index 48402d4..d2dd043 100644 --- a/Core/ReducerKit/Sources/Reducer.swift +++ b/Core/ReducerKit/Sources/Reducer.swift @@ -7,15 +7,15 @@ import Foundation -/// 액션에 따른 상태 변화 로직의 역할을 담당하는 프로토콜 +/// 액션에 따른 상태 변화 로직을 정의하기 위한 프로토콜 public protocol Reducer { - /// View에 표시할 데이터를 정의 + /// View에 표시될 데이터를 정의 associatedtype State /// View에서 발생할 수 있는 동작을 정의 associatedtype Action - /// 액션에 따라 기존 상태를 새로운 상태로 변경 + /// 전달 받은 액션에 따라 기존 상태를 새로운 상태로 변경 func reduce(into state: inout State, action: Action) -> Effect } diff --git a/Core/ReducerKit/Sources/Store.swift b/Core/ReducerKit/Sources/Store.swift index 1956413..8fc30dc 100644 --- a/Core/ReducerKit/Sources/Store.swift +++ b/Core/ReducerKit/Sources/Store.swift @@ -10,14 +10,14 @@ import Foundation /// Store 타입을 좀 더 간편하게 정의(표현). public typealias StoreOf = Store -/// Reducer를 실행하고 UI를 업데이트하는 상태 관리 라이프 사이클 관리자 +/// Reducer를 실행하고 UI를 업데이트하는 상태 관리자 @MainActor @Observable public final class Store { private(set) public var state: State private let reduce: (inout State, Action) -> Effect - // Reducer의 연관 타입 State, Action과 Store의 타입 파라미터 State, Action이 동일함을 표현 + // Store.State == Reducer.State, Store.Action == Reducer.Action public init>( initialState: State, reducer: R