preface

Speaking of inline functions, Android developers familiar with Kotlin must have used them, such as the common apply function:

public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}
Copy the code

We have a pretty good idea of what inlie does, but we haven’t really thought about why, and its two Cousins, Oninline and Crossinline, are also used. In this article, I’m going to try to explain them from a very simple point of view.

The body of the

The first thing you need to know about Kotlin’s lambda and function types is my previous post:

# Kotlin lambda has everything you need to know

The important concept here is that Kotlin’s higher-order function is actually an instance of FunctionN, and that Kotlin’s lambda, when passed as an argument to a parameter, also creates an anonymous inner class and calls the invoke method, which we’ll drill down on here.

Android Studio decompiles the Kotlin code

This is a handy feature of AS. When we write a Kotlin file and want to see how it will compile to Java, we can use AS directly to do this.

For example:

Kotlin bytecode is displayed as follows:

Then click this decompilation button:

The corresponding Java file is then generated:

Here, for example, we can see that the lambda in Kotlin code creates an instance object of OnClickListener, so when unfamiliar with Kotlin code, decomcompiling it into the corresponding Java code makes it much easier to understand.

Lambda is passed as an argument to the Kotlin higher-order function

Here’s a review of what happens when Kotlin’s higher-order functions call a lambda as an argument, as in the following code:

// Define a higher-order function in the Kt file
fun lambdaFun(action: (() -> Unit)){
    Log.i("zyh"."TestLambdaFun: Before call")
    action()
    Log.i("zyh"."TestLambdaFun: after call")}Copy the code

Call the higher-order function above:

fun testHello1(){
    lambdaFun {
        Log.i("zyh"."TestLambdaFun: calling now")}}Copy the code

To convert to Java code:

public final class TestFun {
   public final void testHello1() {
       // Convert lambda to an instance of Function0
      TestInlieKt.lambdaFun((Function0)null.INSTANCE); }}Copy the code

You’ll see that there’s nothing wrong here. It makes sense to convert a lambda to an anonymous inner class implementation, but when there are lots of places that say:

// Call lambda multiple times
fun testHello1(){
    for (i in 0.10){
        lambdaFun {
            Log.i("zyh"."TestLambdaFun: calling now")}}}Copy the code

Decompilation result:

public final class TestFun {
   public final void testHello1() {
      int var1 = 0;
      // Create multiple instances of anonymous inner classes
      for(byte var2 = 10; var1 <= var2; ++var1) {
         TestInlieKt.lambdaFun((Function0)null.INSTANCE); }}}Copy the code

This is where the problem occurs. When there are too many anonymous inner classes, memory increases, which is where the inline keyword comes in.

The inline keyword

The inline keyword modifies the function, which is then called inline. When the function is inline, the body of the function and its type parameters are “inline” to the place of the call.

Two things to understand here are the inlining of the function body inside the function and the inlining of the lambda expression passed in. This is important, and I’ll talk about it later.

To understand, let’s take a simple example:

// Define a non-higher order function
fun normalFun(){
    Log.i("zyh"."TestLambdaFun: Before call")
    Log.i("zyh"."TestLambdaFun: after call")}Copy the code

Call to place, then decompile:

fun testHello(){
    normalFun()
}
Copy the code
/ / decompiling
public final class TestFun {
   public final void testHello(){ TestInlieKt.normalFun(); }}Copy the code

This is the normal call when normalFun is defined as an inline function:

// Add inline to normal functions
inline fun normalFun(){
    Log.i("zyh"."TestLambdaFun: Before call")
    Log.i("zyh"."TestLambdaFun: after call")}Copy the code

Then call, and decompile:

// Decompile the code
public final class TestFun {
   public final void testHello() {
      int $i$f$normalFun = false;
      Log.i("zyh"."TestLambdaFun: Before call");
      Log.i("zyh"."TestLambdaFun: after call"); }}Copy the code

It’s nice to see that the logic in normalFun is copied directly to where it was called.

Note that this operation is done by the Kotlin compiler, so we don’t have to discuss it.

If this were the only effect on ordinary functions, it would not be interesting, and at most it would be a loss of a layer in the call station, but it would not be worthwhile for the compiler to do so much work. The real highlight is higher-order functions.

Inline decorates higher-order functions

Inline not only “flattens” the code inside a function, but also the parameters of a function type, which is key, as shown in the following code:

// Add inline for higher-order functions
inline fun lambdaFun(action: (() -> Unit)){
    Log.i("zyh"."TestLambdaFun: Before call")
    action()
    Log.i("zyh"."TestLambdaFun: after call")}Copy the code

The call is then made to decompile:

// Decompile the code
public final class TestFun {
   public final void testHello() {
      int $i$f$lambdaFun = false;
      Log.i("zyh"."TestLambdaFun: Before call");
      int var2 = false;
      Log.i("zyh"."TestLambdaFun: In call");
      Log.i("zyh"."TestLambdaFun: after call"); }}Copy the code

Surprisingly, the decomcompiled code here has no shadow of an anonymous inner class, which serves our purpose. Even if multiple lambdas are called here, if the function being called is declared inline, it will not create multiple useless classes and will greatly reduce memory usage.

conclusion

Actually inline function is the key, it solved using lambda convenient at the same time to create the disadvantages of an anonymous inner class too much, I prefer here called the change of the inline function is called copy paving, namely the function body code and the function of the type parameter to assign a value to call, paved, of course, this term is just personal understanding.

Since assignment flattens are so handy, we’ll get to the next article about whether the function type parameters of all inline functions can be copied, namely the use of noinlien and Crossinline.

# Kotlin inline, noinline, Crossinline complete analysis 2