咱們在應用中經常要用到 SharedPreferences,如今假設您爲了減小每次向 SharedPreferences 中寫入內容時產生的模板代碼,實現瞭如下實用函數:html
fun SharedPreferences.edit(
commit: Boolean = false,
action: SharedPreferences.Editor.() -> Unit
) {
val editor = edit()
action(editor)
if (commit) {
editor.commit()
} else {
editor.apply()
}
}
複製代碼
而後,您就能夠用這個方法保存一個字符串 "token" :android
private const val KEY_TOKEN = 「token」
class PreferencesManager(private val preferences: SharedPreferences){
fun saveToken(token: String) {
preferences.edit { putString(KEY_TOKEN, token) }
}
}
複製代碼
接下來咱們看看,preferences.edit 被調用時其背後發生了什麼。若是咱們查看 Kotlin 字節碼 (Tools > Kotlin > Decompiled Kotlin to Java),就能看到這裏調用了 NEW 指令。因此雖然咱們沒有調用任何其餘對象的構造函數,卻仍是建立出了一個新的對象:bash
NEW com/example/inlinefun/PreferencesManager$saveToken$1
複製代碼
爲了便於理解,讓咱們查看一下反編譯後的代碼。咱們的 saveToken 函數反編譯後的代碼以下 (我作了註釋和格式化):app
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
public final void saveToken(@NotNull final String token) {
// 咱們定義的修改 SharedPreferences 的擴展方法被調用了
PreferenceManagerKt.edit$default(
this.preferences, // SharedPreferences 實例對象
false,// commit 標記的默認值
(Function1)(new Function1() { // 爲 action 參數建立了新的 Function 對象
// $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); // 咱們 action 參數中的實現
}
}), 1, (Object)null);
}
複製代碼
每一個高階函數都會形成函數對象的建立和內存的分配,從而帶來額外的運行時開銷。函數
爲了提高咱們應用的性能,咱們能夠經過使用 inline 關鍵字,來減小函數對象的建立:性能
inline fun SharedPreferences.edit(
commit: Boolean = false,
action: SharedPreferences.Editor.() -> Unit
) { … }
複製代碼
如今,Kotlin 字節碼中已經不包含任何 NEW 指令的調用了,下面是 saveToken 方法反編譯出的 Java 代碼 (作了註釋和格式化):ui
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
public final void saveToken(@NotNull String token) {
// SharedPreferences.edit 函數中的內容
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;
// action 參數中實現的內容
editor$iv.putString("token", token);
// SharedPreferences.edit 函數中的內容
editor$iv.apply();
}
複製代碼
因爲使用了 inline 關鍵字,編譯器會將內聯函數的內容複製到調用處,從而避免了建立新的函數對象。this
⚠️ 若是您試圖標記爲內聯函數的函數,並無接收另外一個函數做爲參數,您將沒法得到明顯的性能提高,並且 IDE 甚至會建議您移除 inline 標記:google
⚠️ 不要內聯大型函數!spa
⚠️ 使用內聯函數時,您不能持有傳入的函數參數對象的引用,也不能將傳入的函數參數對象傳遞給另外一個函數——這麼作將會觸發編譯器報錯,它會說您非法使用內聯參數 (inline-parameter)。
舉個例子,咱們修改一下 edit 方法和 saveToken 方法。edit 方法得到了一個新的函數參數,並在隨後將其傳遞給了另外一個函數。saveToken 方法則會在新的函數參數中更新一個隨意設置的模擬變量:
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)
}
}
複製代碼
咱們將會看到 myFunction(importantAction) 產生了一個錯誤:
第一種狀況: 若是您的函數有多個函數參數,可是您須要持有其中某個的引用時,您能夠將對應的參數標記爲 noinline。
經過使用 noinline,編譯器就只會爲對應函數建立新的 Function 對象,其他的則依舊會被內聯。
咱們的 edit 函數如今會變成下面這樣:
inline fun SharedPreferences.edit(
commit: Boolean = false,
noinline importantAction: Int.() -> Unit = { },
action: SharedPreferences.Editor.() -> Unit
) {
myFunction(importantAction)
...
}
複製代碼
若是咱們去查看字節碼,將會看到這裏出現了一個 NEW 指令的調用:
NEW com/example/inlinefun/PreferencesManager$saveToken$1
複製代碼
在反編譯後的代碼中,咱們會看到以下內容 (加入了註釋):
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
public final void saveToken(@NotNull String token) {
// saveToken 方法中的功能
final IntRef x = new IntRef();
x.element = 3;
// 內聯 edit 方法中的功能
SharedPreferences $this$edit$iv = this.preferences;
// noinline 函數聲明致使 new Function 被調用
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) {
// saveToken 的功能
x.element = $receiver;
}
});
// 內聯 edit 方法中的功能
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();
}
複製代碼
第二種狀況: 若是您的函數只接收一個函數做爲參數,那麼就乾脆不要使用 inline。若是您執意使用 inline 關鍵字,就必須將參數標記爲 noinline,可是這麼一來,內聯此方法的性能優點微乎其微。
爲了減小 lambda 表達式帶來的額外內存分配,建議您使用 inline 關鍵字!只需注意,標記對象最好是接收一個 lambda 表達式做爲參數的小型函數。若是您須要持有 (做爲內聯函數參數的) lambda 表達式的引用,或者想要將它做爲參數傳遞給另外一個函數,使用 noinline 關鍵字標記對應參數便可。節約開銷,從使用 inline 作起!
點擊這裏瞭解更多關於用 Kotlin 進行 Android 開發的相關資料