[Kotlin] use 확장 함수 알아보기
예전에 이펙티브 코틀린을 읽으면서 use를 써서 리소스를 닫으라는 내용을 봤었다.
https://onlyfor-me-blog.tistory.com/489
[이펙티브 코틀린] 아이템 9. use를 써서 리소스를 닫아라
더 이상 필요하지 않을 때 close()를 써서 명시적으로 닫아야 하는 리소스가 있다. Input/OutputStream java.sql.Connection java.io.Reader(FileReader, BufferedReader, CSSParser) java.new.socket, java.util.Scanner 이런 리소스들
onlyfor-me-blog.tistory.com
요약하면 Input/OutputStream, Scanner 등의 요소들은 더 이상 사용하지 않는다면 close()를 통해 리소스를 거둬들이는 게 좋다는 내용이다.
그러나 use라는 게 무엇이길래 사용하라고 한 것일지 궁금했고, 마침 동영상을 서버로 업로드하는 로직을 구현하는 과정에서 InputStream을 사용하게 됐는데 이 기회에 알아보고자 포스팅한다.
코틀린 공식문서에서 말하는 use와 구현 상세는 아래와 같다.
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/use.html
use - Kotlin Programming Language
kotlinlang.org
이 리소스에서 지정된 블록 함수를 실행한 다음 예외 발생 여부에 상관없이 올바르게 종료한다
구현 코드는 안드로이드 스튜디오에서 확인한 내용을 가져왔다. 추가로 매개변수와 리턴값에 대해서도 설명하고 있어서 이것도 가져왔다.
Params : block - 이 Closeable 자원을 처리하는 함수
Returns : 이 리소스에서 호출된 블록 함수의 결과
@InlineOnly
@RequireKotlin("1.2", versionKind = RequireKotlinVersionKind.COMPILER_VERSION, message = "Requires newer compiler version to be inlined correctly.")
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
var exception: Throwable? = null
try {
return block(this)
} catch (e: Throwable) {
exception = e
throw e
} finally {
when {
apiVersionIsAtLeast(1, 1, 0) -> this.closeFinally(exception)
this == null -> {}
exception == null -> close()
else ->
try {
close()
} catch (closeException: Throwable) {
// cause.addSuppressed(closeException) // ignored here
}
}
}
}
확인해 보면 알겠지만 use 함수는 Closeable이란 파일 안에 위치하고 있다. 이건 또 뭔가?
https://developer.android.com/reference/kotlin/java/io/Closeable
Closeable | Android Developers
developer.android.com
닫을 수 있는 데이터의 소스 또는 대상이다. 객체가 갖고 있는 리소스(열린 파일 등)를 해제하기 위해 close 메서드가 호출된다
close() : 이 스트림을 닫고 연결된 모든 시스템 리소스를 해제한다
Closeable은 WebSocketReader, Http2Connection, FileIntputStream 등의 클래스가 구현하고 있는 인터페이스다. 이 인터페이스의 구현체를 사용한다면 위에서 말했듯 사용 후 close()를 호출하는 코드가 필요하다.
그러나 객체마다 close()를 호출해야 하고 혹시 모를 예외를 막기 위해 try-catch로 감싸서 처리해야 하는데, 그렇게 되면 코드 가독성도 떨어지고 쓸데없이 긴 줄을 차지하게 된다. 이 때 use를 사용하면 close() 호출 처리를 생략하면서 깔끔한 코드를 작성할 수 있다.
아래는 간단한 예시다.
fun main() {
val url = "https://www.naver.com/"
val destination = File("downloaded_data.txt")
downloadFromWeb(url, destination)
println("Data downloaded to ${destination.absolutePath}")
}
fun downloadFromWeb(url: String, destination: File) = URL(url).openStream().use { input ->
destination.outputStream().use { output ->
input.copyTo(output)
}
}
이 코드를 실행하면 네이버 메인 화면을 구성하는 html 태그들을 가져와서 txt 파일에 저장한다. 실행하면 해당 파일이 저장된 경로가 콘솔에 표시될 것이다.
내 경우 이 프로젝트의 루트 폴더에 downloaded_data.txt 파일이 저장됐다. 직접 실행해서 확인해 보자.
downloadFromWeb()이 주목할 함수다. 이 함수의 첫 부분인 URL(url).openStream() 호출을 통해 InputStream을 만들고, destination.outputStream()을 통해 다운받은 데이터를 파일에 쓸 때 사용할 OutputStream을 만든다.
이를 통해 각 스트림마다 use 확장함수를 사용하기 때문에 close()를 2번 호출할 필요 없이 아주 간단하게 스트림들을 닫고 프로그램을 종료할 수 있다.
안드로이드에서도 이미지, 동영상을 다룰 때 InputStream과 OutputStream을 다뤄야 할 때가 종종 있다. 이 때 try-catch 대신 use 확장함수를 써서 처리해 보는 것도 좋을 것이다.