@ -1,4 +1,4 @@
// C o p y r i g h t © 2 0 2 2 R a n g e p r o o f P t y L t d . A l l r i g h t s r e s e r v e d .
// C o p y r i g h t © 2 0 2 4 R a n g e p r o o f P t y L t d . A l l r i g h t s r e s e r v e d .
//
// s t r i n g l i n t : d i s a b l e
@ -6,50 +6,18 @@ import Foundation
import Combine
public class Dependencies {
static let userInfoKey : CodingUserInfoKey = CodingUserInfoKey ( rawValue : " io.oxe n.dependencies.codingOptions" ) !
static let userInfoKey : CodingUserInfoKey = CodingUserInfoKey ( rawValue : " sess ion.dependencies.codingOptions" ) !
private static var _isRTLRetriever : Atomic < ( Bool , ( ) -> Bool ) > = Atomic ( ( false , { false } ) )
private static var singletonInstances : Atomic < [ String : Any ] > = Atomic ( [ : ] )
private static var cacheInstances : Atomic < [ String : Atomic < MutableCacheType > ] > = Atomic ( [ : ] )
private static var userDefaultsInstances : Atomic < [ String : ( any UserDefaultsType ) ] > = Atomic ( [ : ] )
private static var featureInstances : Atomic < [ String : ( any FeatureType ) ] > = Atomic ( [ : ] )
private var featureChangeSubject : PassthroughSubject < ( String , String ? , Any ? ) , Never > = PassthroughSubject ( )
private let featureChangeSubject : PassthroughSubject < ( String , String ? , Any ? ) , Never > = PassthroughSubject ( )
private var storage : Atomic < DependencyStorage > = Atomic ( DependencyStorage ( ) )
// MARK: - S u b s c r i p t A c c e s s
public subscript < S > ( singleton singleton : SingletonConfig < S > ) -> S {
guard let value : S = ( Dependencies . singletonInstances . wrappedValue [ singleton . identifier ] as ? S ) else {
let value : S = singleton . createInstance ( self )
Dependencies . singletonInstances . mutate { $0 [ singleton . identifier ] = value }
return value
}
return value
}
public subscript < M , I > ( cache cache : CacheConfig < M , I > ) -> I {
getValueSettingIfNull ( cache : cache )
}
public subscript ( defaults defaults : UserDefaultsConfig ) -> UserDefaultsType {
guard let value : UserDefaultsType = Dependencies . userDefaultsInstances . wrappedValue [ defaults . identifier ] else {
let value : UserDefaultsType = defaults . createInstance ( self )
Dependencies . userDefaultsInstances . mutate { $0 [ defaults . identifier ] = value }
return value
}
return value
}
public subscript < T : FeatureOption > ( feature feature : FeatureConfig < T > ) -> T {
guard let value : Feature < T > = ( Dependencies . featureInstances . wrappedValue [ feature . identifier ] as ? Feature < T > ) else {
let value : Feature < T > = feature . createInstance ( self )
Dependencies . featureInstances . mutate { $0 [ feature . identifier ] = value }
return value . currentValue ( using : self )
}
return value . currentValue ( using : self )
}
public subscript < S > ( singleton singleton : SingletonConfig < S > ) -> S { getOrCreate ( singleton ) }
public subscript < M , I > ( cache cache : CacheConfig < M , I > ) -> I { getOrCreate ( cache ) . immutable ( cache : cache , using : self ) }
public subscript ( defaults defaults : UserDefaultsConfig ) -> UserDefaultsType { getOrCreate ( defaults ) }
public subscript < T : FeatureOption > ( feature feature : FeatureConfig < T > ) -> T { getOrCreate ( feature ) . currentValue ( using : self ) }
// MARK: - G l o b a l V a l u e s , T i m i n g a n d A s y n c H a n d l i n g
@ -85,20 +53,15 @@ public class Dependencies {
cache : CacheConfig < M , I > ,
_ mutation : ( inout M ) -> R
) -> R {
// / T h e c a s t f r o m ` A t o m i c < M u t a b l e C a c h e T y p e > ` t o ` A t o m i c < M > ` a l w a y s f a i l s s o w e n e e d t o d o s o m e
// / s t u f f i n g a r o u n d t o e n s u r e w e h a v e t h e r i g h t t y p e s - s i n c e w e c a l l ` c r e a t e I n s t a n c e ` m u l t i p l e t i m e s i n
// / t h e b e l o w c o d e w e f i r s t c a l l ` g e t V a l u e S e t t i n g I f N u l l ` t o e n s u r e w e h a v e a p r o p e r i n s t a n c e s t o r e d
// / i n ` D e p e n d e n c i e s . c a c h e I n s t a n c e s ` s o t h a t w e c a n b e r e l i a b l y c e r t a i l w e a r e n ' t a c c e s s i n g s o m e
// / r a n d o m i n s t a n c e t h a t w i l l g o o u t o f m e m o r y a s s o o n a s t h e m u t a t i o n i s c o m p l e t e d
getValueSettingIfNull ( cache : cache )
let cacheWrapper : Atomic < MutableCacheType > = (
Dependencies . cacheInstances . wrappedValue [ cache . identifier ] ? ?
Atomic ( cache . mutableInstance ( cache . createInstance ( self ) ) ) // S h o u l d n e v e r b e c a l l e d
)
return cacheWrapper . mutate { erasedValue in
var value : M = ( ( erasedValue as ? M ) ? ? cache . createInstance ( self ) )
return getOrCreate ( cache ) . mutate { erasedValue in
guard var value : M = ( erasedValue as ? M ) else {
// / T h i s c o d e p a t h s h o u l d n e v e r h a p p e n ( a n d i s e s s e n t i a l l y i n v a l i d i f i t d o e s ) b u t i n o r d e r t o a v o i d n e e i n g t o r e t u r n
// / a n u l l a b l e t y p e o r f o r c e - c a s t i n g t h i s i s h o w w e n e e d t o d o t h i n g s )
Log . critical ( " Failed to convert erased cache value for ' \( cache . identifier ) ' to expected type: \( M . self ) " )
var fallbackValue : M = cache . createInstance ( self )
return mutation ( & fallbackValue )
}
return mutation ( & value )
}
}
@ -107,20 +70,15 @@ public class Dependencies {
cache : CacheConfig < M , I > ,
_ mutation : ( inout M ) throws -> R
) throws -> R {
// / T h e c a s t f r o m ` A t o m i c < M u t a b l e C a c h e T y p e > ` t o ` A t o m i c < M > ` a l w a y s f a i l s s o w e n e e d t o d o s o m e
// / s t u f f i n g a r o u n d t o e n s u r e w e h a v e t h e r i g h t t y p e s - s i n c e w e c a l l ` c r e a t e I n s t a n c e ` m u l t i p l e t i m e s i n
// / t h e b e l o w c o d e w e f i r s t c a l l ` g e t V a l u e S e t t i n g I f N u l l ` t o e n s u r e w e h a v e a p r o p e r i n s t a n c e s t o r e d
// / i n ` D e p e n d e n c i e s . c a c h e I n s t a n c e s ` s o t h a t w e c a n b e r e l i a b l y c e r t a i l w e a r e n ' t a c c e s s i n g s o m e
// / r a n d o m i n s t a n c e t h a t w i l l g o o u t o f m e m o r y a s s o o n a s t h e m u t a t i o n i s c o m p l e t e d
getValueSettingIfNull ( cache : cache )
let cacheWrapper : Atomic < MutableCacheType > = (
Dependencies . cacheInstances . wrappedValue [ cache . identifier ] ? ?
Atomic ( cache . mutableInstance ( cache . createInstance ( self ) ) ) // S h o u l d n e v e r b e c a l l e d
)
return try cacheWrapper . mutate { erasedValue in
var value : M = ( ( erasedValue as ? M ) ? ? cache . createInstance ( self ) )
return try getOrCreate ( cache ) . mutate { erasedValue in
guard var value : M = ( erasedValue as ? M ) else {
// / T h i s c o d e p a t h s h o u l d n e v e r h a p p e n ( a n d i s e s s e n t i a l l y i n v a l i d i f i t d o e s ) b u t i n o r d e r t o a v o i d n e e i n g t o r e t u r n
// / a n u l l a b l e t y p e o r f o r c e - c a s t i n g t h i s i s h o w w e n e e d t o d o t h i n g s )
Log . critical ( " Failed to convert erased cache value for ' \( cache . identifier ) ' to expected type: \( M . self ) " )
var fallbackValue : M = cache . createInstance ( self )
return try mutation ( & fallbackValue )
}
return try mutation ( & value )
}
}
@ -138,41 +96,29 @@ public class Dependencies {
public func popRandomElement < T > ( _ elements : inout Set < T > ) -> T ? {
return elements . popRandomElement ( )
}
// MARK: - I n s t a n c e u p s e r t i n g
@ discardableResult private func getValueSettingIfNull < M , I > ( cache : CacheConfig < M , I > ) -> I {
guard let value : M = ( Dependencies . cacheInstances . wrappedValue [ cache . identifier ] ? . wrappedValue as ? M ) else {
let value : M = cache . createInstance ( self )
let mutableInstance : MutableCacheType = cache . mutableInstance ( value )
Dependencies . cacheInstances . mutate { $0 [ cache . identifier ] = Atomic ( mutableInstance ) }
return cache . immutableInstance ( value )
}
return cache . immutableInstance ( value )
}
// MARK: - I n s t a n c e r e p l a c i n g
public func warmCache < M , I > ( cache : CacheConfig < M , I > ) {
_ = get ValueSettingIfNull( cache : cache )
_ = getOrCreate ( cache )
}
public func set < S > ( singleton : SingletonConfig < S > , to instance : S ) {
Dependencies. singletonInstances . mutate {
$0 [ singleton . identifier ] = instance
threadSafeChange ( for : singleton . identifier ) {
setValue ( instance , typedStorage : . singleton ( instance ) , key : singleton . identifier )
}
}
public func set < M , I > ( cache : CacheConfig < M , I > , to instance : M ) {
Dependencies . cacheInstances . mutate {
$0 [ cache . identifier ] = Atomic ( cache . mutableInstance ( instance ) )
threadSafeChange ( for : cache . identifier ) {
let value : Atomic < MutableCacheType > = Atomic ( cache . mutableInstance ( instance ) )
setValue ( value , typedStorage : . cache ( value ) , key : cache . identifier )
}
}
public func remove < M , I > ( cache : CacheConfig < M , I > ) {
Dependencies. cacheInstances . mutate {
$0 [ cache . identifier ] = nil
threadSafeChange( for : cache . identifier ) {
removeValue ( cache . identifier )
}
}
@ -181,6 +127,17 @@ public class Dependencies {
}
}
// MARK: - C a c h e M a n a g e m e n t
private extension Atomic < MutableCacheType > {
func immutable < M , I > ( cache : CacheConfig < M , I > , using dependencies : Dependencies ) -> I {
return cache . immutableInstance (
( self . wrappedValue as ? M ) ? ?
cache . createInstance ( dependencies )
)
}
}
// MARK: - F e a t u r e M a n a g e m e n t
public extension Dependencies {
@ -215,30 +172,26 @@ public extension Dependencies {
}
func set < T : FeatureOption > ( feature : FeatureConfig < T > , to updatedFeature : T ? ) {
let value : Feature < T > = {
guard let value : Feature < T > = ( Dependencies . featureInstances . wrappedValue [ feature . identifier ] as ? Feature < T > ) else {
let valu e: Feature < T > = feature . createInstance ( self )
Dependencies. featureInstances . mutate { $0 [ feature . identifier ] = value }
return value
}
return value
} ( )
threadSafeChange ( for : feature . identifier ) {
// / U p d a t e t h e c a c h e d & i n - m e m o r y v a l u e s
let instanc e: Feature < T > = (
getValue( feature . identifier ) ? ?
feature . createInstance ( self )
)
instance . setValue ( to : updatedFeature , using : self )
setValue ( instance , typedStorage : . feature ( instance ) , key : feature . identifier )
}
value . setValue ( to : updatedFeature , using : self )
// / N o t i f y o b s e r v e r s
featureChangeSubject . send ( ( feature . identifier , feature . groupIdentifier , updatedFeature ) )
}
func reset < T : FeatureOption > ( feature : FeatureConfig < T > ) {
// / R e s e t t h e c a c h e d v a l u e
switch Dependencies . featureInstances . wrappedValue [ feature . identifier ] as ? Feature < T > {
case . none : break
case . some ( let value ) : value . setValue ( to : nil , using : self )
}
// / R e s e t t h e i n - m e m o r y v a l u e
Dependencies . featureInstances . mutate {
$0 [ feature . identifier ] = nil
threadSafeChange ( for : feature . identifier ) {
// / R e s e t t h e c a c h e d a n d i n - m e m o r y v a l u e s
let instance : Feature < T > ? = getValue ( feature . identifier )
instance ? . setValue ( to : nil , using : self )
removeValue ( feature . identifier )
}
// / N o t i f y o b s e r v e r s
@ -309,6 +262,171 @@ public extension Dependencies {
}
}
// MARK: - D e p e n d e n c i e s E r r o r
public enum DependenciesError : Error {
case missingDependencies
}
// MARK: - S t o r a g e M a n a g e m e n t
private extension Dependencies {
struct DependencyStorage {
var initializationTracker : [ String : DispatchGroup ] = [ : ]
var instances : [ String : Value ] = [ : ]
enum Value {
case singleton ( Any )
case cache ( Atomic < MutableCacheType > )
case userDefaults ( UserDefaultsType )
case feature ( any FeatureType )
func value < T > ( as type : T . Type ) -> T ? {
switch self {
case . singleton ( let value ) : return value as ? T
case . cache ( let value ) : return value as ? T
case . userDefaults ( let value ) : return value as ? T
case . feature ( let value ) : return value as ? T
}
}
}
}
private func getOrCreate < S > ( _ singleton : SingletonConfig < S > ) -> S {
return getOrCreateInstance (
identifier : singleton . identifier ,
constructor : . singleton { singleton . createInstance ( self ) }
)
}
private func getOrCreate < M , I > ( _ cache : CacheConfig < M , I > ) -> Atomic < MutableCacheType > {
return getOrCreateInstance (
identifier : cache . identifier ,
constructor : . cache { Atomic ( cache . mutableInstance ( cache . createInstance ( self ) ) ) }
)
}
private func getOrCreate ( _ defaults : UserDefaultsConfig ) -> UserDefaultsType {
return getOrCreateInstance (
identifier : defaults . identifier ,
constructor : . userDefaults { defaults . createInstance ( self ) }
)
}
private func getOrCreate < T : FeatureOption > ( _ feature : FeatureConfig < T > ) -> Feature < T > {
return getOrCreateInstance (
identifier : feature . identifier ,
constructor : . feature { feature . createInstance ( self ) }
)
}
// MARK: - I n s t a n c e u p s e r t i n g
// / R e t r i e v e s t h e c u r r e n t i n s t a n c e o r , i f o n e d o e s n ' t e x i s t , u s e s t h e ` S t o r a g e H e l p e r . I n f o < V a l u e > ` t o c r e a t e a n e w i n s t a n c e
// / a n d s t o r e i t
private func getOrCreateInstance < Value > (
identifier : String ,
constructor : DependencyStorage . Constructor < Value >
) -> Value {
// / I f w e a l r e a d y h a v e a n i n s t a n c e t h e n j u s t r e t u r n t h a t
if let existingValue : Value = getValue ( identifier ) {
return existingValue
}
return threadSafeChange ( for : identifier ) {
// / N o w t h a t w e a r e w i t h i n a s y n c h r o n i z e d g r o u p , c h e c k t o m a k e s u r e a n i n s t a n c e w a s n ' t c r e a t e d w h i l e w e w e r e w a i t i n g t o
// / e n t e r t h e g r o u p
if let existingValue : Value = getValue ( identifier ) {
return existingValue
}
let result : ( typedStorage : DependencyStorage . Value , value : Value ) = constructor . create ( )
setValue ( result . value , typedStorage : result . typedStorage , key : identifier )
return result . value
}
}
// / C o n v e n i e n c e m e t h o d t o r e t r i e v e t h e e x i s t i n g d e p e n d e n c y i n s t a n c e f r o m m e m o r y i n a t h r e a d - s a f e w a y
private func getValue < T > ( _ key : String ) -> T ? {
guard let typedValue : DependencyStorage . Value = storage . wrappedValue . instances [ key ] else { return nil }
guard let result : T = typedValue . value ( as : T . self ) else {
// / I f t h e r e i s a v a l u e s t o r e d f o r t h e k e y , b u t i t ' s n o t t h e r i g h t t y p e t h e n s o m e t h i n g h a s g o n e w r o n g , a n d w e s h o u l d l o g
Log . critical ( " Failed to convert stored dependency ' \( key ) ' to expected type: \( T . self ) " )
return nil
}
return result
}
// / C o n v e n i e n c e m e t h o d t o s t o r e a d e p e n d e n c y i n s t a n c e i n m e m o r y i n a t h r e a d - s a f e w a y
@ discardableResult private func setValue < T > ( _ value : T , typedStorage : DependencyStorage . Value , key : String ) -> T {
storage . mutate { $0 . instances [ key ] = typedStorage }
return value
}
// / C o n v e n i e n c e m e t h o d t o r e m o v e a d e p e n d e n c y i n s t a n c e f r o m m e m o r y i n a t h r e a d - s a f e w a y
private func removeValue ( _ key : String ) {
storage . mutate { $0 . instances . removeValue ( forKey : key ) }
}
// / T h i s f u n c t i o n c r e a t e s a ` D i s p a t c h G r o u p ` f o r t h e g i v e n i d e n t i f i e r w h i c h a l l o w s u s t o b l o c k i n s t a n c e c r e a t i o n o n a p e r - i d e n t i f i e r b a s i s
// / a n d a v o i d s i t u a t i o n s w h e r e m u l t i t h r e a d i n g c o u l d r e s u l t i n m u l t i p l e i n s t a n c e s o f t h e s a m e d e p e n d e n c y b e i n g c r e a t e d c o n c u r r e n t l y
// /
// / * * N o t e : * * T h i s ` D i s p a t c h G r o u p ` i s a n a d d i t i o n a l m e c h a n i s m o n t o p o f t h e ` A t o m i c < T > ` b e c a u s e t h e i n t e r f a c e i s a l i t t l e s i m p l e r
// / a n d w e d o n ' t n e e d t o w r a p e v e r y i n s t a n c e w i t h i n ` A t o m i c < T > ` t h i s w a y
@ discardableResult private func threadSafeChange < T > ( for identifier : String , change : ( ) -> T ) -> T {
let group : DispatchGroup = storage . mutate { storage in
if let existing = storage . initializationTracker [ identifier ] {
return existing
}
let group = DispatchGroup ( )
storage . initializationTracker [ identifier ] = group
return group
}
group . enter ( )
defer { group . leave ( ) }
return change ( )
}
}
// MARK: - D S L
private extension Dependencies . DependencyStorage {
struct Constructor < T > {
let create : ( ) -> ( typedStorage : Dependencies . DependencyStorage . Value , value : T )
static func singleton ( _ constructor : @ escaping ( ) -> T ) -> Constructor < T > {
return Constructor {
let instance : T = constructor ( )
return ( . singleton ( instance ) , instance )
}
}
static func cache ( _ constructor : @ escaping ( ) -> T ) -> Constructor < T > where T : Atomic < MutableCacheType > {
return Constructor {
let instance : T = constructor ( )
return ( . cache ( instance ) , instance )
}
}
static func userDefaults ( _ constructor : @ escaping ( ) -> T ) -> Constructor < T > where T = = UserDefaultsType {
return Constructor {
let instance : T = constructor ( )
return ( . userDefaults ( instance ) , instance )
}
}
static func feature ( _ constructor : @ escaping ( ) -> T ) -> Constructor < T > where T : FeatureType {
return Constructor {
let instance : T = constructor ( )
return ( . feature ( instance ) , instance )
}
}
}
}