Kotlin 1.8.20의 새로운 기능(What's new in Kotlin 1.8.20)

Kotlin 1.8.20의 새로운 기능(What's new in Kotlin 1.8.20)

Kotlin 1.8.20이 나왔어요. 이번 릴리스에서 가장 큰 변화들부터 소개할게요.

출처: Kotlin 1.8.20의 새로운 기능

본문

출시일: 2023년 4월 25일

  • 새로운 Kotlin K2 컴파일러 업데이트
  • 새로운 실험용 Kotlin/Wasm 타깃
  • Gradle에서 기본 적용되는 새 JVM 증분 컴파일
  • Kotlin/Native 타깃 업데이트
  • Kotlin Multiplatform에서 Gradle 컴포짓 빌드(composite build) 프리뷰
  • Xcode에서 Gradle 오류 출력 개선
  • 표준 라이브러리의 AutoCloseable 인터페이스 실험용 지원
  • 표준 라이브러리의 Base64 인코딩 실험용 지원

이 변경 사항에 대한 짧은 개요는 다음 동영상에서도 확인할 수 있어요.

Kotlin의 릴리스 주기에 대한 자세한 내용은 Kotlin 릴리스 과정에서 확인할 수 있어요.

IDE 지원

1.8.20을 지원하는 Kotlin 플러그인은 다음 환경에서 사용할 수 있어요.

IDE 지원 버전
IntelliJ IDEA 2022.2.x, 2022.3.x, 2023.1.x
Android Studio Flamingo (222)

Kotlin 아티팩트와 의존성을 제대로 다운로드하려면 Gradle 설정이 Maven Central 저장소를 사용하도록 구성해 주세요.

새로운 Kotlin K2 컴파일러 업데이트

Kotlin 팀은 K2 컴파일러를 계속 안정화하고 있어요. Kotlin 1.7.0 발표에서 언급했듯이 여전히 Alpha 단계예요. 이 릴리스는 K2 Beta를 향한 길에 또 다른 개선을 더해요.

이 1.8.20 릴리스부터 Kotlin K2 컴파일러는:

새 컴파일러와 그 이점에 대해 더 알아보고 싶다면 다음 동영상을 확인하세요.

Kotlin K2 컴파일러를 활성화하는 방법

Kotlin K2 컴파일러를 활성화하고 테스트하려면 다음 컴파일러 옵션으로 새 언어 버전을 사용하면 돼요.

-language-version 2.0

build.gradle(.kts) 파일에서 지정할 수 있어요.

kotlin {
   sourceSets.all {
       languageSettings {
           languageVersion = "2.0"
       }
   }
}

이전의 -Xuse-k2 컴파일러 옵션은 deprecated됐어요.

새 K2 컴파일러의 Alpha 버전은 JVM과 JS IR 프로젝트에서만 동작해요. Kotlin/Native나 다른 multiplatform 프로젝트는 아직 지원하지 않아요.

새 K2 컴파일러에 대한 피드백 남기기

여러분의 피드백을 정말 환영해요!

언어

Kotlin이 계속 발전하면서 1.8.20에서 새로운 언어 기능의 프리뷰 버전을 소개해요.

  • Enum 클래스 values 함수의 현대적이고 성능 좋은 대체
  • 데이터 클래스(data class)와 대칭을 이루는 데이터 객체(data object)
  • 인라인 클래스에서 본문이 있는 보조 생성자에 대한 제한 완화

Enum 클래스 values 함수의 현대적이고 성능 좋은 대체

이 기능은 Experimental이에요. 언제든 제거되거나 변경될 수 있어요. Opt-in이 필요하며(아래 세부사항 참고) 평가 목적으로만 사용해 주세요. YouTrack에서 피드백을 기다릴게요.

Enum 클래스에는 정의된 enum 상수 배열을 반환하는 합성(synthetic) values() 함수가 있어요. 그런데 배열을 사용하면 Kotlin과 Java에서 숨겨진 성능 문제가 생길 수 있어요. 게다가 대부분의 API가 컬렉션을 사용하므로 결국 변환이 필요해요. 이런 문제를 해결하기 위해 Enum 클래스에 entries 프로퍼티를 도입했는데, values() 함수 대신 이것을 사용해야 해요. entries 프로퍼티를 호출하면 정의된 enum 상수의 미리 할당된 불변 리스트를 반환해요.

values() 함수는 계속 지원되지만, entries 프로퍼티를 사용할 것을 권장해요.

enum class Color(val colorName: String, val rgb: String) {
    RED("Red", "#FF0000"),
    ORANGE("Orange", "#FF7F00"),
    YELLOW("Yellow", "#FFFF00")
}

@OptIn(ExperimentalStdlibApi::class)
fun findByRgb(rgb: String): Color? = Color.entries.find { it.rgb == rgb }
entries 프로퍼티를 활성화하는 방법

이 기능을 시험해 보려면 @OptIn(ExperimentalStdlibApi)으로 opt-in하고 -language-version 1.9 컴파일러 옵션을 활성화하면 돼요. Gradle 프로젝트에서는 build.gradle(.kts) 파일에 다음을 추가하면 돼요.

tasks
    .withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
    .configureEach {
        compilerOptions
            .languageVersion
            .set(
                org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
            )
    }
tasks
    .withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
    .configureEach {
        compilerOptions.languageVersion =
            org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
    }

IntelliJ IDEA 2023.1부터 이 기능에 opt-in했다면, 적절한 IDE 검사가 values()에서 entries로의 변환을 알려주고 빠른 수정(quick-fix)을 제안해요.

제안에 대한 자세한 내용은 KEEP 노트를 참고하세요.

데이터 클래스와 대칭을 이루는 데이터 객체 프리뷰

데이터 객체를 사용하면 싱글턴 의미와 깔끔한 toString() 표현을 가진 객체를 선언할 수 있어요. 다음 스니펫에서 객체 선언에 data 키워드를 추가하면 toString() 출력의 가독성이 어떻게 좋아지는지 볼 수 있어요.

package org.example
object MyObject
data object MyDataObject

fun main() {
    println(MyObject) // org.example.MyObject@1f32e575
    println(MyDataObject) // MyDataObject
}

특히 sealed 계층(예: sealed class 또는 sealed interface 계층)에서는 data objects가 아주 잘 어울려요. data class 선언과 함께 자연스럽게 사용할 수 있거든요. 다음 스니펫에서 EndOfFile을 일반 object 대신 data object로 선언하면, 수동으로 재정의하지 않아도 예쁜 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
}
데이터 객체의 의미

Kotlin 1.7.20의 첫 프리뷰 버전 이후로 데이터 객체의 의미가 정제됐어요. 컴파일러는 이제 그들을 위해 여러 편의 함수를 자동으로 생성해요.

toString

데이터 객체의 toString() 함수는 객체의 단순 이름(simple name)을 반환해요.

data object MyDataObject {
    val x: Int = 3
}

fun main() {
    println(MyDataObject) // MyDataObject
}
equals와 hashCode

data objectequals() 함수는 당신의 data object 타입을 가진 모든 객체가 동등하다고 간주되도록 보장해요. 대부분의 경우 런타임에는 데이터 객체 인스턴스가 하나뿐이겠지만(어쨌든 data object는 싱글턴을 선언하니까요), 런타임에 같은 타입의 다른 객체가 생성되는 경계 사례(예: java.lang.reflect를 통한 플랫폼 리플렉션이나, 내부적으로 이 API를 사용하는 JVM 직렬화 라이브러리를 사용할 때)에서도 객체들이 동등하게 취급되도록 보장해요.

data objects는 구조적으로(== 연산자 사용)만 비교하고, 절대 참조로(=== 연산자) 비교하지 마세요. 이렇게 해야 런타임에 데이터 객체 인스턴스가 여러 개 있을 때 함정을 피할 수 있어요. 다음 스니펫은 이 특정 경계 사례를 보여줘요.

import java.lang.reflect.Constructor

data object MySingleton

fun main() {
    val evilTwin = createInstanceViaReflection()

    println(MySingleton) // MySingleton
    println(evilTwin) // MySingleton

    // Even when a library forcefully creates a second instance of MySingleton, its `equals` method returns true:
    println(MySingleton == evilTwin) // true

    // Do not compare data objects via ===.
    println(MySingleton === evilTwin) // false
}

fun createInstanceViaReflection(): MySingleton {
    // Kotlin reflection does not permit the instantiation of data objects.
    // This creates a new MySingleton instance "by force" (i.e., Java platform reflection)
    // Don't do this yourself!
    return (MySingleton.javaClass.declaredConstructors[0].apply { isAccessible = true } as Constructor<MySingleton>).newInstance()
}

생성된 hashCode() 함수의 동작은 equals() 함수와 일관적이어서, data object의 모든 런타임 인스턴스가 같은 해시 코드를 가져요.

데이터 객체에는 copy와 componentN 함수가 없음

data objectdata class 선언은 함께 자주 쓰이고 몇 가지 유사점이 있지만, data object에는 생성되지 않는 함수들이 있어요.

data object 선언은 싱글턴 객체로 쓰이도록 의도됐기 때문에 copy() 함수가 생성되지 않아요. 싱글턴 패턴은 클래스의 인스턴스화를 단일 인스턴스로 제한하며, 인스턴스의 복사본을 만들 수 있게 하면 그 제한을 위반하게 돼요.

또한 data class와 달리 data object는 데이터 프로퍼티가 없어요. 그런 객체를 구조 분해(destructure)하려는 시도는 의미가 없으므로 componentN() 함수도 생성되지 않아요.

이 기능에 대한 피드백은 YouTrack에서 기다릴게요.

데이터 객체 프리뷰를 활성화하는 방법

이 기능을 시험해 보려면 -language-version 1.9 컴파일러 옵션을 활성화하면 돼요. Gradle 프로젝트에서는 build.gradle(.kts) 파일에 다음을 추가하면 돼요.

tasks
    .withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
    .configureEach {
        compilerOptions
            .languageVersion
            .set(
                org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
            )
    }
tasks
    .withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
    .configureEach {
        compilerOptions.languageVersion =
            org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
    }

인라인 클래스에서 본문이 있는 보조 생성자 제한 완화 프리뷰

이 기능은 Experimental이에요. 언제든 제거되거나 변경될 수 있어요. Opt-in이 필요하며(아래 세부사항 참고) 평가 목적으로만 사용해 주세요. YouTrack에서 피드백을 기다릴게요.

Kotlin 1.8.20은 인라인 클래스에서 본문이 있는 보조 생성자 사용에 대한 제한을 완화해요.

인라인 클래스는 명확한 초기화 의미를 갖기 위해 init 블록이나 보조 생성자가 없는 public 기본 생성자만 허용했어요. 그 결과 기본값을 캡슐화하거나 특정 제약이 있는 값을 나타내는 인라인 클래스를 만들 수 없었어요.

Kotlin 1.4.30이 init 블록 제한을 완화하면서 이런 문제가 해결됐어요. 이제 한 단계 더 나아가 본문이 있는 보조 생성자를 프리뷰 모드에서 허용해요.

@JvmInline
value class Person(private val fullName: String) {
    // Allowed since Kotlin 1.4.30:
    init { 
        check(fullName.isNotBlank()) {
            "Full name shouldn't be empty"
        }
    }

    // Preview available since Kotlin 1.8.20:
    constructor(name: String, lastName: String) : this("$name $lastName") {
        check(lastName.isNotBlank()) {
            "Last name shouldn't be empty"
        }
    }
}
본문이 있는 보조 생성자를 활성화하는 방법

이 기능을 시험해 보려면 -language-version 1.9 컴파일러 옵션을 활성화하면 돼요. Gradle 프로젝트에서는 build.gradle(.kts)에 다음을 추가하면 돼요.

tasks
    .withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
    .configureEach {
        compilerOptions
            .languageVersion
            .set(
                org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
            )
    }
tasks
    .withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
    .configureEach {
        compilerOptions.languageVersion =
            org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
    }

이 기능을 꼭 사용해 보고 모든 보고서를 YouTrack에 제출해서 Kotlin 1.9.0에서 기본값이 되도록 도와주세요.

Kotlin 인라인 클래스 개발에 대해 더 자세히 알고 싶다면 이 KEEP을 참고하세요.

새로운 Kotlin/Wasm 타깃

Kotlin/Wasm(Kotlin WebAssembly)이 이 릴리스에서 Experimental로 전환됐어요. Kotlin 팀은 WebAssembly를 유망한 기술로 보고, 여러분이 이를 사용해 Kotlin의 모든 이점을 얻을 더 나은 방법을 찾고 싶어 해요.

WebAssembly 바이너리 형식은 자체 가상 머신으로 실행되므로 플랫폼에 독립적이에요. 거의 모든 현대 브라우저가 이미 WebAssembly 1.0을 지원해요. WebAssembly를 실행할 환경을 설정하려면 Kotlin/Wasm이 타깃으로 하는 실험용 가비지 컬렉션 모드만 활성화하면 돼요. 자세한 지침은 여기에서 찾을 수 있어요: Kotlin/Wasm을 활성화하는 방법.

새 Kotlin/Wasm 타깃의 장점을 다음과 같이 강조하고 싶어요.

  • Kotlin/Wasm은 LLVM을 사용하지 않아도 되므로 wasm32 Kotlin/Native 타깃보다 컴파일 속도가 빨라요.
  • Wasm 가비지 컬렉션 덕분에 wasm32 타깃보다 JS와의 상호 운용성, 브라우저와의 통합이 더 쉬워요.
  • Wasm은 컴팩트하고 파싱하기 쉬운 바이트코드를 가지므로 Kotlin/JS와 JavaScript보다 애플리케이션 시작이 잠재적으로 더 빨라요.
  • Wasm은 정적 타입 언어이므로 Kotlin/JS와 JavaScript보다 애플리케이션 런타임 성능이 개선돼요.

1.8.20 릴리스부터 실험용 프로젝트에서 Kotlin/Wasm을 사용할 수 있어요. Kotlin/Wasm용 Kotlin 표준 라이브러리(stdlib)와 테스트 라이브러리(kotlin.test)를 기본 제공해요. IDE 지원은 향후 릴리스에서 추가될 예정이에요.

이 YouTube 동영상에서 Kotlin/Wasm에 대해 더 알아보세요.

Kotlin/Wasm을 활성화하는 방법

Kotlin/Wasm을 활성화하고 테스트하려면 build.gradle.kts 파일을 업데이트하면 돼요.

plugins {
    kotlin("multiplatform") version "1.8.20"
}

kotlin {
    wasm {
        binaries.executable()
        browser {
        }
    }
    sourceSets {
        val commonMain by getting
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test"))
            }
        }
        val wasmMain by getting
        val wasmTest by getting
    }
}

