본문 바로가기
Kotlin/What's new

[Kotlin 번역] What's new in Kotlin 2.0.20

by 노력남자 2024. 11. 12.
반응형

 

Kotlin 2.0.20 릴리스가 공개되었습니다! 이번 버전에는 Kotlin 2.0.0의 성능 개선 및 버그 수정이 포함되어 있으며, 이때 발표된 Kotlin K2 컴파일러는 안정화되었습니다. 이 릴리스의 추가적인 주요 사항은 다음과 같습니다:

 

데이터 클래스 copy 함수가 생성자와 동일한 가시성을 갖게 되었습니다.

멀티플랫폼 프로젝트에서 기본 타겟 계층의 소스 세트에 대해 정적 접근자가 제공됩니다.

Kotlin/Native의 가비지 컬렉터에서 동시 마킹이 가능해졌습니다.

Kotlin/Wasm의 @ExperimentalWasmDsl 주석 위치가 변경되었습니다.

Gradle 8.6–8.8 버전을 지원합니다.

Gradle 프로젝트 간 JVM 아티팩트를 클래스 파일로 공유할 수 있는 새로운 옵션이 추가되었습니다.

Compose 컴파일러가 업데이트되었습니다.

공통 Kotlin 표준 라이브러리에 UUID 지원이 추가되었습니다.

 

IDE 지원

 

Kotlin 2.0.20을 지원하는 Kotlin 플러그인이 최신 IntelliJ IDEA와 Android Studio에 번들로 제공됩니다. IDE에서 Kotlin 플러그인을 업데이트할 필요가 없으며, 빌드 스크립트에서 Kotlin 버전을 2.0.20으로 변경하기만 하면 됩니다.

 

자세한 내용은 “새 릴리스로 업데이트”를 참고하세요.

 

언어

 

Kotlin 2.0.20부터 데이터 클래스의 일관성을 개선하고 Experimental 컨텍스트 수신자 기능을 대체하는 변화를 도입하기 시작했습니다.

 

데이터 클래스 copy 함수가 생성자와 동일한 가시성을 가지도록 변경

 

현재, private 생성자를 사용하여 데이터 클래스를 생성하면 자동 생성된 copy() 함수가 동일한 가시성을 갖지 않아 코드에서 문제가 발생할 수 있습니다. 향후 Kotlin 릴리스에서는 copy() 함수의 기본 가시성이 생성자와 동일해지도록 할 예정입니다. 이 변경 사항은 점진적으로 도입되어 코드 마이그레이션이 원활하게 진행되도록 할 것입니다.

 

Kotlin 2.0.20부터 이 마이그레이션 계획이 시작되며, 향후 가시성이 변경될 코드에서 경고가 발생합니다. 예를 들어:

 

// 2.0.20에서 경고를 발생시킴
data class PositiveInteger private constructor(val number: Int) {
    companion object {
        fun create(number: Int): PositiveInteger? = if (number > 0) PositiveInteger(number) else null
    }
}

fun main() {
    val positiveNumber = PositiveInteger.create(42) ?: return
    // 2.0.20에서 경고를 발생시킴
    val negativeNumber = positiveNumber.copy(number = -1)
    // 경고: Non-public primary constructor is exposed via the generated 'copy()' method of the 'data' class.
    // The generated 'copy()' will change its visibility in future releases.
}

 

이 동작을 더 잘 제어할 수 있도록 Kotlin 2.0.20에는 두 가지 주석이 도입되었습니다:

 

@ConsistentCopyVisibility: 나중에 기본 동작이 되기 전에 미리 선택하여 사용할 수 있습니다.

@ExposedCopyVisibility: 이 동작을 선택하지 않고 선언 위치에서 경고를 억제할 수 있습니다. 이 주석을 사용해도 copy() 함수 호출 시 컴파일러는 여전히 경고를 보고합니다.

 

모듈 전체에 대해 이 새로운 동작을 미리 적용하려면, 개별 클래스가 아닌 모듈에 -Xconsistent-data-class-copy-visibility 컴파일러 옵션을 사용할 수 있습니다. 이 옵션은 모든 데이터 클래스에 @ConsistentCopyVisibility 주석을 추가한 것과 동일한 효과를 가집니다.

 

컨텍스트 수신자를 컨텍스트 매개변수로 대체하는 단계적 변경

 

Kotlin 1.6.20에서는 컨텍스트 수신자를 실험 기능으로 도입했습니다. 커뮤니티의 피드백을 반영하여, 이 접근 방식을 지속하지 않고 다른 방향으로 나아가기로 했습니다.

 

향후 Kotlin 릴리스에서는 컨텍스트 수신자가 컨텍스트 매개변수로 대체됩니다. 컨텍스트 매개변수는 아직 설계 단계에 있으며, 제안서는 KEEP에서 확인할 수 있습니다.

 

컨텍스트 매개변수 구현에는 컴파일러의 상당한 변화가 필요하기 때문에, 컨텍스트 수신자와 컨텍스트 매개변수를 동시에 지원하지 않기로 결정했습니다. 이로 인해 구현이 크게 간소화되며 불안정한 동작의 위험이 최소화됩니다.

 

컨텍스트 수신자가 이미 많은 개발자에게 사용되고 있다는 점을 감안하여, Kotlin 2.0.20부터는 -Xcontext-receivers 컴파일러 옵션과 함께 컨텍스트 수신자를 사용할 때 코드에서 경고가 표시됩니다. 예를 들어:

 

class MyContext

