Kotlin 2.1.0의 새로운 기능(What's new in Kotlin 2.1.0)
Kotlin 2.1.0의 새로운 기능(What's new in Kotlin 2.1.0)
Kotlin 2.1.0 릴리스가 나왔어요! 주요 변경점들을 정리하면 다음과 같아요.
본문
출시일: 2024년 11월 27일
Kotlin 2.1.0 릴리스가 나왔어요! 주요 변경점들은 다음과 같아요.
- 미리 보기(preview)의 새로운 언어 기능: subject가 있는
when의 guard conditions, non-localbreak와continue, multi-dollar 문자열 보간(string interpolation). - K2 컴파일러 업데이트: 컴파일러 검사에 대한 더 많은 유연성과 kapt 구현 개선.
- Kotlin Multiplatform: Swift export에 대한 기본 지원 도입, 컴파일러 옵션용 Stable Gradle DSL 등.
- Kotlin/Native:
iosArm64지원 개선 및 기타 업데이트. - Kotlin/Wasm: 증분 컴파일 지원을 포함한 여러 업데이트.
- Gradle 지원: 더 새로운 Gradle 버전 및 Android Gradle 플러그인과의 호환성 개선, Kotlin Gradle 플러그인 API 업데이트.
- 문서화: Kotlin 문서의 상당한 개선.
Kotlin 릴리스 주기에 대한 자세한 내용은 Kotlin 릴리스 프로세스를 참고하세요.
IDE 지원
2.1.0을 지원하는 Kotlin 플러그인은 최신 IntelliJ IDEA와 Android Studio에 번들로 포함돼 있어요. IDE에서 Kotlin 플러그인을 업데이트할 필요는 없어요. 빌드 스크립트에서 Kotlin 버전을 2.1.0으로 변경하기만 하면 돼요.
자세한 내용은 새 Kotlin 버전으로 업데이트를 참고하세요.
언어
Kotlin 2.0.0과 K2 컴파일러 출시 이후, JetBrains 팀은 새로운 기능으로 언어를 개선하는 데 집중하고 있어요. 이번 릴리스에서는 몇 가지 새로운 언어 설계 개선 사항을 발표하게 되어 기쁘게 생각해요.
이 기능들은 미리 보기로 제공되며, 직접 사용해 보고 피드백을 공유해 주시길 권장해요.
- subject가 있는
when의 guard conditions - Non-local
break와continue - Multi-dollar 보간: 문자열 리터럴에서
$처리 개선
이 모든 기능은 K2 모드가 활성화된 최신 2024.3 버전의 IntelliJ IDEA에서 IDE 지원이 있어요.
IntelliJ IDEA 2024.3 블로그 게시물에서 자세히 알아보세요.
Kotlin 언어 설계 기능 및 제안 전체 목록 보기.
이 릴리스는 또한 다음 언어 업데이트를 가져와요.
- API 확장 시 옵트인 요구 지원
- 제네릭 타입을 가진 함수의 오버로드 해석 개선
- sealed class를 가진
when식의 완전성(exhaustiveness) 검사 개선
subject가 있는 when의 guard conditions
이 기능은 미리 보기(Preview)에 있으며 옵트인이 필요해요(자세한 내용은 아래 참조).
YouTrack에서 피드백을 남겨주시면 감사하겠어요.
2.1.0부터 subject가 있는 when 식이나 문에서 guard conditions을 사용할 수 있어요.
Guard conditions을 사용하면 when 식의 분기에 하나 이상의 조건을 포함할 수 있어서 복잡한 제어 흐름을 더 명시적이고 간결하게 만들고 코드 구조를 평평하게 만들어요.
분기에 guard condition을 포함하려면 기본 조건 뒤에 if로 구분해 배치하세요.
sealed interface Animal {
data class Cat(val mouseHunter: Boolean) : Animal {
fun feedCat() {}
}
data class Dog(val breed: String) : Animal {
fun feedDog() {}
}
}
fun feedAnimal(animal: Animal) {
when (animal) {
// Branch with only the primary condition. Calls `feedDog()` when `animal` is `Dog`
is Animal.Dog -> animal.feedDog()
// Branch with both primary and guard conditions. Calls `feedCat()` when `animal` is `Cat` and is not `mouseHunter`
is Animal.Cat if !animal.mouseHunter -> animal.feedCat()
// Prints "Unknown animal" if none of the above conditions match
else -> println("Unknown animal")
}
}
단일 when 식에서 guard condition이 있는 분기와 없는 분기를 결합할 수 있어요. guard condition이 있는 분기의 코드는 기본 조건과 guard condition이 모두 true인 경우에만 실행돼요. 기본 조건이 일치하지 않으면 guard condition은 평가되지 않아요. 또한 guard conditions은 else if를 지원해요.
프로젝트에서 guard conditions을 활성화하려면 명령줄에서 다음 컴파일러 옵션을 사용하세요.
kotlinc -Xwhen-guards main.kt
또는 Gradle 빌드 파일의 compilerOptions {} 블록에 추가하세요.
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xwhen-guards")
}
}
Non-local break와 continue
이 기능은 미리 보기(Preview)에 있으며 옵트인이 필요해요(자세한 내용은 아래 참조).
YouTrack에서 피드백을 남겨주시면 감사하겠어요.
Kotlin 2.1.0은 오랫동안 기다려온 또 하나의 기능이자 non-local break와 continue를 사용할 수 있는 능력의 미리 보기를 추가해요. 이 기능은 인라인 함수의 범위에서 사용할 수 있는 도구 세트를 확장하고 프로젝트의 상용구 코드를 줄여줘요.
이전에는 non-local return만 사용할 수 있었어요. 이제 Kotlin은 break와 continue 점프 표현식을 non-locally로도 지원해요. 즉, 루프를 둘러싸는 인라인 함수에 인자로 전달된 람다 안에서 이들을 적용할 수 있어요.
fun processList(elements: List<Int>): Boolean {
for (element in elements) {
val variable = element.nullableMethod() ?: run {
log.warning("Element is null or invalid, continuing...")
continue
}
if (variable == 0) return true // If variable is zero, return true
}
return false
}
프로젝트에서 이 기능을 시험해 보려면 명령줄에서 -Xnon-local-break-continue 컴파일러 옵션을 사용하세요.
kotlinc -Xnon-local-break-continue main.kt
또는 Gradle 빌드 파일의 compilerOptions {} 블록에 추가하세요.
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xnon-local-break-continue")
}
}
이 기능을 향후 Kotlin 릴리스에서 Stable로 만들 계획이에요. non-local break와 continue를 사용할 때 문제가 발생하면 이슈 트래커에 보고해 주세요.
Multi-dollar 문자열 보간
이 기능은 미리 보기(Preview)에 있으며 옵트인이 필요해요(자세한 내용은 아래 참조).
YouTrack에서 피드백을 남겨주시면 감사하겠어요.
Kotlin 2.1.0은 multi-dollar 문자열 보간을 지원하여 문자열 리터럴 안에서 달러 기호($)가 처리되는 방식을 개선해요. 이 기능은 템플릿 엔진, JSON 스키마 또는 다른 데이터 형식처럼 여러 달러 기호가 필요한 컨텍스트에서 유용해요.
Kotlin의 문자열 보간은 단일 달러 기호를 사용해요. 하지만 금융 데이터와 템플릿 시스템에서 흔한 문자열 안의 리터럴 달러 기호를 사용하려면 ${'$'} 같은 해결 방법이 필요했어요. multi-dollar 보간 기능을 활성화하면 몇 개의 달러 기호가 보간을 트리거할지 구성할 수 있고, 더 적은 수의 달러 기호는 문자열 리터럴로 취급돼요.
$를 사용해 플레이스홀더가 있는 JSON 스키마 멀티라인 문자열을 생성하는 방법의 예시는 다음과 같아요.
val KClass<*>.jsonSchema : String
get() = $$"""
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/product.schema.json",
"$dynamicAnchor": "meta"
"title": "$${simpleName ?: qualifiedName ?: "unknown"}",
"type": "object"
}
"""
이 예시에서 처음의 $$는 보간을 트리거하려면 두 개의 달러 기호($$)가 필요하다는 뜻이에요. 이렇게 하면 $schema, $id, $dynamicAnchor가 보간 마커로 해석되는 것을 방지해요.
이 접근 방식은 자리 표시자 구문에 달러 기호를 사용하는 시스템에서 작업할 때 특히 유용해요.
이 기능을 활성화하려면 명령줄에서 다음 컴파일러 옵션을 사용하세요.
kotlinc -Xmulti-dollar-interpolation main.kt
또는 Gradle 빌드 파일의 compilerOptions {} 블록을 업데이트하세요.
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.add("-Xmulti-dollar-interpolation")
}
}
코드에서 이미 단일 달러 기호를 사용하는 표준 문자열 보간을 사용한다면 변경할 필요가 없어요. 문자열에 리터럴 달러 기호가 필요할 때마다 $$를 사용하면 돼요.
API 확장 시 옵트인 요구 지원
Kotlin 2.1.0은 @SubclassOptInRequired 어노테이션을 도입해요. 이 어노테이션을 사용하면 라이브러리 작성자가 사용자가 실험적 인터페이스를 구현하거나 실험적 클래스를 확장하기 전에 명시적 옵트인을 요구할 수 있어요.
이 기능은 라이브러리 API가 사용하기에 충분히 안정적이지만 새 추상 함수로 진화할 수 있어 상속에는 불안정할 수 있는 경우에 유용해요.
API 요소에 옵트인 요구를 추가하려면 어노테이션 클래스에 대한 참조와 함께 @SubclassOptInRequired 어노테이션을 사용하세요.
@RequiresOptIn(
level = RequiresOptIn.Level.WARNING,
message = "Interfaces in this library are experimental"
)
annotation class UnstableApi()
@SubclassOptInRequired(UnstableApi::class)
interface CoreLibraryApi
이 예시에서 CoreLibraryApi 인터페이스는 사용자가 구현하기 전에 옵트인을 요구해요. 사용자는 다음과 같이 옵트인할 수 있어요.
@OptIn(UnstableApi::class)
interface MyImplementation: CoreLibraryApi
@SubclassOptInRequired 어노테이션을 사용해 옵트인을 요구할 때, 그 요구 사항은 내부 또는 중첩 클래스에는 전파되지 않아요.
API에서 @SubclassOptInRequired 어노테이션을 사용하는 실제 예시는 kotlinx.coroutines 라이브러리의 SharedFlow 인터페이스를 확인해 보세요.
제네릭 타입을 가진 함수의 오버로드 해석 개선
이전에는 함수에 대해 여러 오버로드가 있고, 일부는 제네릭 타입의 값 매개변수를 가지며 다른 일부는 같은 위치에 함수 타입을 가진 경우, 해석 동작이 때때로 일관되지 않을 수 있었어요.
이로 인해 오버로드가 멤버 함수인지 확장 함수인지에 따라 동작이 달라졌어요. 예를 들어:
class KeyValueStore<K, V> {
fun store(key: K, value: V) {} // 1
fun store(key: K, lazyValue: () -> V) {} // 2
}
fun <K, V> KeyValueStore<K, V>.storeExtension(key: K, value: V) {} // 1
fun <K, V> KeyValueStore<K, V>.storeExtension(key: K, lazyValue: () -> V) {} // 2
fun test(kvs: KeyValueStore<String, Int>) {
// Member functions
kvs.store("", 1) // Resolves to 1
kvs.store("") { 1 } // Resolves to 2
// Extension functions
kvs.storeExtension("", 1) // Resolves to 1
kvs.storeExtension("") { 1 } // Doesn't resolve
}
이 예시에서 KeyValueStore 클래스에는 store() 함수에 대한 두 개의 오버로드가 있는데, 하나는 제네릭 타입 K와 V의 함수 매개변수를 가지며 다른 하나는 제네릭 타입 V를 반환하는 람다 함수를 가져요. 마찬가지로 확장 함수 storeExtension()에 대한 오버로드도 두 개 있어요.
store() 함수가 람다 함수와 함께 그리고 없이 호출되었을 때 컴파일러는 올바른 오버로드를 성공적으로 해석했어요. 그러나 확장 함수 storeExtension()이 람다 함수와 함께 호출되었을 때, 컴파일러는 두 오버로드를 모두 적용 가능하다고 잘못 간주하여 올바른 오버로드를 해석하지 못했어요.
이 문제를 해결하기 위해 새 휴리스틱을 도입해서, 제네릭 타입을 가진 함수 매개변수가 다른 인자의 정보를 기반으로 람다 함수를 받아들일 수 없을 때 컴파일러가 가능한 오버로드를 버릴 수 있게 했어요. 이 변경은 멤버 함수와 확장 함수의 동작을 일관되게 만들며 Kotlin 2.1.0에서 기본으로 활성화돼요.
sealed class를 가진 when 식의 완전성 검사 개선
이전 Kotlin 버전에서 컴파일러는 sealed 상한(upper bound)을 가진 타입 매개변수에 대한 when 식에서 sealed class 계층의 모든 경우를 다뤘음에도 else 분기를 요구했어요. 이 동작은 Kotlin 2.1.0에서 해결되고 개선되어 완전성 검사가 더 강력해지고 불필요한 else 분기를 제거할 수 있어 when 식을 더 깔끔하고 직관적으로 유지할 수 있어요.
변경을 보여주는 예시는 다음과 같아요.
sealed class Result
object Error: Result()
class Success(val value: String): Result()
fun <T : Result> render(result: T) = when (result) {
Error -> "Error!"
is Success -> result.value
// Requires no else branch
}
Kotlin K2 컴파일러
Kotlin 2.1.0에서 K2 컴파일러는 컴파일러 검사와 경고 작업 시 더 많은 유연성을 제공하며 kapt 플러그인 지원도 개선됐어요.
추가 컴파일러 검사
Kotlin 2.1.0에서 K2 컴파일러에서 추가 검사를 활성화할 수 있어요. 이는 보통 컴파일에 중요하지 않지만 다음 경우를 검증하고 싶을 때 유용한 추가 선언, 식 및 타입 검사예요.
| 검사 타입 | 설명 |
|---|---|
REDUNDANT_NULLABLE |
Boolean? 대신 Boolean??를 사용했어요 |
PLATFORM_CLASS_MAPPED_TO_KOTLIN |
kotlin.String 대신 java.lang.String을 사용했어요 |
ARRAY_EQUALITY_OPERATOR_CAN_BE_REPLACED_WITH_EQUALS |
arrayOf("").contentEquals(arrayOf("")) 대신 arrayOf("") == arrayOf("")를 사용했어요 |
REDUNDANT_CALL_OF_CONVERSION_METHOD |
42 대신 42.toInt()를 사용했어요 |
USELESS_CALL_ON_NOT_NULL |
"" 대신 "".orEmpty()를 사용했어요 |
REDUNDANT_SINGLE_EXPRESSION_STRING_TEMPLATE |
string 대신 "$string"을 사용했어요 |
UNUSED_ANONYMOUS_PARAMETER |
람다 식에 매개변수가 전달되었지만 사용되지 않았어요 |
REDUNDANT_VISIBILITY_MODIFIER |
class Klass 대신 public class Klass를 사용했어요 |
REDUNDANT_MODALITY_MODIFIER |
class Klass 대신 final class Klass를 사용했어요 |
REDUNDANT_SETTER_PARAMETER_TYPE |
set(value) 대신 set(value: Int)을 사용했어요 |
CAN_BE_VAL |
var local = 0이 정의되었지만 재할당되지 않아 val local = 42가 될 수 있어요 |
ASSIGNED_VALUE_IS_NEVER_READ |
val local = 42가 정의되었지만 이후 코드에서 사용되지 않았어요 |
UNUSED_VARIABLE |
val local = 0이 정의되었지만 코드에서 사용되지 않았어요 |
REDUNDANT_RETURN_UNIT_TYPE |
fun foo() {} 대신 fun foo(): Unit {}을 사용했어요 |
UNREACHABLE_CODE |
코드 문이 존재하지만 실행될 수 없어요 |
검사가 참이면 문제 수정에 대한 제안과 함께 컴파일러 경고를 받게 돼요.
추가 검사는 기본적으로 비활성화돼 있어요. 활성화하려면 명령줄에서 -Wextra 컴파일러 옵션을 사용하거나 Gradle 빌드 파일의 compilerOptions {} 블록에서 extraWarnings를 지정하세요.
// build.gradle.kts
kotlin {
compilerOptions {
extraWarnings.set(true)
}
}
컴파일러 옵션을 정의하고 사용하는 방법에 대한 자세한 내용은 Kotlin Gradle 플러그인의 컴파일러 옵션을 참고하세요.
전역 경고 억제
2.1.0에서 Kotlin 컴파일러는 많은 요청을 받았던 기능인 전역적으로 경고를 억제하는 기능을 받았어요.
이제 명령줄에서 -Xsuppress-warning=WARNING_NAME 구문이나 빌드 파일의 compilerOptions {} 블록에서 freeCompilerArgs 속성을 사용해 전체 프로젝트에서 특정 경고를 억제할 수 있어요.
예를 들어 프로젝트에서 추가 컴파일러 검사를 활성화했지만 그 중 하나를 억제하고 싶다면 다음을 사용하세요.
// build.gradle.kts
kotlin {
compilerOptions {
extraWarnings.set(true)
freeCompilerArgs.add("-Xsuppress-warning=CAN_BE_VAL")
}
}
경고를 억제하고 싶지만 이름을 모른다면 요소를 선택하고 전구 아이콘을 클릭하세요(또는 Cmd + Enter/Alt + Enter 사용).
새 컴파일러 옵션은 현재 실험적(Experimental)이에요. 다음 세부 사항도 주목할 가치가 있어요.
- 오류 억제는 허용되지 않아요.
- 알 수 없는 경고 이름을 전달하면 컴파일이 오류로 끝나요.
- 한 번에 여러 경고를 지정할 수 있어요.
kotlinc -Xsuppress-warning=NOTHING_TO_INLINE -Xsuppress-warning=NO_TAIL_CALLS_FOUND main.kt
// build.gradle.kts
kotlin {
compilerOptions {
freeCompilerArgs.addAll(
listOf(
"-Xsuppress-warning=NOTHING_TO_INLINE",
"-Xsuppress-warning=NO_TAIL_CALLS_FOUND"
)
)
}
}
K2 kapt 구현 개선
K2 컴파일러용 kapt 플러그인(K2 kapt)은 Alpha 단계예요. 언제든 변경될 수 있어요.
YouTrack에서 피드백을 남겨주시면 감사하겠어요.
현재 kapt 플러그인을 사용하는 프로젝트는 기본적으로 K1 컴파일러로 동작하며 최대 1.9까지의 Kotlin 버전을 지원해요.
Kotlin 1.9.20에서 K2 컴파일러와 함께 kapt 플러그인의 실험적 구현(K2 kapt)을 시작했어요. 이제 기술적 및 성능 문제를 완화하기 위해 K2 kapt의 내부 구현을 개선했어요.
새 K2 kapt 구현은 새 기능을 도입하지 않지만, 이전 K2 kapt 구현에 비해 성능이 크게 개선됐어요. 또한 K2 kapt 플러그인의 동작은 이제 K1 kapt의 동작에 훨씬 더 가까워졌어요.
새 K2 kapt 플러그인 구현을 사용하려면 이전 K2 kapt 플러그인과 마찬가지로 활성화하세요. 프로젝트의 gradle.properties 파일에 다음 옵션을 추가하세요.
kapt.use.k2=true
곧 출시될 릴리스에서는 K1 kapt 대신 K2 kapt 구현이 기본으로 활성화될 예정이므로 더 이상 수동으로 활성화할 필요가 없어요.
새 구현이 안정화되기 전에 피드백을 크게 환영해요.
unsigned 타입과 비기본 타입 간 오버로드 충돌 해석
이 릴리스는 다음 예시처럼 함수가 unsigned 타입과 비기본 타입에 대해 오버로드될 때 이전 버전에서 발생할 수 있었던 오버로드 충돌의 해석 문제를 해결해요.
오버로드된 확장 함수
fun Any.doStuff() = "Any"
fun UByte.doStuff() = "UByte"
fun main() {
val uByte: UByte = UByte.MIN_VALUE
uByte.doStuff() // Overload resolution ambiguity before Kotlin 2.1.0
}
이전 버전에서 uByte.doStuff()를 호출하면 Any와 UByte 확장이 모두 적용 가능했기 때문에 모호함이 발생했어요.
오버로드된 최상위 함수
fun doStuff(value: Any) = "Any"
fun doStuff(value: UByte) = "UByte"
fun main() {
val uByte: UByte = UByte.MIN_VALUE
doStuff(uByte) // Overload resolution ambiguity before Kotlin 2.1.0
}
마찬가지로 doStuff(uByte) 호출은 컴파일러가 Any 버전을 사용할지 UByte 버전을 사용할지 결정할 수 없어서 모호했어요. 2.1.0에서 컴파일러는 이제 더 구체적인 타입(이 경우 UByte)에 우선권을 부여해 모호함을 해결하여 이러한 경우를 올바르게 처리해요.
Kotlin/JVM
버전 2.1.0부터 컴파일러는 Java 23 바이트코드를 포함하는 클래스를 생성할 수 있어요.
JSpecify nullability 불일치 진단 심각도가 strict로 변경
Kotlin 2.1.0은 org.jspecify.annotations의 nullability 어노테이션에 대한 엄격한 처리를 적용하여 Java 상호 운용성의 타입 안전성을 개선해요.
영향을 받는 nullability 어노테이션은 다음과 같아요.
org.jspecify.annotations.Nullableorg.jspecify.annotations.NonNullorg.jspecify.annotations.NullMarkedorg.jspecify.nullness의 레거시 어노테이션(JSpecify 0.2 및 이전 버전)
Kotlin 2.1.0부터 nullability 불일치는 기본적으로 경고에서 오류로 승격돼요. 이로 인해 @NonNull과 @Nullable 같은 어노테이션이 타입 검사 중에 강제되어 런타임에서 예기치 않은 nullability 문제가 발생하는 것을 방지해요.
@NullMarked 어노테이션은 또한 그 범위 안의 모든 멤버의 nullability에 영향을 주어 어노테이션이 있는 Java 코드로 작업할 때 동작을 더 예측 가능하게 만들어요.
새 기본 동작을 보여주는 예시는 다음과 같아요.
// Java
import org.jspecify.annotations.*;
public class SomeJavaClass {
@NonNull
public String foo() { //...
}
@Nullable
public String bar() { //...
}
}
// Kotlin
fun test(sjc: SomeJavaClass) {
// Accesses a non-null result, which is allowed
sjc.foo().length
// Raises an error in the default strict mode because the result is nullable
// To avoid the error, use ?.length instead
sjc.bar().length
}
이 어노테이션의 진단 심각도를 수동으로 제어할 수 있어요. 그러려면 -Xnullability-annotations 컴파일러 옵션을 사용해 모드를 선택하세요.
ignore: nullability 불일치를 무시해요.warning: nullability 불일치에 대한 경고를 보고해요.strict: nullability 불일치에 대한 오류를 보고해요(기본 모드).
자세한 내용은 Nullability 어노테이션을 참고하세요.
Kotlin Multiplatform
Kotlin 2.1.0은 Swift export에 대한 기본 지원을 도입하고 Kotlin Multiplatform 라이브러리 게시를 더 쉽게 만들어요. 또한 컴파일러 옵션 구성용 새 DSL을 안정화하고 Isolated Projects 기능의 미리 보기를 가져오는 Gradle 개선에도 집중해요.
multiplatform 프로젝트용 컴파일러 옵션의 새 Gradle DSL이 Stable로 승격
Kotlin 2.0.0에서 multiplatform 프로젝트 전반에서 컴파일러 옵션 구성을 단순화하기 위한 새 실험적 Gradle DSL을 도입했어요. Kotlin 2.1.0에서 이 DSL이 Stable로 승격됐어요.
전체 프로젝트 구성은 이제 세 개의 계층으로 구성돼요. 가장 높은 곳은 확장 수준이고, 그다음 타깃 수준, 가장 낮은 곳은 컴파일 단위(보통 컴파일 태스크)예요.
다른 수준과 그 사이에서 컴파일러 옵션을 구성하는 방법에 대해 자세히 알아보려면 컴파일러 옵션을 참고하세요.
Kotlin Multiplatform에서 Gradle의 Isolated Projects 미리 보기
이 기능은 실험적(Experimental)이며 현재 Gradle에서 pre-Alpha 상태예요. Gradle 8.10 버전에서만, 그리고 평가 목적으로만 사용하세요. 이 기능은 언제든 제거되거나 변경될 수 있어요.
YouTrack에서 피드백을 남겨주시면 감사하겠어요. 옵트인이 필요해요(자세한 내용은 아래 참조).
Kotlin 2.1.0에서 multiplatform 프로젝트에서 Gradle의 Isolated Projects 기능을 미리 볼 수 있어요.
Gradle의 Isolated Projects 기능은 개별 Gradle 프로젝트의 구성을 서로 "격리"하여 빌드 성능을 개선해요. 각 프로젝트의 빌드 로직은 다른 프로젝트의 변경 가능한 상태에 직접 접근하는 것이 제한되어 안전하게 병렬로 실행할 수 있어요. 이 기능을 지원하기 위해 Kotlin Gradle 플러그인의 모델에 몇 가지 변경을 했으며, 이 미리 보기 단계에서 여러분의 경험에 대해 듣고 싶어요.
Kotlin Gradle 플러그인의 새 모델을 활성화하는 방법은 두 가지가 있어요.
- 옵션 1: Isolated Projects를 활성화하지 않고 호환성 테스트 - Isolated Projects 기능을 활성화하지 않고 Kotlin Gradle 플러그인의 새 모델과의 호환성을 확인하려면 프로젝트의
gradle.properties파일에 다음 Gradle 속성을 추가하세요.
# gradle.properties
kotlin.kmp.isolated-projects.support=enable
- 옵션 2: Isolated Projects를 활성화한 테스트 - Gradle에서 Isolated Projects 기능을 활성화하면 Kotlin Gradle 플러그인이 새 모델을 사용하도록 자동으로 구성돼요. Isolated Projects 기능을 활성화하려면 시스템 속성을 설정하세요. 이 경우 프로젝트에 Kotlin Gradle 플러그인용 Gradle 속성을 추가할 필요가 없어요.
Swift export에 대한 기본 지원
이 기능은 현재 개발 초기 단계예요. 언제든 제거되거나 변경될 수 있어요. 옵트인이 필요하며(자세한 내용은 아래 참조) 평가 목적으로만 사용해야 해요. YouTrack에서 피드백을 남겨주시면 감사하겠어요.
2.1.0 버전은 Objective-C 헤더를 사용하지 않고 Kotlin 소스를 Swift 인터페이스로 직접 내보낼 수 있게 해주는 Kotlin의 Swift export 지원 제공을 향한 첫 단계예요. 이는 Apple 타깃의 multiplatform 개발을 더 쉽게 만들어야 해요.
현재 기본 지원에는 다음 기능이 포함돼요.
- 여러 Gradle 모듈을 Kotlin에서 Swift로 직접 내보내기.
moduleName프로퍼티로 사용자 정의 Swift 모듈 이름 정의.flattenPackage프로퍼티로 패키지 구조의 축소(collapse) 규칙 설정.
다음 빌드 파일을 프로젝트에서 Swift export 설정의 시작점으로 사용할 수 있어요.
// build.gradle.kts
kotlin {
iosX64()
iosArm64()
iosSimulatorArm64()
@OptIn(ExperimentalSwiftExportDsl::class)
swiftExport {
// Root module name
moduleName = "Shared"
// Collapse rule
// Removes package prefix from generated Swift code
flattenPackage = "com.example.sandbox"
// Export external modules
export(project(":subproject")) {
// Exported module name
moduleName = "Subproject"
// Collapse exported dependency rule
flattenPackage = "com.subproject.library"
}
}
}
Swift export가 이미 설정된 공개 샘플을 복제할 수도 있어요.
컴파일러는 필요한 모든 파일(swiftmodule 파일, 정적 a 라이브러리, 헤더 및 modulemap 파일 포함)을 자동으로 생성하고 이를 앱의 빌드 디렉터리에 복사하는데, Xcode에서 접근할 수 있어요.
Swift export 활성화 방법
이 기능은 현재 개발 초기 단계에만 있다는 점을 명심하세요.
Swift export는 현재 iOS 프레임워크를 Xcode 프로젝트에 연결하기 위해 직접 통합을 사용하는 프로젝트에서 동작해요. 이는 Android Studio나 웹 마법사를 통해 만든 Kotlin Multiplatform 프로젝트의 표준 구성이에요.
프로젝트에서 Swift export를 시험해 보려면:
gradle.properties파일에 다음 Gradle 옵션을 추가하세요.
# gradle.properties
kotlin.experimental.swift-export.enabled=true
- Xcode에서 프로젝트 설정을 여세요.
- Build Phases 탭에서
embedAndSignAppleFrameworkForXcode태스크가 있는 Run Script 단계를 찾으세요. - run script 단계에서 스크립트를
embedSwiftExportForXcode태스크를 사용하도록 조정하세요.
./gradlew :<Shared module name>:embedSwiftExportForXcode
Swift export에 대한 피드백 남기기
향후 Kotlin 릴리스에서 Swift export 지원을 확장하고 안정화할 계획이에요. 이 YouTrack 이슈에 피드백을 남겨주세요.
모든 호스트에서 Kotlin 라이브러리 게시 기능
이 기능은 현재 실험적(Experimental)이에요. 옵트인이 필요하며(자세한 내용은 아래 참조) 평가 목적으로만 사용해야 해요. YouTrack에서 피드백을 남겨주시면 감사하겠어요.
Kotlin 컴파일러는 Kotlin 라이브러리 게시를 위한 .klib 아티팩트를 생성해요. 이전에는 Apple 플랫폼 타깃이 Mac 머신을 요구한다는 점을 제외하면 어떤 호스트에서든 필요한 아티팩트를 얻을 수 있었어요. 이는 iOS, macOS, tvOS, watchOS 타깃을 대상으로 하는 Kotlin Multiplatform 프로젝트에 특별한 제약을 두었어요.
Kotlin 2.1.0은 이 제한을 해제하고 교차 컴파일(cross-compilation) 지원을 추가해요. 이제 어떤 지원되는 호스트에서든 .klib 아티팩트를 생성할 수 있어서 Kotlin 및 Kotlin Multiplatform 라이브러리의 게시 과정을 크게 단순화해야 해요.
모든 호스트에서 라이브러리 게시 활성화 방법
프로젝트에서 교차 컴파일을 시험해 보려면 gradle.properties 파일에 다음 이진 옵션을 추가하세요.
# gradle.properties
kotlin.native.enableKlibsCrossCompilation=true
이 기능은 현재 실험적이며 몇 가지 제한 사항이 있어요. 다음 경우에는 여전히 Mac 머신을 사용해야 해요.
- 라이브러리에 cinterop 의존성이 있는 경우.
- 프로젝트에 CocoaPods 통합이 설정된 경우.
- Apple 타깃의 최종 바이너리를 빌드하거나 테스트해야 하는 경우.
모든 호스트에서 라이브러리 게시에 대한 피드백 남기기
향후 Kotlin 릴리스에서 이 기능을 안정화하고 라이브러리 게시를 더욱 개선할 계획이에요. 이슈 트래커 YouTrack에 피드백을 남겨주세요.
자세한 내용은 multiplatform 라이브러리 게시를 참고하세요.
비패킹 klib 지원
Kotlin 2.1.0은 비패킹(non-packed) .klib 파일 아티팩트를 생성할 수 있게 해줘요. 이는 klib를 먼저 압축 해제하지 않고 직접 의존성으로 구성할 수 있는 옵션을 제공해요.
이 변경은 또한 Kotlin/Wasm, Kotlin/JS, Kotlin/Native 프로젝트의 컴파일 및 링크 시간을 줄여 성능을 개선할 수 있어요.
예를 들어 벤치마크에 따르면 1개의 링크와 10개의 컴파일 태스크가 있는 프로젝트(9개의 단순화된 프로젝트에 의존하는 단일 네이티브 실행 바이너리를 빌드하는 프로젝트)에서 총 빌드 시간이 약 3% 개선됐어요. 그러나 빌드 시간에 대한 실제 영향은 하위 프로젝트 수와 각각의 크기에 따라 달라져요.
프로젝트 설정 방법
기본적으로 Kotlin 컴파일 및 링크 태스크는 이제 새 비패킹 아티팩트를 사용하도록 구성돼요.
klib 해석을 위한 사용자 정의 빌드 로직을 설정했고 새 패킹 해제된 아티팩트를 사용하려면 Gradle 빌드 파일에서 klib 패키지 해석의 선호 변형을 명시적으로 지정해야 해요.
// build.gradle.kts
import org.jetbrains.kotlin.gradle.plugin.attributes.KlibPackaging
// ...
val resolvableConfiguration = configurations.resolvable("resolvable") {
// For the new non-packed configuration:
attributes.attribute(KlibPackaging.ATTRIBUTE, project.objects.named(KlibPackaging.NON_PACKED))
// For the previous packed configuration:
attributes.attribute(KlibPackaging.ATTRIBUTE, project.objects.named(KlibPackaging.PACKED))
}
비패킹 .klib 파일은 이전에 패킹된 파일이 있었던 프로젝트 빌드 디렉터리의 같은 경로에 생성돼요. 반면 패킹된 klib는 이제 build/libs 디렉터리에 위치해요.
속성이 지정되지 않으면 패킹된 변형이 사용돼요. 다음 콘솔 명령으로 사용 가능한 속성과 변형 목록을 확인할 수 있어요.
./gradlew outgoingVariants
YouTrack에서 이 기능에 대한 피드백을 남겨주시면 감사하겠어요.
이전 android 타깃의 추가 사용 중단
Kotlin 2.1.0에서 이전 android 타깃 이름에 대한 사용 중단 경고가 오류로 승격됐어요.
현재 Android를 타깃으로 하는 Kotlin Multiplatform 프로젝트에서는 androidTarget 옵션을 사용하는 것을 권장해요. 이는 Google의 곧 출시될 Android/KMP 플러그인을 위해 android 이름을 비우는 데 필요한 임시 해결책이에요.
새 플러그인을 사용할 수 있게 되면 추가 마이그레이션 지침을 제공할 거예요. Google의 새 DSL이 Kotlin Multiplatform의 Android 타깃 지원을 위한 선호 옵션이 될 거예요.
자세한 내용은 Kotlin Multiplatform 호환성 가이드를 참고하세요.
같은 타입의 여러 타깃 선언 지원 제거
Kotlin 2.1.0 이전에는 multiplatform 프로젝트에서 같은 타입의 여러 타깃을 선언할 수 있었어요. 하지만 이로 인해 타깃을 구분하고 공유 소스 세트를 효과적으로 지원하기가 어려워졌어요. 대부분의 경우 별도의 Gradle 프로젝트를 사용하는 것 같은 더 간단한 설정이 더 잘 작동해요. 자세한 지침과 마이그레이션 예시는 Kotlin Multiplatform 호환성 가이드의 여러 유사한 타깃 선언을 참고하세요.
Kotlin 1.9.20은 multiplatform 프로젝트에서 같은 타입의 여러 타깃을 선언하면 사용 중단 경고를 발생시켰어요. Kotlin 2.1.0에서 이 사용 중단 경고는 Kotlin/JS 타깃을 제외한 모든 타깃에 대해 이제 오류가 됐어요. Kotlin/JS 타깃이 예외인 이유를 알아보려면 YouTrack의 이 이슈를 참고하세요.
Kotlin/Native
Kotlin 2.1.0은 iosArm64 타깃 지원 업그레이드, cinterop 캐싱 프로세스 개선 및 기타 업데이트를 포함해요.
iosArm64가 Tier 1로 승격
Kotlin Multiplatform 개발에 중요한 iosArm64 타깃이 Tier 1로 승격됐어요. 이는 Kotlin/Native 컴파일러에서 가장 높은 지원 수준이에요.
이는 타깃이 컴파일 및 실행 가능한지 확인하기 위해 CI 파이프라인에서 정기적으로 테스트된다는 뜻이에요. 또한 컴파일러 릴리스 간에 소스 및 바이너리 호환성을 제공해요.
타깃 티어에 대한 자세한 내용은 Kotlin/Native 타깃 지원을 참고하세요.
LLVM 업데이트: 11.1.0에서 16.0.0으로
Kotlin 2.1.0에서 LLVM을 11.1.0에서 16.0.0으로 업데이트했어요. 새 버전은 버그 수정과 보안 업데이트를 포함해요. 특정 경우에는 컴파일러 최적화와 더 빠른 컴파일도 제공해요.
프로젝트에 Linux 타깃이 있다면 Kotlin/Native 컴파일러가 이제 모든 Linux 타깃에 기본적으로 lld 링커를 사용한다는 점을 주의하세요.
이 업데이트는 코드에 영향을 주지 않아야 하지만 문제가 발생하면 이슈 트래커에 보고해 주세요.
cinterop의 캐싱 변경
Kotlin 2.1.0에서 cinterop 캐싱 프로세스를 변경하고 있어요. 더 이상 CacheableTask 어노테이션 타입이 없어요. 새 권장 접근 방식은 cacheIf 출력 타입을 사용해 태스크 결과를 캐시하는 거예요.
이로 인해 UP-TO-DATE 검사가 정의 파일에 지정된 헤더 파일의 변경을 감지하지 못해 빌드 시스템이 코드를 다시 컴파일하지 못하는 문제가 해결돼야 해요.
mimalloc 메모리 할당자의 사용 중단
Kotlin 1.9.0에서 새 메모리 할당자를 도입하고, 그다음 Kotlin 1.9.20에서 기본으로 활성화했어요. 새 할당자는 가비지 컬렉션을 더 효율적으로 만들고 Kotlin/Native 메모리 매니저의 런타임 성능을 개선하도록 설계됐어요.
새 메모리 할당자는 이전 기본 할당자인 mimalloc을 대체했어요. 이제 Kotlin/Native 컴파일러에서 mimalloc을 사용 중단할 때가 됐어요.
이제 빌드 스크립트에서 -Xallocator=mimalloc 컴파일러 옵션을 제거할 수 있어요. 문제가 발생하면 이슈 트래커에 보고해 주세요.
Kotlin의 메모리 할당자와 가비지 컬렉션에 대한 자세한 내용은 Kotlin/Native 메모리 관리를 참고하세요.
Kotlin/Wasm
Kotlin/Wasm은 증분 컴파일 지원과 함께 여러 업데이트를 받았어요.
증분 컴파일 지원
이전에는 Kotlin 코드에서 무언가를 변경하면 Kotlin/Wasm 툴체인이 전체 코드베이스를 다시 컴파일해야 했어요.
2.1.0부터 Wasm 타깃에 대한 증분 컴파일이 지원돼요. 개발 태스크에서 컴파일러는 이제 마지막 컴파일 이후 변경과 관련된 파일만 다시 컴파일해서 컴파일 시간을 눈에 띄게 줄여줘요.
이 변경은 현재 컴파일 속도를 두 배로 높이며 향후 릴리스에서 더 개선할 계획이에요.
현재 설정에서 Wasm 타깃에 대한 증분 컴파일은 기본적으로 비활성화돼 있어요. 증분 컴파일을 활성화하려면 프로젝트의 local.properties 또는 gradle.properties 파일에 다음 줄을 추가하세요.
# gradle.properties
kotlin.incremental.wasm=true
Kotlin/Wasm 증분 컴파일을 사용해 보고 피드백을 공유하세요. 여러분의 통찰이 이 기능을 더 빨리 Stable로 만들고 기본으로 활성화하는 데 도움이 될 거예요.
Browser API가 kotlinx-browser 독립 라이브러리로 이동
이전에는 웹 API 및 관련 타깃 유틸리티의 선언이 Kotlin/Wasm 표준 라이브러리의 일부였어요.
이 릴리스에서 org.w3c.* 선언은 Kotlin/Wasm 표준 라이브러리에서 새 kotlinx-browser 라이브러리로 이동했어요. 이 라이브러리에는 org.khronos.webgl, kotlin.dom, kotlinx.browser 같은 다른 웹 관련 패키지도 포함돼 있어요.
이 분리는 모듈성을 제공해 Kotlin의 릴리스 주기 밖에서 웹 관련 API의 독립적 업데이트를 가능하게 해요. 또한 Kotlin/Wasm 표준 라이브러리는 이제 어떤 JavaScript 환경에서도 사용할 수 있는 선언만 포함해요.
이동된 패키지의 선언을 사용하려면 프로젝트의 빌드 구성 파일에 kotlinx-browser 의존성을 추가해야 해요.
// build.gradle.kts
val wasmJsMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-browser:0.3")
}
}
Kotlin/Wasm 디버깅 경험 개선
이전에는 웹 브라우저에서 Kotlin/Wasm 코드를 디버깅할 때 디버깅 인터페이스에서 변수 값의 낮은 수준 표현이 표시될 수 있었어요. 이로 인해 애플리케이션의 현재 상태를 추적하기 어려운 경우가 많았어요.
이 경험을 개선하기 위해 변수 보기에 사용자 정의 포맷터(custom formatters)가 추가됐어요. 이 구현은 Firefox와 Chromium 기반 브라우저 같은 주요 브라우저에서 지원되는 custom formatters API를 사용해요.
이 변경으로 이제 변수 값을 더 사용자 친화적이고 이해하기 쉬운 방식으로 표시하고 찾을 수 있어요.
새 디버깅 경험을 시험해 보려면:
wasmJs {}컴파일러 옵션에 다음 컴파일러 옵션을 추가하세요.
// build.gradle.kts
kotlin {
wasmJs {
// ...
compilerOptions {
freeCompilerArgs.add("-Xwasm-debugger-custom-formatters")
}
}
}
- 브라우저에서 사용자 정의 포맷터를 활성화하세요.
- Chrome DevTools에서는 Settings | Preferences | Console에서 사용할 수 있어요.
- Firefox DevTools에서는 Settings | Advanced settings에서 사용할 수 있어요.
Kotlin/Wasm 바이너리 크기 축소
Production 빌드로 생성된 Wasm 바이너리의 크기가 최대 30% 줄어들며 일부 성능 개선도 볼 수 있어요. 이는 --closed-world, --type-ssa, --type-merging Binaryen 옵션이 이제 모든 Kotlin/Wasm 프로젝트에 안전한 것으로 간주되어 기본으로 활성화되기 때문이에요.
Kotlin/Wasm의 JavaScript 배열 상호 운용성 개선
Kotlin/Wasm의 표준 라이브러리는 JavaScript 배열을 위한 JsArray<T> 타입을 제공하지만 JsArray<T>를 Kotlin의 네이티브 Array 또는 List 타입으로 변환하는 직접적인 방법은 없었어요.
이 공백은 배열 변환을 위한 사용자 정의 함수를 만들어야 해서 Kotlin과 JavaScript 코드 사이의 상호 운용성을 복잡하게 만들었어요.
이 릴리스는 JsArray<T>를 Array<T>로 자동 변환하고 그 반대도 수행하는 어댑터 함수를 도입해 배열 작업을 단순화해요.
제네릭 타입 간 변환 예시가 있어요: Kotlin List<T>와 Array<T>를 JavaScript JsArray<T>로.
val list: List<JsString> =
listOf("Kotlin", "Wasm").map { it.toJsString() }
// Uses .toJsArray() to convert List or Array to JsArray
val jsArray: JsArray<JsString> = list.toJsArray()
// Uses .toArray() and .toList() to convert it back to Kotlin types
val kotlinArray: Array<JsString> = jsArray.toArray()
val kotlinList: List<JsString> = jsArray.toList()
타입 배열을 Kotlin 대응물로 변환하는 유사한 메서드도 사용할 수 있어요(예: IntArray와 Int32Array). 자세한 정보와 구현은 kotlinx-browser 저장소를 참고하세요.
타입 배열 간 변환 예시가 있어요: Kotlin IntArray를 JavaScript Int32Array로.
import org.khronos.webgl.*
// ...
val intArray: IntArray = intArrayOf(1, 2, 3)
// Uses .toInt32Array() to convert Kotlin IntArray to JavaScript Int32Array
val jsInt32Array: Int32Array = intArray.toInt32Array()
// Uses toIntArray() to convert JavaScript Int32Array back to Kotlin IntArray
val kotlinIntArray: IntArray = jsInt32Array.toIntArray()
Kotlin/Wasm에서 JavaScript 예외 세부 정보 접근 지원
이전에는 Kotlin/Wasm에서 JavaScript 예외가 발생하면 JsException 타입이 원본 JavaScript 오류의 세부 정보 없이 일반 메시지만 제공했어요.
Kotlin 2.1.0부터 특정 컴파일러 옵션을 활성화하여 JsException이 원본 오류 메시지와 스택 추적을 포함하도록 구성할 수 있어요. 이는 JavaScript에서 발생한 문제를 진단하는 데 더 많은 컨텍스트를 제공해요.
이 동작은 특정 브라우저에서만 사용할 수 있는 WebAssembly.JSTag API에 의존해요.
- Chrome: 115 버전부터 지원
- Firefox: 129 버전부터 지원
- Safari: 아직 지원되지 않음
기본적으로 비활성화된 이 기능을 활성화하려면 build.gradle.kts 파일에 다음 컴파일러 옵션을 추가하세요.
// build.gradle.kts
kotlin {
wasmJs {
compilerOptions {
freeCompilerArgs.add("-Xwasm-attach-js-exception")
}
}
}
새 동작을 보여주는 예시가 있어요.
external object JSON {
fun <T: JsAny> parse(json: String): T
}
fun main() {
try {
JSON.parse("an invalid JSON")
} catch (e: JsException) {
println("Thrown value is: ${e.thrownValue}")
// SyntaxError: Unexpected token 'a', "an invalid JSON" is not valid JSON
println("Message: ${e.message}")
// Message: Unexpected token 'a', "an invalid JSON" is not valid JSON
println("Stacktrace:")
// Stacktrace:
// Prints the full JavaScript stack trace
e.printStackTrace()
}
}
-Xwasm-attach-js-exception 옵션을 활성화하면 JsException이 JavaScript 오류의 특정 세부 정보를 제공해요. 이 옵션이 없으면 JsException은 JavaScript 코드를 실행하는 동안 예외가 발생했다는 일반 메시지만 포함해요.
기본 export의 사용 중단
named export로의 마이그레이션의 일부로, 이전에는 JavaScript에서 Kotlin/Wasm export에 대한 기본 import를 사용할 때 콘솔에 오류가 출력됐어요.
2.1.0에서 named export를 완전히 지원하기 위해 기본 import가 완전히 제거됐어요.
Kotlin/Wasm 타깃을 위한 JavaScript 코딩 시 이제 기본 import 대신 해당하는 이름 있는 import를 사용해야 해요.
이 변경은 named export로 마이그레이션하기 위한 사용 중단 주기의 마지막 단계를 표시해요.
2.0.0 버전: 콘솔에 경고 메시지가 출력되어 기본 export로 엔터티를 내보내는 것이 사용 중단되었음을 알렸어요.
2.0.20 버전: 해당 named import를 사용하라는 오류가 발생했어요.
2.1.0 버전: 기본 import 사용이 완전히 제거됐어요.
하위 프로젝트별 Node.js 설정
rootProject에 대해 NodeJsRootPlugin 클래스의 프로퍼티를 정의하여 프로젝트의 Node.js 설정을 구성할 수 있어요. 2.1.0에서 새 클래스 NodeJsPlugin을 사용해 각 하위 프로젝트에 대해 이러한 설정을 구성할 수 있어요. 하위 프로젝트에 대해 특정 Node.js 버전을 설정하는 방법을 보여주는 예시가 있어요.
// build.gradle.kts
project.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin> {
project.the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsEnvSpec>().version = "22.0.0"
}
전체 프로젝트에 대해 새 클래스를 사용하려면 allprojects {} 블록에 같은 코드를 추가하세요.
// build.gradle.kts
allprojects {
project.plugins.withType<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin> {
project.the<org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsEnvSpec>().version = "your Node.js version"
}
}
Gradle convention 플러그인을 사용해 특정 하위 프로젝트 집합에 설정을 적용할 수도 있어요.
Kotlin/JS
프로퍼티에서 비식별자 문자 지원
Kotlin/JS는 이전에 백틱으로 감싼 공백이 있는 테스트 메서드 이름을 허용하지 않았어요.
마찬가지로 하이픈이나 공백처럼 Kotlin 식별자에서 허용되지 않는 문자를 포함하는 JavaScript 객체 프로퍼티에 접근하는 것도 불가능했어요.
external interface Headers {
var accept: String?
// Invalid Kotlin identifier due to hyphen
var `content-length`: String?
}
val headers: Headers = TODO("value provided by a JS library")
val accept = headers.accept
// Causes error due to the hyphen in property name
val length = headers.`content-length`
이 동작은 이러한 프로퍼티를 비식별자 문자를 사용해 접근할 수 있는 JavaScript와 TypeScript와 달랐어요.
Kotlin 2.1.0부터 이 기능은 기본으로 활성화돼요. Kotlin/JS는 이제 백틱(``)과 @JsName 어노테이션을 사용해 비식별자 문자를 포함하는 JavaScript 프로퍼티와 상호 작용하고 테스트 메서드 이름을 사용할 수 있게 해줘요.
또한 @JsName과 @JsQualifier 어노테이션을 사용해 Kotlin 프로퍼티 이름을 JavaScript 대응물에 매핑할 수 있어요.
object Bar {
val `property example`: String = "bar"
}
@JsQualifier("fooNamespace")
external object Foo {
val `property example`: String
}
@JsExport
object Baz {
val `property example`: String = "bar"
}
fun main() {
// In JavaScript, this is compiled into Bar.property_example_HASH
println(Bar.`property example`)
// In JavaScript, this is compiled into fooNamespace["property example"]
println(Foo.`property example`)
// In JavaScript, this is compiled into Baz["property example"]
println(Baz.`property example`)
}
ES2015 화살표 함수 생성 지원
Kotlin 2.1.0에서 Kotlin/JS는 익명 함수 대신 (a, b) => expression 같은 ES2015 화살표 함수 생성 지원을 도입해요.
화살표 함수를 사용하면 특히 실험적 -Xir-generate-inline-anonymous-functions 모드를 사용할 때 프로젝트의 번들 크기를 줄일 수 있어요. 이는 또한 생성된 코드를 현대 JS에 더 잘 맞게 만들어줘요.
이 기능은 ES2015를 타깃으로 할 때 기본으로 활성화돼요. 또는 -Xes-arrow-functions 명령줄 인자를 사용해 활성화할 수 있어요.
공식 문서에서 ES2015(ECMAScript 2015, ES6)에 대해 자세히 알아보세요.
Gradle 개선 사항
Kotlin 2.1.0은 Gradle 7.6.3부터 8.6까지 완전히 호환돼요. Gradle 8.7~8.10 버전도 한 가지 예외를 제외하고 지원돼요. Kotlin Multiplatform Gradle 플러그인을 사용한다면 JVM 타깃에서 withJava() 함수를 호출할 때 multiplatform 프로젝트에서 사용 중단 경고가 보일 수 있어요. 이 문제는 가능한 한 빨리 고칠 계획이에요.
자세한 내용은 YouTrack의 관련 이슈를 참고하세요.
최신 Gradle 릴리스까지 사용할 수도 있지만, 그렇게 하면 사용 중단 경고가 발생하거나 일부 새 Gradle 기능이 동작하지 않을 수 있다는 점을 명심하세요.
지원되는 최소 AGP 버전이 7.3.1로 상향
Kotlin 2.1.0부터 지원되는 최소 Android Gradle 플러그인 버전은 7.3.1이에요.
지원되는 최소 Gradle 버전이 7.6.3으로 상향
Kotlin 2.1.0부터 지원되는 최소 Gradle 버전은 7.6.3이에요.
Kotlin Gradle 플러그인 확장을 위한 새 API
Kotlin 2.1.0은 Kotlin Gradle 플러그인을 구성하는 자체 플러그인을 더 쉽게 만들 수 있는 새 API를 도입해요. 이 변경은 KotlinTopLevelExtension 및 KotlinTopLevelExtensionConfig 인터페이스를 사용 중단하고 플러그인 작성자를 위해 다음 인터페이스를 도입해요.
| 이름 | 설명 |
|---|---|
KotlinBaseExtension |
전체 프로젝트의 공통 Kotlin JVM, Android, Multiplatform 플러그인 옵션을 구성하기 위한 플러그인 DSL 확장 타입: org.jetbrains.kotlin.jvm, org.jetbrains.kotlin.android, org.jetbrains.kotlin.multiplatform |
KotlinJvmExtension |
전체 프로젝트의 Kotlin JVM 플러그인 옵션을 구성하기 위한 플러그인 DSL 확장 타입. |
KotlinAndroidExtension |
전체 프로젝트의 Kotlin Android 플러그인 옵션을 구성하기 위한 플러그인 DSL 확장 타입. |
예를 들어 JVM과 Android 프로젝트 모두에 대해 컴파일러 옵션을 구성하려면 KotlinBaseExtension을 사용하세요.
configure<KotlinBaseExtension> {
if (this is HasConfigurableKotlinCompilerOptions<*>) {
with(compilerOptions) {
if (this is KotlinJvmCompilerOptions) {
jvmTarget.set(JvmTarget.JVM_17)
}
}
}
}
이것은 JVM과 Android 프로젝트 모두에 대해 JVM 타깃을 17로 구성해요.
JVM 프로젝트에 대해 특별히 컴파일러 옵션을 구성하려면 KotlinJvmExtension을 사용하세요.
configure<KotlinJvmExtension> {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
target.mavenPublication {
groupId = "com.example"
artifactId = "example-project"
version = "1.0-SNAPSHOT"
}
}
이 예시는 마찬가지로 JVM 프로젝트의 JVM 타깃을 17로 구성해요. 또한 프로젝트의 출력이 Maven 저장소에 게시되도록 Maven 게시를 구성해요.
KotlinAndroidExtension을 정확히 같은 방식으로 사용할 수 있어요.
Kotlin Gradle 플러그인 API에서 숨겨진 컴파일러 기호
이전에는 KGP가 런타임 의존성에 org.jetbrains.kotlin:kotlin-compiler-embeddable을 포함하여 내부 컴파일러 기호가 빌드 스크립트 클래스패스에서 사용 가능했어요. 이러한 기호는 내부 전용으로 의도된 것이었어요.
Kotlin 2.1.0부터 KGP는 org.jetbrains.kotlin:kotlin-compiler-embeddable 클래스 파일의 하위 집합을 JAR 파일에 번들하고 점진적으로 제거해요. 이 변경은 호환성 문제를 방지하고 KGP 유지 관리를 단순화하는 것을 목표로 해요.
kotlinter 같은 플러그인처럼 빌드 로직의 다른 부분이 KGP에 번들된 버전과 다른 org.jetbrains.kotlin:kotlin-compiler-embeddable 버전에 의존한다면 충돌과 런타임 예외가 발생할 수 있어요.
이러한 문제를 방지하기 위해 KGP는 이제 KGP와 함께 빌드 클래스패스에 org.jetbrains.kotlin:kotlin-compiler-embeddable이 있으면 경고를 보여줘요.
장기적인 해결책으로 org.jetbrains.kotlin:kotlin-compiler-embeddable 클래스를 사용하는 플러그인 작성자라면 격리된 클래스로더에서 실행하는 것을 권장해요. 예를 들어 클래스로더 또는 프로세스 격리와 함께 Gradle Workers API를 사용해 구현할 수 있어요.
Gradle Workers API 사용
이 예시는 Gradle 플러그인을 생성하는 프로젝트에서 Kotlin 컴파일러를 안전하게 사용하는 방법을 보여줘요. 먼저 빌드 스크립트에 compile-only 의존성을 추가하세요. 이는 컴파일 시간에만 기호를 사용할 수 있게 해줘요.
// build.gradle.kts
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:2.4.20")
}
다음으로 Kotlin 컴파일러 버전을 출력하는 Gradle 작업 액션을 정의하세요.
import org.gradle.workers.WorkAction
import org.gradle.workers.WorkParameters
import org.jetbrains.kotlin.config.KotlinCompilerVersion
abstract class ActionUsingKotlinCompiler : WorkAction<WorkParameters.None> {
override fun execute() {
println("Kotlin compiler version: ${KotlinCompilerVersion.getVersion()}")
}
}
이제 클래스로더 격리를 사용해 이 액션을 워커 실행기에 제출하는 태스크를 만드세요.
import org.gradle.api.DefaultTask
import org.gradle.api.file.ConfigurableFileCollection
import org.gradle.api.tasks.Classpath
import org.gradle.api.tasks.TaskAction
import org.gradle.workers.WorkerExecutor
import javax.inject.Inject
abstract class TaskUsingKotlinCompiler: DefaultTask() {
@get:Inject
abstract val executor: WorkerExecutor
@get:Classpath
abstract val kotlinCompiler: ConfigurableFileCollection
@TaskAction
fun compile() {
val workQueue = executor.classLoaderIsolation {
classpath.from(kotlinCompiler)
}
workQueue.submit(ActionUsingKotlinCompiler::class.java) {}
}
}
마지막으로 Gradle 플러그인에서 Kotlin 컴파일러 클래스패스를 구성하세요.
import org.gradle.api.Plugin
import org.gradle.api.Project
abstract class MyPlugin: Plugin<Project> {
override fun apply(target: Project) {
val myDependencyScope = target.configurations.create("myDependencyScope")
target.dependencies.add(myDependencyScope.name, "$KOTLIN_COMPILER_EMBEDDABLE:$KOTLIN_COMPILER_VERSION")
val myResolvableConfiguration = target.configurations.create("myResolvable") {
extendsFrom(myDependencyScope)
}
target.tasks.register("myTask", TaskUsingKotlinCompiler::class.java) {
kotlinCompiler.from(myResolvableConfiguration)
}
}
companion object {
const val KOTLIN_COMPILER_EMBEDDABLE = "org.jetbrains.kotlin:kotlin-compiler-embeddable"
const val KOTLIN_COMPILER_VERSION = "2.4.20"
}
}
Compose 컴파일러 업데이트
여러 안정성 구성 파일 지원
Compose 컴파일러는 여러 안정성 구성 파일을 해석할 수 있지만, Compose 컴파일러 Gradle 플러그인의 stabilityConfigurationFile 옵션은 이전에 단일 파일만 지정할 수 있게 허용했어요. Kotlin 2.1.0에서 이 기능은 단일 모듈에 여러 안정성 구성 파일을 사용할 수 있도록 다시 설계됐어요.
stabilityConfigurationFile옵션이 사용 중단됐어요.ListProperty<RegularFile>타입의 새 옵션stabilityConfigurationFiles가 생겼어요.
새 옵션을 사용해 Compose 컴파일러에 여러 파일을 전달하는 방법이에요.
// build.gradle.kt
composeCompiler {
stabilityConfigurationFiles.addAll(
project.layout.projectDirectory.file("configuration-file1.conf"),
project.layout.projectDirectory.file("configuration-file2.conf"),
)
}
Pausable composition
Pausable composition은 컴파일러가 스킵 가능한 함수를 생성하는 방식을 변경하는 새 실험적 기능이에요. 이 기능을 활성화하면 런타임 중 스킵 지점에서 composition을 일시 중지할 수 있어서 오래 실행되는 composition 프로세스를 여러 프레임으로 분할할 수 있어요. Pausable composition은 지연 목록(lazy lists) 및 기타 성능 집약적 구성 요소에서 차단 방식으로 실행될 때 프레임이 떨어질 수 있는 콘텐츠를 프리페치하기 위해 사용돼요.
pausable composition을 시험해 보려면 Compose 컴파일러의 Gradle 구성에 다음 기능 플래그를 추가하세요.
// build.gradle.kts
composeCompiler {
featureFlags = setOf(
ComposeFeatureFlag.PausableComposition
)
}
이 기능에 대한 런타임 지원은 androidx.compose.runtime의 1.8.0-alpha02 버전에서 추가됐어요. 이 기능 플래그는 더 오래된 런타임 버전과 사용하면 효과가 없어요.
open 및 재정의된 @Composable 함수 변경
가상(open, abstract, overridden) @Composable 함수는 더 이상 재시작 가능(restartable)할 수 없어요. 재시작 가능한 group의 코드 생성이 상속과 올바르게 작동하지 않는 호출을 생성하여 런타임 충돌을 일으켰어요.
이는 가상 함수가 재시작되거나 스킵되지 않는다는 뜻이에요. 상태가 무효화될 때마다 런타임이 대신 그 부모 composable을 재구성해요. 재구성에 민감한 코드라면 런타임 동작의 변화를 알아차릴 수 있어요.
성능 개선
Compose 컴파일러는 @Composable 타입을 변환하기 위해 모듈의 IR 전체 복사본을 만들곤 했어요. Compose와 관련 없는 요소를 복사할 때 메모리 소비가 증가하는 것 외에도, 이 동작은 특정 경계 사례에서 다운스트림 컴파일러 플러그인을 깨뜨리기도 했어요.
이 복사 작업이 제거되어 컴파일 시간이 더 빨라질 수 있어요.
표준 라이브러리
표준 라이브러리 API의 사용 중단 심각도 변경
Kotlin 2.1.0에서 여러 표준 라이브러리 API의 사용 중단 심각도 수준을 경고에서 오류로 올리고 있어요. 코드가 이러한 API에 의존한다면 호환성을 보장하기 위해 업데이트해야 해요. 가장 주목할 만한 변경은 다음과 같아요.
Char와String에 대한 로케일 민감(locale-sensitive) 대소문자 변환 함수가 사용 중단됐어요:Char.toLowerCase(),Char.toUpperCase(),String.toUpperCase(),String.toLowerCase()같은 함수가 이제 사용 중단되었고 이를 사용하면 오류가 발생해요. 이를 로케일 무관 함수 대안이나 다른 대소문자 변환 메커니즘으로 대체하세요. 기본 로케일을 계속 사용하려면String.toLowerCase()같은 호출을String.lowercase(Locale.getDefault())로 바꾸고 로케일을 명시적으로 지정하세요. 로케일 무관 변환을 위해서는 기본적으로 불변 로케일(invariant locale)을 사용하는String.lowercase()로 바꾸세요.- Kotlin/Native freezing API가 사용 중단됐어요: 이전에
@FreezingIsDeprecated어노테이션으로 표시된 freezing 관련 선언을 사용하면 이제 오류가 발생해요. 이 변경은 스레드 간 공유를 위해 객체를 freezing해야 했던 Kotlin/Native의 레거시 메모리 매니저에서의 전환을 반영해요. 새 메모리 모델에서 freezing 관련 API로부터 마이그레이션하는 방법은 Kotlin/Native 마이그레이션 가이드를 참고하세요. 자세한 내용은 freezing 사용 중단 공지를 참고하세요. appendln()이appendLine()을 위해 사용 중단됐어요:StringBuilder.appendln()및Appendable.appendln()함수가 이제 사용 중단되었고 이를 사용하면 오류가 발생해요. 대신StringBuilder.appendLine()또는Appendable.appendLine()함수를 사용하세요.appendln()함수는 Kotlin/JVM에서line.separator시스템 속성을 사용하는데 OS마다 기본값이 다르기 때문에 사용 중단됐어요. Kotlin/JVM에서 이 속성은 Windows에서는\\r\\n(CR LF), 다른 시스템에서는\\n(LF)을 기본값으로 해요. 반면appendLine()함수는 일관되게\\n(LF)을 줄 구분자로 사용해 플랫폼 간 일관된 동작을 보장해요.
이 릴리스에서 영향을 받는 API의 전체 목록은 KT-71628 YouTrack 이슈를 참고하세요.
java.nio.file.Path용 Stable 파일 트리 순회 확장
Kotlin 1.7.20은 파일 트리를 순회할 수 있는 java.nio.file.Path 클래스용 확장 함수를 실험적으로 도입했어요. Kotlin 2.1.0에서 다음 파일 트리 순회 확장은 이제 Stable이에요.
walk()는 지정된 경로에 루트를 둔 파일 트리를 지연(lazily) 순회해요.fileVisitor()는FileVisitor를 별도로 만들 수 있게 해줘요.FileVisitor는 순회 중 디렉터리와 파일에 대해 수행할 작업을 지정해요.visitFileTree(fileVisitor: FileVisitor, ...)는 파일 트리를 순회하며 만나는 각 항목에 지정된FileVisitor를 호출하고 내부적으로java.nio.file.Files.walkFileTree()함수를 사용해요.visitFileTree(..., builderAction: FileVisitorBuilder.() -> Unit)는 제공된builderAction으로FileVisitor를 만들고visitFileTree(fileVisitor, ...)함수를 호출해요.sealed interface FileVisitorBuilder는 사용자 정의FileVisitor구현을 정의할 수 있게 해줘요.enum class PathWalkOption은Path.walk()함수에 대한 순회 옵션을 제공해요.
아래 예시는 이러한 파일 순회 API를 사용해 사용자 정의 FileVisitor 동작을 만드는 방법을 보여줘요. 이를 통해 파일과 디렉터리를 방문하기 위한 특정 작업을 정의할 수 있어요.
예를 들어 FileVisitor를 명시적으로 만들고 나중에 사용할 수 있어요.
val cleanVisitor = fileVisitor {
onPreVisitDirectory { directory, attributes ->
// Placeholder: Add logic on visiting directories
FileVisitResult.CONTINUE
}
onVisitFile { file, attributes ->
// Placeholder: Add logic on visiting files
FileVisitResult.CONTINUE
}
}
// Placeholder: Add logic here for general setup before traversal
projectDirectory.visitFileTree(cleanVisitor)
builderAction으로 FileVisitor를 만들고 순회에 즉시 사용할 수도 있어요.
projectDirectory.visitFileTree {
// Defines the builderAction:
onPreVisitDirectory { directory, attributes ->
// Some logic on visiting directories
FileVisitResult.CONTINUE
}
onVisitFile { file, attributes ->
// Some logic on visiting files
FileVisitResult.CONTINUE
}
}
또한 walk() 함수로 지정된 경로에 루트를 둔 파일 트리를 순회할 수 있어요.
fun traverseFileTree() {
val cleanVisitor = fileVisitor {
onPreVisitDirectory { directory, _ ->
if (directory.name == "build") {
directory.toFile().deleteRecursively()
FileVisitResult.SKIP_SUBTREE
} else {
FileVisitResult.CONTINUE
}
}
// Deletes files with the .class extension
onVisitFile { file, _ ->
if (file.extension == "class") {
file.deleteExisting()
}
FileVisitResult.CONTINUE
}
}
// Sets up the root directory and files
val rootDirectory = createTempDirectory("Project")
// Creates the src directory with A.kt and A.class files
rootDirectory.resolve("src").let { srcDirectory ->
srcDirectory.createDirectory()
srcDirectory.resolve("A.kt").createFile()
srcDirectory.resolve("A.class").createFile()
}
// Creates the build directory with a Project.jar file
rootDirectory.resolve("build").let { buildDirectory ->
buildDirectory.createDirectory()
buildDirectory.resolve("Project.jar").createFile()
}
// Uses the walk() function:
val directoryStructure = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES)
.map { it.relativeTo(rootDirectory).toString() }
.toList().sorted()
println(directoryStructure)
// "[, build, build/Project.jar, src, src/A.class, src/A.kt]"
// Traverses the file tree with cleanVisitor, applying the rootDirectory.visitFileTree(cleanVisitor) cleanup rules
val directoryStructureAfterClean = rootDirectory.walk(PathWalkOption.INCLUDE_DIRECTORIES)
.map { it.relativeTo(rootDirectory).toString() }
.toList().sorted()
println(directoryStructureAfterClean)
// "[, src, src/A.kt]"
}
문서 업데이트
Kotlin 문서에 몇 가지 눈에 띄는 변경이 있었어요.
언어 개념
- 개선된 Null safety 페이지 - 코드에서
null값을 안전하게 처리하는 방법을 알아보세요. - 개선된 객체 선언과 표현식 페이지 - 단일 단계에서 클래스를 정의하고 인스턴스를 만드는 방법을 알아보세요.
- 개선된 when 표현식과 문 섹션 -
when조건식과 그 사용법을 알아보세요. - 업데이트된 Kotlin 로드맵, Kotlin 진화 원칙, Kotlin 언어 기능 및 제안 페이지 - Kotlin의 계획, 진행 중인 개발, 지침 원칙을 알아보세요.
Compose 컴파일러
- Compose 컴파일러 문서가 이제 컴파일러 및 플러그인 섹션에 위치해요 - Compose 컴파일러, 컴파일러 옵션, 마이그레이션 단계를 알아보세요.
API 참조
- 새로운 Kotlin Gradle 플러그인 API 참조 - Kotlin Gradle 플러그인과 Compose 컴파일러 Gradle 플러그인의 API 참조를 살펴보세요.
Multiplatform 개발
- 새로운 multiplatform용 Kotlin 라이브러리 빌드 페이지 - Kotlin Multiplatform용 Kotlin 라이브러리를 설계하는 방법을 알아보세요.
- 새로운 Kotlin Multiplatform 소개 페이지 - Kotlin Multiplatform의 핵심 개념, 의존성, 라이브러리 등을 알아보세요.
- 새로운 iOS 통합 섹션 - Kotlin Multiplatform 공유 모듈을 iOS 앱에 통합하는 방법을 알아보세요.
- 새로운 Kotlin/Native 정의 파일 페이지 - C 및 Objective-C 라이브러리를 사용하기 위한 정의 파일을 만드는 방법을 알아보세요.
- WASI로 시작하기 - 다양한 WebAssembly 가상 머신에서 WASI를 사용해 간단한 Kotlin/Wasm 애플리케이션을 실행하는 방법을 알아보세요.
도구
- 새 Dokka 마이그레이션 가이드 - Dokka Gradle 플러그인 v2로 마이그레이션하는 방법을 알아보세요.
Kotlin 2.1.0 호환성 가이드
Kotlin 2.1.0은 기능 릴리스이므로 이전 언어 버전용으로 작성된 코드와 호환되지 않는 변경을 가져올 수 있어요. 이러한 변경의 자세한 목록은 Kotlin 2.1.0 호환성 가이드에서 확인하세요.
Kotlin 2.1.0 설치
IntelliJ IDEA 2023.3과 Android Studio Iguana (2023.2.1) Canary 15부터 Kotlin 플러그인은 IDE에 포함된 번들 플러그인으로 배포돼요. 즉, 더 이상 JetBrains Marketplace에서 플러그인을 설치할 수 없어요.
새 Kotlin 버전으로 업데이트하려면 빌드 스크립트에서 Kotlin 버전을 2.1.0으로 변경하세요.