본문 바로가기
Android 개발/Kotlin

Scope Function 총정리 # Kotlin also let run with apply

by Developer88 2019. 9. 29.
반응형

이번에는 Scope 함수 라는 것을 정리해 보고자 합니다.

이 함수들은, 객체의 컨텍스트를 유지하면서, 코드 블록을 받아서 실행시키는데요.

let, apply, with, run, also 같이 종류가 무려 5개나 됩니다.

 

이 함수들은 대부분 중요해서 android개발하면서 계속 마주치게 되므로,

잘 정리해서 사용해 보는 것이 좋은데요.

 

이 글에서는 5가지 Scope함수와 더불어 함께 사용할 수 있는 함수인,

takeIf그리고 takeUnless까지 같이 정리해 보겠습니다.

1. Scope Functions

ScopeFunction이라는 함수명에서 알 수 있듯이,

이 함수들을 lambda식을 이용해서 호출하면,

일시적인 Scope(범위)가 생기게 됩니다.

이 범위안에서는 객체에 대해 "it" 혹은 "this"라고 하는 Context Object를 통해서 자유롭게 접근할 수 있게 됩니다.

 

많이 쓰이는 let함수의 코드를 보도록 하겠습니다.

Student객체를 생성하고 let이라는 scope함수를 호출하였습니다.

여기서 객체에 it으로 접근하여 it.age를 print하거나,

객체의 함수를 실행하여 property를 변경하기도 하였습니다.

 

 

이것을 let을 쓰지 않고 코딩해보면 아래와 같은데요.

위의 것이 객체의 사용범위도 더욱 잘 읽히고 좀더 명확하게 보이시나요?

아무래도 다른 코드들과 섞여있을 때 장점이 더 명확하게 보이기는 할 것 같네요.

 

 

Scope함수의 대표주자인 let을 보았구요.

이제는 각각의 함수를 구분하는 기준을 보면서 조금씩 알아보도록 하겠습니다.

 

2. Context Object와 Return값에 따른 구분

아래 표를 보면 각각의 함수가 어떻게 다른 context object를 사용하고,

return값은 어떻게 다른지 알 수 있습니다.

  context object return
run this lambda result
with this lambda result
apply this context object
also it context object
let it lambda result

 

 

각각에 대한 구체적인 구분은 아래에서 하나씩 하겠지만,

이 함수들의 context object가 무엇이고, return값이 무엇인지를 잘 생각하면,

좀 더 효율적으로 사용할 수 있을 것 입니다.

예를 들어, 객체를 받아서, 어떠한 계산등을 통해 결과 값을 사용해야 하는지,

또는 블록 안에서 객체에 접근해서 객체를 수정하려고 하는지 등에 따라 선택할 함수에 따라 달라지는 것이지요.

 

이제 구체적으로 각각의 함수에 대해서 보도록 하겠습니다.

3. 5가지 함수의 구별

3-1. let

위에서 보았던 let으로 부터 시작해 보겠습니다.

let은 함수를 호출하는 객체를 context object인 it을 통해서 코드블록으로 넘겨주고요,

lamda식의 결과를 return 해 줍니다.

let이 많이 사용되는 두가지 경우는 다음과 같습니다.

 

A. chain operation에서의 사용

아래와 같이 chain operation에서 하나이상의 함수를 사용하기 위해서 사용할 수 있습니다.

아래 코드에서는 print만 사용하였지만 여러 함수를

context object인 it을 통해 사용할 수 있습니다.

 

 

B. non-null value를 가지고 코드블록을 실행시키고자 할 때 사용

'?.let { }' 코드블록 안에는 non-null인 값만 들어올 수 있으므로,

non-null이 필요한 경우에 사용할 수 있습니다.

또한 let은 lamda값의 결과 값을 return하므로,

이 값을 변수에 담아서 어떤 과업을 할 수도 있습니다.

 

 

아래와 같이 list의 forEach에도 사용해 볼 수 있을 것 같네요.

(물론 list에는 filterNotNull()이라는 메소드가 존재하기 때문에 아래와 같이 사용할 일이 많지는 않겠지요)

 

 

3-2. with

with는 단어에서 알 수 잇듯이, 이 객체를 가지고 다음의 어떤 것을 하기 위한 목적을 가지고 있습니다.

인자로 받는 객체를 코드블록 안에 this라는 context object로 전달하는데요.

lambda의 결과 값을 return해 줍니다.

with의 경우 인자로 객체를 받아서 사용을 하는데요.

인자로 받아서 사용하는 것이 효율적일 때는 with를 사용하면 더 좋습니다.

 

A. 람다식의 결과값을 필요로 하지 않고, 함수 호출들을  grouping해서 사용할 때

 

 

B. 객체가 필요로 하는 값을 계산해 주는 경우

this를 context object로 받는 경우는 생략이 가능하므로,

아래에서는 생략하여 first()와 last()메소드를 이용한 값을 계산하였습니다.

 

 

 

3-3. run

with와 context object나 return값도 같아서 비슷한 역할을 하지만, 객체를 받는 위치가 다릅니다.

