Skip to content

wjdtjq6/SeeSea

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

29 Commits
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๐ŸŒŠ SeeSea (์”จ์”จ)

์„œํผ๋ฅผ ์œ„ํ•œ ์‹ค์‹œ๊ฐ„ ํŒŒ๋„ ์˜ˆ๋ณด & ์›น์บ  ์•ฑ

SeeSea App Screenshot

๐Ÿ† App Store ๋‚ ์”จ ์นดํ…Œ๊ณ ๋ฆฌ ๋ฌด๋ฃŒ ์•ฑ ์ˆœ์œ„ 9์œ„ ๋‹ฌ์„ฑ!

Download on the App Store

๐Ÿ“ฑ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

๊ฐœ๋ฐœ ๊ธฐ๊ฐ„ : 2024. 9. 13 ~ 2024. 10. 3 (3์ฃผ)
๊ฐœ๋ฐœ ์ธ์› : 1์ธ (๊ธฐํš/๋””์ž์ธ/๊ฐœ๋ฐœ)




๐Ÿ›  ๊ธฐ์ˆ  ์Šคํƒ

iOS

  • Language: Swift 5.10
  • Framework: SwiftUI
  • Minimum Target: iOS 17.0

์•„ํ‚คํ…์ฒ˜ & ๋””์ž์ธ ํŒจํ„ด

  • Architecture: MVVM
  • Design Pattern: Repository Pattern

๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค & ๋„คํŠธ์›Œํ‚น

  • Local Database: Realm
  • Network: URLSession

์™ธ๋ถ€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • Media: YouTubePlayerKit

๊ฐœ๋ฐœ ํ™˜๊ฒฝ

  • IDE: Xcode 15.3

๐Ÿ“‹ ์ฃผ์š” ๊ธฐ๋Šฅ

Open API ๊ธฐ๋ฐ˜ ์‹ค์‹œ๊ฐ„ ํŒŒ๋„ ์˜ˆ๋ณด ์‹œ์Šคํ…œ ๊ตฌํ˜„

  • ๊ธฐ์ƒ์ฒญ API์™€ URLSession์„ ํ™œ์šฉํ•œ ๋น„๋™๊ธฐ ๋„คํŠธ์›Œํฌ ํ†ต์‹ ์œผ๋กœ ์‹ค์‹œ๊ฐ„ ํ•ด์–‘ ๋ฐ์ดํ„ฐ fetching
  • APIEndPoint enum๊ณผ Result type์„ ํ™œ์šฉํ•œ type-safe ๋„คํŠธ์›Œํ‚น ๋ ˆ์ด์–ด ๊ตฌํ˜„
  • Published ํ”„๋กœํผํ‹ฐ์™€ ObservableObject๋ฅผ ํ™œ์šฉํ•œ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ๋ฐ UI ์—…๋ฐ์ดํŠธ

๋ฉ€ํ‹ฐ ํฌ๋งท ์›น์บ  ์ŠคํŠธ๋ฆฌ๋ฐ ์ง€์›

  • WKWebView์™€ YouTubePlayerKit์„ ํ™œ์šฉํ•œ ์ด๋ฏธ์ง€/๋น„๋””์˜ค/์œ ํŠœ๋ธŒ ์ŠคํŠธ๋ฆฌ๋ฐ ๊ตฌํ˜„
  • URL ํŒจํ„ด ๋งค์นญ์„ ํ†ตํ•œ ์ž๋™ ํฌ๋งท ๊ฐ์ง€ ๋ฐ ์ ์ ˆํ•œ ๋ทฐ ์ปดํฌ๋„ŒํŠธ ์ „ํ™˜
  • ์ฃผ๊ธฐ์  ๋ฐ์ดํ„ฐ ๋ฆฌํ”„๋ ˆ์‹œ์™€ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ฒ˜๋ฆฌ๋กœ ๋Š๊น€์—†๋Š” ์‹ค์‹œ๊ฐ„ ์˜์ƒ ์ œ๊ณต
  • ๋‹คํฌ๋ชจ๋“œ ๋Œ€์‘์„ ์œ„ํ•œ ๋™์  JavaScript ์ธ์ ์…˜

