LoginSignup
39
37

More than 5 years have passed since last update.

KotlinのSingletonについて

Last updated at Posted at 2017-07-08

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

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

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ではないのでこれをベースにもうちょっと処理が必要。

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方法で必要なパラメタを取得するのがいいと思います。

39
37
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
39
37