Kotlin/Wasm 예제가 있는 GitHub 저장소를 확인해 보세요.

Kotlin/Wasm 프로젝트를 실행하려면 타깃 환경의 설정을 업데이트해야 해요.

  • 버전 109의 경우: --js-flags=--experimental-wasm-gc 커맨드라인 인자로 애플리케이션을 실행해요.
  • 버전 110 이상의 경우:
    1. 브라우저에서 chrome://flags/#enable-webassembly-garbage-collection으로 이동해요.
    2. WebAssembly Garbage Collection을 활성화해요.
    3. 브라우저를 다시 시작해요.

버전 109 이상의 경우:

  1. 브라우저에서 about:config로 이동해요.
  2. javascript.options.wasm_function_referencesjavascript.options.wasm_gc 옵션을 활성화해요.
  3. 브라우저를 다시 시작해요.

버전 109 이상의 경우:

--js-flags=--experimental-wasm-gc 커맨드라인 인자로 애플리케이션을 실행해요.

Kotlin/Wasm에 대한 피드백 남기기

여러분의 피드백을 정말 환영해요!

Kotlin/JVM

Kotlin 1.8.20은 Java 합성 프로퍼티 참조의 프리뷰와 kapt 스텁(stub) 생성 태스크의 JVM IR 백엔드 기본 지원을 소개해요.