MapKit ๊ธฐ๋ฐ˜ ์œ„์น˜ ํŠธ๋ž˜ํ‚น ์„œ๋น„์Šค

  • CoreLocation๊ณผ MapKit ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ํ™œ์šฉํ•œ ์‹ค์‹œ๊ฐ„ ์œ„์น˜ ํŠธ๋ž˜ํ‚น
  • MKCoordinateRegion๊ณผ MapCameraPosition์„ ํ™œ์šฉํ•œ ์ง€๋„ ๋ทฐ ์ปจํŠธ๋กค
  • MapAnnotation์„ ํ™œ์šฉํ•œ ์ปค์Šคํ…€ ๋งˆ์ปค ๋ฐ ์‹ค์‹œ๊ฐ„ ํŒŒ๊ณ  ์ •๋ณด ๊ตฌํ˜„

Realm ๊ธฐ๋ฐ˜ ๋กœ์ปฌ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ

  • Repository Pattern์„ ํ™œ์šฉํ•œ ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ณ„์ธต ์ถ”์ƒํ™”
  • Protocol ๊ธฐ๋ฐ˜ ์˜์กด์„ฑ ์ฃผ์ž…์œผ๋กœ ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ ํ™•๋ณด
  • Realm DB๋ฅผ ํ™œ์šฉํ•œ ์ฆ๊ฒจ์ฐพ๊ธฐ ๋ฐ ๋‹ค์ด์–ด๋ฆฌ ๋ฐ์ดํ„ฐ์˜ CRUD ์ž‘์—… ๊ตฌํ˜„

๐Ÿ”ง ์‹œํ–‰์ฐฉ์˜ค

1. ๋ฉ€ํ‹ฐ ์—”๋“œํฌ์ธํŠธ ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™” ์ตœ์ ํ™”

๋ฌธ์ œ์ƒํ™ฉ

  • ํŒŒ๊ณ /์ˆ˜์˜จ/์˜ˆ๋ณด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ๊ฐ ๋ณ„๋„ API๋กœ ํ˜ธ์ถœํ•˜์—ฌ ๋ฐœ์ƒํ•˜๋Š” ์„ฑ๋Šฅ ์ด์Šˆ๋กœ ์ธํ•ด ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ์˜ ์ƒํƒœ ๊ด€๋ฆฌ์™€ UI ์—…๋ฐ์ดํŠธ ์‹œ Race Condition ๋ฐœ์ƒ

ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

// Swift Concurrency์˜ TaskGroup์„ ํ™œ์šฉํ•œ ๋™์‹œ์„ฑ ์ œ์–ด
func fetchBeachData() async throws -> BeachData {
    try await withThrowingTaskGroup(of: APIResponse.self) { group in
        group.addTask { await fetchWaveHeight() }
        group.addTask { await fetchWaterTemperature() }
        group.addTask { await fetchForecast() }
        
        return try await group.reduce(into: BeachData()) { result, response in
            result.update(with: response)
        }
    }
}

2. WKWebView ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐ ์„ฑ๋Šฅ ์ตœ์ ํ™”

๋ฌธ์ œ์ƒํ™ฉ

  • ๋‹ค์ˆ˜์˜ ์›น์บ  ์ŠคํŠธ๋ฆผ ๋™์‹œ ๋กœ๋“œ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๊ธ‰์ฆ
  • WKWebView ์žฌ์‚ฌ์šฉ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ๋ฆฌ์†Œ์Šค ๋ˆ„์ˆ˜

ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

final class WebViewManager {
    private var webViewPool: [String: WeakWebView] = [:]
    
    func dequeueWebView(for url: URL) -> WKWebView {
        cleanUnusedViews()
        if let weakView = webViewPool[url.absoluteString],
           let view = weakView.view {
            return view
        }
        let view = configureNewWebView()
        webViewPool[url.absoluteString] = WeakWebView(view: view)
        return view
    }
    
