Koltin - 内联函数(Inline functions)

一个app包含了许多的函数,其中也包含一些函数把其他函数当作参数。如果需要通过改善一些额外的对象内存分配来提示应用的性能,可以使用内联(inline)

假如在应用中经常使用SharedPreferences而创建了下面的扩展函数来减少代码量

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

如下使用该函数来保存字符串:

private const val KEY_TOKEN = "token"

class SharePreferencesManager(private val preferences: SharedPreferences){

fun saveToken(token:String){
preferences.edit {
putString(KEY_TOKEN,token)
}
}
}

通过查看saveToken函数的部分Kotlin字节码可以知道创建了一个对象,但是代码中却没有生成对象的相关代码:

public final saveToken(Ljava/lang/String;)V
 // annotable parameter count: 1 (visible)
 // annotable parameter count: 1 (invisible)
 @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
 L0
 ALOAD 1
 LDC "token"
 INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
 L1
 LINENUMBER 26 L1
 ALOAD 0
 GETFIELD org/nobody/auto/extend/SharePreferencesManager.preferences : Landroid/content/SharedPreferences;
 ICONST_0
 NEW org/nobody/auto/extend/SharePreferencesManager$saveToken$1
 DUP
 ALOAD 1
 INVOKESPECIAL org/nobody/auto/extend/SharePreferencesManager$saveToken$1.<init> (Ljava/lang/String;)V
 CHECKCAST kotlin/jvm/functions/Function1
 ICONST_1
 ACONST_NULL
 INVOKESTATIC org/nobody/auto/extend/SharePreferencesKt.edit$default (Landroid/content/SharedPreferences;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
 L2
 LINENUMBER 29 L2
 RETURN
 L3
 LOCALVARIABLE this Lorg/nobody/auto/extend/SharePreferencesManager; L0 L3 0
 LOCALVARIABLE token Ljava/lang/String; L0 L3 1
 MAXSTACK = 5
 MAXLOCALS = 2

 

将字节码反编译为Java代码后可以看到saveToken函数调用扩展方法时,创建一个Function1的对象,这就是高阶函数所造成的额外开销:

public final void saveToken(@NotNull final String token) {
   Intrinsics.checkParameterIsNotNull(token, "token");
   SharePreferencesKt.edit$default(this.preferences, false, (Function1)(new Function1() {
      // $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);
}

为了提高应用性能,可以避免创建新函数对象,只需添加关键字inline

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

 

添加inline关键字后的Kotlin字节码中就没有NEW操作了。从下面反编译后的Java代码中的saveToken函数可以看出在使用inline关键字后,编译器会复制内联函数的内容到调用处从而避免创建新的对象。

public final void saveToken(@NotNull String token) {
   Intrinsics.checkParameterIsNotNull(token, "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;
   editor$iv.putString("token", token);
   editor$iv.apply();
}

如果尝试将一个没有函数作为参数的函数标记为内联时并不会有任何优化效果,并且IDE会给出警告:

inline fun add(a:Int,b:Int) = a+b

因为内联会导致生成的代码大量增长,应该尽量避免内联大型函数,在Kotlin Stdlib中的内联函数大多数都是1到3行。

在使用内联函数时,不允许保留对传递函数的引用,即不允许将该函数参数进行传递:

fun function(action:SharedPreferences.Editor.()->Unit){

}

inline fun SharedPreferences.edit(
commit:Boolean = false,
action:SharedPreferences.Editor.()->Unit
){
//编译错误
function(action)
}

如果你使用多个函数作为参数,如果你只需要引用其中的一个,可以将他标记为noinline,编译器会为该函数创建一个新对象不让其内联,而其他的函数则会内联:

fun function( action:SharedPreferences.Editor.()->Unit){

}

inline fun SharedPreferences.edit(
commit:Boolean = false,
noinline action:SharedPreferences.Editor.()->Unit,
action2:SharedPreferences.Editor.()->Unit
){
//编译成功
function(action)
}

综上所述,为了减少通过lambda表达式造成的内存分配,在开发中可以合理地使用内联函数。

 

 

点赞

发表评论

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像

Title - Artist
0:00