context(MyContext)
// 경고: Experimental context receivers are deprecated and will be superseded by context parameters.
// Please don't use context receivers. You can either pass parameters explicitly or use members with extensions.
fun someFunction() {
}

 

이 경고는 향후 Kotlin 릴리스에서 오류로 변경될 예정입니다.

 

컨텍스트 수신자를 사용하는 경우 다음과 같은 방식으로 코드를 마이그레이션할 것을 권장합니다:

 

1. 명시적 매개변수를 사용

 

Before

context(ContextReceiverType)
fun someFunction() {
    contextReceiverMember()
}

 

After

fun someFunction(explicitContext: ContextReceiverType) {
    explicitContext.contextReceiverMember()
}

 

2. 확장 멤버 함수 사용 (가능한 경우)

 

Before

context(ContextReceiverType)
fun contextReceiverMember() = TODO()

context(ContextReceiverType)
fun someFunction() {
    contextReceiverMember()
}

 

After

class ContextReceiverType {
    fun contextReceiverMember() = TODO()
}

fun ContextReceiverType.someFunction() {
    contextReceiverMember()
}

 

대안으로, 컨텍스트 매개변수를 컴파일러에서 지원하는 Kotlin 릴리스까지 기다릴 수 있습니다. 컨텍스트 매개변수는 초기에는 실험 기능으로 도입될 예정입니다.

 

Kotlin Multiplatform

 

Kotlin 2.0.20은 멀티플랫폼 프로젝트의 소스 세트 관리 개선 및 Gradle의 Java 플러그인과의 호환성 폐기를 도입했습니다. 이는 Gradle의 최근 변경 사항에 따른 것입니다.

 

기본 타겟 계층의 소스 세트를 위한 정적 접근자

 

Kotlin 1.9.20부터 기본 계층 템플릿이 모든 Kotlin Multiplatform 프로젝트에 자동으로 적용됩니다. 기본 계층 템플릿의 모든 소스 세트에 대해 Kotlin Gradle 플러그인은 타입 안전한 접근자를 제공하여 by getting이나 by creating 같은 구문 없이도 지정된 타겟의 소스 세트에 접근할 수 있었습니다.

 

Kotlin 2.0.20은 IDE 경험을 더욱 개선하여 기본 계층 템플릿의 모든 소스 세트에 대해 sourceSets {} 블록에서 정적 접근자를 제공합니다. 이를 통해 이름으로 소스 세트에 접근하는 것이 더 쉬워지고 예측 가능해질 것으로 기대합니다.

 

이제 각 소스 세트에는 샘플과 진단 메시지가 포함된 자세한 KDoc 주석이 있으며, 해당 타겟을 먼저 선언하지 않고 소스 세트에 접근할 경우 경고 메시지가 표시됩니다.

 

kotlin {
    jvm()
    linuxX64()
    linuxArm64()
    mingwX64()

    sourceSets {
        commonMain.languageSettings {
            progressiveMode = true
        }

        jvmMain { }
        linuxX64Main { }
        linuxArm64Main { }
        // 경고: 타겟을 등록하지 않고 소스 세트에 접근 중
        iosX64Main { }
    }
}

 

 

소스 세트를 이름으로 접근하기 Kotlin Multiplatform의 계층적 프로젝트 구조에 대해 자세히 알아보세요.

 

Kotlin Multiplatform Gradle 플러그인과 Gradle Java 플러그인의 호환성 폐기

 

Kotlin 2.0.20부터 Kotlin Multiplatform Gradle 플러그인과 다음 Gradle Java 플러그인 중 하나를 같은 프로젝트에 적용할 때 경고가 표시됩니다: Java, Java Library, Application. 멀티플랫폼 프로젝트 내 다른 Gradle 플러그인이 Gradle Java 플러그인을 적용할 때도 경고가 표시됩니다. 예를 들어, Spring Boot Gradle 플러그인은 자동으로 Application 플러그인을 적용합니다.

 

Kotlin Multiplatform의 프로젝트 모델과 Gradle Java 에코시스템 플러그인 간의 근본적인 호환성 문제로 인해 이 경고가 추가되었습니다. Gradle의 Java 에코시스템 플러그인은 현재 다른 플러그인이 다음을 수행할 수 있다는 점을 고려하지 않습니다:

 

Java 에코시스템 플러그인과 다른 방식으로 JVM 타겟을 위한 퍼블리싱 또는 컴파일 수행

동일한 프로젝트에서 JVM과 Android 같은 두 가지 다른 JVM 타겟 사용

여러 개의 비-JVM 타겟이 포함된 복잡한 멀티플랫폼 프로젝트 구조

 

현재 Gradle은 이러한 문제를 해결하기 위한 API를 제공하지 않습니다.

 

이전에 Kotlin Multiplatform에서는 Java 에코시스템 플러그인 통합을 위해 몇 가지 해결 방법을 사용했으나, 이러한 해결 방법은 근본적인 호환성 문제를 해결하지 못했으며, Gradle 8.8 릴리스 이후에는 더 이상 이러한 해결 방법이 불가능합니다. 자세한 내용은 YouTrack 이슈를 참고하세요.

 

이 문제를 해결하는 방법은 아직 정해지지 않았지만, 멀티플랫폼 프로젝트에서 Java 소스 컴파일을 지원하기 위해 지속적으로 노력할 것입니다. 최소한, Java 소스의 컴파일과 멀티플랫폼 프로젝트에서 Gradle의 java-base 플러그인 사용은 지원할 예정입니다.

 

한편, 멀티플랫폼 프로젝트에서 이 경고가 나타나면 다음을 권장합니다:

프로젝트에서 Gradle Java 플러그인이 실제로 필요한지 확인하세요. 필요하지 않다면 제거를 고려하세요.

Gradle Java 플러그인이 단일 작업에만 사용된다면 제거할 수 있는지 확인하세요. 예를 들어, 작업이 Javadoc JAR 파일 생성을 위해 Gradle Java 플러그인을 사용하는 경우, Javadoc 작업을 수동으로 정의할 수 있습니다.

 

또한 멀티플랫폼 프로젝트에서 Kotlin Multiplatform Gradle 플러그인과 Java 플러그인을 모두 사용하고자 한다면:

1. 멀티플랫폼 프로젝트 내에 별도의 하위 프로젝트를 생성하세요.

2. 별도의 하위 프로젝트에 Gradle Java 플러그인을 적용하세요.

3. 별도의 하위 프로젝트에서 부모 멀티플랫폼 프로젝트에 대한 종속성을 추가하세요.

 

별도의 하위 프로젝트는 멀티플랫폼 프로젝트가 아니어야 하며, 멀티플랫폼 프로젝트에 대한 종속성을 설정하는 데만 사용해야 합니다.

 

예를 들어, my-main-project라는 멀티플랫폼 프로젝트가 있고, JVM 애플리케이션을 실행하기 위해 Application Gradle 플러그인을 사용하고자 하는 경우:

 

하위 프로젝트를 생성한 후, 이를 subproject-A라고 하면, 부모 프로젝트 구조는 다음과 같아야 합니다:

 

.
├── build.gradle.kts
├── settings.gradle
├── subproject-A
    └── build.gradle.kts
    └── src
        └── Main.java

 

하위 프로젝트의 build.gradle.kts 파일에서 plugins {} 블록에 Application 플러그인을 적용하세요:

 

plugins {
    id("application")
}

 

하위 프로젝트의 build.gradle.kts 파일에서 부모 멀티플랫폼 프로젝트에 대한 종속성을 추가하세요:

 

dependencies {
    implementation(project(":my-main-project")) // 부모 멀티플랫폼 프로젝트 이름
}

 

이제 부모 프로젝트가 두 플러그인과 함께 작동하도록 설정되었습니다.

 

Kotlin/Native

 

Kotlin/Native는 가비지 컬렉터(GC)와 Swift/Objective-C에서 Kotlin 서스펜딩 함수를 호출하는 기능에서 개선을 받았습니다.

 

가비지 컬렉터에서 동시 마킹

 

Kotlin 2.0.20에서 JetBrains 팀은 Kotlin/Native 런타임 성능 개선을 위해 한 걸음 더 나아갔습니다. 가비지 컬렉터(GC)에서 동시 마킹(concurrent marking)을 실험적으로 지원하게 되었습니다.

 

기본적으로, GC가 힙에서 객체를 마킹할 때 애플리케이션 스레드를 중지해야 합니다. 이는 Compose Multiplatform으로 구축된 UI 애플리케이션처럼 지연 시간이 중요한 애플리케이션의 성능에 큰 영향을 미칩니다.

 

이제 가비지 수집의 마킹 단계가 애플리케이션 스레드와 동시에 실행될 수 있습니다. 이는 GC 일시 중지 시간을 크게 단축하고 애플리케이션 반응성을 개선하는 데 도움이 됩니다.

 

활성화 방법

 

이 기능은 현재 실험적입니다. 활성화하려면 gradle.properties 파일에 다음 옵션을 설정하세요:

 

kotlin.native.binary.gc=cms

 

문제가 발생하면 YouTrack의 이슈 트래커에 보고해 주세요.

 

Bitcode 임베딩 지원 중단

 

Kotlin 2.0.20부터 Kotlin/Native 컴파일러는 bitcode 임베딩을 더 이상 지원하지 않습니다. Bitcode 임베딩은 Xcode 14에서 사용 중단되었으며 Xcode 15에서 모든 Apple 타겟에서 제거되었습니다.

 

따라서 프레임워크 구성의 embedBitcode 매개변수, -Xembed-bitcode, -Xembed-bitcode-marker 명령줄 인수가 더 이상 사용되지 않습니다.

 

이전 버전의 Xcode를 여전히 사용하고 Kotlin 2.0.20으로 업그레이드하려면 Xcode 프로젝트에서 bitcode 임베딩을 비활성화하세요.

 

Signposts를 사용한 GC 성능 모니터링 변경

 

Kotlin 2.0.0에서는 Xcode Instruments를 통해 Kotlin/Native GC의 성능을 모니터링할 수 있는 기능을 추가했습니다. Instruments의 signposts 도구는 이벤트로 GC 일시 중지를 표시할 수 있어 iOS 앱의 GC 관련 중지 문제를 확인할 때 유용합니다.

 

이 기능은 기본적으로 활성화되었으나, Xcode Instruments와 애플리케이션을 동시에 실행할 때 충돌이 발생하는 문제가 있었습니다. Kotlin 2.0.20부터는 다음 컴파일러 옵션을 통해 명시적으로 활성화해야 합니다:

 

-Xbinary=enableSafepointSignposts=true

 

GC 성능 분석에 대한 자세한 내용은 문서를 참조하세요.

 

비-메인 스레드에서 Swift/Objective-C로 Kotlin 서스펜딩 함수 호출 가능

 