    private func configureNewWebView() -> WKWebView {
        let config = WKWebViewConfiguration()
        config.allowsInlineMediaPlayback = true
        config.mediaTypesRequiringUserActionForPlayback = []
        return WKWebView(frame: .zero, configuration: config)
    }

3. Realm ๋ฐ์ดํ„ฐ ๋™๊ธฐํ™”

๋ฌธ์ œ์ƒํ™ฉ

  • Realm ๊ฐ์ฒด ์—…๋ฐ์ดํŠธ ์‹œ SwiftUI View ๊ฐฑ์‹  ๋ˆ„๋ฝ
  • ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์Šค๋ ˆ๋“œ์—์„œ UI ์—…๋ฐ์ดํŠธ ์‹œ๋„๋กœ ์ธํ•œ ํฌ๋ž˜์‹œ

ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

class BeachViewModel: ObservableObject {
    private var notificationTokens: [NotificationToken] = []
    
    func observeRealmChanges() {
        let token = realm.objects(Beach.self).observe { [weak self] changes in
            DispatchQueue.main.async {
                self?.objectWillChange.send()
            }
        }
        notificationTokens.append(token)
    }
}

๐Ÿ“ ํšŒ๊ณ 

์ž˜ํ•œ ์ 

1. ๋ชจ๋“ˆํ™”๋œ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„

protocol DataBase {
    func read<T: Object>(_ object: T.Type) -> Results<T>
    func write<T: Object>(_ object: T)
    func delete<T: Object>(_ object: T)
}

protocol NetworkService {
    func fetch<T: Decodable>(endpoint: APIEndPoint) async throws -> T
}
  • Protocol์„ ํ™œ์šฉํ•ด ๋ชจ๋“ˆ ๊ฐ„ ๊ฒฐํ•ฉ๋„ ์ตœ์†Œํ™”
  • Repository Pattern์„ ํ†ตํ•œ ๋ฐ์ดํ„ฐ ์ ‘๊ทผ ๊ณ„์ธต ์ผ์›ํ™”

์•„์‰ฌ์šด ์ 

1. ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ๋ถ€์žฌ

// ํ…Œ์ŠคํŠธ๋ฅผ ๊ณ ๋ คํ•œ ์˜์กด์„ฑ ์ฃผ์ž… ํ•„์š”
class BeachViewModel {
    private let networkService: NetworkService
    private let database: DataBase
    
    init(
        networkService: NetworkService = LiveNetworkService(),
        database: DataBase = RealmDatabase()
    ) {
        self.networkService = networkService
        self.database = database
    }
}
  • Unit Test ๋ฐ UI Test ์ฝ”๋“œ ๋ฏธ๊ตฌํ˜„
  • Mock ๊ฐ์ฒด๋ฅผ ํ™œ์šฉํ•œ ํ…Œ์ŠคํŠธ ์‹œ๋‚˜๋ฆฌ์˜ค ๋ถ€์žฌ

2. ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ฒด๊ณ„ ๋ฏธํก

// ์ฒด๊ณ„์ ์ธ ์—๋Ÿฌ ํƒ€์ž… ์ •์˜ ํ•„์š”
enum AppError: Error {
    case network(NetworkError)
    case database(DatabaseError)
    case validation(ValidationError)
    
    var localizedDescription: String {
        // ์‚ฌ์šฉ์ž ์นœํ™”์ ์ธ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€
    }
}
  • // ์ฒด๊ณ„์ ์ธ ์—๋Ÿฌ ํƒ€์ž… ์ •์˜ ํ•„์š”
  • ํ†ตํ•ฉ๋œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ ๋ถ€์žฌ

์‹œ๋„ํ•  ์ 

1. CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ•

2.์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง ์‹œ์Šคํ…œ ๋„์ž…

3. ์ฝ”๋“œ ํ’ˆ์งˆ ๊ด€๋ฆฌ ๋„์ž…

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages