본문 바로가기
Kotlin

Kotlin - inline, noinline, clossline, reified

by 봄석 2019. 3. 9.

Kotlin - inline, noinline, clossline

일급함수

inline, noinline을 이해하기 위해서는 일급함수에 대한 이해가 필수적입니다.이미 알고있으신 내용이면 편하게 스크롤을 내려주세요.일급함수는 스스로 객체로써 취급되는 함수로 다른 함수를 파라미터로 전달받고 반환할 수 있는 함수를 뜻합니다. 이부분은 코드를 보는 편이 더 좋을 것 같습니다.

fun print(body:(Int,Int) -> Int) {
    println(body(5,5))
}
 

print({a,b->a})


위 함수를 보겠습니다. 위 함수는 두개의 정수를 받아 하나의 정수값만을 출력하는 함수입니다. 위와같이 선언을 하게된다면 java에서 아래와 같은 코드로 변환을하여 사용할 수 있습니다.


public final void print(@NotNull Function2 body){
    Intrinsics.checkParameterIsNotNull(body, "body");
    Object result = body.invoke(5,5);
    System.out.println(result);
}



위와같이 자동으로 컨버팅이 될 수 있기때문에 아래와 같은 유동적인 방법도 사용이 가능합니다.


class InlineText {
    private fun printResult(body: (Int, Int) -> Int) = body(10, 5)
 
    fun sum(a: Int, b: Int) = a + b
    fun subtract(a: Int, b: Int) = a - b
 
    @Test
    fun printResultSumTest() {
        Assert.assertEquals(15, printResult(::sum))
    }
 
    @Test
    fun printResultSubtractTest(){
        Assert.assertEquals(5,printResult(::subtract))
    }
 
}



Inline과 noinline

fun doSometing(body: () -> Unit) {
        body()
}

fun callFunction(){
        doSometing { println("문자열출력!") }
}

이 함수를 자바로 표현한다면

    public void doSometing(Function body){
        body.invoke();
    }
    public void callFunction(){
        doSometing(System.out.println("문자열출력"));
    }



위 코드와 같습니다. 그리고 이 자바코드는 아래와 같이 변환되게됩니다.

public void callingFunction(){
    doSometing(new Function(){
        @Override
        public void invoke(){
            System.out.println("문자열출력")
        }
    });

}


문제는 위 sum이나 Substract처럼 조합하는 함수가 많아질수록 계속 N개만큼의 function 오브젝트가 생성됩니다. 이럴때 사용하게되는것이 inline 키워드입니다.




Decompile해보기 

fun doSometing(body: () -> Unit) {
    body()
}
fun callFunction(){
    doSometing { println("문자열출력!") }

}


위 코드를 디컴파일 해보면 아래와 같은 결과를 볼 수 있습니다.

   public final void doSometing(@NotNull Function0 body) {
      Intrinsics.checkParameterIsNotNull(body, "body");
      body.invoke();
   }
 
   public final void callFunction() {
      this.doSometing((Function0)null.INSTANCE);
   }



callFunction 함수에서 doSometing 함수를 호출하는것을 볼 수 있습니다

  inline fun doSometing(body: () -> Unit) {
        body()
    }
    fun callFunction(){
        doSometing { println("문자열출력!") }
    }



 위 코드 처럼 인라인키워드를 추가하고 다시 디컴파일 해보면 

  public final void doSometing(@NotNull Function0 body) {
      int $i$f$doSometing = 0;
      Intrinsics.checkParameterIsNotNull(body, "body");
      body.invoke();
   }
 
   public final void callFunction() {
      int $i$f$doSometing = false;
      int var3 = false;
      String var4 = "문자열출력!";
      System.out.println(var4);
   }



CallFunction 함수 내부안에 바로 삽입되어 선언되게 됩니다.


여기서 주의할 점이 있는데 inline 함수는 private 키워드를 사용하여 정의할 수 없습니다. 하지만 대신 다른 접근한정자인 internal을 사용할 수 있습니다.



noinline

모든 람다함수에 inline을 쓰고싶지 않을 수 있습니다. 이 경우 아래와같이 해당 람다함수에 noinline 키워드를 추가하여줍니다.

   inline fun callingLmabda(aLambda:()-> Unit,
                             noinline dontInlineLambda:()->Unit,
                             aLambd2a: () -> Unit){
        /////////
        //내부로직
        ////////
        
    }


모든 함수를 inline을 사용하여 내부로 컨버팅 되어지길 원치않을 수 있습니다. 이 경우 위처럼 inline을 먼저 선언한 뒤 람다함수중 사용하지 않을 함수에 noinline키워드를 붙여줍니다



crossinline

일부 인라인 함수는 파라미터로 전달받은 람다를 호출할 때 함수 몸체에서 직접 호출하지 않고 다른 실행 컨텍스트를 통해(예, 로컬 객체나 중첩 함수,스레드) 호출해야 할 때가 있다. 이 경우 람다 안에서 비-로컬 흐름을 제어할 수 없다. 이를 지정하려면 람다 파라미터에crossinline 제한자를 붙이면 됩니다.

inline fun calc(crossinline body: () -> Unit): Long {
  val start = System.nanoTime()
  val f = object : Runnable {
    override fun run() = body()
  }
  
  return System.nanoTime() - start
}



reified


다음은 reified 타입 파라미터인데  Kotlin 가이드 문서에 있는 내용으로 확인해 보겠습니다.


fun <T> TreeNode.findParentOfType(clazz: Class<T>): T? {
  var p = parent
  while (p != null && !clazz.isInstance(p)) { 
    p = p?.parent
  } 
  
  @Suppress("UNCHECKED_CAST") 
  return p as T
}



위 함수는 노드가 특정 타입을 가졌는지 확인하기 위해 트리를 탐색하고 리플렉션을 사용하는 코드입니다 위 함수를 호출할 때 다음과 같이 호출할 것입니다. findParentOfType(SomeClass::class.java) 이렇게 파라미터로 보기 좋지 않은 값을 전달하게 된다. 우리는 조금 더 간결하게 findParentOfType<SomeClass> 이렇게 표현하고 싶다면, 그럴 경우 reified를 이용하여 인라인 함수를 정의하면 됩니다.

inline fun <reified T> TreeNode.findParentOfType(): T? { 
  var p = parent
  while (p != null && p !is T) { 
    p = p?.parent
  }
  
  return p as T 
}



위와 같이 인라인 함수를 정의하였다면 findParentOfType<SomeClass>() 이렇게 호출할 수 있습니다. 타입 파라미터에 reified를 정의하면 마치 클래스처럼 타입 파라미터에 접근할 수 있습니다. 인라인 함수이므로 리플렉션이 필요 없고 is나 as와 같은 일반 연산자가 동작합니다. 참고로 인라인이 아닌 일반 함수에는 reified를 사용할 수 없습니다.

댓글