์ฌ์ด๋ ํ๋ก์ ํธ๋ฅผ ๋ชจ์งํ๋ ํ๋ก์ ํธ connect
๋ธ๋ฐ์น ๋ค์ด๋ฐ๊ณผ ๊ด๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ํ ๋ด์ฉ. ๋ธ๋ฐ์น ์์ฑ ๋ฐ ๋ฐ์์ ์ฐธ๊ณ ํด์ฃผ์ธ์!
๐ Git Flow(Notion)
๊น ์ปค๋ฐ ๋ฉ์์ง ์์ฑ์ ๊ท์น์ ์ง์ผ์ ์ปค๋ฐ์ ์์ฑํด์ฃผ์ธ์! (ํ๋จ ์ฐธ๊ณ ๋งํฌ์ ํ ํ๋ฆฟ๊ณผ๋ ๋ค๋ฅธ ๋ด์ฉ ์ ๋๋ค. ๊ธ์์ ๋ฐ ์ค๋ช ์ด ๋ ์์ธํด์ ๊ฐ์ ธ์์ด์)
# <ํค์๋>: <์ ๋ชฉ>
############### ์ ๋ชฉ์ 50์ ๊น์ง ################# ->|
# ๋ณธ๋ฌธ ๋ด์ฉ
######### ๋ณธ๋ฌธ ๋ด์ฉ์ 72์ ๊น์ง ####################################### -> |
# ํ ์ค ๋ด์ฉ์ ๊ทธ๋ฅ ์์ฑํ๋ค
# - ์ฌ๋ฌ ์ค์ผ ๋๋ ๋งจ ์์ '-'๋ฅผ ๋ฃ๋๋ค
# --- COMMIT END ---
#
# ํค์๋
# fix (์์ ) : ๋์, ์คํ ๋ฑ์ ๊ณ ์ณค์ ๋ #(์ด์ ๋๋ฒ ํน์ URL)
# feat (๊ธฐ๋ฅ) : ๊ธฐ๋ฅ ์ถ๊ฐ ์
# refactor (๋ฆฌํฉํ ๋ง) : ๊ธฐ์กด ์ฝ๋ ๋ฆฌํฉํ ๋ง ์งํ
# docs (๋ฌธ์) : ์ฝ๋ ํ์ผ๋จ์ ์ถ๊ฐ, ์์ , ์ญ์ , ์ด๋ ๋ฑ
# test (ํ
์คํธ) : ํ
์คํธ ์์ฑ ๋ฐ ์์
# style (๋์์ธ) : css ๋ฑ ์ฝ๋์์
์ด ์๋ ๋์์ธ ์์
# build (๋น๋) : ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ถ๊ฐ ์ญ์ ๋ฑ ๋น๋๋จ์ ์ค์ ๋ณ๊ฒฝ
# ------------------
# ๊ท์น
# - ์ ๋ชฉ์ค์ ๋๋ฌธ์๋ก ์์ํ๋ค.
# - ์ ๋ชฉ์ค์ ๋ช
๋ น์ด๋ก ์์ฑํ๋ค.
# - ์ ๋ชฉ์ค์ ๋ง์นจํ๋ก ๋๋ด์ง ์๋๋ค.
# - ๋ณธ๋ฌธ๊ณผ ์ ๋ชฉ์๋ ๋น์ค์ ๋ฃ์ด์ ๊ตฌ๋ถํ๋ค.
# - ๋ณธ๋ฌธ์๋ "์ด๋ป๊ฒ" ๋ณด๋ค๋ "์"์ "๋ฌด์์" ์ค๋ช
ํ๋ค.
# - ๋ณธ๋ฌธ์ ๋ชฉ๋ก์ ๋ํ๋ผ๋๋ "-"๋ก ์์ํ๋ค.
# --------------------
๐ ์ปค๋ฐ ๋ฉ์์ง ํ ํ๋ฆฟ ์ ์ฉ๋ฐฉ๋ฒ
ํ๋ก์ ํธ ํ์ผ ๋ฐ ํด๋ ๋ณ๊ฒฝ์ .xcodeproj๊ฐ ์์๋ก ๋ณ๊ฒฝ๋์ด ํ์ ์ ๋น๋ฒํ ๊น ์ถฉ๋๋ฑ์ ์ด์ ํด๊ฒฐ์ ์ํด ์ฌ์ฉํฉ๋๋ค.
โ ์ฃผ์! CocoaPod ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ์ผ๋ก ์๋์์์ ๋ฐ๋ผ ๋ช ๋ น์ด ์งํ ํ์
1๏ธโฃ
tuist fetchSPM, Carthage๋ก ์ ์๋ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๊ฐ์ ธ์จ๋ค.
2๏ธโฃ
TUIST_EXCLUEDED_FRAMEWORK=TRUE tuist generate && pod installCocoaPod์ ์ฌ์ฉํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ์ธํ๊ณ ๋น๋ ์งํ && ์ดํ CocoaPod์ ์ด์ฉํ์ฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฐ์ ธ์จ๋ค.
3๏ธโฃ
Xcode CloseXcode๋ฅผ ์ข ๋ฃํ๋ค (๋ช ๋ น์ด ์๋ ์๋์ผ๋ก ์์ ์ข ๋ฃ ์งํํ๋ค).
4๏ธโฃ
tuist generate๋ชจ๋ ์์กด์ฑ์ ํฌํจํ์ฌ Xcode workspace ์์ฑํ๋ค.
- ๋ํ ๋ช ๋ น์ด 3๊ฐ์ง
1๏ธโฃ
tuist generateProject.swift ํ์ผ์ ์์ฑ๋ ๋ด์ฉ๋๋ก ํ๋ก์ ํธ ์์ฑ ๋ช ๋ น์ด.
2๏ธโฃ
tuist editTuist ์ค์ ์ ๋ณ๊ฒฝํ๊ณ ์ถ๋ค๋ฉด Project.swift ํ์ผ ์์ ์ ๋ช ๋ น์ด.
3๏ธโฃ
tuist fetchDependencies.swift์ ์์ฑ๋ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐ์์ ๋ช ๋ น์ด. ์ต์ด ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐ์์ดํ Dependencies.swift ๋ด์ฉ์ ๋ณ๋์ด ์๋ค๋ฉด ์ถ๊ฐ ์ํํ์ง ์์๋ ๋ฉ๋๋ค.
ํ๋ก์ ํธ ์์ฑ์ ์ํ ํ ํ๋ฆฟ ํ์ผ
// Project+Templates.swift
import ProjectDescription
extension Project {
public static func feature(
name: String, // ์ค์ ํ ๋ชจ๋์ ์ด๋ฆ
bundleId: String = "", // 'com.sideproj.\(name)' ๋์ ์ฌ์ฉํ ๋ฒ๋ค Id (Option)
products: [COProduct], // .framework(.dynamic | .static) ํ๋ ์์ํฌ ์ง์
isExcludedFramework: Bool = false, // CocoaPod์ฌ์ฉ์ผ๋ก Pod์ ํด๋นํ๋ ํ๋ ์์ํฌ๋ฅผ ์ ์ธํ๊ณ ๋น๋ํ๊ธฐ ์ํ ํ๊ฒฝ๋ณ์.
infoExtension: [String: InfoPlist.Value] = [:], // ๊ธฐ๋ณธ ์ง์ ๋ info.plist์ ๋ ์ถ๊ฐํ ๋ด์ฉ์ด ์๋๊ฒฝ์ฐ ์์ฑ.
settings: Settings? = .default,
dependencies: [TargetDependency] = [], // ํด๋น ๋ชจ๋์ ์์กด์ฑ.
testDependencies: [TargetDependency] = [], // ํ
์คํธ์ฉ ์์กด์ฑ.
externalDependencies: [TargetDependency] = [] // isExcludedFramework์ ํด๋นํ๋ ์์กด์ฑ.
) -> Project {
var targets: [Target] = []
var schemes: [Scheme] = []
var infoPlist: InfoPlist = .base(name: name)
if !infoExtension.isEmpty {
infoPlist = .custom(
name: name,
bundleId: bundleId,
extentions: infoExtension
)
}
// ๋ฉ์ธ ์ฑ ํ๊ฒ.
if products.contains(.app) {
let target: Target = .init(
name: name,
platform: .iOS,
product: .app,
bundleId: bundleId.isEmpty ? "com.sideproj.\(name)" : bundleId,
deploymentTarget: .iOS(targetVersion: "15.0", devices: [.iphone]),
infoPlist: infoPlist,
sources: ["Sources/**"],
resources: ["Resources/**"],
entitlements: .relativeToRoot("App/connect.entitlements"),
dependencies: isExcludedFramework ? dependencies : dependencies + externalDependencies,
settings: settings
)
targets.append(target)
}
// Feature ๋ชจ๋ ๋ฐ๋ชจ ์ฑ ํ๊ฒ.
if products.contains(.demoApp) {
let appTarget: Target = .init(
name: "\(name)DemoApp",
platform: .iOS,
product: .app,
bundleId: "com.sideproj.\(name)DemoApp",
deploymentTarget: .iOS(targetVersion: "15.0", devices: [.iphone]),
infoPlist: .base(name: "\(name)DemoApp"),
sources: ["Sources/**"],
resources: ["Resources/**"],
dependencies: [.target(name: name)],
settings: settings
)
targets.append(appTarget)
let scheme: Scheme = .init(
name: "\(name)DemoApp",
shared: true,
hidden: false,
buildAction: .init(targets: ["\(name)DemoApp"]),
runAction: .runAction(executable: "\(name)DemoApp")
)
schemes.append(scheme)
}
// static, dynamic ํ๋ ์์ํฌ ํ์ผ.
if products.filter({ $0.isFramework }).count != 0 {
let frameworkTarget: Target = .init(
name: name,
platform: .iOS,
product: products.contains(.framework(.static)) ? .staticFramework : .framework,
bundleId: "com.sideproj.\(name)",
deploymentTarget: .iOS(targetVersion: "15.0", devices: [.iphone]),
infoPlist: infoPlist,
sources: ["Sources/**"],
resources: ["Resources/**"],
dependencies: isExcludedFramework ? dependencies : dependencies + externalDependencies,
settings: settings
)
targets.append(frameworkTarget)
}
// static, dynamic ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ผ.
if products.filter({ $0.isLibrary }).count != 0 {
let target: Target = .init(
name: name,
platform: .iOS,
product: products.contains(.library(.static)) ? .staticLibrary : .dynamicLibrary,
bundleId: "com.sideproj.\(name)",
deploymentTarget: .iOS(targetVersion: "15.0", devices: [.iphone]),
infoPlist: infoPlist,
sources: ["Sources/**"],
resources: ["Resources/**"],
dependencies: isExcludedFramework ? dependencies : dependencies + externalDependencies,
settings: settings
)
targets.append(target)
}
// ์ ๋ํ
์คํธ ํ์ผ.
if products.contains(.unitTests) {
var dependencies: [TargetDependency] = [.target(name: name), .xctest]
dependencies += testDependencies
let target: Target = .init(
name: "\(name)Tests",
platform: .iOS,
product: .unitTests,
bundleId: "com.sideproj.\(name)Tests",
infoPlist: .default,
sources: ["\(name)Tests/**"],
resources: ["\(name)Tests/**"],
dependencies: dependencies
)
targets.append(target)
}
// UIํ
์คํธ ํ์ผ.
if products.contains(.uiTests) {
let target: Target = .init(
name: "\(name)UITests",
platform: .iOS,
product: .uiTests,
bundleId: "com.sideproj.\(name)UITests",
sources: "\(name)UITests/**",
dependencies: [.target(name: name)]
)
targets.append(target)
}
return Project(
name: name,
targets: targets,
schemes: schemes
)
}
}import Foundation
import ProjectDescription
import ProjectDescriptionHelpers
// CocoaPod ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ต์ด install์ ํ๋ก์ ํธ ํ์ผ์ด ์๋๊ฒฝ์ฐ ๋น๋์ ์ธ์ํค๊ธฐ ์ํ ํ๊ฒฝ๋ณ์.
let excludedFramework = ProcessInfo.processInfo.environment["TUIST_EXCLUEDED_FRAMEWORK"]
let isExcludedFramework = (excludedFramework == "TRUE")
let app = Project.feature(
name: "App",
bundleId: "com.sideproj.connect",
products: [.app, .unitTests, .uiTests],
isExcludedFramework: isExcludedFramework,
infoExtension: [
"LSApplicationQueriesSchemes": .array(
[.string("kakaokompassauth"), .string("naversearchapp"), .string("naversearchthirdlogin")]
),
"CFBundleURLTypes": .array([
.dictionary([
"CFBundleURLSchemes": .array(["connectIT"]),
"CFBundleURLName": .string("connectIT")
]),
.dictionary([
"CFBundleURLSchemes": .array(["kakaoee72a7c08c0e36ae98010b8d02f646cf"])
])
]),
"NMFClientId": .string("y5sse5c8he"),
"NSLocationAlwaysAndWhenInUseUsageDescription": .string("์ฌ์ฉ์์ ์์น๋ฅผ ๊ฐ์ ธ์ต๋๋ค."),
"NSLocationWhenInUseUsageDescription": .string("์ฌ์ฉ์์ ์์น๋ฅผ ๊ฐ์ ธ์ต๋๋ค."),
"NSLocationAlwaysUsageDescription": .string("์ฌ์ฉ์์ ์์น๋ฅผ ๊ฐ์ ธ์ต๋๋ค.")
],
dependencies: [
.project(target: "COFoundation", path: .relativeToRoot("Core/COFoundation")),
.project(target: "COCommonUI", path: .relativeToRoot("UI/COCommonUI")),
.project(target: "COThirdParty", path: .relativeToRoot("Core/COThirdParty")),
],
externalDependencies: [
.project(target: "Sign", path: .relativeToRoot("Features/Sign")),
.xcframework(path: .CocoaPods.Framework.naverLogin),
.xcframework(path: .CocoaPods.Framework.naverMaps)
]
)์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์กด์ฑ๊ด๋ฆฌ ๋ด์ฉ ์ ๋ฐ์ดํธ ์ ์ฉ์
tuist fetchํฐ๋ฏธ๋ ๋ช ๋ น์ด ์คํํฉ๋๋ค.
import ProjectDescription
let dependencies = Dependencies(
swiftPackageManager: [ // SPM ์ฌ์ฉ ์์
.remote(
url: "https://github.com/ReactorKit/ReactorKit.git", /// ์๊ฒฉ ์ ์ฅ์ ์ฃผ์์
requirement: .upToNextMajor(from: "3.0.0") /// ๋ฒ์ ์ ๊ธฐ์
ํ๋ค.
),
.remote(
url: "https://github.com/SnapKit/SnapKit.git",
requirement: .upToNextMajor(from: "5.0.1")
)
],
platforms: [.iOS]
)ReactorKit์ ๋จ๋ฐฉํฅ ๋ฐ์ดํฐ ์ฒ๋ฆฌ๊ฐ ํน์ง์ด๋ฉฐ, Reactor(ViewModel์ ์ญํ )์ ํํ๊ฐ ์ ํํ ๋์ด์์ต๋๋ค.
ํ๋ฒ ์ฝ๋ํ๋ฆ์ ํ์ ํด๋๋ฉด ๋ค๋ฅธ ๊ฐ๋ฐ์๊ฐ ์์ฑํ ์ฝ๋ ์ฒ๋ฆฌ๋ํ ๋๋ถ๋ถ ๋์ผํ๊ธฐ ๋๋ฌธ์ ํ์ ์ ๋ค๋ฅธ ๊ฐ๋ฐ์์ ์ฝ๋๋ฅผ ํ์ ํ ๋ ์ ๋ฆฌํ ๊ฒ์ผ๋ก ์๊ฐ๋์ด ๋ฐ์ ํ์์ต๋๋ค.
RxSwift์ ์ข ์๋์ด ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก RxSwift๋ ์๋์ผ๋ก ํฌํจ๋ฉ๋๋ค.
Flow๋ Action(View์ ์ํด ์ ๋ฌ๋ ์ด๋ฒคํธ) -> mutate(Action -> Mutation ๋ณ๊ฒฝ) -> reduce(Mutation -> State๋ก ์ฒ๋ฆฌ๋๋ฉฐ View์ ์ด๋ฒคํธ๋ฅผ Reactor๊ฐ ์ ๋ฌ๋ฐ์ ๋ณ๊ฒฝ๋ State๋ฅผ View์์ ๊ฐ์งํ์ฌ ํ๋ก๊ทธ๋๋ฐํ๋ ๋ฐฉ์ ์ ๋๋ค.
์๋ ๊ทธ๋ฆผ๊ณผ ์์์ฝ๋๋ฅผ ๊ฐ์ด ์ฐธ๊ณ ํ์๋๊ฑธ ์ถ์ฒํฉ๋๋ค!
ReactorKit์์ ์ ๊ณตํ๋ ์ ์ผ ๊ฐ๋จํ ์์ ์ ๋๋ค. ์ฐธ๊ณ ๋ถํ๋๋ฆฝ๋๋ค. :link: Counter Example
Github ์ ์ฅ์ :link: Github
