Kotlin之邊學邊分析

Kotlin之邊學邊分析


什麼是委託模式?以及委託模式的原理和使用場景

koltin中聲明的成員屬性或者超類不在當前類中去實現,而是交給其他類去完成,叫做委託,使用by關鍵字來實現。
其使用場景適合那些需要複雜計算並且可以重複使用的場景

lazy懶惰性委託

lazy委託模式不會在聲明時就計算好值,而是在第一次使用時纔會計算值;並且以後也是用這個計算好的值;lazy(() -> t)參數是一個高階函數,其內部原理根據傳入參數分爲三種類型:

LazyThreadSafetyMode.SYNCHRONIZED -> SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION -> SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE -> UnsafeLazyImpl(initializer)

SYNCHRONIZED:
默認參數類型,lazy計算時會加鎖進入線程同步模式,性能可能會下降
PUBLICATION:
多線程安全模式,內部採用自旋鎖CAS方式比較判斷寫入值,性能比同步好點
NONE:
不會加任何索,多線程不安全,適用於單線程操作

自定義委託模式類

屬性委託關鍵在於提供get方法,kotlin提供超類ReadOnlyProperty 或 ReadWriteProperty兩個接口,根據使用場景繼承他們,重寫get和set(var型變量)方法即可;在Android中我們可以的SharedPreference可以使用屬性委託來很好的爲我們工作

類委託(代理模式)

上面講的都是屬性委託,而類的委託則適用於另一個場景,必須委託的基類是接口;其實質就是一種代理模式,類委託基本結構分三類:基礎接口類Base、已實現Base的類Base1,以及我們需要代理的類Base2,如下:

interface Base{
	fun print()
}
class Base1 : Base{
	fun print(){
		System.out.println("----")
	}
}

class Base2(b : Base) : Base by b{
}

以上Base2傳入參數b,以及後面委託b,實質就是Base2靜態代理b對象,無須本身去實現,kotlin幫你去生成中間代碼,反編譯情況確實如此:
在這裏插入圖片描述

更多相關知識請點擊https://blog.csdn.net/zxc123e/article/details/73836013


Kotlin語法糖apply

如題,apply關鍵字作爲語法糖,有着提高開發效率,優化代碼結構的好處!但是我認爲也有不好的地方,請看下面的分析

基本使用

class AFragment : Fragment() {
	private val data : Bundle = Bundle()
	private fun test(){
        LogHelper.de_i("--------")
    }
	companion object {
		AFragment().apply {
		      內部操作相當於在AFragment環境下操作,
		      可以執行AFragment的函數和成員;
		      私有成員也可以調用;
		      test()
		      data.putString("a", "a")
		}
	}
}

使用場景: 可以用到一些實例初始化,需要很多參數初始化那種,不需要寫以前那種建造者模式了,方便得很

原理分析

看看java層源碼對Apply這個語法糖是如何定義:

inline內聯函數,傳入block高階函數,而且是在T模板下的高階函數,所以可以
執行T模板類下的函數
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    執行高階函數
    block()
    return this
}

看到上面大致明白了,apply傳入的T類型下的高階函數,所以可以執行T的函數;但是T類型的私有函數僅僅憑上面的操作是辦不到的,因爲私有成員只能自己類裏面調用,外部是無法調用的,如何破?

kotlin採用編譯時,根據部分註解,爲這些私有成員生成一些公有靜態方法,其參數傳入T類,在靜態方法內部調用這些私有成員,反編譯上面的基本使用的樣例代碼,看看生成了哪些
a. 先看看AFragment.smali類,省去部分代碼保留關鍵代碼

# static fields
這是我能定義的伴生對象Companion Object,自動生成一個靜態對象,在類加載器加載時創建
.field public static final Companion:Lcom/jz/appframe/AFragment$Companion;
創建的私有成員data
.field private final data:Landroid/os/Bundle;

# direct methods
cinit爲類加載器加載時執行的構造方法,而init是創建對象時執行
.method static constructor <clinit>()V
    .locals 2
	創建AFragment$Companion對象,保存到v0寄存器
    new-instance v0, Lcom/jz/appframe/AFragment$Companion;

    const/4 v1, 0x0
	初始化AFragment$Companion對象
    invoke-direct {v0, v1}, Lcom/jz/appframe/AFragment$Companion;-><init>(Lkotlin/jvm/internal/DefaultConstructorMarker;)V
	把v0寄存器內容賦值給成員Companion:Lcom
    sput-object v0, Lcom/jz/appframe/AFragment;->Companion:Lcom/jz/appframe/AFragment$Companion;
    return-void
