본문 바로가기
Kotlin

DID 스터디 3회차(kotlin let/run/also/apply/with)

by ybs 2023. 3. 18.
반응형

DID 안드로이드 앱개발을 위해 kotlin 을 다시 공부하는 중이다.

let 확장함수 코드를 보고 이전에 배웠던 let/run/apply/also/with 들을 다시 정리했다.

 

그전에 코틀린에서 많이 보이는 함수 패턴 중 하나를 간단히 살펴보자.

아래 func 함수는 두개의 인자를 받는다.

1. Int 타입의 age

2. Int 를 받아 Unit 을 리턴(Void 리턴) 하는 process(함수 타입)

 

process 변수에 담길 함수는 전달받은 age 만 출력하는 단순한 로직이지만 확장성 있는 구조를 보여준다. preProcess 와 postProcess 를 계속 재활용하면서 process 부분만 새롭게 바꿀 수 있다.

@Test
fun test1() {
    // 코틀린에서 많이 보이는 패턴
    func(34) { age ->
        println("age : $age")
    }
}

private fun func(age: Int, process: (Int) -> Unit) {
    // preProcess
    process(age)
    // postProcess
}

 

이제 let 확장함수를 살펴보자. Sample 클래스에서 let 함수를 만든적이 없지만 Sample(1, 1, 1).let 을 사용한걸 확인할 수 있다. let 정의를 보면 T.let 으로 되어 있기 때문에 가능한 일이다. let 두번째 인자인 block 함수에서는 만든 Sample 객체를 전달받아 별도 R 타입으로 리턴이 가능하다. 다시말해 리턴타입을 별도로 지정할 수 있다. 아래 코드에서는 "ybs" 문자열을 리턴시켰다(리턴 지시자를 안써도 된다 마지막줄이 리턴될 값이다).

class UnitTest {
    @Test
    fun testLet() {
        val name : String = Sample(1, 1, 1).let { sample ->
            sample.a
            sample.b // private 이라 접근 못함
            sample.c // Unresolved reference: c

            "ybs"
        }
        print(name)
    }
}

class Sample(
    var a: Int,
    private val b: Int,
    c: Int
)


- - - - - - - - -

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

 

이제 let 을 사용하는 안드로이드 코드를 살펴보자. onCreate 함수에서 arguments 변수 값이 존재하면 null 이었던 item 값이 채워진다. 

class credentialDetailFragment : Fragment() {

  private var item: JSONObject? = null

  override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)

      arguments?.let {
          item = JSONObject(it.getString(ARG_CREDENTIAL))
          activity?.toolbar_layout?.title = getString(R.string.title_credential_detail)
      }
  }
  
...

 

그리고 onCreateView 함수에서 item 값이 있으면(변수명은 item 이지만 JSONObject 객체) 중괄호 안에서 it 으로 전달받고 사용하는것을 볼 수 있다.

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    val rootView = inflater.inflate(R.layout.credential_detail, container, false)

    item?.let {
        val attrs = it.getJSONObject("attrs")
        val date = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH).parse(attrs.getString("date")).toLocaleString()
        rootView.credential_detail.text =
            """
                Issuer organization: ${attrs.getString("organization")}
                Vaccine name: ${attrs.getString("vaccine")}
                Total doses: ${attrs.getString("doses")}
                Recipient name: ${attrs.getString("target")}
                Date of issueance: $date
                    
                Revocation ID: ${it.getString("cred_rev_id")}
           """.trimIndent()
    }

    return rootView
}

 

위 코드들을 보면서 let 확장함수는 null 이 아닐 경우에 이어지는 작업을 자연스럽게 진행시켜준다는 느낌을 받았다.

cf) let 과 비슷한 확장함수로 run 도 있다.

@Test
fun testRun() {
    // fun <T, R> T.run(block: T.() -> R): R {
    val name: String = Sample(1, 1, 1).run {
        this.a
        "ybs"
    }
    println(name)
}

 

다음은 also / apply 에 대해서 알아보자. let / run 과는 다르게 리턴타입을 바꿀 수 없다. 항상 T 타입으로 고정이므로 Sample 객체로 고정이다. 그래서 Sample 객체를 생성한 후 추가적인 셋팅이 필요할 때 사용하면 좋다.

@Test
fun testAlso() {
    // fun <T> T.also(block: (T) -> Unit): T , 현재 사용중인 객체에 also 를 집어 넣는 확장함수
    val sample = Sample(1, 1, 1).also { sample ->
        sample.a = 10
        // return 값은 항상 sample 객체다. 왜냐면 also 확장함수 return 이 항상 this 이므로.
    }

    // also 는 객체를 만들고 나서 추가적인 set 이 필요할 때 쓰면 좋다.
    println(sample.a)
}

@Test
fun testApply() {
    // fun <T> T.apply(block: T.() -> Unit): T {
    // also 는 파라미터로 this 를 넣어줬는데 apply 는 안넣어준다.
    val sample = Sample(1, 1, 1).apply {
        this.a = 10
    }

    // also 와 큰 차이없고, 마찬가지로 추가적인 set 할 때 쓰면 좋다.
    println(sample.a)
}

 

마지막으로 with 은 T 타입의 receiver 파라미터를 받아 block 함수를 호출한다. block 함수는 R 타입을 리턴하므로 sample 객체와는 다른 값으로 리턴이 가능하다.

@Test
fun testWith() {
    val sample = Sample(1, 1, 1)

    val result: Int = with(sample) {
        println(a) // a 프로퍼티는 sample 에 있는것
        println(this.a)
        println(sample.a)


        100 + a // 마지막이 return 값
    }

    println(result)
}

- - - - - - - - - - - - - -

@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

 

 

반응형

'Kotlin' 카테고리의 다른 글

fixture monkey BuilderGroups  (0) 2021.12.27
코틀린 재귀호출 최적화(Tail-Call)  (0) 2021.12.16
fixture monkey 로 예외 발생 테스트  (0) 2021.12.15
form data 를 string으로 변환하기  (0) 2021.11.15