이전에는 Swift 및 Objective-C에서 Kotlin 서스펜딩 함수를 호출할 때 메인 스레드에서만 호출할 수 있는 기본 제한이 있었습니다. Kotlin 2.0.20에서는 이 제한을 해제하여 Swift/Objective-C에서 어떤 스레드에서도 Kotlin 서스펜딩 함수를 실행할 수 있도록 했습니다.

 

이전에 kotlin.native.binary.objcExportSuspendFunctionLaunchThreadRestriction=none 바이너리 옵션을 사용하여 메인 스레드가 아닌 스레드에서 이 동작을 전환했다면, 이제 이 옵션을 gradle.properties 파일에서 제거할 수 있습니다.

 

Kotlin/Wasm

 

Kotlin 2.0.20에서는 Kotlin/Wasm의 명명된(named) 내보내기(exports) 방식으로의 마이그레이션이 지속되며, @ExperimentalWasmDsl 애노테이션의 위치가 변경되었습니다.

 

기본 내보내기 사용 시 오류 발생

 

Kotlin/Wasm 내보내기에 대해 JavaScript에서 기본 가져오기(default import)를 사용할 경우, 이제 경고 메시지가 오류로 업그레이드되었습니다. 기본 가져기를 사용하면 다음과 같은 오류 메시지가 나타납니다:

 

Do not use default import. Use the corresponding named import instead.

 

이 변경 사항은 명명된 내보내기로의 마이그레이션을 위한 단계적 폐기 과정의 일부입니다. 각 버전에서의 변경 사항은 다음과 같습니다:

 

버전 2.0.0: 기본 내보내기로 엔티티를 내보내는 것이 더 이상 권장되지 않음을 설명하는 경고 메시지가 콘솔에 출력됩니다.

버전 2.0.20: 오류가 발생하며, 해당 이름이 있는 가져기를 사용하도록 요청합니다.

버전 2.1.0: 기본 가져기의 사용이 완전히 제거됩니다.

 

ExperimentalWasmDsl 애노테이션의 새 위치

 

이전에 WebAssembly(Wasm) 기능을 위한 @ExperimentalWasmDsl 애노테이션은 다음 위치에 있었습니다:

 

org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl

 

2.0.20에서 이 애노테이션은 다음 위치로 이동했습니다:

 

org.jetbrains.kotlin.gradle.ExperimentalWasmDsl

 

기존 위치는 이제 사용이 권장되지 않으며, 해제되지 않은 참조로 인해 빌드 오류를 유발할 수 있습니다.

 

@ExperimentalWasmDsl 위치를 반영하려면 Gradle 빌드 스크립트에서 가져오기(import) 문을 업데이트하세요. 새 위치를 명시적으로 가져오려면 다음을 사용하십시오:

 

import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl

 

또는 이전 패키지에서 별표 가져오기 문을 제거하십시오:

 

import org.jetbrains.kotlin.gradle.targets.js.dsl.*

 

Kotlin/JS

 

Kotlin/JS는 JavaScript에서 정적 멤버를 지원하고 JavaScript로부터 Kotlin 컬렉션을 생성할 수 있는 몇 가지 실험적 기능을 도입했습니다.

 

JavaScript에서 Kotlin 정적 멤버 사용 지원

 

이 기능은 실험적(Experimental)으로, 언제든지 제거되거나 변경될 수 있습니다. 평가 목적으로만 사용하고, YouTrack에 피드백을 제공해 주시면 감사하겠습니다.

 

Kotlin 2.0.20부터 @JsStatic 애노테이션을 사용할 수 있습니다. 이 애노테이션은 @JvmStatic과 유사하게 동작하며, 컴파일러에게 대상 선언에 추가적인 정적 메서드를 생성하도록 지시합니다. 이를 통해 JavaScript에서 Kotlin 코드를 통해 정적 멤버에 직접 접근할 수 있습니다.

 

@JsStatic 애노테이션은 명명된 객체(named object)나 클래스 및 인터페이스 내의 동반 객체(companion object)에 정의된 함수에 사용할 수 있습니다. 컴파일러는 객체의 정적 메서드와 객체 자체 내의 인스턴스 메서드를 모두 생성합니다. 예를 들어:

 

class C {
    companion object {
        @JsStatic
        fun callStatic() {}
        fun callNonStatic() {}
    }
}

 

JavaScript에서 callStatic()은 정적이지만 callNonStatic()는 그렇지 않습니다:

 

C.callStatic();              // 작동, 정적 함수 접근
C.callNonStatic();           // 오류, 생성된 JavaScript에서 정적 함수가 아님
C.Companion.callStatic();    // 인스턴스 메서드 유지됨
C.Companion.callNonStatic(); // 유일한 작동 방법

 

또한 @JsStatic 애노테이션을 객체 또는 동반 객체의 속성에 적용하여, 해당 객체나 동반 객체가 포함된 클래스의 getter 및 setter 메서드를 정적 멤버로 만들 수도 있습니다.

 

JavaScript로부터 Kotlin 컬렉션 생성 기능

 

이 기능은 실험적(Experimental)으로, 언제든지 제거되거나 변경될 수 있습니다. 평가 목적으로만 사용하고, YouTrack에 피드백을 제공해 주시면 감사하겠습니다.

 

Kotlin 2.0.0에서는 Kotlin 컬렉션을 JavaScript(및 TypeScript)로 내보낼 수 있는 기능이 도입되었습니다. Kotlin 2.0.20에서는 JavaScript/TypeScript 측에서 Kotlin 컬렉션을 직접 생성할 수 있는 기능을 추가하여 컬렉션 간 상호운용성을 개선했습니다.

 