Java 합성 프로퍼티 참조 프리뷰

이 기능은 Experimental이에요. 언제든 제거되거나 변경될 수 있어요. 평가 목적으로만 사용해 주세요. YouTrack에서 피드백을 기다릴게요.

Kotlin 1.8.20은 Java 합성 프로퍼티에 대한 참조를 만들 수 있는 기능을 소개해요. 예를 들어 이런 Java 코드에 대해서 말이죠.

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

Kotlin은 항상 person.age처럼 쓰는 걸 허용해 왔는데, 여기서 age는 합성 프로퍼티예요. 이제 Person::ageperson::age 참조도 만들 수 있어요. name에도 모두 동일하게 적용돼요.

val persons = listOf(Person("Jack", 11), Person("Sofie", 12), Person("Peter", 11))
    persons
        // Call a reference to Java synthetic property:
        .sortedBy(Person::age)
        // Call Java getter via the Kotlin property syntax:
        .forEach { person -> println(person.name) }
Java 합성 프로퍼티 참조를 활성화하는 방법

이 기능을 시험해 보려면 -language-version 1.9 컴파일러 옵션을 활성화하면 돼요. Gradle 프로젝트에서는 build.gradle(.kts)에 다음을 추가하면 돼요.

tasks
    .withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
    .configureEach {
        compilerOptions
            .languageVersion
            .set(
                org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
            )
    }
tasks
    .withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
    .configureEach {
        compilerOptions.languageVersion =
            org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
    }

kapt 스텁 생성 태스크의 JVM IR 백엔드를 기본으로 지원

Kotlin 1.7.20에서 kapt 스텁 생성 태스크의 JVM IR 백엔드 지원을 도입했어요. 이 릴리스부터 이 지원이 기본으로 동작해요. 활성화하기 위해 gradle.propertieskapt.use.jvm.ir=true를 지정할 필요가 없어요. 이 기능에 대한 피드백은 YouTrack에서 기다릴게요.

Kotlin/Native

Kotlin 1.8.20은 지원되는 Kotlin/Native 타깃 변경, Objective-C와의 상호 운용성 변경, CocoaPods Gradle 플러그인 개선 등을 포함해요.

  • Kotlin/Native 타깃 업데이트
  • 레거시 메모리 매니저 deprecated
  • @import 지시문이 있는 Objective-C 헤더 지원
  • Cocoapods Gradle 플러그인의 link-only 모드 지원
  • UIKit에서 Objective-C 확장을 클래스 멤버로 가져오기
  • 컴파일러의 컴파일러 캐시 관리 재구현
  • Cocoapods Gradle 플러그인의 useLibraries() deprecated

Kotlin/Native 타깃 업데이트

Kotlin 팀은 Kotlin/Native가 지원하는 타깃 목록을 재검토하고, 타깃을 계층(tier)으로 나누고, 그중 일부를 Kotlin 1.8.20부터 deprecated하기로 결정했어요. 지원되고 deprecated된 타깃의 전체 목록은 Kotlin/Native 타깃 지원 섹션을 참고하세요.

다음 타깃은 Kotlin 1.8.20에서 deprecated됐고 1.9.20에서 제거될 예정이에요.

  • iosArm32
  • watchosX86
  • wasm32
  • mingwX86
  • linuxArm32Hfp
  • linuxMips32
  • linuxMipsel32

나머지 타깃은 Kotlin/Native 컴파일러에서 얼마나 잘 지원되고 테스트되는지에 따라 이제 세 가지 지원 계층이 있어요. 타깃은 다른 계층으로 이동할 수 있어요. 예를 들어 앞으로 iosArm64에 대한 완전한 지원을 제공하기 위해 최선을 다할 거예요. Kotlin Multiplatform에 중요하거든요.

라이브러리 작성자라면 이런 타깃 계층이 CI 도구에서 어떤 타깃을 테스트할지, 어떤 타깃을 건너뛸지 결정하는 데 도움이 돼요. Kotlin 팀은 kotlinx.coroutines 같은 공식 Kotlin 라이브러리를 개발할 때도 같은 접근 방식을 사용할 거예요.

이 변경의 이유에 대해 더 알아보려면 블로그 게시물을 확인해 보세요.

레거시 메모리 매니저 deprecated

1.8.20부터 레거시 메모리 매니저는 deprecated되고 1.9.20에서 제거될 예정이에요. 새 메모리 매니저는 1.7.20에서 기본으로 활성화됐고 계속 안정성 업데이트와 성능 개선을 받고 있어요.

여전히 레거시 메모리 매니저를 사용하고 있다면 gradle.properties에서 kotlin.native.binary.memoryModel=strict 옵션을 제거하고 마이그레이션 가이드에 따라 필요한 변경을 해 주세요.

새 메모리 매니저는 wasm32 타깃을 지원하지 않아요. 이 타깃도 이 릴리스부터 deprecated되며 1.9.20에서 제거될 예정이에요.

@import 지시문이 있는 Objective-C 헤더 지원

이 기능은 Experimental이에요. 언제든 제거되거나 변경될 수 있어요. Opt-in이 필요하며(아래 세부사항 참고) 평가 목적으로만 사용해 주세요. YouTrack에서 피드백을 기다릴게요.

Kotlin/Native는 이제 @import 지시문이 있는 Objective-C 헤더를 가져올 수 있어요. 이 기능은 자동 생성된 Objective-C 헤더를 가진 Swift 라이브러리나 Swift로 작성된 CocoaPods 의존성의 클래스를 사용할 때 유용해요.

예전에는 cinterop 도구가 @import 지시문을 통해 Objective-C 모듈에 의존하는 헤더를 분석하는 데 실패했어요. -fmodules 옵션을 지원하지 않았기 때문이에요.