.end method

私有函數
.method private final test()V
    .locals 1
    .line 36
    const-string v0, "--------"
    invoke-static {v0}, Lcom/jz/appframe/util/LogHelper;->de_i(Ljava/lang/String;)V

    .line 37
    return-void
.end method

共有靜態方法,對應上面的私有test方法
.method public static final synthetic access$test(Lcom/jz/appframe/AFragment;)V
    .locals 0
    .param p0, "$this"    # Lcom/jz/appframe/AFragment;

    .line 21
    調用test方法,傳入p0是一個AFragment對象
    invoke-direct {p0}, Lcom/jz/appframe/AFragment;->test()V

    return-void
.end method

共有靜態方法,對應上面的私有成員
.method public static final synthetic access$getData$p(Lcom/jz/appframe/AFragment;)Landroid/os/Bundle;
    .locals 1
    .param p0, "$this"    # Lcom/jz/appframe/AFragment;
    .line 21
    同上test理解
    iget-object v0, p0, Lcom/jz/appframe/AFragment;->data:Landroid/os/Bundle;
    return-object v0
.end method

這是針對伴生對象Companion對象方法生成靜態方法,所有可以直接使用外部類調用newInstance方法,
實質調用的是這個方法,然後內部在使用半生對象實例調用newInsatnce
.method public static final newInstance(Ljava/lang/String;Ljava/lang/String;)Lcom/jz/appframe/AFragment;
    .locals 1
    .annotation runtime Lkotlin/jvm/JvmStatic;
    .end annotation
	get拿到伴生對象Companion
    sget-object v0, Lcom/jz/appframe/AFragment;->Companion:Lcom/jz/appframe/AFragment$Companion;
	調用Companion的newInstance方法
    invoke-virtual {v0, p0, p1}, Lcom/jz/appframe/AFragment$Companion;->newInstance(Ljava/lang/String;Ljava/lang/String;)Lcom/jz/appframe/AFragment;
    move-result-object v0
    return-object v0
.end method

上面AFragment除了我們定義的私有成員之外,還生成了許多輔助的公有靜態方法,目的是爲了可以從外部調用AFragment的私有成員;看看半生對象做了啥操作,主要看newInstance:

.method public final newInstance(Ljava/lang/String;Ljava/lang/String;)Lcom/jz/appframe/AFragment;
    .locals 8
    p1和p2是參數
    .param p1, "param1"    # Ljava/lang/String;
    .param p2, "param2"    # Ljava/lang/String;
    .annotation runtime Lkotlin/jvm/JvmStatic;
    .end annotation

    const-string v0, "param1"
	檢查參數是否爲空
    invoke-static {p1, v0}, Lkotlin/jvm/internal/Intrinsics;->checkParameterIsNotNull(Ljava/lang/Object;Ljava/lang/String;)V

    const-string v1, "param2"

    invoke-static {p2, v1}, Lkotlin/jvm/internal/Intrinsics;->checkParameterIsNotNull(Ljava/lang/Object;Ljava/lang/String;)V

    .line 57
    創建AFragment對象
    new-instance v2, Lcom/jz/appframe/AFragment;
	執行構造方法
    invoke-direct {v2}, Lcom/jz/appframe/AFragment;-><init>()V
	把AFragment對象同時給了v3和v2,爲什麼要這麼做,還不是apply語法糖;
	相當於兩個指針同時存儲一個對象,一個指針用於apply內部的高階函數操作;
	另一個保存這個初始指針使用,下面的代碼也是這麼做的
    move-object v3, v2
	這個怪?竟然還有這種用法,第一次遇到,不難理解,這就是對v3這個本地寄存器
	解釋,v3類型是AFragment,名字是$this$apply;v3作爲apply內部指針使用
	v2作爲返回對象使用
    .local v3, "$this$apply":Lcom/jz/appframe/AFragment;
    const/4 v4, 0x0 

    .line 58
    .local v4, "$i$a$-apply-AFragment$Companion$newInstance$1":I
    創建一個Bundle
    new-instance v5, Landroid/os/Bundle;
    invoke-direct {v5}, Landroid/os/Bundle;-><init>()V
    move-object v6, v5
    .local v6, "$this$apply":Landroid/os/Bundle;
    const/4 v7, 0x0
    .line 59
    .local v7, "$i$a$-apply-AFragment$Companion$newInstance$1$1":I
    invoke-virtual {v6, v0, p1}, Landroid/os/Bundle;->putString(Ljava/lang/String;Ljava/lang/String;)V
    .line 60
    invoke-virtual {v6, v1, p2}, Landroid/os/Bundle;->putString(Ljava/lang/String;Ljava/lang/String;)V
    .line 61
     v6寄存器結束使用了
    .end local v6    # "$this$apply":Landroid/os/Bundle;
    .end local v7    # "$i$a$-apply-AFragment$Companion$newInstance$1$1":I
    nop

    .line 58
   	v5設置到AFragment的bundle上
    invoke-virtual {v3, v5}, Lcom/jz/appframe/AFragment;->setArguments(Landroid/os/Bundle;)V

    .line 62
    調用了AFragment的共有靜態方法去設置私有成員,access$test是AFragment中自動生成的
    invoke-static {v3}, Lcom/jz/appframe/AFragment;->access$test(Lcom/jz/appframe/AFragment;)V

    .line 63
    data私有成員也是一樣的,調用公有靜態方法去設置
    invoke-static {v3}, Lcom/jz/appframe/AFragment;->access$getData$p(Lcom/jz/appframe/AFragment;)Landroid/os/Bundle;

    move-result-object v0

    const-string v1, "a"

    invoke-virtual {v0, v1, v1}, Landroid/os/Bundle;->putString(Ljava/lang/String;Ljava/lang/String;)V
    .line 64
    寄存器結束
    .end local v3    # "$this$apply":Lcom/jz/appframe/AFragment;
    .end local v4    # "$i$a$-apply-AFragment$Companion$newInstance$1":I
    nop
    .line 57
    nop
    .line 64
    return-object v2
