코틀린 1.9.0 릴리즈가 올해 7월 6일에 출시되으며 JVM용 K2 컴파일러 또한 베타 버전이 되었다.
아래는 코틀린 1.9.0의 주 특성이다.
..<
연산자 안정화코틀린 1.9.0을 지원하는 IDE는 아래와 같다.
IDE | 지원하는 버전 |
---|---|
IntelliJ IDEA | 2022.3.x, 2023.1.x |
Android Studio | Giraffe (223), Hedgehog (231) |
또한, 코틀린 1.9.0은 추후에 출시될 IntelliJ IDEA 2023.2에도 지원될 것이다.
코틀린 아티팩트와 종속성을 다운로드하려면 Maven Central Repository를 사용하도록 Gradle 설정을 필수로 구성해야 한다.
K2 컴파일러는 Kotlin/Native 및 다중 플랫폼 프로젝트에 대한 기본적인 지원이 있다.
k2 컴파일러와 kapt 플러그인을 같은 프로젝트에서 사용할 수 있지만, 몇 가지 제한 사항이 존재한다. languageVersion
을 2.0으로 설정했음에도 kapt 컴파일러는 구식 컴파일러를 사용한다.
languageVersion
이 2.0으로 설정된 프로젝트에서 kapt 컴파일러 플러그인을 실행하면 kapt는 자동으로 1.9로 전환되고 특정 버전 호환성 검사를 비활성화한다. 이 동작은 아래의 명령 인수를 포함하는 것과 같다.
-Xskip-metadata-version-check
-Xskip-prerelease-check
-Xallow-unstable-dependencies
이러한 검사는 kapt 작업에만 사용이 불가능하다. 타 컴파일 작업은 k2 컴파일을 계속 사용할 것이다.
코틀린 1.9.0부터 2.0까지 kotlin.experimental.tryK2=true
gradle 속성으로 k2 컴파일러를 테스트할 수 있다. 혹은 아래의 명령어를 실행해 테스트할 수 있다.
$ ./gradlew assemble -Pkotlin.experimental.tryK2=true
해당 gradle 속성은 자동으로 언어 버전으로 2.0으로 설정하고, 현재 컴파일러와 비교하여 k2 컴파일러를 사용해 컴파일한 코틀린 태스크의 수로 빌드 리포트를 업데이트한다.
##### 'kotlin.experimental.tryK2' results (Kotlin/Native not checked) #####
:lib:compileKotlin: 2.0 language version
:app:compileKotlin: 2.0 language version
##### 100% (2/2) tasks have been compiled with Kotlin 2.0 #####
gradle build 리포트는 코드 컴파일에 현재 컴파일러의 사용 여부와 k2 컴파일러의 사용 여부가 표시된다. 코틀린 1.9.0에서는 해당 정보를 gradle 빌드 스캔에서 확인할 수 있다.
프로젝트에서 사용된 코틀린 버전 또한 빌드 리포트에서 찾을 수 있다.
Task info:
Kotlin language version: 1.9
gradle 프로젝트에서 k2를 활성화하면 아래와 같은 경우, gradle 버전 8.3 미만을 사용하는 프로젝트에 영향을 미칠 수 있는 몇 가지 제한 사항이 존재한다.
buildSrc
에서 소스 코드 컴파일위에서 언급한 문제 중 하나가 발생했을 경우, 다음 단계를 수행하여 해결할 수 있다.
buildSrc
, 모든 gradle 플러그인 및 해당 종속성에 대한 언어 버전을 설정kotlin {
compilerOptions {
languageVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9)
}
}
코틀린 1.9.0에서는 상술한 새로운 기능에 대한 안정화를 진행하고 있다.
코틀린 1.8.20에서 열거형 클래스에 대한 entries
프로퍼티는 실험 기능으로 도입됐다.
entries
프로퍼티는 합성 value()
함수를 안정적으로 대체한다.(value()
함수는 지원되지만 공식 문서에서는 entries
프로퍼티 사용을 권장)
enum class Color(val colorName: String, val rgb: String) {
RED("Red", "#FF0000"),
ORANGE("Orange", "#FF7F00"),
YELLOW("Yellow", "#FFFF00")
}
fun findByRgb(rgb: String): Color? = Color.entries.find { it.rgb == rgb }
열거형 클래스의 entries
프로퍼티에 대한 내용은 What's new in Kotlin 1.8.20을 참고하길 바란다.
코틀린 1.8.20에 도입된 데이터 객체선언은 안정화가 되었다. 여기에는 데이터 클래스와의 대칭을 위해 추가된 함수인 toString()
, equals()
, hashCode()
함수가 포함된다.
해당 기능은 데이터 객체 선언과 함께 편리하게 사용할 수 있기에 sealed
계층(sealed 클래스나 sealed 인터페이스 계층)에 특히 유용하다. 아래의 예제에서 EndOfFile
을 일반 개체 대신 데이터 개체로 선언하면 재정의할 필요 없이 toString()
함수를 자동으로 포함한다. 이는 함께 제공되는 데이터 클래스 정의와 대칭을 유지한다.
sealed interface ReadResult
data class Number(val number: Int) : ReadResult
data class Text(val text: String) : ReadResult
data object EndOfFile : ReadResult
fun main() {
println(Number(7)) // Number(number=7)
println(EndOfFile) // EndOfFile
}
자세한 내용은 What's new in Kotlin 1.8.20을 참고하길 바란다.
코틀린 1.9.0부터 인라인 value 클래스에 본문이 있는 보조 생성자를 사용할 수 있다.
@JvmInline
value class Person(private val fullName: String) {
// 코틀린 1.4.30부터 허용
init {
check(fullName.isNotBlank()) {
"Full name shouldn't be empty"
}
}
// 코틀린 1.9.0부터 기본적으로 허용
constructor(name: String, lastName: String) : this("$name $lastName") {
check(lastName.isNotBlank()) {
"Last name shouldn't be empty"
}
}
}
이전에 코틀린은 인라인 클래스에서 공개 기본 생성자만 허용했다. 결과적으로 기본 값을 캡슐화하거나 일부 제한된 값을 나타내는 인라인 클래스를 만드는 것은 불가능했다.
코틀린이 개발되며 이러한 문제가 수정되었다. 코틀린 1.4.30에서는 init
블록에 대한 제한이 해제되었고 코틀린 1.8.20에서는 본문이 있는 보조 생성자의 미리보기가 제공되었다. 이제 기본적으로 사용할 수 있게 됐다. 이 페이지에서 코틀린 인라인 클래스의 개발 과정에 대한 내용이 기술되어 있다.
코틀린 1.9.0부터 컴파일러는 jvm 20에 해당하는 바이트코드 버전으로 클래스를 생성할 수 있다. 또한 JvmDefault
어노테이션 및 레거시 -Xjvm-default
모드는 더 이상 사용되지 않는다.
코틀린 1.5부터 JvmDefault
어노테이션은 사용되지 않으며 -Xjvm-default
모드(all 및 all-compatibility)를 우선하는 방식으로 대체되었다. 코틀린 1.4에 도입된 JvmDefaultWithoutCompatibility
와 코틀린 1.6에 도입된 JvmDefaultWithCompatibility
와 함께, 이러한 모듈들은 DefaultImpls
클래스의 생성에 대한 포괄적인 제어를 제공하며, 이전 코틀린 코드와의 원활한 호환성을 보장한다.
결과적으로 코틀린 1.9.0에서는 JvmDefault
어노테이션이 지원되지 않기 때문에 사용 시, 오류가 발생하며, 추후 코틀린에서 제거될 예정이다.
코틀린 1.9.0 릴리즈는 Kotlin/Native 메모리 관리자에게 견고성과 성능 향상을 위한 추가적인 개선점을 제공한다.
코틀린 1.9.0은 사용자 지정 메모리 할당기에 대한 미리보기를 제공한다. 해당 할당 시스템은 Kotlin/Native 메모리 관리자의 런타임 성능을 향상시킨다.
현재 kotlin/native의 객체 할당 시스템은 효율적인 Garbage Collection(이하 gc)을 위한 기능이 없는 범용 할당기를 사용한다. 이를 보완하기 위해 gc가 할당된 모든 객체를 단일 목록으로 병합하기 전에 스레드-로컬의 링크드 리스트를 유지한다. 해당 접근 방식에는 아래와 같은 성능 저하가 있다.
이러한 문제를 해결하기 위해 코틀린 1.9.0에서 사용자 지정 할당기 미리보기를 도입한다. 시스템 메모리를 페이지로 분할하여 순차적으로 독립적인 스위핑이 가능하다. 각 할당은 페이지 내 블록이 되고, 페이지는 블록의 크기를 추적한다. 서로 다른 페이지 유형은 다양한 할당 크기에 최적화되어 있으며 메모리 블록들의 연속적인 배열은 할당된 모든 블록들을 통해 효율적인 반복을 보장한다.
스레드가 메모리를 할당할 때 할당 크기에 따라 적합한 페이지를 검색한다. 스레드는 다양한 크기 범주에 대한 페이지 집합을 유지한다. 일반적으로 현재 페이지는 주어진 크기에 대한 할당을 수용할 수 있다. 그렇지 않으면 스레드는 공유 할당 공간에서 다른 페이지를 요청하게 되고, 해당 페이지는 이미 사용 중이거나 스위핑이 필요할 수 있으며, 먼저 생성되어야 할 수도 있다.
새로운 할당기는 복수의 할당 공간을 동시에 가질 수 있게 해주는데, 코틀린팀은 성능 향상을 위한 다양한 페이지 레이아웃을 실험적으로 사용할 수 있게 해준다.
새 할당기 설계에 대한 내용은 이 문서를 참조하면 된다.
-Xallocator=custom
을 추가하면 사용할 수 있다.
kotlin {
macosX64("native") {
binaries.executable()
compilations.configureEach {
compilerOptions.configure {
freeCompilerArgs.add("-Xallocator=custom")
}
}
}
}
코틀린 1.9.0부터 objective-C 혹은 Swift 객체 할당 해제 hook는 객체가 코틀린에게 전달되면 메인 스레드에서 호출된다. kotlin/native 메모리 관리자가 이전에 objective-c 객체에 대한 참조를 처리하는 방식에 의해 메모리 누수 문제가 발생할 수 있었다. 새로운 동작은 메모리 관리자의 견고성을 향상시킬 수 있다.
예를 들어 인수로 전달되거나 함수에 의해 리턴되거나 컬렉션에서 검색될 때 코틀린 코드에서 참조되는 objective-c 객체를 생각해보면 코틀린은 objective-c 객체에 대한 참조를 보유하는 자체 객체를 생성한다. 코틀린 객체가 할당이 해제되면 kotlin/native 런타임은 objc_release
함수를 호출하여 objective-c 참조를 해제한다.
이전에는 kotlin/native 메모리 관리자가 objc_release
를 special gc 스레드에서 실행했으며 그것이 마지막 개체의 참조라면 개체의 할당이 해제된다. objective-c 객체에 objective-c의 delloc
메소드나 swift의 deinit
블록과 같은 사용자 지정 할당 해제 hook가 있고 이러한 hook가 특정 스레드에서 호출된다면 문제가 발생할 수 있다.
일반적으로 메인 스레드의 객체 hook가 거기서 호출된다고 가정하기 때문에 kotlin/native 런타임은 메인 스레드에서도 objc_release
를 호출한다. objective-c 객체가 메인 스레드의 코틀린에 전달되어 코틀린 peer 객체가 생성된 경우에 대해 설명한다. 일반 UI 애플리케이션의 경우, 메인 디스패치 대기열이 처리되는 경우에만 동작한다. 메인 큐가 아니거나 메인 스레드 이외에 타 스레드로 코틀린에 넘겨받았을 경우, 이전과 동일하게 special gc 스레드에서 objc_release
가 호출된다.
문제가 발생했을 경우, 아래의 옵션을 사용해 gradle.properties
에서 해당 동작을 비활성화할 수 있다.
kotlin.native.binary.objcDisposeOnMain=false
코틀린 1.9.0부터 kotlin/native 백엔드 const val
필드에 액세스할 때 객체를 초기화하지 않는다.
object MyObject {
init {
println("side effect!")
}
const val y = 1
}
fun main() {
println(MyObject.y) // 처음에는 초기화 없음
val x = MyObject // 초기화
println(x.y)
}
이 동작은 kotlin/jvm에 통합되어 구현은 자바와 일관하고 있어, 이 경우에는 객체 초기화가 진행되지 않는다. 이 변경으로 kotlin/native 프로젝트의 성능 향상도 기대해 볼 수 있다.
기본적으로 kotlin/native iOS 시뮬레이터 테스트를 실행하는 경우 --standalone
플래그를 사용하여 수동 시뮬레이터 부팅 및 종료를 방지한다. 1.9.0에서는 독립 실행형 속성을 사용하여 gradle 작업에서 해당 플래그의 사용 여부를 설정할 수 있다. 기본적으로 --standalone
플래그가 사용되므로 독립 실행형 모드는 활성화된다.
아래의 코드는 build.gradle.kts
에서 독립 실행형 모드를 비활성화하는 코드이다.
tasks.withType<org.jetbrains.kotlin.gradle.targets.native.tasks.KotlinNativeSimulatorTest>().configureEach {
standalone.set(false)
}
독립 실행형 모드를 사용하지 않도록 설정한 경우, 시뮬레이터를 수동으로 부팅해야 한다. cli에서 시뮬레이터를 부팅하려면 아래의 명령어를 사용하면 된다.
$ /usr/bin/xcrun simctl boot <DeviceId>
코틀린 1.9.0부터 kotlin/native 컴파일러는 코틀린 라이브러리와 링크 문제를 kotlin/jvm과 동일하게 처리하게 된다. 어느 서드파티 코틀린 라이브러리 작성자가 타 서드파티 코틀린 라이브러리에서 사용하는 실험적 API에 호환되지 않은 변경을 가하는 경우, 이러한 문제에 직면하게 된다.
타사 코틀린 라이브러리 간의 링크 문제가 발생하더라도 컴파일 도중 빌드가 실패할 일은 없다. 대신, jvm의 경우와 같이 이러한 에러는 런타임에서만 발생한다.
kotlin/native 컴파일러는 라이브러리 링크 문제를 감지하면 경고를 보고한다. 예를 들어 컴파일 로그에서 아래와 같은 경고를 발견했다고 했을 때
No function found for symbol 'org.samples/MyRemovedClass.doSomething|3657632771909858561[0]'
Can not get instance of singleton 'MyEnumClass.REMOVED_ENTRY': No enum entry found for symbol 'org.samples/MyEnumClass.REMOVED_ENTRY|null[0]'
Function 'getMyRemovedClass' can not be called: Function uses unlinked class symbol 'org.samples/MyRemovedClass|null[0]'
프로젝트에서 아래의 동작을 추가로 구성하거나 비활성화할 수 있다.
-Xpartial-linkage-loglevel=INFO
컴파일 옵션을 설정하면 된다. 해당 옵션은 경고 심각도를 info 레벨로 낮추어 표시한다.-Xpartial-linkage-loglevel=ERROR
로 설정하여 보고된 경고 심각도를 에러 레벨로 올려서 표시한다. 이 경우에는 컴파일이 실패하고 컴파일 로그에 모든 오류가 표시된다. 해당 옵션을 사용하면 링크 문제를 더 상세히 확인할 수 있다.-Xpartial-linkage=disable
컴파일 옵션을 사용해 해당 기능을 해제할 수 있다.// gradle 빌드 파일을 통해 컴파일 옵션을 지정하는 예제
kotlin {
macosX64("native") {
binaries.executable()
compilations.configureEach {
compilerOptions.configure {
// 경고 레벨을 info로 낮춤
freeCompilerArgs.add("-Xpartial-linkage-loglevel=INFO")
// 경고 레벨을 error로 높임
freeCompilerArgs.add("-Xpartial-linkage-loglevel=ERROR")
// 기능 비활성화
freeCompilerArgs.add("-Xpartial-linkage=disable")
}
}
}
}
kotlin/native의 안정화를 진행하면서, 묵시적 정수 변환이 가능한 c언어 상호운용 컴파일 옵션을 도입했다. 이전에는 묵시적인 정수 변환을 사용하기 위해 컴파일 옵션을 구성하지 않아도 됐으나 신중히 검토한 결과, 해당 기능에 대한 개선의 여지가 보였고 최고품질의 API를 구현하기 위해 의도치 않은 사용을 방지하기 위해 해당 컴파일 옵션을 도입했다.
아래의 코드에서는 묵시적 정수 변환으로 인해 options
의 타입이 UInt
이지만 0은 부호가 있는 정수(signed)임에도 options = 0
이 할당되었다.
val today = NSDate()
val tomorrow = NSCalendar.currentCalendar.dateByAddingUnit(
unit = NSCalendarUnitDay,
value = 1,
toDate = today,
options = 0
)
네이티브 interop 라이브러리에서 묵시적 변환을 사용하려면 -XXLanguage:+ImplicitSignedToUnsignedIntegerConversion
컴파일 옵션을 사용해야 한다.
이는 아래의 코드와 같이 build.gradle.kts
에서 구성할 수 있다.
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile>().configureEach {
compilerOptions.freeCompilerArgs.addAll(
"-XXLanguage:+ImplicitSignedToUnsignedIntegerConversion"
)
}
코틀린 1.9.0에서는 코틀린 멀티플랫폼의 DX(개발자 경험)의 향상을 위해 아래와 같은 업데이트가 진행됐다.
구글의 안드로이드팀이 코틀린 멀티 플랫폼에서 안드로이드를 지원하는 자체 gradle 플러그인을 제공할 예정이다.
또한, 새로운 솔루션에 대한 가능성을 열기 위해 1.9.0 버전에서 현재 코틀린 dsl의 android
블록의 이름을 변경한다. 빌드 스크립트에서의 android
블록을 androidTarget
으로 변경해야 한다. 이는 추후에 출시될 구글 dsl의 android
이름을 확보하기 위한 일시적인 변경이다.
구글 플러그인은 다중 플랫폼 프로젝트에서 안드로이드를 사용할 때 권장되는 방안이다. 준비되면 필요한 마이그레이션 지침을 제공하여 이전과 동일하게 android
이름을 사용할 수 있게 될 것이다.
코틀린 1.9.0부터 신규 안드로이드 소스 세트 레이아웃이 기본 설정으로 적용된다. 이전의 디렉토리 네이밍 스키마가 혼동스러웠던 것을 대체했으며, 신규 레이아웃은 아래와 같은 이점을 제공한다.
SourceDirectories
배열이 보다 일관적이게 구성되어 코드를 조직화하고 소스 파일를 쉽게 찾을 수 있다.KotlinSourceSets
와 AndroidSourceSets
에서 더 일관되고 예측 가능한 스키마를 사용한다.신규 레이아웃은 android gradle 7.0 이상과 Android Studio 2022.3 이상의 버전의 환경에서 사용할 수 있다. build.gradle(.kts)
에서 필요한 변경 사항을 반영하려면 마이그레이션 가이드를 참고하길 바란다.
코틀린 1.9.0은 멀티플랫폼 라이브러리에서 gradle 환경 설정 캐시를 지원한다. 라이브러리 개발자는 이미 빌드 성능의 향상으로 이점을 누릴 수 있다.
gradle 환경 설정 캐시는 환경 설정 단계의 결과를 후속 빌드에 재사용하여 빌드 프로세스의 속도를 향상시킨다. 해당 기능은 gradle 8.1 이후에 안정화었으며 이를 활용하려면 gradle 문서의 지침을 참고하길 바란다.
코틀린팀은 신규 kotlin/wasm을 대상으로 실험을 하고 있다. 이 릴리즈에서 성능과 용량 관련 최적화와 자바스크립트 상호 운용성이 업데이트됐다.
코틀린 1.9.0에서 웹 어셈블리 프로젝트 용량이 개선되었다. 위의 그래프는 1.8.20에서 작성된 Hello World
프로젝트와 1.9.0에서 작성된 Hello World
프로젝트의 용량을 나타내며, 코드의 용량이 1.8.20에 비해 1/10 수준으로 감소한 것을 확인할 수 있다.
이러한 용량 최적화로 코틀린 코드로 wsam 플랫폼에서 더 효율적인 리소스 활용과 향상된 성능이라는 결과를 가져오게 된다.
이번 업데이트에서는 kotlin/wasm의 코틀린과 자바스크립트 간 상호 운용성이 포함된다. kotlin/wasm은 실험적인 기능이기에 상호 운용성에 대한 일부 제한 사항이 적용된다.
코틀린 1.9.0부터 더 이상 kotlin/wasm에서 동적 타입을 지원하지 않는다. 이제 이러한 동적 타입은 새로운 범용 JsAny
타입으로 대체되어 js 상호 운용성을 용이하게 한다.
자세한 내용은 이 문서을 참고하길 바란다.
kotlin/wasm은 자바스크립트와 값을 교환할 때 특정 코틀린 정적 타입에 대한 변환을 지원한다. 이러한 지원되는 타입은 다음과 같다.
Boolean
, Char
등String
기타 타입들은 변환 없이 불분명한 참조로 전달되어 자바스크립트와 코틀린의 하위 타입 사이에서 불일치가 발생했다.
이를 해결하기 위해 코틀린은 자바스크립트 상호 운용성이 잘 지원되는 타입으로 제한된다. 코틀린 1.9.0부터는 kotlin.wasm 자바스크립트 상호 운영성에서 오직 external, primitive, string과 함수 타입만을 지원한다. 더불어, kotlin/wasm 객체에 대한 핸들을 자바스크립트 상호 운용성에 사용하기 위해 별도의 명시적 타입인 JsReference
가 도입됐다.
자세한 내용은 kotlin/wasm과 자바스크립트 간의 상호 운용성 문서를 참고하길 바란다.
코틀린 플레이그라운드는 kotlin/wasm 대상을 지원한다. kotlin/wasm을 대상으로 하는 코틀린 코드의 작성, 실행 및 공유가 가능하다.
이곳에서 확인이 가능하다.
kotlin/wasm을 활성하기 위해서는 브라우저에서 실험 기능을 활성화해야 한다. 이러한 기능을 활성화하는 방법은 이 문서에 나와있다.
이번 kotlin/js 업데이트에는 gradle 플러그인의 지원 중단과 es6에 대한 실험적 지원을 포함한다.
코틀린 1.9.0부터 kotlin-js
gradle 플러그인 지원이 중단되었다. 대신 js()
와 함께 kotlin-multiplatform
플러그인을 사용하는 것을 권장한다.
kotlin/js gradle 플러그인은 kotlin-multiplatform
플러그인을 그대로 복제해서 본질적으로 동일하다. 이러한 중복으로 인해 코틀린팀의 혼란과 유지보수 부담이 증가됐다.
마이그레이션 지침은 코틀린 멀티플랫폼 호환성 가이드를 참고하길 바란다.
코틀린 1.9.0에서는 코틀린 외부에 존재할 수 없는 entries
와 같은 정적 열거형 멤버와 관련된 문제로 인해 외부 열거형을 사용하지 않는 것을 권장한다. 대신 외부 sealed class
를 객체 하위 클래스와 같이 사용하는 것을 권장한다.
// 이전
external enum class ExternalEnum { A, B }
// 이후
external sealed class ExternalEnum {
object A: ExternalEnum
object B: ExternalEnum
}
객체 하위 클래스가 있는 외부 sealed class
로 전환하면 기본 메소드와 관련된 문제를 피하면서 외부 열거형과 유사한 기능을 제공할 수 있다.
상술한 바와 같이 코틀린 1.9.0부터 외부 열거형 사용은 권장되지 않으며, 호환성과 향후 유지보수를 위해 외부 sealed class
구현하도록 코드를 업데이트하는 것이 좋다.
이번 장에서는 업데이트에서는 es6 모듈과 es6 클래스 생성에 대한 실험적 지원에 대해 소개한다.
이러한 기능을 사용하기 위해서는 아래와 같이 build.gradle.kts
를 구성해주어야 한다.
kotlin {
js(IR) {
useEsModules() // es6 모듈 활성화
browser()
}
}
// es6 클래스 생성 사용
tasks.withType<KotlinJsCompile>().configureEach {
kotlinOptions {
useEsClasses = true
}
}
자세한 내용은 ecma 스크립트 2015(es6) 문서를 참고하길 바란다.
코틀린 1.9.0 이전 버전에서는 배포 대상이 되는 디렉토리가 build/distributions
였다. 그러나 이는 gradle 아카이브의 일반적인 디렉토리이기에 이 문제를 해결하기 위해 코틀린 1.9.0의 기본 배포 디렉토리를 build/dist/<targetName>/<binaryName>
으로 변경했다.
예를 들어 productionExecutable
는 원래 build/distributions
디렉토리에 존재했으나 build/dist/js/productionExecutable
디렉토리로 변경됐다.
만약, 빌드 결과를 사용하는 파이프라인(pipeline)이 이미 구축되어 있다면 해당 디렉토리를 업데이트를 필수적으로 해주어야 한다.
코틀린 1.9.0 이후로 stdlib-js
는 더 이상 org.w3c
임포팅을 사용하지 않는다. 다만, 해당 임포팅들은 별도의 gradle 종속성으로 이관됐다. 코틀린 멀티플랫폼 gradle 플러그인을 build.gradle.kts
에 추가하면 표준 라이브러리와 같이 임포팅은 프로젝트에 자동으로 포함된다.
필요한 조정은 자동으로 처리되기 때문에 수동 작업이나 마이그레이션은 하지 않아도 된다.
코틀린 1.9.0은 신규 gradle 컴파일러 옵션을 제공한다.
코틀린 1.7.0에서 KotlinCompile
의 classpath
에 대한 지원 중단에 대해 언급한 바가 있다. 지원 중단 수준은 1.8.0에서 ERROR
로 상향 조정되었고, 이번 업데이트에서 classpath
속성을 제거했다. 이제 컴파일에 필요한 라이브러리 목록은 libraries
에 입력해야 한다.
코틀린 gradle 플러그인은 opt-ins와 컴파일러 프로그레시브 모드를 위한 신규 속성을 제공한다.
optIn
속성을 사용해 optIn.set(listOf(a, b, c))
와 같은 문자열 목록의 전달이 가능하다.progressiveMode.set(true)
로 프로그레시브 모드를 활성화할 수 있다.코틀린 1.9.0부터 코틀린 kotlin
환경 설정 블록 내에서 compilerOptions
블록을 사용할 수 있다.
kotlin {
compilerOptions {
jvmTarget.set(JVM.Target_11)
}
}
compilerOptions
블록은 컴파일러 옵션을 구성하는 것을 간편화를 돕는다. 그러나 몇 가지 주의 사항이 있다.
android {
kotlinOptions {}
}
android.kotlinOptions
와 kotlin.compilerOptions
환경 설정 블록은 서로 덮어쓴다. 최종적으로 빌드 파일에서 두 설정 중 가장 마지막으로 설정된 블록이 적용(빌드 시, 빌드 파일을 순차적으로 읽어들이기 때문)된다.moduleName
이 프로젝트 레벨에서 설정된 경우 컴파일러로 전달할 때 값이 변경될 가능성이 있다. 메인 컴파일은 예외이지만, 테스트 소스와 같이 다른 유형의 경우에는 코틀린 gradle 플러그인에 의해 _test
접미사가 붙는다.tasks.withType<KotlinJvmCompile>().configureEach {}
(혹은 tasks.named<KotlinJvmCompile>("compileKotlin") {}
) 내부의 구성은 kotlin.compilerOptions
와 ndroid.kotlinOptions
를 모두 덮어쓴다.kotlin/native 모듈명 컴파일러 옵션을 코틀린 gradle 플러그인에서 쉽게 사용할 수 있다.
해당 옵션은 컴파일 모듈명을 지정하며 objective-c로 내보낼 때 선언에 이름 접두사를 추가하는데 사용할 수도 있다.
gradle 빌드 파일의 compilerOptions
블록에서 모듈명을 직접 설정할 수 있다.
tasks.named<org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile>("compileKotlinLinuxX64") {
compilerOptions {
moduleName.set("my-module-name")
}
}
코틀린 1.9.0에 공식 라이브러리를 위한 별도의 컴파일러 플러그인을 도입했다. 이전에는 컴파일러 플러그인이 해당 gradle 플러그인에 내장되어 있어, 컴파일러 플러그인이 gradle 빌드의 코틀린 런타임 버전보다 높은 코틀린 버전으로 컴파일된 경우에 호환성 문제가 발생할 수 있었다.
이제 컴파일러 플러그인이 별도의 종속성으로 추가되므로 더 이상 구 버전의 gradle과의 호환성 문제에 부딪힐 일은 없을 것이다. 새로운 접근법의 다른 장점은 새로운 컴파일러 플러그인을 바젤과 같이 타 빌드 시스템과 함께 사용이 가능하다는 점이다.
아래는 maven central에 게시 중인 신규 컴파일러 플러그인이다.
각 플러그인은 -embeddable
버전이 있다. 예를 들어, kotlin-allopen-compiler-plugin-embeddable
은 kotlin-compiler-embeddable
아티팩트와 함께 동작하도록 설계됐다. 이것이 스크립트 아티팩트의 기본 옵션이다.
gradle은 이러한 플러그인들을 컴파일러 인자로 추가하여 기존 프로젝트를 수정할 필요가 없다.
코틀린 1.9.0부터 지원되는 최소 안드로이드 gradle 플러그인의 버전은 4.2.2이다.
코틀린 gradle 플러그인과 gradle 버전의 호환성에 관래서는 이 문서를 참고하길 바란다.
1.9.0 이전에는 kapt 컴파일러 플러그인이 코틀린 컴파일 작업의 구성된 인스턴스를 요청하여 eager task(즉시 생성되는 작업)을 생성했다. 이러한 동작은 1.9.0에 수정되었으며 build.gradle.kts
에 기본 환경 설정을 사용하는 경우 설정은 해당 변경 사항의 영향을 받지 않는다.
사용자 지정 환경 설정을 사용하면 설정에 악영향을 미친다. 예를 들어 gradle의 tasks api를 사용해 KotlinJvmCompile
작업을 수정한 경우 빌드 스크립트에서 KaptGenerateStubs
작업 또한 유사하게 수정해야 한다.
예를 들어 스크립트에 KotlinJvmCompile
작업에 대해 아래와 같은 설정이 있는 경우
tasks.named<KotlinJvmCompile>("compileKotlin") {
// 사용자 지정 설정
}
이 경우 KaptGenerateStubs
작업의 일부로, 동일한 수정이 포함되도록 해야 한다.
tasks.named<KaptGenerateStubs>("kaptGenerateStubs") {
// 사용자 지정 설정
}
자세한 내용은 YouTrack ticket을 참고하길 바란다.
코틀린 1.9.0 이전에는 자바와 코틀린 간의 jvm 대상 비호환성 탐지를 조정하는 방법은 프로젝트의 루트 디렉토리의 gradle.properties
에서 kotlin.jvm.target.validation.mode=ERROR
를 설정하는 방법 1가지 밖에 없었다.
1.9.0에서는 build.gradle.kts
에서 task 레벨에서도 설정이 가능하다.
tasks.named<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile>("compileKotlin") {
jvmTargetValidationMode.set(org.jetbrains.kotlin.gradle.dsl.jvm.JvmTargetValidationMode.WARNING)
}
코틀린 1.9.0에서는 표준 라이브러리에 대한 몇 가지 큰 개선 사항이 있다.
개방형 범위 연산자인 ..<
는 코틀린 1.7.20에 처음 도입되어 1.8.0에 안정화가 되었다. 1.9.0에서는 개방형 범위를 다루는 표준 라이브러리 api도 안정화됐다.
..<
연산자는 개방 범위가 선언된 경우 이해가 쉬워진다. 오히려 until
중위 함수를 사용하면 상한값이 포함된 것으로 오해하는 실수를 쉽게 범할 수 있다.
아래는 until
함수를 사용한 예시이다.
fun main() {
for (number in 2 until 10) {
if (number % 2 == 0) {
print("$number ")
}
}
// 2 4 6 8
}
그리고 아래는 ..<
연산자를 사용한 예시이다.
fun main() {
for (number in 2..<10) {
if (number % 2 == 0) {
print("$number ")
}
}
// 2 4 6 8
}
인텔리제이 2023.1.1부터는 ..<
연산자의 사용 가능한 경우를 강조하는 새로운 코드 검사 기능이 제공되며 해당 연산자로 수행 가능한 작업에 대한 내용은 What's new in Kotlin 1.7.20를 참고하길 바란다.
1.3.50부터 신규 시간 특정 api에 대해 미리 소개한 바가 있다. 해당 api의 지속 시간 부분은 1.6.0에서 안정화됐고, 1.9.0에서 남은 시간을 측정하는 api도 안정화됐다.
구 api에서는 measureTimeMillis
함수와 measureNanoTime
함수가 제공되고 있었다만, 이것들은 직관적인 사용이 가능한 것이 아니다. 둘 다 다른 단위의 시간을 측정하는 것은 틀림없지만, measureTimeMillisis
가 시간을 측정하기 위해 wall clock(실제 시간을 측정하는 시계를 뜻하며, 현재 시간을 알아내거나 경과 시간을 측정하는데 사용되는 시계가 이에 해당)을 사용해 시간을 측정하고, measureNanoTime
은 단조 시간 소스를 사용(사실 둘 다 명확하지 않다고 한다)한다.
신규 시간 api는 상술한 문제와 기타 문제를 해결하여 api를 좀 더 용이하게 사용할 수 있게 한다.\
신규 시간 api를 사용하면 좀 더 쉽게 아래의 동작을 수행할 수 있다.
코드 블록의 실행 시간을 측정하려면 measureTime
인라인 함수를 사용하면 된다.
코드 블록의 실행 시간을 측정하고 코드 블록의 결과를 리턴하려면 measureTimedValue
인라인 함수를 사용하면 된다.
기본적으로 두 함수 모두 단조 시간 소스를 사용한다. 실경과 시간 소스를 사용하고자 해도 가능하다. 예를 들어, 안드로이드에서는 기본 시간 소스인 System.nanoTime()
이 단말기가 활성화된 시간 동안에만 계산을 한다. 디바이스가 deep sleep 모드에 들어가면 시간 계산이 중단되기 때문에 디바이스가 절전된 상태에서도 시간을 계산하려면 SystemClock.elapsedRealtimeNanos()
를 사용하면 된다.
object RealtimeMonotonicTimeSource : AbstractLongTimeSource(DurationUnit.NANOSECONDS) {
override fun read(): Long = SystemClock.elapsedRealtimeNanos()
}
특정 시점을 표시하려면 TimeSource
인터페이스와 markNow()
함수를 사용하여 TimeMark
를 생성하면 된다. 동일한 시간 소스에서 TimeMark
사이의 차이를 측정하려면 뺄셈 연산자-
를 사용하면 된다.
import kotlin.time.*
fun main() {
val timeSource = TimeSource.Monotonic
val mark1 = timeSource.markNow()
Thread.sleep(500) // 0.5초 대기
val mark2 = timeSource.markNow()
repeat(4) { n ->
val mark3 = timeSource.markNow()
val elapsed1 = mark3 - mark1
val elapsed2 = mark3 - mark2
println("Measurement 1.${n + 1}: elapsed1=$elapsed1, elapsed2=$elapsed2, diff=${elapsed1 - elapsed2}")
}
// 시간 표시를 서로 비교하는 것도 가능하다.
println(mark2 > mark1) // mark2가 mark2보다 늦게 캡쳐됐기 때문에 true가 출력된다.
}
특정 시점이 지났는지 혹은 시간이 초과됐는지 확인하려면 hasPassNow()
및 hasNotPassNow()
확장 함수를 사용하면 된다.
import kotlin.time.*
import kotlin.time.Duration.Companion.seconds
fun main() {
val timeSource = TimeSource.Monotonic
val mark1 = timeSource.markNow()
val fiveSeconds: Duration = 5.seconds
val mark2 = mark1 + fiveSeconds
// 아직 5초가 지나지 않은 시점
println(mark2.hasPassedNow())
// false
// 6초 대기
Thread.sleep(6_000)
println(mark2.hasPassedNow())
// true
}
kotlin/native를 위한 표준 라이브러리가 지속적으로 업데이트됨에 따라 표준 라이브러리가 적절한 조건을 만족하는지 검토가 필요하다고 판단되어 기존의 공개 서명(라이브러리나 API의 외부에서 접근 가능한 함수, 클래스, 인터페이스 등의 구성 요소)을 주의깊게 검토했다. 각 서명에 대해 아래와 같은 사항을 고려했다.
이러한 사항을 바탕으로 아래와 같은 결정을 내렸다.
private
으로 표시기존이 서명이 다음과 같은 경우
WARNING
레벨로 격하된다. 인텔리제이에서 코드 검사 시 자동으로 대체하는 것이 좋다.WARNING
이 된다.여기에 검토 결과 전부는 아니지만, 몇 가지 강조점이 있다.
kotlinx.cinterop
의 실험적 지정했으며 해당 패키지의 사용을 위해 다른 opt-in이 필요하다.Worker
클래스 및 관련 api의 사용 중단BitSet
클래스 사용 중단kotlin.native.internal
패키지의 모든 public
api를 private
로 전환하거나 타 패키지로 이관양질의 api 유지하기 위해 kotlinx.cinterop
을 실험적인 상태로 유지하기로 결정했다. kotlinx.cinterop
의 검증은 철저히 진행됐지만, 아직 완벽하지 않은 부분이 남아있기 때문이다. 해당 api에 대해 아직은 프로젝트에서 사용하는 특정 부분에 한하여 사용하는 것을 권장한다. 이렇게 하면 api를 안정화하는 과정에서의 마이그레이션 작업이 더 용이해질 것이다.
C 스타일의 포인터와 외부 api를 사용하려면 @OptIn(ExperimentalForeignApi)
으로 opt-in해야 하며, 그렇지 않으면 코드는 컴파일되지 않는다.
objective-c/swift 상호 운용성을 다루는 kotlinx.cinterop
의 나머지 부분을 사용하려면 @OptIn(BetaInteropApi)
에서 선택해야 한다. opt-in 없이 해당 api를 사용하려면 코드가 컴파일은 되지만 컴파일러가 해당 동작에 대해 명확한 설명과 함께 경고를 생성한다.
해당 어노테이션에 대한 자세한 정보는 Annotations.kt
의 소스 코드를 참고하길 바란다.
또한, 이번 검토에 대한 모든 변경 내용에 대한 자세한 내용은 YouTrack 티켓을 참고하길 바란다.
var
속성에 @Volatile
어노테이션을 사용하면 백킹 필드가 표시되므로 필드에 대한 읽기/쓰기는 원자적인 상태가 되며 쓰기는 항상 다른 스레드에서 볼 수 있도록 처리된다.
1.8.20 이전에는 kotlin.jvm.Volatile
어노테이션을 공통 표준 라이브러리에서만 사용할 수 있었다. 다만, 해당 어노테이션은 jvm에서만 유효하고, 타 플랫폼에서 사용하면 무시되어 오류가 발생했다.
1.8.20에서는 실험적인 공통 어노테이션인 kotlin.concurrent.Volatile
을 도입했다. 해당 어노테이션은 jvm과 kotlin/native에서 미리 확인이 가능하다.
1.9.0에서는 kotlin.concurrent.Volatile
가 안정화됐다. 멀티플랫폼 프로젝트에서 kotlin.jvm.Volatile
을 사용 중이라면, kotlin.concurrent.Volatile
로 마이그레이션하는 것을 권장한다.
1.9.0 이전에는 각 플랫폼 별로 정규 표현식 매치에서 정규 표현식 캡쳐 그룹을 얻기 위한 고유 확장 함수는 있었으나, 공통 함수는 존재하지 않았다. 1.8.0 이전에는 공통 함수를 가질 수 없었는데 이는 표준 라이브러리가 아직 jvm 1.6과 1.7을 지원했기 때문이다.
1.8.0부터 표준 라이브러리는 jvm 1.8로 컴파일 됐으며, 1.9.0에서는 정규 표현식 매치의 그룹 이름을 사용해 해당 그룹의 내용을 검색하는데 사용하는 표준 함수 groups
가 추가됐다. groups
는 특정 캡쳐 그룹에 속하는 정규 표현식 매치 결과에 접근하고자 할 때 유용하게 사용할 수 있다.
아래는 세 캡쳐 그룹(도시, 주, 지역 코드)을 포함하는 정규식의 예제이다. 해당 그룹 이름을 사용하여 일치하는 값에 액세스할 수 있다.
fun main() {
val regex = """\b(?<city>[A-Za-z\s]+),\s(?<state>[A-Z]{2}):\s(?<areaCode>[0-9]{3})\b""".toRegex()
val input = "Coordinates: Austin, TX: 123"
val match = regex.find(input)!!
println(match.groups["city"]?.value)
// Austin
println(match.groups["state"]?.value)
// TX
println(match.groups["areaCode"]?.value)
// 123
}
코틀린 1.9.0에는 신규 createParentDirectories()
확장 함수가 추가됐다. 해당 함수를 사용하여 필요한 모든 상위 디렉토리에서 파일을 생성할 수 있다. createParentDirectories()
에 파일 경로를 제공하면 해당 경로에 상위 디렉토리의 존재 여부를 확인하고 이미 존재할 경우에는 아무 동작도 하지 않지만, 그렇지 않으면 자동으로 생성한다.
createParentDirectories()
는 파일을 복사할 때 유용하게 사용할 수 있다. 아래와 같이 copyToRecursively()
함수와 같이 사용 가능하다.
sourcePath.copyToRecursively(
destinationPath.createParentDirectories(),
followLinks = false
)
신규 HexFormat
클래스와 관련 확장 함수들은 현재 실험적 기능으로, 사용하기 위해서는 @OptIn(ExperimentalStdlibApi::class)
어노테이션을 사용하거나 컴파일 인자로 -opt-in=kotlin.ExperimentalStdlibApi
를 사용해야 한다.
1.9.0에서는 HexFormat
클래스와 관련된 확장 함수가 실험적 기능으로 제공된다. 해당 기능은 숫자 값과 16진수 문자열 간의 변환을 한다. 구체적으로, 확장 함수를 사용하여 16진수 문자열과 ByteArray
혹은 타 숫자 타입(Int
, Short
, Long
) 간의 변환이 가능하다.
예를 들어 아래와 같이 사용할 수 있다.
println(93.toHexString()) // "0000005d"
HexFormat
클래스에는 HexFormat{}
빌더로 구성 가능한 서식 옵션이 포함되어 있다.
ByteArray
와 함께 작업하려는 경우 아래와 같은 옵션이 존재하며, 이는 속성으로 구성이 가능하다.
옵션 | 설명 |
---|---|
upperCase | 16진수 숫자의 대소문자 여부를 설정한다. 기본적으로 upperCase = false 즉, 소문자로 설정되어 있다. |
bytes.bytesPerLine | 한 줄당 최대 바이트 수 |
bytes.bytesPerGroup | 그룹당 최대 바이트 수 |
bytes.bytesSeparator | 바이트 사이의 구분자로, 기본적으로 구분자는 없다. |
bytes.bytesPrefix | 각 바이트의 두 자리 16진수 표현 바로 앞에 있는 문자열로, 기본적으로 아무것도 없다. |
bytes.bytesSuffix | 각 바이트의 두 자리 16진수 표현 바로 다음에 오는 문자열로, 기본적으로 아무것도 없다. |
아래와 같이 사용할 수 있다.
val macAddress = "001b638445e6".hexToByteArray()
// HexFormat{} 빌더를 사용해 16진수 문자열을 콜론으로 구분
println(macAddress.toHexString(HexFormat { bytes.byteSeparator = ":" }))
// "00:1b:63:84:45:e6"
// HexFormat{} 빌더로 아래의 작업을 수행
// * 16진수 문자열을 대문자로 변환
// * 바이트를 쌍으로 그룹화
// * 점으로 구분
val threeGroupFormat = HexFormat { upperCase = true; bytes.bytesPerGroup = 2; bytes.groupSeparator = "." }
println(macAddress.toHexString(threeGroupFormat))
// "001B.6384.45E6"
숫자형 타입을 다루는 경우, 아래와 같은 옵션들이 존재하며 이들은 속성을 통해 구성 가능하다.
옵션 | 설명 |
---|---|
number.prefix | 16진수 문자열의 접두사, 기본값은 없다. |
number.suffix | 16진수 문자열의 접미사, 기본값은 없다. |
number.removeLeadingZeros | 16진수 문자열에서 선행하는 0의 제거 여부, 기본적으로 number.removeLeadingZeros = false 즉, 제거하지 않는다. |
아래와 같이 사용 가능하다.
// HexFormat{} 빌더를 사용해 접두사가 "0x"인 16진수 구문을 해석
println("0x3a".hexToInt(HexFormat { number.prefix = "0x" })) // "58"
코틀린 문서에 몇 가지 큰 수정이 있었다.
인텔리제이 2022.3.3 및 2023.1.1에서는 코틀린 플러그인을 자동으로 1.9.0으로 업데이트하는 것을 제안한다. 2023.2에서는 코틀린 1.9.0 플러그인이 자동으로 포함될 예정이다.
안드로이드 스튜디오 Giraffe(223)와 Hedgehog(231)은 곧 출시될 릴리즈에서 코틀린 1.9.0을 지원한다.
신규 command-line 컴파일러는 github release에서 다운로드할 수 있다.
코틀린 아티팩트와 종속성을 다운로드하려면 setting.gradle(.kts)
를 업데이트하여 maven central 저장소를 사용해야 한다.
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
}
저장소가 지정되지 않은 경우 gradle은 현재 서비스 종료 상태인 jcenter 저장소를 사용하게 되고, 이는 코틀린 아티팩트에 관련하여 문제를 일으킬 수 있다.
코틀린 1.9.0은 기능 업데이트이기 때문에, 이전 버전으로 작성된 코드와 호환되지 않을 수 있다. 코틀린 1.9.0에 대한 자세한 변경 사항은 코틀린 1.9.0 호환성 가이드에서 확인 가능하다.
개발자로서 배울 점이 많은 글이었습니다. 감사합니다.