Kotlin 1.8.20부터 @import가 있는 Objective-C 헤더를 사용할 수 있어요. 이렇게 하려면 정의 파일에서 compilerOpts-fmodules 옵션을 컴파일러에 전달하면 돼요. CocoaPods 통합을 사용한다면 pod() 함수의 구성 블록에서 cinterop 옵션을 이렇게 지정해요.

kotlin {
    ios()

    cocoapods {
        summary = "CocoaPods test library"
        homepage = "https://github.com/JetBrains/kotlin"

        ios.deploymentTarget = "13.5"

        pod("PodName") {
            extraOpts = listOf("-compiler-option", "-fmodules")
        }
    }
}

이것은 많은 사람이 기다리던 기능이에요. 이 기능이 향후 릴리스에서 기본이 되도록 YouTrack에서 피드백을 남겨주세요.

Cocoapods Gradle 플러그인의 link-only 모드 지원

Kotlin 1.8.20부터 동적 프레임워크와 함께 Pod 의존성을 cinterop 바인딩 생성 없이 링킹에만 사용할 수 있어요. 이는 cinterop 바인딩이 이미 생성된 경우에 유용할 수 있어요.

라이브러리와 앱, 두 모듈이 있는 프로젝트를 생각해 보세요. 라이브러리는 Pod에 의존하지만 프레임워크가 아니라 .klib만 생성해요. 앱은 라이브러리에 의존하고 동적 프레임워크를 생성해요. 이 경우 라이브러리가 의존하는 Pod와 이 프레임워크를 링크해야 하지만, 라이브러리에는 cinterop 바인딩이 이미 생성됐으므로 그건 필요 없어요.

이 기능을 활성화하려면 Pod에 대한 의존성을 추가할 때 linkOnly 옵션 또는 빌더 프로퍼티를 사용하면 돼요.

cocoapods {
    summary = "CocoaPods test library"
    homepage = "https://github.com/JetBrains/kotlin"

    pod("Alamofire", linkOnly = true) {
        version = "5.7.0"
    }
}

이 옵션을 정적 프레임워크와 함께 사용하면 Pod가 정적 프레임워크 링킹에 사용되지 않으므로 Pod 의존성이 완전히 제거돼요.

UIKit에서 Objective-C 확장을 클래스 멤버로 가져오기

Xcode 14.1부터 Objective-C 클래스의 일부 메서드가 카테고리 멤버로 옮겨졌어요. 그로 인해 다른 Kotlin API가 생성되었고, 이 메서드들은 메서드 대신 Kotlin 확장으로 가져와졌어요.

UIKit을 사용해 메서드를 재정의할 때 이로 인한 문제를 겪었을 수 있어요. 예를 들어 Kotlin에서 UIViewController를 서브클래싱할 때 drawRect()layoutSubviews() 메서드를 더는 재정의할 수 없게 됐었어요.

1.8.20부터 NSView와 UIView 클래스와 같은 헤더에 선언된 카테고리 멤버는 이 클래스들의 멤버로 가져와져요. 따라서 NSView와 UIView에서 서브클래싱하는 메서드를 다른 메서드처럼 쉽게 재정의할 수 있게 됐어요.

모든 것이 순조롭게 진행되면 이 동작을 모든 Objective-C 클래스에 기본으로 활성화할 계획이에요.

컴파일러의 컴파일러 캐시 관리 재구현

컴파일러 캐시의 발전 속도를 높이기 위해 컴파일러 캐시 관리를 Kotlin Gradle 플러그인에서 Kotlin/Native 컴파일러로 옮겼어요. 이는 컴파일 시간과 컴파일러 캐시 유연성에 관한 개선을 포함한 여러 중요한 개선 작업을 가능하게 해요.

문제가 생겨 기존 동작으로 돌아가야 한다면 kotlin.native.cacheOrchestration=gradle Gradle 프로퍼티를 사용하면 돼요.

YouTrack에서 피드백을 기다릴게요.

Cocoapods Gradle 플러그인의 useLibraries() deprecated

Kotlin 1.8.20은 정적 라이브러리용 CocoaPods 통합에서 사용되는 useLibraries() 함수의 deprecation 주기를 시작해요.

정적 라이브러리를 포함한 Pod에 대한 의존성을 허용하기 위해 useLibraries() 함수를 도입했어요. 하지만 시간이 지나면서 이 케이스는 매우 드물어졌어요. 대부분의 Pod는 소스로 배포되고, Objective-C 프레임워크나 XCFramework가 바이너리 배포의 일반적인 선택이 됐어요.

이 함수가 인기가 없고, Kotlin CocoaPods Gradle 플러그인 개발을 복잡하게 만드는 문제를 만들어냈기 때문에 deprecated하기로 결정했어요.

프레임워크와 XCFramework에 대한 자세한 내용은 최종 네이티브 바이너리 빌드를 참고하세요.

Kotlin Multiplatform

Kotlin 1.8.20은 Kotlin Multiplatform에 대한 다음 업데이트로 개발자 경험을 개선하려고 해요.

  • 소스 세트 계층 구조를 설정하는 새로운 접근 방식
  • Kotlin Multiplatform의 Gradle 컴포짓 빌드 지원 프리뷰
  • Xcode에서 Gradle 오류 출력 개선

소스 세트 계층 구조의 새로운 접근 방식

소스 세트 계층 구조의 새 접근 방식은 Experimental이에요. 예고 없이 향후 Kotlin 릴리스에서 변경될 수 있어요. Opt-in이 필요하며(아래 세부사항 참고) YouTrack에서 피드백을 기다릴게요.

Kotlin 1.8.20은 multiplatform 프로젝트에서 소스 세트 계층 구조를 설정하는 새로운 방식, 즉 기본 타깃 계층 구조(default target hierarchy)를 제공해요. 새 접근 방식은 설계 결함이 있는 ios 같은 타깃 단축키(shortcut)를 대체하기 위한 것이에요.

기본 타깃 계층 구조의 아이디어는 간단해요. 프로젝트가 컴파일되는 모든 타깃을 명시적으로 선언하면, Kotlin Gradle 플러그인이 지정된 타깃을 기반으로 공유 소스 세트를 자동으로 생성해요.

프로젝트 설정하기

간단한 multiplatform 모바일 앱의 예를 생각해 보세요.

@OptIn(ExperimentalKotlinGradlePluginApi::class)
kotlin {
    // Enable the default target hierarchy:
    targetHierarchy.default()

    android()
    iosArm64()
    iosSimulatorArm64()
}

기본 타깃 계층 구조를 가능한 모든 타깃과 그 공유 소스 세트의 템플릿으로 생각할 수 있어요. 코드에서 최종 타깃 android, iosArm64, iosSimulatorArm64를 선언하면 Kotlin Gradle 플러그인이 템플릿에서 적합한 공유 소스 세트를 찾아서 생성해줘요. 결과 계층 구조는 다음과 같아요.

초록색 소스 세트는 실제로 생성되어 프로젝트에 존재하는 것이고, 기본 템플릿의 회색 것은 무시돼요. 보시다시피 Kotlin Gradle 플러그인은 프로젝트에 watchOS 타깃이 없으므로 예를 들어 watchos 소스 세트를 생성하지 않아요.

