본문 바로가기

코틀린

코틀린(Kotlin) - lambda with receiver(수신 객체 지정 람다) : with, apply

lambda with receiver (수신 객체 지정 람다)

개발을 하다 보면 객체를 반복 사용하면서 그 객체에 대한 연산을 명시하는 경우가 많습니다.

수신 객체 지정 람다는 이러한 수신 객체를 반복적으로 명시하지 않고도 람다의 본문 안에서 다른 객체의 메소드를 호출할 수 있게 합니다.

이런 람다를 수신 객체 지정 람다(lambda with receiver) 라고 합니다.

with 함수

/* with 없이 반복 사용하는 경우 */
val settings = webView.settings

settings.javaScriptEnabled = true
settings.javaScriptCanOpenWindowsAutomatically = true
settings.useWideViewPort = true
settings.loadWithOverviewMode = true
settings.builtInZoomControls = false
settings.domStorageEnabled = true
settings.defaultTextEncodingName = "UTF-8"

 

위 예제에서 WebView Setting을 위해 여러 메소드를 호출하면서 매번 settings를 반복 사용하는 것을 알 수가 있습니다.

이런 경우, 코드가 훨씬 더 길어지거나 반복을 자주해야한다면 어땠을까요? 

그만큼 중복해서 쓰는 코드가 많아지는걸 보며 굉장히 개운치 않은 마음이 들게 될 겁니다...😨

 

이제 위의 예제를 with로 수정해보도록 하겠습니다.  

 

/* with를 사용한 경우 */
val settings = webView.settings

with(settings) { //수신 객체 지정
    javaScriptEnabled = true
    javaScriptCanOpenWindowsAutomatically = true
    useWideViewPort = true
    loadWithOverviewMode = true
    builtInZoomControls = false
    domStorageEnabled = true
    defaultTextEncodingName = "UTF-8"
}

 

기존에 반복해서 사용하던 settings가 제거되어 훨씬 간결하고 명확해서 가독성이 좋아진 것 같습니다! 😀

 

with문은 코틀린 언어가 제공하는 특별한 구문처럼 보이지만, 실제로는 파라미터가 2개인 함수입니다.

위의 예제를 기준으로 보면, with는 파라미터가 하나인것 처럼 보이지만 사실은 with(settings, { ... }) 형태로 첫 번째 파라미터는 settings 이고, 두 번째 파라미터는 람다입니다.

with가 반환하는 값은 람다 코드를 실행한 결과입니다.

 

람다는 괄호 밖으로 빼내어 작성하는 관례에 따라 with(settings) { ... } 로 쓰기 때문에 특별한 구문처럼 보이네요!

 

with 함수는 첫 번째 파라미터로 받은 객체를 두 번째 파라미터로 받은 람다의 수신 객체로 만들기 때문에 람다의 본문에서는 수신 객체의 멤버에 접근할 수 있습니다.

 

위 예제를 리팩토링해서 불필요한 val settings 를 없앨 수도 있습니다.

 

with(webView.settings) { //수신 객체 지정
    javaScriptEnabled = true
    javaScriptCanOpenWindowsAutomatically = true
    useWideViewPort = true
    loadWithOverviewMode = true
    builtInZoomControls = false
    domStorageEnabled = true
    defaultTextEncodingName = "UTF-8"
}

 

apply 함수

apply 함수는 거의 with와 같습니다. 유일한 차이는 apply는 항상 자신에게 전달된 객체(수신 객체)를 반환한다는 점입니다.

 

fun createTitleTextView(context: Context, title: String): TextView {
    return TextView(context).apply {
        layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
        text = title
        setTextColor(Color.BLACK)
        setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15f)
    }
}

 

위 예제에서 apply는 아래와 같이 사용됩니다.

1. 새로운 TextView를 만들고

2. 즉시 그 객체가 apply에 전달되어 람다 안에서는 TextView가 수신 객체가 됩니다.

3. 따라서 원하는 대로 TextView의 메소드를 호출하거나 프로퍼티를 설정할 수 있고

4. 최종적으로 apply 람다에 의해 초기화된 TextView 객체가 반환됩니다.

 

apply는 전달받은 객체를 다시 반환하기 때문에 객체를 생성하면서 즉시 객체의 프로퍼티를 초기화해야 하는 경우 굉장히 유용합니다.