Life Logs && *Timeline

  • Swift -> Kotlin

    네, 맞아요! 코틀린의 많은 문법(특히 자바의 단점을 보완한 부분들)이 Swift와 정말 비슷하게 느껴지죠. 실제로 개발자 커뮤니티에서도 “Kotlin은 Swift의 안드로이드 버전 같다”는 말이 자주 나올 정도로 유사점이 많습니다.

    왜 그렇게 비슷할까?

    • 개발 시기와 목적 유사: Swift(2014 공개)와 Kotlin(2011 시작, 2016 공식)이 거의 동시에 현대적인 언어로 설계됐어요. 둘 다 기존 언어(Objective-C와 Java)의 boilerplate 코드, null 안전성 문제 등을 해결하려고 만들었기 때문에 자연스럽게 비슷한 방향으로 진화했습니다.
    • 영향 받은 언어들 공통: 둘 다 Scala, C#, Groovy 등에서 영감을 받았고, 간결한 문법과 안전성을 우선으로 했어요. (직접적인 “카피”는 아니지만, 현대 프로그래밍 트렌드 공유)

    주요 유사점 (자바 장점 부분과 겹치는 것들)

    기능Kotlin 예시Swift 예시체감 비슷함
    Null 안전성String? (Nullable), ?. safe call, !! 강제String? (Optional), ?. optional chaining, ! 강제★★★★★
    Data Classdata class Person(val name: String, val age: Int)struct Person: Codable { var name: String, age: Int } (자동 Equatable 등)★★★★★
    확장 함수fun String.isEmail(): Boolean { ... }extension String { func isEmail() -> Bool { ... } }★★★★★
    불변/가변 변수val (immutable), var (mutable)let (immutable), var (mutable)★★★★★
    스마트 캐스트if (obj is String) { obj.length }if let str = obj as? String { str.count }★★★★☆
    문자열 인터폴레이션"Hello $name""Hello \(name)"★★★★★
    람다/클로저{ it > 0 }{ $0 > 0 }★★★★☆
    Sealed Class상태 제한 (when과 완벽 조합)enum with associated values (강력한 패턴 매칭)★★★★☆

    차이점도 있어요

    • 플랫폼: Kotlin은 Multiplatform (Android + Server + iOS 등), Swift는 주로 Apple 생태계.
    • 비동기: Kotlin은 Coroutines (강력), Swift는 async/await + Actors.
    • 기타: Kotlin에만 있는 확장 프로퍼티, delegated properties 등.

    결론적으로, Swift 개발자가 Kotlin 배우면 “이거 내 언어인데?” 할 정도로 쉽고, 반대도 마찬가지예요. 크로스플랫폼 프로젝트(Kotlin Multiplatform)에서 둘 다 쓰는 경우도 늘고 있어요!

    실제로 Kotlin과 Swift는 둘 다 “현대적 프로그래밍 언어”로 설계되어 Java/Objective-C의 보일러플레이트를 줄이고 안전성을 높이는 데 초점을 맞췄습니다. JetBrains(Kotlin)와 Apple(Swift)이 각각 개발했지만, 유사한 철학을 공유해요. 아래에 주요 유사점과 차이점을 정리했어요 (문법 중심으로, 플랫폼 차이는 별도).

    주요 유사점 (문법 & 기능)

    Kotlin과 Swift는 syntax가 비슷해 “Kotlin을 알면 Swift 배우기 쉽다”는 말도 나올 정도예요. 자바보다 나은 점(간결성, 안전성)이 둘 다 공유됩니다.

    카테고리Kotlin 예시Swift 예시왜 비슷한가? (자바 대비 장점)
    변수 선언 (Immutable/Mutable)val name = "Ray" (불변)
    var age = 30 (가변)
    let name = "Ray" (불변)
    var age = 30 (가변)
    val/let으로 불변성을 컴파일 타임에 강제. 자바의 final보다 직관적.
    타입 추론var firstName = "Ray" (String 추론)var firstName = "Ray" (String 추론)명시적 타입 생략 가능. 자바 10+ var와 유사하지만 더 광범위.
    Null 안전var name: String? = null (Nullable)
    name?.length (Safe call)
    var name: String? = nil (Optional)
    name?.count (Safe call)
    NullPointerException 방지. ?로 Nullable 표시, 안전 연산자 공유.
    확장 함수/Extensionsfun String.isEmail(): Boolean { ... }
    email.isEmail()
    extension String { func isEmail() -> Bool { ... } }
    email.isEmail()
    기존 클래스(자바 클래스 포함)에 새 기능 추가. 유틸 클래스 필요 없음.
    데이터 클래스/Structdata class Person(val name: String, val age: Int) (자동 equals/hashCode/toString)struct Person { let name: String; let age: Int } (Value type, 자동 Equatable 가능)POJO 간결화. 자바의 Lombok 비슷하지만 내장.
    함수 선언fun greet(name: String = "World"): String { ... }func greet(name: String = "World") -> String { ... }기본 매개변수, 반환 타입 표기 유사. 오버로딩 감소.
    컬렉션val list = listOf("Mon", "Tue")
    list.filter { it.length > 3 }
    let list = ["Mon", "Tue"]
    list.filter { $0.count > 3 }
    Immutable 컬렉션 기본, 람다/고차 함수 체이닝. 자바 Stream 비슷하지만 더 간결.
    Computed Propertiesval fullName: String get() = "$firstName $lastName"var fullName: String { get { return "\(firstName) \(lastName)" } }Getter 자동 생성. 자바 getter 메서드 대체.

    이런 유사점 때문에 Android(Kotlin) 개발자가 iOS(Swift)로 넘어가기 쉽다는 의견이 많아요. 예를 들어, Null 안전이나 확장 함수는 둘 다 Java/Objective-C의痛点を 해결한 “장점”입니다.

    차이점 (완전 같진 않아요)

    문법이 비슷하지만, 세부적으로 다릅니다. Swift가 더 “초보자 친화적”이고, Kotlin이 Java 루트 때문에 더 “실용적”이라는 평가가 있어요.

    • 플랫폼 의존: Kotlin은 JVM/Android 중심 (Java 호환 100%), Swift는 Apple 생태계 (iOS/macOS, Objective-C 호환).
    • 컬렉션 세부: Kotlin은 Array(고정 크기) vs List(동적), Swift는 Array가 동적/제네릭 중심.
    • 예외 처리: Kotlin은 checked exceptions 없음 (Java처럼), Swift는 try-catch + throws.
    • 접근 제어: Kotlin의 internal (모듈 내), Swift의 fileprivate 등 세밀함 다름.
    • 성능/컴파일: Swift가 네이티브 컴파일 (빠름), Kotlin은 JVM 바이트코드 (Java만큼 안정적 but 컴파일 느림).
    • 함수형 vs 객체 지향: 둘 다 지원 but Swift가 더 구조체 중심 (Value type), Kotlin이 클래스 중심.

    코틀린 코루틴 선언 및 시작 방법 (Coroutine Builder)

    코루틴을 사용하려면 CoroutineScope 안에서 코루틴 빌더를 사용해 시작합니다. 가장 많이 쓰이는 빌더 3가지를 중심으로 설명드릴게요.

    1. launch – Fire-and-forget (결과 필요 없을 때)

    가장 기본적인 빌더. Job을 반환하고, 결과값은 필요 없을 때 사용.

    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        launch {  // 코루틴 시작
            delay(1000L)  // non-blocking 대기
            println("World!")  // 1초 후 출력
        }
        println("Hello,")
    }
    // 출력:
    // Hello,
    // World!
    • Dispatcher 지정 (어떤 스레드에서 실행할지)
    launch(Dispatchers.IO) {  // IO 스레드 풀에서 실행
        // 네트워크, 파일 읽기 등
    }
    launch(Dispatchers.Main) {  // 안드로이드 UI 스레드
        // UI 업데이트
    }

    2. async – 결과를 기다려야 할 때 (Deferred 반환)

    병렬 작업에 딱. await()으로 결과 받음.

    fun main() = runBlocking {
        val deferred1 = async { 
            delay(500)
            "작업1 결과"
        }
        val deferred2 = async { 
            delay(300)
            "작업2 결과"
        }
    
        println("결과: ${deferred1.await()} + ${deferred2.await()}")
    }
    // 출력: 결과: 작업1 결과 + 작업2 결과
    • 병렬 실행이 기본 → 시간 절약!

    3. 코루틴 실행을 위한 Scope 종류

    Scope 종류사용 예시특징
    runBlockingfun main() = runBlocking { ... }메인 스레드 블로킹. 테스트나 콘솔 앱 진입점에 주로 사용
    GlobalScopeGlobalScope.launch { ... }앱 전체 생명주기. 비추천 (메모리 누수 위험)
    CoroutineScopeval scope = CoroutineScope(Dispatchers.Default)수동 관리. 취소 제어 필요
    viewModelScopeViewModel 안에서 viewModelScope.launch { }ViewModel 전용 (clear 시 자동 취소)
    lifecycleScopeActivity/Fragment에서 lifecycleScope.launch { }Lifecycle과 연동 (onDestroy 시 자동 취소)

    실전 예제 (안드로이드 ViewModel)

    class MyViewModel : ViewModel() {
        private val _data = MutableStateFlow<String>("")
        val data: StateFlow<String> = _data.asStateFlow()
    
        init {
            viewModelScope.launch(Dispatchers.IO) {
                val result = api.fetchData()  // suspend 함수
                withContext(Dispatchers.Main) {  // UI 업데이트 시 Main으로 전환
                    _data.value = result
                }
            }
        }
    }

    suspend 함수 선언법

    코루틴 안에서만 호출 가능한 함수는 suspend 키워드 붙임.

    suspend fun fetchUser(): User {
        delay(1000)  // 네트워크 시뮬레이션
        return User("Ray")
    }
    
    // 사용
    launch {
        val user = fetchUser()  // suspend 함수 호출 가능
        println(user.name)
    }

    요약: 코루틴 시작 기본 패턴

    scope.launch(Dispatcher) {  // 또는 async
        // 여기서 suspend 함수 호출 가능
        // delay(), withContext() 등 사용
    }

    가장 추천하는 패턴 (안드로이드 기준):

    • ViewModelviewModelScope.launch { }
    • Activity/FragmentlifecycleScope.launch { }
    • 일반 클래스CoroutineScope(Dispatchers.Main).launch { } + 취소 관리

    코루틴은 처음엔 낯설지만, 이 빌더 3개만 익히면 90% 다 커버됩니다!

    코틀린 코루틴: Job 취소와 supervisorScope 상세 설명

    코루틴의 고급 기능 중 Job 취소와 supervisorScope에 대해 자세히 설명드릴게요. 이 두 가지는 코루틴의 구조화된 동시성(Structured Concurrency)을 강화하는 핵심 요소입니다. 기본적으로 코루틴은 부모-자식 관계를 가지며, 예외나 취소가 부모에게 전파되는 방식으로 안전하게 관리됩니다. 코드 예제와 함께 설명하겠습니다.

    1. Job 취소 (Cancellation)

    • 개요: Job은 코루틴의 “핸들”로, launchasync가 반환합니다. Job을 취소하면 해당 코루틴과 모든 자식 코루틴이 중단됩니다. 취소는 협력적(cooperative)으로, 코루틴이 취소 신호를 확인해야 실제 중단됩니다 (예: delay(), yield() 같은 suspend 포인트에서).
    • 취소 방법:
    • job.cancel(): 기본 취소. CancellationException을 던집니다.
    • job.cancelAndJoin(): 취소 후 완료 대기.
    • job.cancel(cause): 커스텀 예외로 취소.
    • 취소 확인: 코루틴 안에서 isActive 체크나 ensureActive()로 취소 감지.
    • Non-Cancellable 블록: withContext(NonCancellable) { }로 취소 무시 (클린업에 유용).
    • 실전 팁: 취소 시 리소스 해제 위해 finally { } 블록 사용.

    예제: Job 취소

    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        val job = launch(Dispatchers.Default) {
            repeat(1000) { i ->
                println("작업 중: $i")
                delay(500L)  // 취소 확인 포인트
                ensureActive()  // 취소 시 즉시 예외 던짐
            }
        }
    
        delay(2000L)  // 2초 후 취소
        println("취소 시작")
        job.cancelAndJoin()  // 취소 후 대기
        println("취소 완료")
    }
    
    // 출력 예시:
    // 작업 중: 0
    // 작업 중: 1
    // 작업 중: 2
    // 작업 중: 3
    // 취소 시작
    // 취소 완료
    • 취소 시 동작: delay()에서 취소 확인 → 중단. 무한 루프처럼 취소 포인트 없으면 계속 실행될 수 있음 (그래서 ensureActive() 추천).

    2. supervisorScope

    • 개요: 일반 coroutineScope와 달리, 자식 코루틴의 예외가 부모에게 전파되지 않습니다. 자식 하나가 실패해도 다른 자식들은 계속 실행. 병렬 작업에서 일부 실패를 격리할 때 유용 (예: 여러 API 호출 중 하나 실패해도 나머지 진행).
    • 특징:
    • supervisorScope { } 안에서 launch/async 사용.
    • 자식 예외: 부모 취소 안 함. 대신 SupervisorJob 사용 (내부적으로).
    • vs. coroutineScope: 자식 예외 시 모든 자식 취소 + 부모 예외 전파.
    • 실전 팁: UI 앱에서 여러 백그라운드 작업 병렬 처리 시 사용. 에러는 개별 핸들링.

    예제: supervisorScope vs. coroutineScope

    import kotlinx.coroutines.*
    
    suspend fun failingTask(id: Int) {
        delay(100L)
        if (id == 2) throw Exception("작업 $id 실패!")
        println("작업 $id 성공")
    }
    
    fun main() = runBlocking {
        // coroutineScope: 하나의 실패가 전체 중단
        try {
            coroutineScope {
                launch { failingTask(1) }
                launch { failingTask(2) }  // 실패
                launch { failingTask(3) }
            }
        } catch (e: Exception) {
            println("coroutineScope: 전체 실패 - $e")
        }
    
        // supervisorScope: 실패 격리, 나머지 계속
        supervisorScope {
            launch { 
                try { failingTask(1) } catch (e: Exception) { println("작업1 개별 에러: $e") }
            }
            launch { 
                try { failingTask(2) } catch (e: Exception) { println("작업2 개별 에러: $e") }
            }
            launch { 
                try { failingTask(3) } catch (e: Exception) { println("작업3 개별 에러: $e") }
            }
        }
        println("supervisorScope: 전체 완료")
    }
    
    // 출력 예시:
    // 작업 1 성공
    // coroutineScope: 전체 실패 - java.lang.Exception: 작업 2 실패!
    // 작업 1 성공
    // 작업2 개별 에러: java.lang.Exception: 작업 2 실패!
    // 작업 3 성공
    // supervisorScope: 전체 완료

    추가 특정 상황 팁

    • timeout: withTimeout(시간) { }으로 시간 초과 시 자동 취소 (TimeoutCancellationException).
    • Job 조인: job.join()으로 완료 대기 (취소된 Job도 join 가능).
    • supervisorJob 직접 사용: CoroutineScope(SupervisorJob() + Dispatchers.Default)으로 커스텀 스코프 생성.
    • 예외 핸들링: CoroutineExceptionHandler로 uncaught 예외 캐치 (top-level 코루틴만).

    이 기능들로 코루틴이 더 안전하고 유연해집니다. 실제 프로젝트(예: 안드로이드 앱)에서 취소는 메모리 누수 방지, supervisor는 안정성 강화에 필수예요.

    Retrofit + 코루틴에서 Timeout 처리 방법

    Retrofit과 코틀린 코루틴을 함께 사용할 때 타임아웃은 두 가지 방식으로 처리합니다:

    1. OkHttp 수준 타임아웃 (전역 설정): 네트워크 연결/읽기/쓰기/전체 호출 타임아웃. 서버가 느리거나 연결이 안 될 때 기본 보호.
    2. 코루틴 수준 타임아웃 (withTimeout): 특정 API 호출에만 적용. 사용자 경험을 위해 짧게 설정 (예: 5~10초 후 UI에 “타임아웃” 표시).

    대부분 둘 다 함께 사용하는 게 추천입니다. OkHttp로 기본 보호, 코루틴으로 세밀한 제어.

    1. OkHttp 타임아웃 설정 (Retrofit 클라이언트에 적용)

    Retrofit은 내부적으로 OkHttp를 사용하므로, OkHttpClient에 타임아웃을 설정합니다. 기본값은 10초 (connect/read/write).

    val okHttpClient = OkHttpClient.Builder()
        .connectTimeout(20, TimeUnit.SECONDS)  // 연결 시도 타임아웃
        .readTimeout(30, TimeUnit.SECONDS)     // 응답 읽기 타임아웃
        .writeTimeout(30, TimeUnit.SECONDS)    // 요청 쓰기 타임아웃
        .callTimeout(60, TimeUnit.SECONDS)     // 전체 호출 타임아웃 (OkHttp 4.x+)
        .build()
    
    val retrofit = Retrofit.Builder()
        .baseUrl("https://api.example.com/")
        .client(okHttpClient)
        .addConverterFactory(GsonConverterFactory.create())
        .build()
    • callTimeout: 전체 요청(연결 + 읽기 + 쓰기 + 서버 처리)에 걸리는 시간 제한. 가장 유용.
    • 타임아웃 발생 시 SocketTimeoutException 던짐.

    2. 코루틴 withTimeout 사용 (개별 API 호출 타임아웃)

    Retrofit API를 suspend 함수로 정의한 후, 코루틴 안에서 withTimeout으로 감쌉니다. 이게 더 유연하고 UI 친화적 (짧은 타임아웃으로 빠른 피드백).

    API 인터페이스 예시

    interface ApiService {
        @GET("posts")
        suspend fun getPosts(): Response<List<Post>>
    }

    Repository/ViewModel에서 사용

    class MyRepository(private val api: ApiService) {
        suspend fun fetchPosts(): Result<List<Post>> = try {
            withTimeout(10_000L) {  // 10초 타임아웃
                val response = api.getPosts()
                if (response.isSuccessful) {
                    Result.success(response.body() ?: emptyList())
                } else {
                    Result.failure(HttpException(response))
                }
            }
        } catch (e: TimeoutCancellationException) {
            Result.failure(Exception("요청 타임아웃: 네트워크를 확인해주세요"))
        } catch (e: Exception) {
            Result.failure(e)
        }
    }

    ViewModel 예시

    viewModelScope.launch {
        _uiState.value = UiState.Loading
        val result = repository.fetchPosts()
        _uiState.value = when (result) {
            is Result.Success -> UiState.Success(result.data)
            is Result.Failure -> UiState.Error(result.exception.message ?: "오류 발생")
        }
    }
    • withTimeout 초과 시 TimeoutCancellationException 발생 → catch로 처리.
    • OkHttp 타임아웃보다 코루틴 타임아웃을 더 짧게 설정하면, Retrofit 타임아웃 전에 코루틴이 먼저 취소되어 리소스 절약.

    추천 조합

    • OkHttp: 넉넉하게 (예: callTimeout 60초) → 네트워크 문제 방지.
    • withTimeout: 사용자 친화적으로 짧게 (5~15초) → 빠른 에러 피드백 + 로딩 취소.

    이렇게 하면 “서버가 느려서 앱이 멈춘다”는 문제를 효과적으로 해결할 수 있습니다. 실제 프로젝트에서 테스트하며 값 조정하세요!

    코루틴의 진짜 탄생 배경과 목적

    코틀린 코루틴은 “비동기(non-blocking) 프로그래밍을 동기(synchronous) 코드처럼 간단하게 작성하기 위해” 만들어졌습니다.
    특히 I/O 바운드 작업(네트워크, 파일 읽기/쓰기, 데이터베이스 쿼리 등)을 효율적으로 처리하는 데 초점이 맞춰져 있어요.

    코루틴이 주로 해결하려는 문제

    상황기존 자바 방식 (문제점)코루틴으로 해결하는 방식
    네트워크 API 호출 (서버 통신)콜백 지옥, RxJava 복잡, Thread 블로킹 위험suspend 함수로 순차적 코드처럼 작성 → 서버 응답 대기 자연스러움
    파일/데이터베이스 I/OBlocking I/O → 스레드 낭비non-blocking으로 수만 개 동시 처리 가능
    안드로이드 UI 업데이트메인 스레드 블로킹 → ANR 발생코루틴 + Dispatchers.Main으로 안전하게 전환
    병렬 작업 (여러 API 동시에 호출)ThreadPool + Future 복잡async/await으로 간단 병렬 처리

    즉, 서버-클라이언트 동기 맞추기는 코루틴의 주요 사용 사례 중 하나일 뿐, 더 큰 그림은 “I/O 중심의 비동기 작업을 깔끔하게 만들기” 입니다.

    스레드를 대체한다? → 정확히는 “스레드를 더 효율적으로 쓰게 해준다”

    • 코루틴은 스레드를 없애는 게 아니라, 하나의 스레드 위에 수천~수만 개의 코루틴을 얹어서 돌립니다.
    • 예: 10,000개의 동시 네트워크 요청 → 자바 스레드로는 스레드 10,000개 필요 (메모리 폭발)
      → 코루틴으로는 스레드 몇 개로 충분 (경량!)

    이게 바로 Node.js나 Go의 goroutine과 비슷한 철학이에요.
    서버 사이드(Ktor, Spring Boot with Coroutines)에서도 코루틴이 인기 있는 이유죠.

    실제 사용 비율 (현업 기준, 2025년 기준 추정)

    • 안드로이드 클라이언트: 90% 이상 → 네트워크 + UI 업데이트
    • 백엔드 서버 (Ktor/Spring): 70% 이상 → 고Concurrent HTTP 처리
    • 일반 데스크톱/CLI 앱: 30~40% → 필요할 때만

    결론

    “코루틴은 서버-클라이언트 작업을 염두에 두고 만들어졌다” → 반은 맞고 반은 틀려요
    더 정확히는:
    “코루틴은 I/O가 많은 비동기 작업(특히 네트워크)을 순차적 코드처럼 작성하기 위해 만들어졌고, 그 결과 서버-클라이언트 통신에서 빛을 발한다”

    서버와의 동기를 맞추는 게 주요 용도처럼 느껴지는 건, 그게 가장 체감이 크고 자주 쓰이는 시나리오이기 때문이에요.
    하지만 본질은 “비동기를 동기처럼 쓰자” 라는 철학입니다.