.end method

總結

apply確實可以提高手動編寫代碼的開發效率,爲了完成私有成員調用,編譯時生成了許多公有靜態方法來調用!同時apply在完成高階函數執行操作時,每次都多創建一個寄存器,如上move-object v6, v5;這樣看來apply爲了完成高效的代碼操作也付出了很多東西;一個靜態代碼區新增了方法,二是運行時,寄存器會比平時多一倍,所以在內存上還是有開銷存在的,並且調用邏輯是多了幾個步驟的,AFragment.newInstance創建AFragment變量,調用邏輯依次是從AFragment.newInstance -> AFragment$Companion.newInstance ->AFragment.access$test -> access$getData$p,完成了AFragment的創建和初始化了;所以使用apply模式使用要注意了


Kotlin語法糖let

與apply不同,let則是將調用者當做參數傳遞進入高階函數,並且將高階函數最後的返回值返回,當然,如果高階函數沒有返回值也沒關係!看看他的定義就知道了:

內斂函數
block高階函數,T是入參 R是回參
public inline fun <T, R> T.let(block: (T) -> R): R {
	contract是契約,告知編譯器在合適的地方調用,也就是外層函數執行時去調用
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    直接返回
    return block(this)
}

逆向let爲smali

對這段kotlin逆向的結果查看

源碼 arguments爲bundle
arguments?.let {
            param1 = it.getString(ARG_PARAM1)
}

.line 29
獲取arguments
invoke-virtual {p0}, Lcom/jz/appframe/AFragment;->getArguments()Landroid/os/Bundle;
move-result-object v0
檢查arguments等於0if-eqz v0, :cond_0
解釋v0的含義,名字爲it,類型爲bundle
.local v0, "it":Landroid/os/Bundle;
const/4 v1, 0x0 

.line 30
這個v1寄存器不知道是幹啥用的,只標記了一下,根本沒用上
.local v1, "$i$a$-let-AFragment$onCreate$1":I
const-string v2, "param1"

invoke-virtual {v0, v2}, Landroid/os/Bundle;->getString(Ljava/lang/String;)Ljava/lang/String;

move-result-object v2

.line 32
.end local v0    # "it":Landroid/os/Bundle;
.end local v1    # "$i$a$-let-AFragment$onCreate$1":I

如上,let也沒有什麼神奇的,常規操作,判空arguments後,直接用他的寄存器進行操作;可以適用於一些判空後要對調用者進行配置的場景,不過裏面會多一個整型類型的寄存器,也沒用到,不知道爲何,有知道的請留言交流交流,上面的apply也是


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章