코틀린(Kotlin)

코틀린(kotlin) : Inline Function 인라인 메서드

알통몬_ 2018. 2. 6. 15:32
반응형


공감 및 댓글은 포스팅 하는데

 아주아주 큰 힘이 됩니다!!

포스팅 내용이 찾아주신 분들께 

도움이 되길 바라며

더 깔끔하고 좋은 포스팅을 

만들어 나가겠습니다^^

 


이번 포스팅에서는 Inline Function 에 대해 공부합니다.


Inline Functions

고차 함수를 사용하게 되면 런타임 시 특정 패널티가 부과됩니다.

각 메서드는 객체이고, 메서드의 body 에서 엑세스 되는 변수인 closure를 캡처 합니다.

메모리 할당과 가상 호출은 런타임 오버 헤드를 초래합니다.


하지만, 많은 경우에 이런 오버 헤드는 람다 식을 inlining 함으로써 제거될 수 있습니다.

아래의 함수가 이러한 상황의 좋은 예입니다.

lock(l) { foo() }

매개 변수에 대한 함수 객체를 만들고 호출을 생성하는 대신 

컴파일러에서 다음 코드를 생성 할 수 있습니다.

l.lock() try { foo() } finally { l.unlock() }

컴파일러가 작업을 수행하게 하려면 lock() 메서드를 inline 수정자로 표시해야 합니다.

inline fun <T> lock(lock: Lock, body: () -> T): T { // ... }


인라인 수정자는 메서드 자체와 전달된 람다에 모두 영향을 미칩니다.



noinline

인라인 메서드에 전달된 일부 람다만 인라인되게 하려면 noinline 수정자를 사용하면

됩니다.

inline fun foo(inlined: () -> Unit, noinline notInlined: () -> Unit) { // ... }


inlinable 람다는 오직 인라인 메서드 내에서만 호출되거나, 인라인 가능 인자로

전달될 수 있지만, noinline 은 우리가 선호하는 어떤 방식으로도 조작 가능합니다.


* 인라인 메서드에 인라인 메서드 파라미터가 없고, 구체화된 타입의 파라미터가 없는

경우 컴파일러에서 해당 메서드를 인라인하면 매우 유용하지 않기 때문에

컴파일러에서 경고를 표시합니다.


Non-local returns

코틀린에서는 명명되지 않은 메서드나, 익명 함수를 종료하기 위해

'return' 만 사용할 수 있습니다.

즉, 람다를 빠져나오려면 label을 사용해야 하고, 람다가 둘러싼 메서드를 반환할 수

없기 때문에 'return'을 금지합니다.

fun foo() {
ordinaryFunction {
return // ERROR: can not make `foo` return here
}

}


하지만, 람다가 전달된 메서드가 인라인되면, 리턴도 인라인될 수 있으므로 허용됩니다.


fun foo() {
inlineFunction {
return // OK: the lambda is inlined
}
}

이러한 반환(람다에 위치해 있지만, 포함 메서드를 빠져나옴)을 

non-local returns 라고 부릅니다.

우리는 인라인 메서드가 자주 묶는 아래와 같은 메서드에 익숙합니다.

fun hasZeros(ints: List<Int>): Boolean {
ints.forEach {
if (it == 0) return true
}
return false
}


일부 인라인 메서드는 파라미터로 전달된 람다를 body에서 직접 호출하지 않고,

로컬 오브젝트나, 중첩 메서드와 같은 실행 컨텍스트에서 호출할 수 있습니다.

이러한 경우에는 non-local control flow는 람다에서 허용되지 않습니다.

이것을 나타내기 위해서 람다 파라미터를 crossinline 수정자를 표시해야 합니다.

inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run() = body()
}
// ...
}

인라인 람다에서 아직까지는 break 와 continue는 허용되지 않지만,

지원하려고 계획 중에 있다고 합니다.

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


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



Reified type paramaters : 구체화된 타입 파라미터

때때로 전달된 타입을 매개 변수로 접근해야 합니다.

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?
}

treeNode.findParentOfType(MyTreeNode::class.java)

위처럼 사용하는데요, 별로 코드가 이쁘지 않습니다.


우리가 실제로 원하는 것은 아래 호출처럼 메서드에 유형을 전달하는 것입니다.

treeNode.findParentOfType<MyTreeNode>()

이 것을 가능하게 하기 위한 인라인 메서드는 구체화된 타입의 파라미터를 지원하기 때문에

아래처럼 작성할 수 있습니다.

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

reified 수정자로 타입 파라미터를 정규화했기 때문에 이제 메서드 내부에서

일반 클래스처럼 엑세스할 수 있습니다. 메서드가 인라인 되어있기 때문에

reflection 할 필요는 없습니다. 

그리고 위에 언급한 것처럼 호출할 수 있습니다.

treeNode.findParentOfType<MyTreeNode>()

대부분의 경우 리플렉션이 필요하지 않지만, 구체화된 타입 파라미터로

리플렉션을 사용할 수 있습니다.

inline fun <reified T> membersOf() = T::class.members

fun main(s: Array<String>) {
println(membersOf<StringBuilder>().joinToString("\n"))
}


inline 마크가 없는 일반 메서드는 구체화된 파라미터를 가질 수 없습니다.

런타임 표현이 없는 타입은 구체화된 파라미터의 인수로 사용할 수 없습니다.



Inline properties(1.1버전부터)

인라인 수정자는 backing field 가 없는 프로퍼티의 접근자에서 사용할 수 있습니다.

ex)

val aaa: AAA
inline get() = AAA()

var bbb: BBB
get() = ...
inline set(v) { ... }

class AAA
class BBB

프로퍼티 앞에 inline 수정자를 붙여주면 속성 전체를 인라인으로 표시합니다.

inline var bbb: BBB
get() = ...
set(v) { ... }

호출 영역에서 인라인 접근자는 일반 인라인 메서드로 인라인됩니다.



이상입니다.

감사합니다.

다음 포스팅에서는

Destructuring Declarations

에 대해서 공부합니다.

반응형