κ°μΉκ΄ μΆ©λμμ μμνλ 1:1 μ² ν λ°°ν νλ«νΌ, Picke
π― Features | π Architecture | π Quick Start | π OAuth Flow
Picke λ μΌμμ κ°μΉκ΄ μ°¨μ΄λ₯Ό 1:1 ν λ‘ μΌλ‘ νμ΄λ΄λ λͺ¨λ°μΌ ν λ‘ Β·ν¬ν νλ«νΌμ λλ€. "μ€λμ λ°°ν" μ£Όμ μ λν μ¬μ Β·μ¬ν ν¬ν, μ€μκ° 1:1 μ±ν ν λ‘ , κ·Έλ¦¬κ³ λ¦¬μΊ‘ μΉ΄λκΉμ§ ν νλ¦μΌλ‘ μ΄μ΄μ§λλ€.
π‘ μ λ§λ€μλ? SNS μ λ¨λ°©ν₯ μ견 νμΆ λμ , μ§§κ³ λͺ νν 1:1 ν λ‘ μ ν΅ν΄ "λ΄κ° μ κ·Έλ κ² μκ°νλμ§" λ₯Ό μ 리νκ³ λ€λ₯Έ κ°μΉκ΄μ λ§μ£Όνλ κ²½νμ μ 곡ν©λλ€.
νλ‘μ νΈ κ·μΉμ AGENTS.md / CLAUDE.md μ μ μλμ΄ μμ΅λλ€.
ln -s AGENTS.md CLAUDE.md- Google / Kakao: WKWebView κΈ°λ°
authorize β code κ°λ‘μ±κΈ°β λ°±μλ ν ν° κ΅ν - Apple Sign-In:
ASAuthorizationAppleIDProviderλ€μ΄ν°λΈ ν΅ν© - μλ ν ν° κ°±μ :
AccessTokenCredentialJWT exp λμ½λ© + λ§λ£ 5λΆ μ μλ refresh - 401 μλ μ²λ¦¬:
AuthInterceptorκ° 401 κ°μ§ β refresh μλ β μ€ν¨ μ μλ λ‘κ·Έμμ μλ¦Ό λ°μ‘
- μ¬μ ν¬ν β 1:1 μ±ν ν λ‘ β μ¬ν ν¬ν μ ν νλ¦
- μ¬ν¬ν λ‘ κ°μΉκ΄μ΄ λ°λμλμ§ μΆμ
- 리캑 μΉ΄λ μλ μμ± + 곡μ
- μ±ν λ°©ν 1:1 ν λ‘
- μ½ν μΈ λ³ λκΈΒ·λλκΈ
- μ κ³ Β·μ°¨λ¨
- νλ μ΄ν λ ν νΌλ
- μΉ΄ν κ³ λ¦¬Β·νκ·Έ νμ
- ν ν½ κ²μ
- λ΄ μ½ν μΈ νλ / ν λ‘ κΈ°λ‘
- λμ μ² νμ μ ν
- ν¬μΈνΈ λ΄μ / μλ¦Ό / μ€μ
Picke-iOS/
βββ π± Projects/
β βββ App/ # λ©μΈ μ ν리μΌμ΄μ
νκ²
β β βββ Sources/
β β β βββ Application/ # AppDelegate, SceneDelegate
β β β βββ Di/ # WeaveDI λ±λ‘ (DiRegister, AppPresentationContextProvider)
β β β βββ Reducer/ # TCA Root AppReducer
β β β βββ View/ # Root Views
β β βββ Derived/ # Tuist μμ± plist
β β
β βββ Presentation/ # π¨ UI Layer
β β βββ Auth/ # λ‘κ·ΈμΈ / μ½λλ€μ΄ν° / Toast
β β βββ Home/ # ν νΌλ / νλ μ΄ν
/ μ€μΌλ ν€
β β βββ MainTab/ # ν λΌμ°ν
/ GNB
β β βββ Splash/ # μ€νλμ
β β βββ Presentation/ # κ³΅ν΅ νλ μ ν
μ΄μ
μ νΈ
β β
β βββ Domain/ # π₯ Business Logic Layer
β β βββ Entity/ # Auth / Home / OAuth / Error λλ©μΈ μν°ν°
β β βββ DomainInterface/ # Auth / Home / OAuth Repository + Manager μΈν°νμ΄μ€
β β βββ UseCase/ # AuthUseCaseImpl, UnifiedOAuthUseCase, Provider/{Apple,Google,Kakao}
β β
β βββ Data/ # π‘ Data Layer
β β βββ Model/ # BaseResponseDTO / AuthΒ·Home DTO + Entity Mapper
β β βββ API/ # PieckeDomain, AuthAPI, HomeAPI, BaseAPI
β β βββ Service/ # AuthService / HomeService (BaseTargetType), μμ² λ°λ
β β βββ Repository/ # AuthΒ·Home RepositoryImpl + OAuth Repository
β β βββ Auth/ # Interceptor, RefreshToken Session, Pool, MoyaProvider νμ₯
β β βββ OAuth/ # Apple / Google / Kakao / Web OAuth ꡬν
β β
β βββ Network/ # π Network Layer
β β βββ Networking/ # λ€νΈμν¬ ν΄λΌμ΄μΈνΈ export
β β βββ Foundations/ # APIHeader / TokenProviding / KeychainTokenProvider
β β βββ ThirdPartys/ # AsyncMoya / WeaveDI λ± SPM μ¬λ
ΈμΆ
β β
β βββ Shared/ # π§ Shared Layer
β βββ DesignSystem/ # κ³΅ν΅ UI / μ»¬λ¬ / μ΄λ―Έμ§ / Toast
β βββ Shared/ # 곡μ λͺ¨λΈΒ·νμ₯
β βββ ThirdParty/ # μ¨λνν° λνΌ
β βββ Utill/ # κ³΅ν΅ μ νΈλ¦¬ν°
β
βββ π§ Tuist/
β βββ Package.swift # SPM μμ‘΄μ± μ μ
β βββ ProjectDescriptionHelpers/ # λͺ¨λ ν
νλ¦Ώ / Plist ν¬νΌ
βββ π§© Plugins/
βββ DependencyPlugin/ # λͺ¨λ μμ‘΄μ± ν¬νΌ (.Data / .Domain / .Network ...)
βββ DependencyPackagePlugin/ # SPM μμ‘΄μ± ν¬νΌ (.SPM.asyncMoya ...)
βββ ProjectTemplatePlugin/ # ProjectConfig / Project.makeModule
graph TD
A[π¨ Presentation Layer] --> B[π₯ Domain Layer]
B --> C[π‘ Data Layer]
D[π Network Layer] --> C
E[π§ Shared Layer] --> A
E --> B
E --> C
A -.-> F[SwiftUI Views]
A -.-> G[TCA Reducers]
B -.-> H[UseCases]
B -.-> I[Entities]
C -.-> J[Repositories]
C -.-> K[API Services]
λ μ΄μ΄λ³λ‘ λ¬Άμ΄ λ³΄κ±°λ(Grouped) λͺ¨λ λͺ¨λμ νΌμ³ λ³Έ(Expanded) μκ°νμ λλ€. (TuistSpider κ²°κ³Ό)
Presentation β Domain (UseCase / Entity)
β
Domain/UseCase β Domain (Interface / Entity)
β
Data/Repository β Domain (Interface / Entity) + Data (Model + Service + API)
β
Data/Service β Data (API) + Network/Foundations (APIHeader) + Domain/Entity (μμ² μλ³κ°)
β
Network/Foundations β Network/ThirdPartys (AsyncMoya, WeaveDI)
ν΅μ¬ μ€κ³ μμΉ
- β Presentation μ Domain UseCase / Entity λ§ μ§μ μ°Έμ‘°
- β Domain μ μΈλΆ κ³μΈ΅μ μμ‘΄νμ§ μλ μμ λΉμ¦λμ€ λ‘μ§
- β Data/Repository λ Domain μΈν°νμ΄μ€λ₯Ό ꡬν, DTO β Entity λ§€ν λ΄λΉ
- β Data/Service λ endpoint / header / method / parameter μ μλ§ λ΄λΉνκ³ DTO Model μ μμ‘΄νμ§ μμ
- β λͺ¨λ λ°μ΄ν° νλ¦μ Domain μ μ€μ¬μΌλ‘ μ§ν
μ±
β authorize URL (response_type=code, redirect_uri=https://picke.store/oauth/<p>)
βΌ
WKWebView (OAuthWebViewController)
β μ¬μ©μ λμ β ꡬκΈ/μΉ΄μΉ΄μ€κ° redirect_uri λ‘ 302
β WKNavigationDelegate.decidePolicyFor κ° picke.store/oauth/<p>?code=... κ°λ‘μ±κΈ°
β decisionHandler(.cancel) β 401 μλ΅ μ‘μ μ°¨λ¨
βΌ
authorizationCode μΆμΆ β dismiss
βΌ
UnifiedOAuthUseCase
β POST /api/v1/auth/login/<provider>
β body: { authorizationCode, redirectUri }
βΌ
AuthRepositoryImpl
β BaseResponseDTO<LoginDataDTO> λμ½λ© β LoginEntity
βΌ
KeychainManager μ μ₯ + AuthSessionManager.credential κ°±μ
ASAuthorizationAppleIDProvider λ‘ λ°μ credential / nonce / authorizationCode λ₯Ό κ·Έλλ‘ λ°±μλμ μ λ¬.
AccessTokenCredentialκ° access token JWT μexpλ₯Ό λμ½λ©ν΄ λ§λ£ μμ 보κ΄AuthInterceptor.adaptμμ λ§λ£ 5λΆ μ μ΄λ©΄TokenRefreshManagerκ° λ¨μΌνλ refresh μν- 401 μλ΅ μ
retryλ‘ ν ν° κ°±μ ν μ¬μλ, μ€ν¨ μNSNotification.refreshTokenExpiredλ°μ‘ + μλ λ‘κ·Έμμ
- π― Architecture: The Composable Architecture (TCA)
- π¦ Modularization: Tuist 4.x (Micro Feature Architecture)
- π Dependency Injection: WeaveDI 3.4.1
- π Navigation: TCAFlow (컀μ€ν )
- β‘ Concurrency: Swift Concurrency (async/await)
- ComposableArchitecture β λ¨λ°©ν₯ μν κ΄λ¦¬
- TCAFlow βοΈ β TCA κΈ°λ° νλ©΄ μ ν / λ€λΉκ²μ΄μ (컀μ€ν )
- WeaveDI βοΈ β μμ‘΄μ± μ£Όμ 컨ν μ΄λ (컀μ€ν )
- AuthenticationServices β Apple Sign-In, ASWebAuthenticationSession
- WebKit β WKWebView κΈ°λ° server-mediated OAuth (Google / Kakao)
- AppAuth-iOS β OAuth 2.0 / OpenID Connect ν΄λΌμ΄μΈνΈ (μ΅μ )
- AsyncMoya βοΈ β async/await κΈ°λ° HTTP ν΄λΌμ΄μΈνΈ (컀μ€ν )
- Alamofire / Moya β AsyncMoya μ κΈ°λ° μ€ν
- SwiftUI β μ μΈν UI
- SDWebImageSwiftUI β λΉλκΈ° μ΄λ―Έμ§ λ‘λ© / μΊμ±
- Firebase iOS SDK β Crashlytics / Messaging
- Mixpanel β νλ λΆμ / Session Replay
- Google Mobile Ads β κ΄κ³
- LogMacro β 컀μ€ν λ‘κΉ λ§€ν¬λ‘
- IssueReporting β κ°λ° λ¨κ³ μ΄μ μΆμ
- XCTestDynamicOverlay β ν μ€νΈ νκ²½ μ€λ²λ μ΄
- Clocks β μκ° κ΄λ ¨ μ νΈλ¦¬ν°
- ConcurrencyExtras β Swift Concurrency νμ₯
- Swift 6.0 β μ΅μ Swift μΈμ΄ κΈ°λ₯
- Tuist β νλ‘μ νΈ μμ± / λͺ¨λ μμ‘΄μ± κ΄λ¦¬
- Swift Package Manager β ν¨ν€μ§ μμ‘΄μ± κ΄λ¦¬
- fastlane β μλνλ λΉλ / λ°°ν¬ (μμ )
- π» Xcode: 16.0 μ΄μ
- π± iOS: 17.0 μ΄μ
- β‘ Swift: 6.0 μ΄μ
- π§ Tuist: 4.x μ΄μ
- π» Xcode: 16.0 μ΄μ
- π± iOS: 17.0 μ΄μ
- β‘ Swift: 6.0 μ΄μ
- π§ Tuist: 4.x μ΄μ
git clone https://github.com/Roy-wonji/Picke-iOS.git
cd Picke-iOScurl -Ls https://install.tuist.io | bash# μ 체 μν¬νλ‘μ° (κΆμ₯)
./make build # clean β install β generate
# λ¨κ³λ³ μ€ν
./make clean # λΉλ μ°μΆλ¬Ό μ 리
./make install # SPM μμ‘΄μ± μ€μΉ
./make generate # Xcode νλ‘μ νΈ μμ±open Picke.xcworkspaceλ€μ ν€λ€μ Picke-Dev.xcconfig / Picke-Stage.xcconfig / Picke-Prod.xcconfig μ μ±μμ£ΌμΈμ.
BASE_URL = picke.store
GOOGLE_CLIENT_ID = YOUR_GOOGLE_WEB_CLIENT_ID
GOOGLE_IOS_CLIENT_ID = YOUR_GOOGLE_IOS_CLIENT_ID
REVERSED_CLIENT_ID = YOUR_REVERSED_CLIENT_ID
KAKAO_REST_API_KEY = YOUR_KAKAO_REST_API_KEY
| Provider | redirect_uri | λΉκ³ |
|---|---|---|
https://picke.store/oauth/google |
Web client ID + Google Cloud Console λ±λ‘ νμ | |
| Kakao | https://picke.store/oauth/kakao |
Kakao Developers μ½μ λ±λ‘ νμ |
| Apple | (λ€μ΄ν°λΈ) | App Store Connect β Sign in with Apple |
./make build # μ 체 λΉλ νλ‘μΈμ€ (κΆμ₯)
./make generate # νλ‘μ νΈ μμ±λ§
./make clean # λΉλ μ°μΆλ¬Ό μ 리
./make install # μμ‘΄μ± μ€μΉtuist clean # Tuist μΊμ μ 리
./make clean # λͺ¨λ λΉλ νμΌ μ 리tuist graph # μμ‘΄μ± κ·Έλν μμ±
tuist test # μ 체 ν
μ€νΈ μ€νμ΄ νλ‘μ νΈλ MIT λΌμ΄μ μ€ νμ λ°°ν¬λ©λλ€. μμΈν λ΄μ©μ LICENSE νμΌμ μ°Έκ³ νμΈμ.
- iOS Lead Developer: μμμ§ (@Roy-wonji)
- main: νλ‘λμ λ°°ν¬μ©
- develop: κ°λ° ν΅ν© λΈλμΉ
- feature/*: κΈ°λ₯λ³ κ°λ° λΈλμΉ
- fix/*: λ²κ·Έ ν½μ€ λΈλμΉ
- develop μμ feature/ λΈλμΉ μμ±
- κΈ°λ₯ κ°λ° β μ체 μ»€λ° λ¨μ SRP λΆλ¦¬
- feature/ β develop Pull Request, μ½λ 리뷰
- develop β main λ°°ν¬ Pull Request
- νκ΅μ΄ μ¬μ©
- κ΄λ ¨ GitHub μ΄μ λ²νΈ λ§€μΉ (μ:
#20 #2) - νμ:
<type>: <μμ½> #<issue> feat / fix / refactor / chore / docs / test
- π§ μ΄λ©μΌ: suhwj81@gmail.com
- π λ²κ·Έ μ κ³ : Issues
- π‘ κΈ°λ₯ μ μ: Discussions