필요에 따라서는 앞에 safeCall(?.)을 붙여서 null이 아닌 값에만 run을 실행할 수 있구요.

이것은 with보다는 run을 더 자주 사용하게 될 이유중 하나가 될 수도 있겠습니다.

 

A. 객체를 초기화하고 return 값의 계산이 필요한 경우

 

 

B.  expression이 필요한 여러줄의 코드를 한번에 처리할 때

 

 

C. let과 run의 조합

let과 run그리고 elvis operator(?:)를 조합해서 아래코드와 같이 사용하기도 하는데요.

null이 아닐경우는  let블록을 실행해 사용하고,

null일 경우는 run블록이 실행되도록 하는 것 이지요.

한가지 주의해야 할 점은 let은 lamda result값을 return 하기 때문에,

let블록 안의 return 값이 null을 반환할 경우,

의도치 않게 elvis operator뒤의 run블록이 실행되어 버릴 수 있으므로 주의해 주어야 합니다.

따라서, 블록에서의 계산 값을 사용하지 않을 거라면, 밑에서 나오는 apply와 같이 사용하는 것도 방법일 수 있습니다.

 

(참고로 위와 같이 null일 경우 실행할 코드가 한줄뿐이라면, 굳이 run을 사용하지 않고,

?: throw NullPointerException()과 같이 사용해도 괜찮습니다.

여러줄의 코드블록을 실행할 때 run을 사용하는 것 이지요)

 

 

3-4. apply

호출하는 객체를 코드블록안에 context object인 this로 전달을 하므로,

객체 자체의 property를 바꿀 때 효과적이구요.

let 이나 run이 lambda함수의 마지막 줄을 리턴해주는 것에 비해서,

apply의 return값은 object자신입니다.

 

블록에서 받는 자기자신에 접근하는 경우, 아래와 같이 this를 생략할 수 있습니다.

그래서 코드가 괸잔히 간단해 집니다.

아래에서는 생성한 파일에 delete함수를 사용해서 파일을 지웠습니다.

 

fun deleteIgnoredFile() {
    getFile(uriString)?.apply {
        delete()
        if(exists()){
            Log.d(TAG, "File still exists after deletion")
        } else {
            Log.d(TAG, "File deleted successfully")
        }
    }
}

 

대신 this가 사라지면, 이것이 객체의 멤버인 외부 변수인지 구분하기 힘든 단점이 있습니다.

 

아래 코드에서는 AlertDialog에 사용하였는데요.

이런 Builder패턴의 경우에도 사용해 볼 수 있을 것 같습니다.

 

 

3-5. also

also는 context object가 it으로 return값은 object 자신입니다.

이것은 객체를 수정하거나 하지않고, 디버깅을 위한 로깅을 하는등의 부가적이거나 추가적인 일을 하려고 할 때 사용합니다.

 

안드로이드의 startAcivity에도 사용할 수 있겠네요.

 

 

 

4. 이용 목적별  구분

마지막으로 무조건 적인 것은 아니지만,

공식문서에서 추천하는 목적별 사용 구분을 알아보고 다시 정리해 보도록 하겠습니다.

 

- Non-null객체에 lambda를 사용하고자 할 경우 => let (람다 결과값 리턴)

- 객체를 변경하고자 할 경우 => apply (context object 리턴)

- 추가적인 효과 => also (context object 리턴)

- 객체를 변경하고 결과 값을 계산할 때 => run (람다 결과값 리턴)

-  Expression이 필요한 여러줄의 코드를 처리하고자 할 때 => run (람다 결과값 리턴)

- 객체에 함수호출들을 grouping할 때 => with (람다 결과값 리턴)

 

5. takeIf와 takeUnless

마지막으로 위에서 정리한 5가지 함수를 활용하는데 도움이 되는

takeIf와 takeUnless에 대해서 알아보도록 하겠습니다.

 

우선 takeIf와 takeUnless는 it이 contextObject이구요.

contextObject를 return해 줍니다.

메소드명에서 바로 알 수 있듯이 takeIf블록에 조건을 달아서 필터링 할 수 있는 것인데요.

 

그 조건을 달성한 경우 값을 받아서 사용할 수 있겠습니다.

takeUnless는 takeIf와는 반대로 동작하구요.(영어의 unless를 생각하시면 이해가 빠르겠습니다)

 

한가지 주의해야 할 점은 takeIf나 takeUnless는 조건을 만족하지 않을 경우,

null을 return한다는 점 입니다.

따라서, let을 safe call(?.)과 같이 사용해 주면 가장 좋겠지요.

 

 

6. 정리

5개나 되는 비슷한 함수들이 사용범위가 중첩되어 있으므로,

자주 사용해 보며 효율적인 convention등을 정리해서 사용하는 것이 좋을 것 같네요.

마지막으로 중요한 것은 이 5가지 함수들은 저희가 하고자 하는 과업을 위한 도구일 뿐이지,

함수를 위해서 과업을 하는 것은 아니므로, 어떻게 사용해야 한다는 정답은 없다는 것을 잊으면 않될 것 같습니다.

 

scope function과 관련된 내용은 이 글에서 업데이트 하도록 하겠습니다.

 

728x90

댓글