JavaScript에서 Kotlin 컬렉션을 생성하고, 이를 내보낸 생성자나 함수에 인수로 전달할 수 있습니다. 내보낸 선언에서 컬렉션을 언급하기만 하면 Kotlin은 JavaScript/TypeScript에서 사용할 수 있는 컬렉션용 팩토리를 생성합니다.

 

다음 내보낸 함수를 보십시오:

 

@JsExport
fun consumeMutableMap(map: MutableMap<String, Int>)

 

MutableMap 컬렉션이 언급되었으므로, Kotlin은 JavaScript/TypeScript에서 사용할 수 있는 팩토리 메서드가 포함된 객체를 생성합니다. 이 팩토리 메서드는 JavaScript의 Map에서 MutableMap을 생성합니다:

 

import { consumeMutableMap } from "an-awesome-kotlin-module"
import { KtMutableMap } from "an-awesome-kotlin-module/kotlin-kotlin-stdlib"

consumeMutableMap(
    KtMutableMap.fromJsMap(new Map([["First", 1], ["Second", 2]]))
)

 

이 기능은 Set, Map, List Kotlin 컬렉션 유형과 이들의 변경 가능한(mutable) 대응 유형에 대해 사용할 수 있습니다.

 

Gradle

 

Kotlin 2.0.20은 Gradle 6.8.3부터 8.6까지 완벽하게 호환됩니다. Gradle 8.7과 8.8도 지원되지만, 한 가지 예외가 있습니다. Kotlin Multiplatform Gradle 플러그인을 사용할 경우 JVM 타겟에서 withJava() 함수 호출 시 deprecation 경고가 발생할 수 있습니다. 이 문제는 조속히 해결될 예정입니다.

 

또한, 최신 Gradle 버전까지 사용할 수 있지만, 이 경우 deprecation 경고가 발생하거나 일부 새로운 Gradle 기능이 제대로 작동하지 않을 수 있습니다.

 

이번 버전에서는 JVM 히스토리 파일 기반의 이전 증분 컴파일 방식의 사용 중단 절차가 시작되었으며, 프로젝트 간 JVM 아티팩트 공유 방식에 대한 새로운 접근 방식이 도입되었습니다.

 

JVM 히스토리 파일 기반 증분 컴파일의 사용 중단

 

Kotlin 2.0.20에서는 JVM 히스토리 파일 기반 증분 컴파일 방식이 사용 중단(deprecated)되었습니다. 대신, Kotlin 1.8.20부터 기본적으로 활성화된 새로운 증분 컴파일 방식이 사용됩니다.

 

JVM 히스토리 파일 기반 증분 컴파일 방식은 Gradle의 빌드 캐시와 호환되지 않으며, 컴파일 회피를 지원하지 않는 등의 제한이 있었습니다. 반면, 새로운 증분 컴파일 방식은 이러한 제한을 극복하고 좋은 성능을 보이고 있습니다.

 

따라서 kotlin.incremental.useClasspathSnapshot Gradle 속성은 Kotlin 2.0.20에서 deprecated 되며, 이를 사용하면 경고가 표시됩니다.

 

프로젝트 간 JVM 아티팩트 공유 방식 (클래스 파일로 공유)

 

이 기능은 실험적(experimental)입니다. 언제든지 변경되거나 삭제될 수 있으므로 평가 용도로만 사용하고, 피드백을 YouTrack에 남기시기 바랍니다.

 

Kotlin 2.0.20에서는 Kotlin/JVM 컴파일 출력물(예: JAR 파일)을 프로젝트 간에 공유하는 새로운 방식이 도입되었습니다. 이제 Gradle의 apiElements 구성에는 컴파일된 .class 파일이 포함된 디렉토리에 접근할 수 있는 추가 변형(secondary variant)이 생겼습니다. 이 방식이 설정되면 프로젝트는 압축된 JAR 아티팩트를 요청하는 대신, 해당 디렉토리를 사용합니다. 이는 JAR 파일을 압축하고 해제하는 횟수를 줄여, 특히 증분 빌드에서 성능을 향상시킵니다.

 

이 새로운 접근 방식은 Linux와 macOS에서는 빌드 성능 향상 효과가 있지만, Windows에서는 I/O 작업 처리 방식 때문에 성능이 저하될 수 있습니다.

 

이 방법을 사용하려면, gradle.properties 파일에 다음 속성을 추가하십시오:

 

kotlin.jvm.addClassesVariant=true

 

기본적으로 이 속성은 false로 설정되어 있으며, apiElements 변형은 압축된 JAR 아티팩트를 요청합니다.

 

Java 전용 프로젝트에서는 다음 속성을 사용하여 압축된 JAR 아티팩트만 컴파일 시 노출되도록 설정할 수 있습니다:

 

org.gradle.java.compile-classpath-packaging=true

 

Kotlin Gradle 플러그인과 Java Test Fixtures 플러그인의 종속성 동작 정렬

 

Kotlin 2.0.20 이전에는 java-test-fixtures 플러그인을 사용할 경우, Gradle과 Kotlin Gradle 플러그인 간에 종속성 전파 방식에 차이가 있었습니다.

 

Kotlin Gradle 플러그인은 java-test-fixtures 플러그인의 implementationapi 종속성을 테스트 소스셋 컴파일 클래스 경로에 전파했습니다. 그러나 Gradle은 api 종속성만 전파했습니다.

 

