作用域
Koin 提供了一個簡單的 API,讓您可以定義與有限生命週期綁定的實例。
什麼是作用域?
作用域是指物件存在的固定時間長度或方法呼叫期間。 另一種看法是,將作用域視為物件狀態持續存在的時間長度。 當作用域上下文結束時,任何綁定於該作用域下的物件都無法再被注入(它們會從容器中移除)。
作用域定義
預設情況下,在 Koin 中我們有三種作用域:
single
定義:建立一個與整個容器生命週期持續存在的物件(無法被移除)。factory
定義:每次都建立一個新物件。生命週期短暫。在容器中不持久存在(無法共享)。scoped
定義:建立一個與關聯作用域生命週期綁定並持續存在的物件。
要宣告一個 scoped
定義,請像這樣使用 scoped
函數。作用域將 scoped
定義聚集為一個邏輯時間單位。
要為給定類型宣告一個作用域,我們需要使用 scope
關鍵字:
module {
scope<MyType>{
scoped { Presenter() }
// ...
}
}
作用域 ID 與作用域名稱
一個 Koin 作用域由其以下兩點定義:
- 作用域名稱 - 作用域的限定符 (qualifier)
- 作用域 ID - 作用域實例的唯一識別符 (unique identifier)
NOTE
scope<A> { }
等同於 scope(named<A>()){ }
,但寫起來更方便。請注意,您也可以使用字串限定符,例如:scope(named("SCOPE_NAME")) { }
從一個 Koin
實例中,您可以存取:
createScope(id : ScopeID, scopeName : Qualifier)
- 使用給定的 ID 和作用域名稱建立一個封閉的作用域實例getScope(id : ScopeID)
- 使用給定的 ID 檢索先前建立的作用域getOrCreateScope(id : ScopeID, scopeName : Qualifier)
- 使用給定的 ID 和作用域名稱建立作用域實例,如果已建立則檢索之
NOTE
預設情況下,對物件呼叫 createScope
不會傳遞作用域的「來源 (source)」。您需要將其作為參數傳遞:T.createScope(<source>)
作用域組件:將作用域關聯到組件 [2.2.0]
Koin 具有 KoinScopeComponent
的概念,以幫助將作用域實例帶入其類別:
class A : KoinScopeComponent {
override val scope: Scope by lazy { createScope(this) }
}
class B
KoinScopeComponent
介面提供了幾個擴充功能:
createScope
:從目前組件的作用域 ID 和名稱建立作用域get
、inject
:從作用域中解析實例(等同於scope.get()
和scope.inject()
)
讓我們為 A 定義一個作用域,以解析 B:
module {
scope<A> {
scoped { B() } // 綁定到 A 的作用域
}
}
然後,我們可以透過 org.koin.core.scope
的 get
和 inject
擴充功能直接解析 B
的實例:
class A : KoinScopeComponent {
override val scope: Scope by lazy { newScope(this) }
// 以注入方式解析 B
val b : B by inject() // 從作用域注入
// 解析 B
fun doSomething(){
val b = get<B>()
}
fun close(){
scope.close() // 別忘了關閉目前作用域
}
}
在作用域內解析依賴
要使用作用域的 get
和 inject
函數解析依賴:val presenter = scope.get<Presenter>()
作用域的目的是為 scoped
定義定義一個共同的邏輯時間單位。它也允許從給定的作用域內解析定義。
// 假設有這些類別
class ComponentA
class ComponentB(val a : ComponentA)
// 帶有作用域的模組
module {
scope<A> {
scoped { ComponentA() }
// 將從目前作用域實例中解析
scoped { ComponentB(get()) }
}
}
依賴解析隨後將直截了當:
// 建立作用域
val myScope = koin.createScope<A>()
// 從同一個作用域
val componentA = myScope.get<ComponentA>()
val componentB = myScope.get<ComponentB>()
INFO
預設情況下,如果目前作用域中沒有找到定義,所有作用域都會回退 (fallback) 到主作用域中解析。
關閉作用域
一旦您的作用域實例完成,只需使用 close()
函數關閉它:
// 從 KoinComponent
val scope = getKoin().createScope<A>()
// 使用它 ...
// 關閉它
scope.close()
INFO
請注意,您無法再從已關閉的作用域中注入實例。
取得作用域的來源值
Koin Scope API 在 2.1.4 版本中允許您在定義中傳遞作用域的原始來源 (original source)。讓我們看一個下面的例子。 假設我們有一個單例實例 A
:
class A
class BofA(val a : A)
module {
single { A() }
scope<A> {
scoped { BofA(getSource() /* or even get() */) }
}
}
透過建立 A 的作用域,我們可以將作用域來源(A 實例)的引用轉發給作用域的底層定義:scoped { BofA(getSource()) }
甚至 scoped { BofA(get()) }
。
這是為了避免層疊的參數注入,並直接在 scoped
定義中檢索我們的來源值。
val a = koin.get<A>()
val b = a.scope.get<BofA>()
assertTrue(b.a == a)
NOTE
getSource()
與 get()
的區別:getSource()
將直接取得來源值。get()
將嘗試解析任何定義,如果可能則回退 (fallback) 到來源值。因此,getSource()
在效能方面更有效率。
作用域連結
Koin Scope API 在 2.1 版本中允許您將一個作用域連結到另一個作用域,然後允許解析聯合的定義空間。讓我們舉一個例子。 在這裡,我們定義了兩個作用域空間:一個用於 A 的作用域和一個用於 B 的作用域。在 A 的作用域中,我們無法存取 C(C 定義在 B 的作用域中)。
module {
single { A() }
scope<A> {
scoped { B() }
}
scope<B> {
scoped { C() }
}
}
透過作用域連結 API,我們可以直接從 A 的作用域中解析 B 的作用域實例 C。為此,我們在作用域實例上使用 linkTo()
:
val a = koin.get<A>()
// 讓我們先從 A 的作用域取得 B
val b = a.scope.get<B>()
// 讓 A 的作用域連結到 B 的作用域
a.scope.linkTo(b.scope)
// 我們從 A 或 B 作用域取得了相同的 C 實例
assertTrue(a.scope.get<C>() == b.scope.get<C>())