본문 바로가기
Language/Kotlin

[Kotlin] 스코프 함수(Scope Functions) #1

by 신숭이 2022. 1. 17.

 

[Kotlin] 스코프 함수(Scope Functions)  #1

 

 

 

코틀린 표준함수 중에는 리팩토링 또는 자원 낭비를 방지할 수 있도록 돕는 스코프 함수 몇 가지가 있다. 이 함수들은 확장함수로 구현된 함수로 람다식을 이용해 접근하는 것이 일반적이다. 이 함수들의 가장 큰 목적은 블록 내의 코드를 각 객체의 문맥(Context) 내에서 실행하도록 한다.

 

스코프 함수는 크게 5가지가 있다.

 

 

여기서 let, run, apply, with는 제네릭 확장함수 형태이므로 어디서든 적용이 가능하다.

 

 

 

let : 주로 Null 검사 생략 용도

 

 

주로 해당 객체가 Null이 아니면 어떠한 코드 블록을 실행하고, Null이면 실행하지 않도록 구현할 때 사용한다. 람다의 매개변수는 호출한 객체 자신이며 it으로 참조 가능하다. 따라서 Null을 허용한 객체에 대해 사용한다. 다음의 예시를 보면, 기존방식은 해당 객체가 Null인지 확인해야하는 상황에 다음과 같이 했다. Java 스럽기 그지없다.

 


var str:String? = "Hello"

if(str != null){
    println("size : "+str.length)
}

 

let 스코프 함수를 사용하면 아래와 같다.

 


var str:String? = null

str?.let { println("size : "+it.length) }

 

만약 특정 객체의 Null 유무에 따라 if~else 구문을 수행한다고 하자.

 


var str:String? = "Hello"

if(str != null){
    println("size : "+str.length)
}
else{
    println("String is Null")
}

 

이때는 엘비스 연산자(?:) 까지 곁들이면 된다.

 


var str:String? = null

str?.let { println("size : "+it.length) } ?: println("String is null")

 

예시는 실제로 개발중인 프로젝트에서 찾아봤다.

 

val newItem = favoriteDataList.find {
    it.soundName == mediaItem?.mediaId
}

if (newItem != null) {
    binding.playerTitle.text = newItem.soundName
    binding.playerCategory.text = newItem.categoryName
    Glide.with(this@FavoriteListActivity)
        .load(GosleepApp.resourceServer+newItem.categoryThumbnail)
        .into(binding.playerThumbnail)
}

 

위 코드는 어떤 리스트에서 특정 데이터(newItem)를 찾고(find) 그것이 존재하면, UI를 초기화하는 안드로이드 Kotlin 코드이다. 이것을 let을 이용하여 바꾸면 아래와 같다.

 

favoriteDataList.find { it.soundName == mediaItem?.mediaId }?.let {
    binding.playerTitle.text = it.soundName
    binding.playerCategory.text = it.categoryName
    Glide.with(this@FavoriteListActivity)
        .load(GosleepApp.resourceServer+it.categoryThumbnail)
        .into(binding.playerThumbnail)
}

 

결과는 문제없이 동일하게 작동하며 val newItem 과 같은 변수 할당은 필요없어졌다. 그리고 즉각 let 을 이용하여 it으로 참조하여 UI를 초기화 하였다. 우선 메모리면에서는 더 우수하다. 그렇다면 가독성은? 그건 취존

 

추가로 let 스코프 함수는 코드 블록의 마지막 줄 수행 결과를 반환한다.

 

var a = 1
var b = a.let {
    var i = it + 3
    i
}
println(b)		// 4

 

 

 

also : 주로 유효성 검사 용도

 

 

also는 let과 사용법이 거의 유사하나, 마지막 줄 수행 결과를 반환하는 let과는 다르게, 자기 자신 객체를 반환한다. 람다 블록 내에서 호출한 객체를 참조할 때, it을 사용한다. 다음의 예시를 보자. 

 

data class Developer(var name:String,var stack:String)
var dev = Developer("alpha","kotlin")

var res1 = dev.let{
    it.stack = "Dart"
    "Success"
}

println(dev)
println(res1)

var res2 = dev.also{
    it.stack = "Swift"
    "Success"
}

println(dev)
println(res2)

 

결과는 아래와 같다. res1에는 "Success" 라는 문자열이 할당되었으나, res2에는 Developer 객체가 할당되었다.

 

Developer(name=alpha, stack=Dart)
Success
Developer(name=alpha, stack=Swift)
Developer(name=alpha, stack=Swift)

 

보통 자기 자신 객체를 반환하기때문에 메서드 체인으로 함수를 구성할때 유용하게 사용되는 것으로 보인다.
(이건 코틀린에 더 능숙해지면 예시를 추가해보겠다.)

 

 

 

 

 

apply : 주로 객체의 할당과 동시에 초기화 용도

 

 

이건 블록내에서 호출한 객체를 this로 참조하며, this는 생략가능하다. 결과는 this로 반환된다.

 

data class Developer(var name:String,var stack:String)
var dev = Developer("beta","Kotlin")

dev.apply { 
    name = "alhpa"
    stack = "Java"
}

 

이건 반복적으로 객체 이름을 적어가며 초기화 하는 구문에 아주 유용하다. 다음은 안드로이드에서 ViewPager2를 초기화 하는 부분이다. 멤버 함수, 멤버 변수 구분없이 다 호출 가능하다.

 

binding.viewPagerSleepEnv.apply {
    setPageTransformer { page, position -> page.translationX = position * -offsetPx }
    adapter = ViewPagerAdapter1(sleepEnvDataList)
    orientation = ViewPager2.ORIENTATION_HORIZONTAL
    offscreenPageLimit = 3
}

 

 

 

 

run : 익명 함수 또는 결과 반환

 

 

먼저 run은 다음과 같이 익명 함수 처럼 사용할 수 있다. 


var stack = "Kotlin"
var a = 50
var res = run {
    val str = "stack : $stack  , level : $a"
    str
}
println(res)

 

다음은 apply와 거의 비슷하나, 자신을 반환하는 apply와 다르게 run은 마지막 줄을 실행한 결과를 반환한다. 자신을 this로 참조할 수 있기에, 생략가능하다.

 

data class Developer(var name:String,var stack:String)
var dev = Developer("beta","Kotlin")

var res = dev.run{
    name = "alpha"
    stack = "Java"
    "Success"
}
println(res) 	// "Success" 출력

 

 

 

with : run과 유사, 단독 함수

 

 

 

run과 유사하나 확장함수가 아니라 단독 함수 형태로 사용한다.  마지막 표현식을 반환한다. (run과 동일)

 

data class Developer(var name:String,var stack:String)
var dev = Developer("beta","Kotlin")

var res = with(dev) {
    name = "alpha"
    stack = "Java"
    "Success"
}
println(res) 	// "Success" 출력

 

단 세이프콜(?.)이 불가능하기에 let과 함께 쓰이곤 한다.

data class Developer(var name:String,var stack:String)
var dev :Developer? = Developer("beta","Kotlin")

var res = dev?.let {
    with(it) {
        name = "alpha"
        stack = "Java"
        "Success"
    }
}
println(res)

 

 

끝.

 

댓글