Kotlin 2.0.20부터는 Kotlin Gradle 플러그인의 동작이 Gradle의 java-test-fixtures 플러그인과 일치하도록 변경되어 이 문제는 더 이상 발생하지 않습니다.

 

이로 인해 일부 종속성이 testtestFixtures 소스셋에서 더 이상 접근되지 않을 수 있습니다. 이 경우, 종속성 선언 유형을 implementation에서 api로 변경하거나, 영향을 받는 소스셋에 새로운 종속성 선언을 추가하십시오.

 

드물게 컴파일 작업에 필요한 아티팩트 입력에 대한 작업 종속성 추가

 

Kotlin 2.0.20 이전에는 드물게 컴파일 작업에 필요한 아티팩트 입력에 대한 작업 종속성이 누락되어 컴파일 작업이 불안정하게 수행되는 경우가 있었습니다. 이 문제를 해결하기 위해, Kotlin Gradle 플러그인은 이제 이러한 시나리오에서 필요한 작업 종속성을 자동으로 추가합니다.

 

이 새로운 동작이 드물게 순환 종속성 오류를 일으킬 수 있습니다. 예를 들어, 여러 개의 컴파일 작업에서 하나의 컴파일 작업이 다른 컴파일 작업의 모든 내부 선언을 볼 수 있고, 생성된 아티팩트가 두 작업 모두의 출력을 의존하는 경우, 다음과 같은 오류가 발생할 수 있습니다:

 

FAILURE: Build failed with an exception.

What went wrong:
Circular dependency between the following tasks:
:lib:compileKotlinJvm
--- :lib:jvmJar
     \--- :lib:compileKotlinJvm (*)

 

이 순환 종속성 오류를 해결하려면 Gradle 속성 archivesTaskOutputAsFriendModule을 사용하면 됩니다.

 

기본적으로 이 속성은 true로 설정되어 작업 종속성을 추적합니다. 컴파일 작업에서 아티팩트를 사용하지 않도록 설정하려면 gradle.properties 파일에 다음을 추가하십시오:

 

kotlin.build.archivesTaskOutputAsFriendModule=false

 

더 자세한 정보는 YouTrack의 이슈에서 확인하실 수 있습니다.

 

Compose compiler

 

2.0.0에서 발생한 불필요한 재구성 문제 수정

 

Compose 컴파일러 2.0.0에서는 멀티플랫폼 프로젝트에서 비-JVM 타겟을 사용할 때, 타입의 안정성을 잘못 추론하여 불필요한(혹은 끝없는) 재구성이 발생하는 문제가 있었습니다. Kotlin 2.0.0으로 만든 Compose 앱은 2.0.10 이상의 버전으로 업데이트하는 것이 강력히 권장됩니다.

 

만약 앱이 Compose 컴파일러 2.0.10 이상으로 빌드되었지만, 2.0.0 버전으로 빌드된 종속성을 사용하고 있다면, 여전히 재구성 문제를 겪을 수 있습니다. 이를 방지하려면 앱과 동일한 Compose 컴파일러 버전으로 빌드된 종속성으로 업데이트하십시오.

 

새로운 컴파일러 옵션 구성 방식

 

Compose 컴파일러에서는 이제 상위 수준의 매개변수를 생성하거나 제거하는 방식으로 테스트하는 것이 어려워짐에 따라, 새로운 옵션 구성 메커니즘을 도입하였습니다. 이로 인해, composeCompiler {} 블록에서 strongSkippingModenonSkippingGroupOptimizations 같은 옵션들은 이제 featureFlags 속성을 통해 설정됩니다. 이 방식은 새로운 Compose 컴파일러 옵션을 테스트하는 데 사용되며, 이 옵션들이 결국 기본값이 될 것입니다.

 

이 변경은 Compose 컴파일러 Gradle 플러그인에도 적용되었습니다. 앞으로 기능 플래그를 구성하려면 다음 구문을 사용하십시오(기본값을 모두 변경하는 코드):

 

composeCompiler {
    featureFlags = setOf(
        ComposeFeatureFlag.IntrinsicRemember.disabled(),
        ComposeFeatureFlag.OptimizeNonSkippingGroups,
        ComposeFeatureFlag.StrongSkipping.disabled()
    )
}

 

혹은 Compose 컴파일러를 직접 구성하는 경우 다음 구문을 사용하십시오:

 

-P plugin:androidx.compose.compiler.plugins.kotlin:featureFlag=IntrinsicRemember

 

enableIntrinsicRemember, enableNonSkippingGroupOptimization, enableStrongSkippingMode 속성은 더 이상 사용되지 않습니다.

 

기본적으로 활성화된 강력한 스킵 모드

 

이번 버전에서는 Compose 컴파일러의 강력한 스킵 모드(Strong Skipping Mode)가 기본적으로 활성화되었습니다.

 

강력한 스킵 모드는 어떤 composable이 스킵될 수 있는지에 대한 규칙을 변경하는 Compose 컴파일러 구성 옵션입니다. 강력한 스킵 모드가 활성화되면, 불안정한 매개변수를 가진 composable도 스킵될 수 있습니다. 또한, 이 모드는 composable 함수에서 사용되는 람다를 자동으로 기억하므로, 더 이상 재구성을 피하기 위해 람다를 remember로 감쌀 필요가 없습니다.

 

자세한 내용은 강력한 스킵 모드 문서를 참조하십시오.

 

기본적으로 활성화된 Composition Trace Markers

 