watchosArm64 같은 watchOS 타깃을 추가하면 watchos 소스 세트가 생성되고, apple, native, common 소스 세트의 코드도 watchosArm64로 컴파일돼요.

기본 타깃 계층 구조의 완전한 도식은 문서에서 찾을 수 있어요.

이 예에서 applenative 소스 세트는 iosArm64iosSimulatorArm64 타깃에만 컴파일돼요. 따라서 이름과 달리 두 소스 세트는 전체 iOS API에 접근할 수 있어요. 이는 native 같은 소스 세트에게는 직관에 반할 수 있어요. 모든 네이티브 타깃에서 사용 가능한 API만 접근할 수 있으리라 기대할 수도 있으니까요. 이 동작은 향후 변경될 수 있어요.

단축키를 왜 대체하나요

소스 세트 계층 구조를 만드는 일은 장황하고 오류가 발생하기 쉬우며 초보자에게 불친절할 수 있어요. 이전 해결책은 계층 구조의 일부를 자동으로 만들어주는 ios 같은 단축키를 도입하는 것이었어요. 하지만 단축키를 다루다 보니 큰 설계 결함이 있음이 드러났어요. 바로 변경하기 어렵다는 점이에요.

예를 들어 ios 단축키는 iosArm64iosX64 타깃만 만들어요. 이는 혼란스러울 수 있고, iosSimulatorArm64 타깃도 필요한 M1 기반 호스트에서 작업할 때 문제를 일으킬 수 있어요. 그런데 iosSimulatorArm64 타깃을 추가하는 것은 사용자 프로젝트에 매우 파괴적인 변경이 될 수 있어요.

  • iosMain 소스 세트에서 사용하는 모든 의존성이 iosSimulatorArm64 타깃을 지원해야 해요. 그렇지 않으면 의존성 해석이 실패해요.
  • iosMain에서 사용하는 일부 네이티브 API가 새 타깃을 추가하면 사라질 수 있어요(iosSimulatorArm64의 경우에는 그럴 가능성이 낮지만요).
  • 일부 경우, 예를 들어 Intel 기반 MacBook에서 작은 개인 프로젝트를 작성할 때는 이 변경이 필요하지 않을 수도 있어요.

단축키가 계층 구조 구성의 문제를 해결하지 못한다는 게 분명해졌고, 그래서 어느 시점부터 새 단축키 추가를 중단했어요.

기본 타깃 계층 구조는 언뜻 단축키와 비슷해 보일 수 있지만 결정적인 차이가 있어요. 사용자가 타깃 집합을 명시적으로 지정해야 한다는 점이에요. 이 집합은 프로젝트가 어떻게 컴파일되고 게시되며 의존성 해석에 어떻게 참여하는지를 정의해요. 이 집합이 고정되어 있으므로 Kotlin Gradle 플러그인의 기본 구성 변경이 생태계에 훨씬 적은 혼란을 일으켜야 하고, 도구 지원 마이그레이션을 제공하기도 훨씬 쉬워져요.

기본 계층 구조를 활성화하는 방법

이 새 기능은 Experimental이에요. Kotlin Gradle 빌드 스크립트의 경우 @OptIn(ExperimentalKotlinGradlePluginApi::class)으로 opt-in해야 해요.

자세한 내용은 계층적 프로젝트 구조를 참고하세요.

피드백 남기기

이것은 multiplatform 프로젝트에 상당한 변경이에요. 이를 더 좋게 만드는 데 도움이 되도록 피드백을 남겨주세요.

Kotlin Multiplatform의 Gradle 컴포짓 빌드 지원 프리뷰

이 기능은 Kotlin Gradle Plugin 1.8.20부터 Gradle 빌드에서 지원됐어요. IDE 지원에는 IntelliJ IDEA 2023.1 Beta 2 (231.8109.2) 이상과, 어떤 Kotlin IDE 플러그인이든 Kotlin Gradle 플러그인 1.8.20이 필요해요.

1.8.20부터 Kotlin Multiplatform이 Gradle 컴포짓 빌드를 지원해요. 컴포짓 빌드를 사용하면 별도의 프로젝트 빌드나 같은 프로젝트의 일부를 단일 빌드에 포함할 수 있어요.

몇 가지 기술적 난관 때문에 Kotlin Multiplatform과 함께 Gradle 컴포짓 빌드를 사용하는 것은 부분적으로만 지원됐어요. Kotlin 1.8.20에는 더 다양한 프로젝트에서 동작해야 하는 개선된 지원의 프리뷰가 들어 있어요. 시험해 보려면 gradle.properties에 다음 옵션을 추가하면 돼요.

kotlin.mpp.import.enableKgpDependencyResolution=true

이 옵션은 새 가져오기(import) 모드의 프리뷰를 활성화해요. 컴포짓 빌드 지원 외에도, 가져오기를 더 안정적으로 만들기 위한 큰 버그 수정과 개선을 포함했으므로 multiplatform 프로젝트에서 더 매끄러운 가져오기 경험을 제공해요.

알려진 문제

아직 더 안정화가 필요한 프리뷰 버전이라 가져오는 과정에서 몇 가지 문제를 만날 수 있어요. Kotlin 1.8.20의 최종 릴리스 전에 고치려고 계획 중인 알려진 문제는 다음과 같아요.

  • 아직 IntelliJ IDEA 2023.1 EAP용 Kotlin 1.8.20 플러그인이 없어요. 그래도 Kotlin Gradle 플러그인 버전을 1.8.20으로 설정하고 이 IDE에서 컴포짓 빌드를 시험해 볼 수는 있어요.
  • 프로젝트에 지정된 rootProject.name이 있는 빌드가 포함되면 컴포짓 빌드가 Kotlin 메타데이터를 해석하지 못할 수 있어요. 해결 방법과 세부사항은 이 Youtrack 이슈를 참고하세요.

이 기능을 꼭 사용해 보고 모든 보고서를 YouTrack에 제출해서 Kotlin 1.9.0에서 기본값이 되도록 도와주세요.

Xcode에서 Gradle 오류 출력 개선

Xcode에서 multiplatform 프로젝트를 빌드하는 데 문제가 있었다면 "Command PhaseScriptExecution failed with a nonzero exit code" 오류를 겪었을 수 있어요. 이 메시지는 Gradle 호출이 실패했음을 알리지만, 문제를 파악하기에는 별로 도움이 되지 않아요.

Kotlin 1.8.20부터 Xcode는 Kotlin/Native 컴파일러의 출력을 파싱할 수 있어요. 게다가 Gradle 빌드가 실패하면 Xcode에서 근본 원인 예외의 추가 오류 메시지를 볼 수 있어요. 대부분의 경우 근본 문제를 식별하는 데 도움이 될 거예요.

새 동작은 Xcode 통합을 위한 표준 Gradle 태스크, 예를 들어 multiplatform 프로젝트의 iOS 프레임워크를 Xcode의 iOS 애플리케이션에 연결할 수 있는 embedAndSignAppleFrameworkForXcode 같은 태스크에서 기본으로 활성화돼요. kotlin.native.useXcodeMessageStyle Gradle 프로퍼티로 활성화(또는 비활성화)할 수도 있어요.

Kotlin/JavaScript

