The inline keyword

Function call – how it works

We often use SharedPreferences in our applications. Now suppose you implement the following utility functions to reduce the template code generated each time you write to SharedPreferences:


fun SharedPreferences.edit(
    commit: Boolean = false,
    action: SharedPreferences.Editor.() -> Unit
) {
    val editor = edit()
    action(editor)
    if (commit) {
        editor.commit()
    } else {
        editor.apply()
    }
}
Copy the code

You can then save a string “token” with this method:

Private const val KEY_TOKEN = "token" class preference Manager(private val preferences: SharedPreferences){ fun saveToken(token: String) { preferences.edit { putString(KEY_TOKEN, token) } } }Copy the code

Let’s take a look at what happens behind the scenes when preferences. Edit is called. If we look at Kotlin bytecode (Tools > Kotlin > Decomcompiled Kotlin to Java), we can see that the NEW instruction is called here. So we created a new object even though we didn’t call any other object’s constructor:


NEW com/example/inlinefun/PreferencesManager$saveTokenThe $1
Copy the code

To make it easier to understand, let’s look at the decompiled code. Our decompiled saveToken function looks like this (I annotated and formatted it):

/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ public Final void saveToken(@notnull final String token) {// The extension method we defined to modify SharedPreferences was called PreferenceManagerKt.edit$defaultPreferences, // SharedPreferences instance objectfalse,// Commit the default value of the tag (Function1)(newFunction1() {// creates a new Function object for the action argument //$FF: synthetic method
            // $FF: bridge method
            public Object invoke(Object var1) {
                this.invoke((Editor)var1);
                return Unit.INSTANCE;
            }
            public final void invoke(@NotNull Editor $this$edit) {
                Intrinsics.checkParameterIsNotNull($this$edit."$receiver");
                $this$edit.putString("token", token); }}), 1, (Object)null); }Copy the code

Each higher-order function incurs additional runtime overhead by creating function objects and allocating memory.

Inline functions – How they work

To improve the performance of our application, we can reduce the creation of function objects by using the inline keyword:

inline fun SharedPreferences.edit(
    commit: Boolean = falseAction: SharedPreferences. Editor. () - > Unit) {... }Copy the code

Now that the Kotlin bytecode no longer contains any calls to the NEW instruction, here is the Java code decomcompiled (annotated and formatted) from the saveToken method:

/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ public Final void saveToken(@notnull String token) {// SharedPreferences$this$edit$iv = this.preferences;
  boolean commit$iv = false;
  int $i$f$edit = false;
  Editor editor$iv = $this$edit$iv.edit();
  Intrinsics.checkExpressionValueIsNotNull(editor$iv."editor");
  int var7 = false; // The content implemented in the action argument editor$iv.putString("token", token); // SharedPreferences. Edit Function content editor$iv.apply();
}
Copy the code

Because of the inline keyword, the compiler copies the contents of the inline function to the call, avoiding the creation of a new function object.

Where should inline tags be used?

⚠️ If you try to mark a function as an inline function and do not receive another function as a parameter, you will not get a significant performance improvement, and the IDE may even suggest that you remove the inline flag:

Avoid inlining large functions

⚠️ Do not inline large functions!

⚠️ When using an inline function, you cannot hold a reference to the function argument object passed in, nor can you pass the function argument object passed in to another function — doing so will trigger an error from the compiler that says you are using an inline-parameter illegally.

For example, let’s modify the Edit and saveToken methods. The Edit method takes a new function parameter and then passes it to another function. The saveToken method updates an arbitrarily set mock variable in the new function argument:


fun myFunction(importantAction: Int.() -> Unit) {
    importantAction(-1)
}

inline fun SharedPreferences.edit(
    commit: Boolean = false, importantAction: Int.() -> Unit = { }, action: SharedPreferences.Editor.() -> Unit ) { myFunction(importantAction) ... }... fun saveToken(token: String) { var dummy = 3 preferences.edit(importantAction = { dummy = this}) { putString(KEY_TOKEN, token) } }Copy the code

We will see that myFunction(importantAction) generates an error:

Case 1: If your function has multiple function arguments, but you need to hold a reference to one of them, you can mark the corresponding argument as noinline.

By using noinline, the compiler only creates new Function objects for the corresponding Function, and the rest is still inline.

Our edit function will now look like this:


inline fun SharedPreferences.edit(
    commit: Boolean = false,
    noinline importantAction: Int.() -> Unit = { },
    action: SharedPreferences.Editor.() -> Unit
) {
    myFunction(importantAction)
    ...
}
Copy the code

If we look at the bytecode, we’ll see a call to the NEW directive:

NEW com/example/inlinefun/PreferencesManager$saveTokenThe $1
Copy the code

In the decompiled code, we should see something like this (annotated):

/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ public final void saveToken(@notnull String token) {final IntRef x = new IntRef(); x.element = 3; // Inline the edit method with SharedPreferences$this$edit$iv= this.preferences; // noinline Function declaration causes new Function to be called Function1 importantAction$iv = (Function1)(new Function1() {
        // $FF: synthetic method
        // $FF: bridge method
        public Object invoke(Object var1) {
            this.invoke(((Number)var1).intValue());
            return Unit.INSTANCE;
        }
        public final void invoke(int $receiver) {// The saveToken function x. lement =$receiver; }}); // Inline Edit method function Boolean commit$iv = false;
   int $i$f$edit = false;
   PreferenceManagerKt.myFunction(importantAction$iv);
   Editor editor$iv = $this$edit$iv.edit();
   Intrinsics.checkExpressionValueIsNotNull(editor$iv."editor");
   int var9 = false;
   editor$iv.putString("token", token);
   editor$iv.apply();
}
Copy the code

Case two: If your function accepts only one function as an argument, then don’t use inline at all. If you wanted to use the inline keyword, you would have to mark the parameter as noinline, but inlining this method would have little performance advantage.

To reduce the extra memory allocation caused by lambda expressions, it is recommended that you use the inline keyword! Just note that the tag object is best served as a small function that takes a lambda expression as an argument. If you need to hold a reference to a lambda expression (as an argument to an inline function) or want to pass it as an argument to another function, use the noinline keyword to mark the corresponding argument. Save money by using inline!

Click here to learn more about Android development with Kotlin