includeTraceMarkers 옵션은 이제 기본값으로 true로 설정되어 있으며, 이는 Android Studio 시스템 추적 프로파일러에서 composable 함수들을 볼 수 있도록 합니다. Composition 트레이싱에 대한 자세한 내용은 이 Android Developers 블로그 게시물을 참조하십시오.

 

비스킵 그룹 최적화

 

이번 릴리스에는 새로운 컴파일러 옵션이 추가되었습니다. 이 옵션을 활성화하면, 비스킵 가능하고 비재시작 가능한 composable 함수들은 더 이상 그 함수 본문 주위에 그룹을 생성하지 않습니다. 이는 메모리 할당을 줄여 성능을 향상시킬 수 있습니다. 이 옵션은 실험적이며 기본값은 비활성화되어 있지만, 위에서 설명한 OptimizeNonSkippingGroups 기능 플래그를 통해 활성화할 수 있습니다.

 

이 기능 플래그는 더 넓은 테스트를 위해 준비되었습니다. 기능을 활성화했을 때 문제가 발생하면 Google 문제 추적기에 보고할 수 있습니다.

 

추상 composable 함수에서 기본 매개변수 지원

 

이제 추상 composable 함수에서 기본 매개변수를 추가할 수 있습니다.

 

이전에는 Compose 컴파일러가 기본 매개변수를 가진 추상 composable 함수에서 오류를 보고했지만, 이는 유효한 Kotlin 코드였으며, 이제 Compose 컴파일러에서 이를 지원하도록 변경되었습니다. 이 변경은 기본 Modifier 값을 포함하는 경우에 특히 유용합니다:

 

abstract class Composables {
    @Composable
    abstract fun Composable(modifier: Modifier = Modifier)
}

 

하지만 open composable 함수에 대한 기본 매개변수는 여전히 2.0.20에서는 제한됩니다. 이 제한은 향후 릴리스에서 해결될 예정입니다.

 

표준 라이브러리

 

UUID 지원 (실험적 기능)

 

Kotlin 2.0.20에서는 UUID(범용 고유 식별자)를 표준 라이브러리에서 지원하는 기능을 추가했습니다. 이 기능은 실험적이며, 사용하려면 @ExperimentalUuidApi 주석을 추가하거나 -opt-in=kotlin.uuid.ExperimentalUuidApi 컴파일러 옵션을 사용해야 합니다.

 

이 기능은 UUID를 생성하고, 문자열 표현에서 UUID를 파싱하거나 포맷하는 기능을 제공합니다. 또한, 128비트 값을 사용해 UUID를 생성하거나 UUID의 128비트 값을 직접 액세스할 수 있는 API도 제공됩니다.

 

다음은 UUID 관련 작업을 수행하는 코드 예제입니다:

 

// UUID 생성을 위한 바이트 배열 생성
val byteArray = byteArrayOf(
    0x55, 0x0E, 0x84.toByte(), 0x00, 0xE2.toByte(), 0x9B.toByte(), 0x41, 0xD4.toByte(),
    0xA7.toByte(), 0x16, 0x44, 0x66, 0x55, 0x44, 0x00, 0x00
)

val uuid1 = Uuid.fromByteArray(byteArray)
val uuid2 = Uuid.fromULongs(0x550E8400E29B41D4uL, 0xA716446655440000uL)
val uuid3 = Uuid.parse("550e8400-e29b-41d4-a716-446655440000")

println(uuid1) // 550e8400-e29b-41d4-a716-446655440000
println(uuid1 == uuid2) // true
println(uuid2 == uuid3) // true

// UUID의 비트에 접근
val version = uuid1.toLongs { mostSignificantBits, _ ->
    ((mostSignificantBits shr 12) and 0xF).toInt()
}
println(version) // 4

// 랜덤 UUID 생성
val randomUuid = Uuid.random()

println(uuid1 == randomUuid) // false

 

또한, java.util.UUIDkotlin.uuid.Uuid 간의 변환을 위해 두 가지 확장 함수가 제공됩니다: .toJavaUuid().toKotlinUuid(). 예를 들어:

 

val kotlinUuid = Uuid.parseHex("550e8400e29b41d4a716446655440000")
// Kotlin UUID를 java.util.UUID로 변환
val javaUuid = kotlinUuid.toJavaUuid()

val javaUuid = java.util.UUID.fromString("550e8400-e29b-41d4-a716-446655440000")
// Java UUID를 kotlin.uuid.Uuid로 변환
val kotlinUuid = javaUuid.toKotlinUuid()

 

UUID는 여러 플랫폼에서 코드 공유를 용이하게 하여 멀티플랫폼 소프트웨어 개발에 유용합니다. 예를 들어, UUID는 고유한 식별자를 생성해야 하는 환경에서 적합합니다.

 

예시 사용 사례:

 

데이터베이스 레코드에 고유한 ID 할당

웹 세션 식별자 생성

고유 식별자 또는 추적이 필요한 시나리오

 

HexFormat에서 minLength 지원

 

HexFormat 클래스와 그 속성들은 실험적 기능입니다. 사용하려면 @OptIn(ExperimentalStdlibApi::class) 주석을 추가하거나 -opt-in=kotlin.ExperimentalStdlibApi 컴파일러 옵션을 사용해야 합니다.

 

Kotlin 2.0.20에서는 HexFormat.number를 통해 minLength 속성을 추가하여 숫자 값을 16진수로 표현할 때 최소 자릿수를 지정하고, 필요 시 자릿수를 맞추기 위해 0을 패딩할 수 있습니다. 또한, removeLeadingZeros 속성을 사용하여 앞의 0을 제거할 수 있습니다:

 

