Kotlin
singleton

KotlinのSingletonについて


注意:objectなどkotlinのsingletonを乱用しないようにしましょう。

少なくともAndroidでは参照がないとGarbage Collectionで回収される可能性があるのでApp全体のグローバル的にデータの保持とかする時は注意して使いましょう。

https://discuss.kotlinlang.org/t/singleton-with-object-declarations-gets-garbage-collected/4786/10

ActivityA->ActivityBで行った時にActivityAが参照しているobjectシングルToneクラスは回収されて

ActivityB->ActivityAで再び初期化が走る可能性があります。当然保持して使おうとしているデータも消えるはず。

AndroidではちゃんとonSaveInstanceStateとかを対応した方がいいです。

Javaの時代もそうですがstatic変数/クラス参照も乱用するのはよくないことでしたね。


KotlinのSingletonを調べて見た。

Kotlin documentにも書いているが

https://kotlinlang.org/docs/reference/object-declarations.html

object declarations are initialized lazily, when accessed for the first time

これはKotlin bytecodeでのjava codeを見るとstatic initializeで初期化されることが分かる

   static {

new Singleton();
}

なので


結論


1.基本的objectを使った方がいい。(Thread safe)

//kotlin bytecodeで生成されたjava codeを見ると分かるが

//objectはSingleTone(static)+lazily(static initialize)にinstanceを作成する
object Singleton{}

例:

//AndroidのPreferencesでSharedpreferenceを初期化して使うときに便利

object PreferencesHelper{
private lateinit var prefs: Sharedpreference
fun setUp(context: Context){
prefs = PreferenceManager.getDefaultSharedPreferences(context)
}

    var userId: String
get() = prefs.getString("userId","")
set(value) { prefs.edit().putString("userId", value).apply() }
}

もうちょっといい方法:

もし、初期化にパラメタが必要な場合はDIで必要なインスタンスを渡すことをオススメします。

万が一GCでコレクションされでも問題なく必要なパラメタで初期化ができます。

DIはKotlinで定番(個人的に) Koinを使います。

object PreferencesHelper: KoinComponent{

private var prefs: Sharedpreference
private val context: Context by inject()

init{
prefs = context.getSharedPreferences("prefName", Context.MODE_PRIVATE)
}
}


2.lazy方式でSingleToneをやる方式は推奨できない,ただスレッドセーフを求めていなければLazyThreadSafetyMode.NONEで使ってもいい。

理由はlazyのdefault mode SYNCHRONIZEDが実際はjavaのSingleTone方式:DoubleCheck方式なのでoverheadがある。また初期化パラメタを1.と同じく別途設定する必要がある

class LazyThreadSafeDoubleCheck private constructor() {

companion object {
//lazy modeディフォルトはLazyThreadSafetyMode.SYNCHRONIZEDである
//スレッドセーフを求めていなければlazy(LazyThreadSafetyMode.NONE)とパラメタをづける

val instance by lazy(LazyThreadSafetyMode.NONE) {
LazyInit()
}
}
}


3.ただparameterも含めで厳密に一回だけ初期したい場合はかきの方法がいいだろう。Thread safeではないのでこれをベースにもうちょっと処理が必要。

https://stackoverflow.com/a/40437274

class TasksLocalDataSource private constructor(context: Context) : TasksDataSource {

private val mDbHelper = TasksDbHelper(context)

companion object {
private var instance : TasksLocalDataSource? = null

fun getInstance(context: Context): TasksLocalDataSource {
if (instance == null)
instance = TasksLocalDataSource(context)

return instance!!
}
}
}

もうちょっといい方法

上記の1。で説明したDI方法で必要なパラメタを取得するのがいいと思います。