Kotlin 1.8.20은 TypeScript 정의를 생성하는 방식을 바꿔요. 디버깅 경험을 개선하기 위한 변경도 포함해요.

  • Gradle 플러그인에서 Dukat 통합 제거
  • 소스 맵에서 Kotlin 변수와 함수 이름
  • TypeScript 정의 파일 생성 opt-in

Gradle 플러그인에서 Dukat 통합 제거

Kotlin 1.8.20에서는 Kotlin/JavaScript Gradle 플러그인에서 Experimental Dukat 통합을 제거했어요. Dukat 통합은 TypeScript 선언 파일(.d.ts)을 Kotlin 외부 선언으로 자동 변환하는 것을 지원했어요.

대신 Dukat 도구를 사용해 TypeScript 선언 파일(.d.ts)을 Kotlin 외부 선언으로 변환할 수 있어요.

Dukat 도구는 Experimental이에요. 언제든 제거되거나 변경될 수 있어요.

소스 맵에서 Kotlin 변수와 함수 이름

디버깅을 돕기 위해 Kotlin 코드에서 선언한 변수와 함수의 이름을 소스 맵에 추가하는 기능을 도입했어요. 1.8.20 이전에는 이 이름들이 소스 맵에 없어서 디버거에서 항상 생성된 JavaScript의 변수·함수 이름만 볼 수 있었어요.

무엇을 추가할지는 Gradle 파일 build.gradle.ktssourceMapNamesPolicy 또는 -source-map-names-policy 컴파일러 옵션으로 구성할 수 있어요. 아래 표에 가능한 설정이 나와 있어요.

설정 설명 예시 출력
simple-names 변수 이름과 단순 함수 이름이 추가돼요. (기본값) main
fully-qualified-names 변수 이름과 정규화된 함수 이름이 추가돼요. com.example.kjs.playground.main
no 변수나 함수 이름이 추가되지 않아요. 해당 없음

build.gradle.kts 파일에서의 예시 구성은 아래와 같아요.

tasks.withType<org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile>().configureEach {
    compilercompileOptions.sourceMapNamesPolicy.set(org.jetbrains.kotlin.gradle.dsl.JsSourceMapNamesPolicy.SOURCE_MAP_NAMES_POLICY_FQ_NAMES) // or SOURCE_MAP_NAMES_POLICY_NO, or SOURCE_MAP_NAMES_POLICY_SIMPLE_NAMES
}

Chromium 기반 브라우저에서 제공되는 것 같은 디버깅 도구는 소스 맵에서 원래 Kotlin 이름을 가져와 스택 트레이스의 가독성을 높일 수 있어요. 즐거운 디버깅 되세요!

소스 맵에 변수와 함수 이름을 추가하는 것은 Experimental이에요. 언제든 제거되거나 변경될 수 있어요.

TypeScript 정의 파일 생성 opt-in

예전에는 실행 파일(binaries.executable())을 생성하는 프로젝트가 있으면 Kotlin/JS IR 컴파일러가 @JsExport로 표시된 최상위 선언을 수집하고 .d.ts 파일에 TypeScript 정의를 자동으로 생성했어요.

이것이 모든 프로젝트에 유용한 것은 아니므로 Kotlin 1.8.20에서 동작을 바꿨어요. TypeScript 정의를 생성하려면 Gradle 빌드 파일에서 명시적으로 구성해야 해요. js 섹션build.gradle.kts.filegenerateTypeScriptDefinitions()를 추가하면 돼요. 예를 들어:

kotlin {
    js {
        binaries.executable()
        browser {
        }
        generateTypeScriptDefinitions()
    }
}

TypeScript 정의(d.ts) 생성은 Experimental이에요. 언제든 제거되거나 변경될 수 있어요.

Gradle

Kotlin 1.8.20은 Multiplatform 플러그인의 몇 가지 특수한 경우를 제외하면 Gradle 6.8부터 7.6까지 완전히 호환돼요. 최신 Gradle 릴리스까지의 Gradle 버전도 사용할 수 있지만, 그 경우 deprecation 경고를 만날 수 있고 일부 새 Gradle 기능이 동작하지 않을 수 있다는 점을 유의하세요.

이 버전은 다음 변경을 가져와요.

  • Gradle 플러그인 버전의 새 정렬
  • Gradle에서 기본 적용되는 새 JVM 증분 컴파일
  • 컴파일 태스크 출력의 정밀 백업
  • 모든 Gradle 버전에서 지연(lazy) Kotlin/JVM 태스크 생성
  • 컴파일 태스크 destinationDirectory의 비기본 위치
  • HTTP 통계 서비스에 컴파일러 인자를 보고하는 것을 opt-out하는 기능

새 Gradle 플러그인 버전 정렬

Gradle은 함께 동작해야 하는 의존성이 항상 버전이 정렬되도록 보장하는 방법을 제공해요. Kotlin 1.8.20도 이 접근 방식을 채택했어요. 기본으로 동작하므로 활성화하기 위해 구성을 바꾸거나 업데이트할 필요가 없어요. 또한 Kotlin Gradle 플러그인의 전이적 의존성 해석을 위한 이 해결 방법에 의존할 필요도 없어요.

이 기능에 대한 피드백은 YouTrack에서 기다릴게요.

Gradle에서 기본 적용되는 새 JVM 증분 컴파일

Kotlin 1.7.0부터 사용 가능했던 새로운 증분 컴파일 접근 방식이 이제 기본으로 동작해요. 활성화하기 위해 gradle.propertieskotlin.incremental.useClasspathSnapshot=true를 지정할 필요가 없어요.

이에 대한 피드백을 기다릴게요. YouTrack에 이슈를 등록할 수 있어요.

컴파일 태스크 출력의 정밀 백업

컴파일 태스크 출력의 정밀 백업(precise backup)은 Experimental이에요. 사용하려면 gradle.propertieskotlin.compiler.preciseCompilationResultsBackup=true를 추가하면 돼요. YouTrack에서 피드백을 기다릴게요.

Kotlin 1.8.20부터 정밀 백업을 활성화할 수 있는데, 이 경우 증분 컴파일에서 Kotlin이 다시 컴파일하는 클래스만 백업돼요. 전체 백업과 정밀 백업 모두 컴파일 오류 후 빌드를 다시 증분 실행하는 데 도움이 돼요. 정밀 백업은 전체 백업에 비해 빌드 시간도 절약해요. 전체 백업은 대규모 프로젝트나 많은 태스크가 백업을 만들 때, 특히 프로젝트가 느린 HDD에 있는 경우 눈에 띄는 빌드 시간이 들 수 있어요.

이 최적화는 Experimental이에요. gradle.properties 파일에 kotlin.compiler.preciseCompilationResultsBackup Gradle 프로퍼티를 추가해서 활성화할 수 있어요.

kotlin.compiler.preciseCompilationResultsBackup=true
JetBrains에서의 정밀 백업 사용 예

다음 차트에서 전체 백업과 비교한 정밀 백업 사용 예를 볼 수 있어요.

첫 번째와 두 번째 차트는 Kotlin 프로젝트의 정밀 백업이 Kotlin Gradle 플러그인 빌드에 어떻게 영향을 주는지 보여줘요.

  1. 많은 모듈이 의존하는 모듈에 작은 ABI 변경(새 public 메서드 추가)을 한 후.
  2. 다른 모듈이 의존하지 않는 모듈에 작은 비-ABI 변경(private 함수 추가)을 한 후.