fun main() {
    println(93.toHexString(HexFormat {
        number.minLength = 4
        number.removeLeadingZeros = true
    }))
    // "005d"
}

 

minLength 속성은 구문 분석에는 영향을 미치지 않지만, 이제 구문 분석 시 16진수 문자열이 타입의 폭보다 더 많은 자릿수를 가질 수 있으며, 그 추가 자릿수는 0으로 채워집니다.

 

Base64 디코더 동작 변경

 

Base64 클래스와 관련된 기능들은 실험적입니다. 사용하려면 @OptIn(ExperimentalEncodingApi::class) 주석을 추가하거나 -opt-in=kotlin.io.encoding.ExperimentalEncodingApi 컴파일러 옵션을 사용해야 합니다.

 

Kotlin 2.0.20에서는 Base64 디코더의 동작에 두 가지 주요 변경 사항이 도입되었습니다:

1. Base64 디코더는 이제 패딩을 요구합니다.

2. 패딩 구성을 위한 withPadding 함수가 추가되었습니다.

 

패딩 요구 사항

 

Base64 인코더는 기본적으로 패딩을 추가하며, 디코더는 패딩을 요구하고 0이 아닌 패딩 비트를 디코딩할 수 없습니다.

 

withPadding 함수

 

withPadding() 함수를 사용하여 Base64 인코딩 및 디코딩 시 패딩 동작을 제어할 수 있습니다:

 

val base64 = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT_OPTIONAL)

 

withPadding() 함수는 Base64 인스턴스를 생성할 때 다른 패딩 옵션을 선택할 수 있도록 해줍니다:

 

PRESENT: 패딩 추가, 디코딩 시 패딩 필수

ABSENT: 패딩 생략, 디코딩 시 패딩 불가

PRESENT_OPTIONAL: 패딩 추가, 디코딩 시 패딩 선택적

ABSENT_OPTIONAL: 패딩 생략, 디코딩 시 패딩 선택적

 

다음 예시 코드는 Base64 인코딩 및 디코딩 시 패딩 동작을 설정하는 방법을 보여줍니다:

 

import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi

@OptIn(ExperimentalEncodingApi::class)
fun main() {
    // 인코딩할 데이터
    val data = "fooba".toByteArray()

    // URL-safe 알파벳과 PRESENT 패딩 옵션으로 Base64 인스턴스 생성
    val base64Present = Base64.UrlSafe.withPadding(Base64.PaddingOption.PRESENT)
    val encodedDataPresent = base64Present.encode(data)
    println("Encoded data with PRESENT padding: $encodedDataPresent")
    // Encoded data with PRESENT padding: Zm9vYmE=

    // URL-safe 알파벳과 ABSENT 패딩 옵션으로 Base64 인스턴스 생성
    val base64Absent = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT)
    val encodedDataAbsent = base64Absent.encode(data)
    println("Encoded data with ABSENT padding: $encodedDataAbsent")
    // Encoded data with ABSENT padding: Zm9vYmE

    // 데이터를 다시 디코딩
    val decodedDataPresent = base64Present.decode(encodedDataPresent)
    println("Decoded data with PRESENT padding: ${String(decodedDataPresent)}")
    // Decoded data with PRESENT padding: fooba

    val decodedDataAbsent = base64Absent.decode(encodedDataAbsent)
    println("Decoded data with ABSENT padding: ${String(decodedDataAbsent)}")
    // Decoded data with ABSENT padding: fooba
}

 

문서 업데이트 사항

 

Kotlin 문서에 몇 가지 주요 변경 사항이 있었습니다:

 

표준 입력 페이지 개선: Java의 Scannerreadln()을 사용하여 입력을 읽는 방법을 배울 수 있습니다.

K2 컴파일러 마이그레이션 가이드 개선: 성능 개선 사항, Kotlin 라이브러리와의 호환성, 사용자 정의 컴파일러 플러그인 처리 방법에 대해 배울 수 있습니다.

예외 처리 페이지 개선: 예외에 대해 배우고, 예외를 던지거나 잡는 방법에 대해 설명합니다.

JUnit을 사용한 테스트 코드 개선 (JVM): JUnit을 사용하여 테스트를 만드는 방법을 배울 수 있는 튜토리얼이 제공됩니다.

Swift/Objective-C와의 상호 운용성 페이지 개선: Kotlin 선언을 Swift/Objective-C 코드에서 사용하거나, Objective-C 선언을 Kotlin 코드에서 사용하는 방법에 대해 설명합니다.

Swift 패키지 내보내기 설정 페이지 개선: Swift 패키지 관리자가 의존성으로 사용할 수 있는 Kotlin/Native 출력을 설정하는 방법에 대해 배울 수 있습니다.

 

Kotlin 2.0.20 설치

 

IntelliJ IDEA 2023.3과 Android Studio Iguana (2023.2.1) Canary 15부터 Kotlin 플러그인은 IDE에 번들로 포함되어 배포됩니다. 이는 더 이상 JetBrains Marketplace에서 플러그인을 설치할 수 없다는 의미입니다.

 

새 Kotlin 버전으로 업데이트하려면, 빌드 스크립트에서 Kotlin 버전을 2.0.20으로 변경하세요.

 

원문

 

https://kotlinlang.org/docs/whatsnew2020.html#install-kotlin-2-0-20

반응형

댓글