세 번째 차트는 Space 프로젝트의 정밀 백업이, 많은 모듈이 의존하는 Kotlin/JS 모듈에 작은 비-ABI 변경(private 함수 추가)을 한 후 웹 프론트엔드 빌드에 어떻게 영향을 주는지 보여줘요.

이 측정은 Apple M1 Max CPU를 탑재한 컴퓨터에서 수행됐으며, 다른 컴퓨터에서는 결과가 조금씩 다를 수 있어요. 성능에 영향을 주는 요인은 다음과 같지만 이에 국한되지는 않아요.

  • Kotlin daemonGradle daemon이 얼마나 "따뜻한" 상태인지.
  • 디스크가 얼마나 빠른지/느린지.
  • CPU 모델과 얼마나 바쁜지.
  • 변경 사항이 영향을 주는 모듈과 그 모듈의 크기.
  • 변경이 ABI인지 비-ABI인지.
빌드 리포트로 최적화 평가하기

이 최적화가 컴퓨터·프로젝트·시나리오에서 미치는 영향을 추정하려면 Kotlin 빌드 리포트를 사용할 수 있어요. gradle.properties 파일에 다음 프로퍼티를 추가해 텍스트 파일 형식으로 리포트를 활성화해요.

kotlin.build.report.output=file

정밀 백업을 활성화하기 전 리포트의 관련 부분 예시는 다음과 같아요.

Task ':kotlin-gradle-plugin:compileCommonKotlin' finished in 0.59 s
<...>
Time metrics:
 Total Gradle task time: 0.59 s
 Task action before worker execution: 0.24 s
  Backup output: 0.22 s // Pay attention to this number 
<...>

정밀 백업 활성화 후 리포트의 관련 부분 예시는 다음과 같아요.

Task ':kotlin-gradle-plugin:compileCommonKotlin' finished in 0.46 s
<...>
Time metrics:
 Total Gradle task time: 0.46 s
 Task action before worker execution: 0.07 s
  Backup output: 0.05 s // The time has reduced
 Run compilation in Gradle worker: 0.32 s
  Clear jar cache: 0.00 s
  Precise backup output: 0.00 s // Related to precise backup
  Cleaning up the backup stash: 0.00 s // Related to precise backup
<...>

모든 Gradle 버전에서 지연 Kotlin/JVM 태스크 생성

Gradle 7.3+에서 org.jetbrains.kotlin.gradle.jvm 플러그인을 사용하는 프로젝트의 경우, Kotlin Gradle 플러그인은 더 이상 compileKotlin 태스크를 즉시(eagerly) 생성·구성하지 않아요. 낮은 Gradle 버전에서는 드라이 런에서 모든 태스크를 등록만 하고 구성하지 않아요. Gradle 7.3+를 사용할 때도 이제 같은 동작이 적용돼요.

컴파일 태스크 destinationDirectory의 비기본 위치

다음 중 하나를 하는 경우 빌드 스크립트에 몇 가지 추가 코드를 넣어 업데이트해야 해요.

  • Kotlin/JVM KotlinJvmCompile/KotlinCompile 태스크의 destinationDirectory 위치를 재정의하는 경우.
  • deprecated된 Kotlin/JS/Non-IR 변형을 사용하고 Kotlin2JsCompile 태스크의 destinationDirectory를 재정의하는 경우.

JAR 파일에서 sourceSets.main.kotlin.classesDirectoriessourceSets.main.outputs에 명시적으로 추가해야 해요.

tasks.jar(type: Jar) {
    from sourceSets.main.outputs
    from sourceSets.main.kotlin.classesDirectories
}

HTTP 통계 서비스에 컴파일러 인자 보고를 opt-out하는 기능

이제 Kotlin Gradle 플러그인이 HTTP 빌드 리포트에 컴파일러 인자를 포함할지 여부를 제어할 수 있어요. 때로는 플러그인이 이런 인자를 보고할 필요가 없을 수도 있어요. 프로젝트에 모듈이 많으면 리포트의 컴파일러 인자가 매우 무거워지고 그리 도움이 되지 않을 수 있어요. 이제 이를 비활성화해서 메모리를 절약할 수 있어요. gradle.properties 또는 local.properties에서 kotlin.build.report.include_compiler_arguments=(true|false) 프로퍼티를 사용하면 돼요.

이 기능에 대한 피드백은 YouTrack에서 기다릴게요.

표준 라이브러리

Kotlin 1.8.20은 다양한 새 기능을 추가하는데, 그중 일부는 Kotlin/Native 개발에 특히 유용해요.

  • AutoCloseable 인터페이스 지원
  • Base64 인코딩·디코딩 지원
  • Kotlin/Native의 @Volatile 지원
  • Kotlin/Native에서 regex 사용 시 스택 오버플로 버그 수정

AutoCloseable 인터페이스 지원

AutoCloseable 인터페이스는 Experimental이고, 사용하려면 @OptIn(ExperimentalStdlibApi::class) 또는 컴파일러 인자 -opt-in=kotlin.ExperimentalStdlibApi로 opt-in해야 해요.

AutoCloseable 인터페이스가 공통 표준 라이브러리에 추가됐으므로, 리소스를 닫기 위해 모든 라이브러리에서 하나의 공통 인터페이스를 사용할 수 있어요. Kotlin/JVM에서 AutoCloseable 인터페이스는 java.lang.AutoClosable의 별칭이에요.

또한 확장 함수 use()가 포함됐는데, 선택한 리소스에 주어진 블록 함수를 실행한 다음 예외가 발생하든 아니든 정확히 닫아줘요.

공통 표준 라이브러리에는 AutoCloseable 인터페이스를 구현하는 public 클래스가 없어요. 아래 예제에서 XMLWriter 인터페이스를 정의하고, 이를 구현하는 리소스가 있다고 가정해요. 예를 들어 이 리소스는 파일을 열고, XML 내용을 쓰고, 닫는 클래스일 수 있어요.

interface XMLWriter : AutoCloseable {
    fun document(encoding: String, version: String, content: XMLWriter.() -> Unit)
    fun element(name: String, content: XMLWriter.() -> Unit)
    fun attribute(name: String, value: String)
    fun text(value: String)
}

fun writeBooksTo(writer: XMLWriter) {
    writer.use { xml ->
        xml.document(encoding = "UTF-8", version = "1.0") {
            element("bookstore") {
                element("book") {
                    attribute("category", "fiction")
                    element("title") { text("Harry Potter and the Prisoner of Azkaban") }
                    element("author") { text("J. K. Rowling") }
                    element("year") { text("1999") }
                    element("price") { text("29.99") }
                }
                element("book") {
                    attribute("category", "programming")
                    element("title") { text("Kotlin in Action") }
                    element("author") { text("Dmitry Jemerov") }
                    element("author") { text("Svetlana Isakova") }
                    element("year") { text("2017") }
                    element("price") { text("25.19") }
                }
            }
        }
    }
}

Base64 인코딩 지원

새 인코딩·디코딩 기능은 Experimental이고, 사용하려면 @OptIn(ExperimentalEncodingApi::class) 또는 컴파일러 인자 -opt-in=kotlin.io.encoding.ExperimentalEncodingApi로 opt-in해야 해요.

Base64 인코딩·디코딩 지원을 추가했어요. 각각 다른 인코딩 방식을 사용하고 다른 동작을 보이는 클래스 인스턴스 3개를 제공해요. 표준 Base64 인코딩 방식에는 Base64.Default 인스턴스를 사용해요.

"URL and Filename safe" 인코딩 방식에는 Base64.UrlSafe 인스턴스를 사용해요.

MIME 인코딩 방식에는 Base64.Mime 인스턴스를 사용해요. Base64.Mime 인스턴스를 사용하면 모든 인코딩 함수가 76자마다 줄 구분자를 삽입해요. 디코딩의 경우 불법 문자는 건너뛰고 예외를 던지지 않아요.

Base64.Default 인스턴스는 Base64 클래스의 동반 객체(companion object)예요. 따라서 Base64.Default.encode()Base64.Default.decode() 대신 Base64.encode()Base64.decode()로 함수를 호출할 수 있어요.

val foBytes = "fo".map { it.code.toByte() }.toByteArray()
Base64.Default.encode(foBytes) // "Zm8="
// Alternatively:
// Base64.encode(foBytes)

val foobarBytes = "foobar".map { it.code.toByte() }.toByteArray()
Base64.UrlSafe.encode(foobarBytes) // "Zm9vYmFy"

Base64.Default.decode("Zm8=") // foBytes
// Alternatively:
// Base64.decode("Zm8=")

Base64.UrlSafe.decode("Zm9vYmFy") // foobarBytes

바이트를 기존 버퍼로 인코딩·디코딩하고, 인코딩 결과를 제공된 Appendable 타입 객체에 추가하는 추가 함수도 사용할 수 있어요.

Kotlin/JVM에서는 입력·출력 스트림으로 Base64 인코딩·디코딩을 수행할 수 있게 해주는 확장 함수 encodingWith()decodingWith()도 추가했어요.

Kotlin/Native의 @Volatile 지원

Kotlin/Native의 @VolatileExperimental이에요. 언제든 제거되거나 변경될 수 있어요. Opt-in이 필요하며(아래 세부사항 참고) 평가 목적으로만 사용해 주세요. YouTrack에서 피드백을 기다릴게요.

var 프로퍼티에 @Volatile을 붙이면 백킹 필드가 표시되어 이 필드에 대한 읽기나 쓰기가 원자적(atomic)이고, 쓰기는 항상 다른 스레드에 보이게 돼요.

1.8.20 이전에는 kotlin.jvm.Volatile 애너테이션이 공통 표준 라이브러리에서 사용 가능했어요. 그런데 이 애너테이션은 JVM에서만 효과가 있어요. Kotlin/Native에서 사용하면 무시되어 오류로 이어질 수 있어요.

1.8.20에서 JVM과 Kotlin/Native 양쪽에서 사용할 수 있는 공통 애너테이션 kotlin.concurrent.Volatile을 도입했어요.

활성화 방법

이 기능을 시험해 보려면 @OptIn(ExperimentalStdlibApi)으로 opt-in하고 -language-version 1.9 컴파일러 옵션을 활성화하면 돼요. Gradle 프로젝트에서는 build.gradle(.kts) 파일에 다음을 추가하면 돼요.

tasks
    .withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask<*>>()
    .configureEach {
        compilerOptions
            .languageVersion
            .set(
                org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
            )
    }
tasks
    .withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask.class)
    .configureEach {
        compilerOptions.languageVersion =
            org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_1_9
    }

Kotlin/Native에서 regex 사용 시 스택 오버플로 버그 수정

이전 Kotlin 버전에서는 regex 패턴이 아주 간단하더라도 regex 입력에 문자가 많으면 크래시가 발생할 수 있었어요. 1.8.20에서 이 문제가 해결됐어요. 자세한 내용은 KT-46211을 참고하세요.

직렬화 업데이트

Kotlin 1.8.20은 Kotlin K2 컴파일러에 대한 Alpha 지원과 함께, 동반 객체를 통한 직렬화기 커스터마이징 금지를 제공해요.

Kotlin K2 컴파일러용 직렬화 컴파일러 플러그인 프로토타입

K2용 직렬화 컴파일러 플러그인 지원은 Alpha 단계예요. 사용하려면 Kotlin K2 컴파일러를 활성화하면 돼요.

1.8.20부터 직렬화 컴파일러 플러그인이 Kotlin K2 컴파일러와 함께 동작해요. 사용해 보고 피드백을 남겨주세요!

동반 객체를 통한 암시적 직렬화기 커스터마이징 금지

현재는 @Serializable 애너테이션으로 클래스를 직렬화 가능하게 선언하면서, 동시에 동반 객체에 @Serializer 애너테이션으로 커스텀 직렬화기를 선언할 수 있어요.

예를 들어:

import kotlinx.serialization.*

@Serializable
class Foo(val a: Int) {
    @Serializer(Foo::class)
    companion object {
        // Custom implementation of KSerializer<Foo>
    }
}

이 경우 @Serializable 애너테이션만으로는 어떤 직렬화기가 사용되는지 분명하지 않아요. 실제로는 클래스 Foo에 커스텀 직렬화기가 있어요.

이런 혼란을 막기 위해 Kotlin 1.8.20에서 이 시나리오가 감지되면 컴파일러 경고를 도입했어요. 경고에는 이 문제를 해결하기 위한 가능한 마이그레이션 경로가 포함돼요.

코드에서 이런 구조를 사용한다면 다음처럼 업데이트할 것을 권장해요.

import kotlinx.serialization.*

@Serializable(Foo.Companion::class)
class Foo(val a: Int) {
    // Doesn't matter if you use @Serializer(Foo::class) or not
    companion object: KSerializer<Foo> {
        // Custom implementation of KSerializer<Foo>
    }
}

이 접근 방식에서는 Foo 클래스가 동반 객체에 선언된 커스텀 직렬화기를 사용한다는 것이 분명해요. 자세한 내용은 YouTrack 티켓을 참고하세요.

Kotlin 2.0에서 이 컴파일 경고를 컴파일러 오류로 승격할 계획이에요. 이 경고가 보이면 코드를 마이그레이션할 것을 권장해요.

문서 업데이트

Kotlin 문서에 주목할 만한 변경이 몇 가지 있었어요.

Kotlin 1.8.20 설치

IDE 버전 확인

IntelliJ IDEA 2022.2와 2022.3은 Kotlin 플러그인을 1.8.20으로 업데이트하도록 자동으로 제안해요. IntelliJ IDEA 2023.1에는 Kotlin 플러그인 1.8.20이 내장돼 있어요.

Android Studio Flamingo (222)와 Giraffe (223)는 다음 릴리스에서 Kotlin 1.8.20을 지원할 예정이에요.

새 커맨드라인 컴파일러는 GitHub 릴리스 페이지에서 다운로드할 수 있어요.

Gradle 설정 구성

Kotlin 아티팩트와 의존성을 제대로 다운로드하려면 Maven Central 저장소를 사용하도록 settings.gradle(.kts) 파일을 업데이트하면 돼요.

pluginManagement {
    repositories {
        mavenCentral()
        gradlePluginPortal()
    }
}

저장소가 지정되지 않으면 Gradle이 폐기된 JCenter 저장소를 사용해서 Kotlin 아티팩트에 문제가 생길 수 있어요.

더 알아보기