@@ -22,6 +22,8 @@ import Foundation
2222  @preconcurrency   internal  import  GoogleUtilities
2323#endif // SWIFT_PACKAGE 
2424
25+ internal  import  FirebaseCoreInternal
26+ 
2527/// CacheKey is like a "key" to a "safe". It provides necessary metadata about the current cache to
2628/// know if it should be expired.
2729struct  CacheKey :  Codable  { 
@@ -57,53 +59,84 @@ final class SettingsCache: SettingsCacheClient {
5759    static  let  forCacheKey  =  " firebase-sessions-cache-key " 
5860  } 
5961
60-   /// UserDefaults holds values in memory, making access O(1) and synchronous within the app, while
61-   /// abstracting away async disk IO.
62-   private  let  cache :  GULUserDefaults  =  . standard( ) 
62+   private  let  userDefaults :  GULUserDefaults 
63+   private  let  persistenceQueue = 
64+     DispatchQueue ( label:  " com.google.firebase.sessions.settings.persistence " ) 
65+ 
66+   // This lock protects the in-memory properties.
67+   private  let  inMemoryCacheLock :  UnfairLock < CacheData > 
68+ 
69+   private  struct  CacheData  { 
70+     var  content :  [ String :  Any ] 
71+     var  key :  CacheKey ? 
72+   } 
73+ 
74+   init ( userDefaults:  GULUserDefaults  =  . standard( ) )  { 
75+     self . userDefaults =  userDefaults
76+     let  content  =  ( userDefaults. object ( forKey:  UserDefaultsKeys . forContent)  as?  [ String :  Any ] )  ?? 
77+       [ : ] 
78+     var  key :  CacheKey ? 
79+     if  let  data =  userDefaults. object ( forKey:  UserDefaultsKeys . forCacheKey)  as?  Data  { 
80+       do  { 
81+         key =  try JSONDecoder ( ) . decode ( CacheKey . self,  from:  data) 
82+       }  catch  { 
83+         Logger . logError ( " [Settings] Decoding CacheKey failed with error:  \( error) " ) 
84+       } 
85+     } 
86+     inMemoryCacheLock =  UnfairLock ( CacheData ( content:  content,  key:  key) ) 
87+   } 
6388
6489  /// Converting to dictionary is O(1) because object conversion is O(1)
6590  var  cacheContent :  [ String :  Any ]  { 
6691    get  { 
67-       return  ( cache . object ( forKey :   UserDefaultsKeys . forContent )   as?   [ String :   Any ] )   ??   [ : ] 
92+       return  inMemoryCacheLock . value ( ) . content 
6893    } 
6994    set  { 
70-       cache. setObject ( newValue,  forKey:  UserDefaultsKeys . forContent) 
95+       inMemoryCacheLock. withLock  {  $0. content =  newValue } 
96+       persistenceQueue. async { 
97+         self . userDefaults. setObject ( newValue,  forKey:  UserDefaultsKeys . forContent) 
98+       } 
7199    } 
72100  } 
73101
74102  /// Casting to Codable from Data is O(n)
75103  var  cacheKey :  CacheKey ? { 
76104    get  { 
77-       if  let  data =  cache. object ( forKey:  UserDefaultsKeys . forCacheKey)  as?  Data  { 
105+       return  inMemoryCacheLock. value ( ) . key
106+     } 
107+     set  { 
108+       inMemoryCacheLock. withLock  {  $0. key =  newValue } 
109+       persistenceQueue. async { 
78110        do  { 
79-           return  try JSONDecoder ( ) . decode ( CacheKey . self,  from:  data) 
111+           try self . userDefaults. setObject ( JSONEncoder ( ) . encode ( newValue) , 
112+                                           forKey:  UserDefaultsKeys . forCacheKey) 
80113        }  catch  { 
81-           Logger . logError ( " [Settings] Decoding  CacheKey failed with error:  \( error) " ) 
114+           Logger . logError ( " [Settings] Encoding  CacheKey failed with error:  \( error) " ) 
82115        } 
83116      } 
84-       return  nil 
85-     } 
86-     set  { 
87-       do  { 
88-         try . setObject ( JSONEncoder ( ) . encode ( newValue) ,  forKey:  UserDefaultsKeys . forCacheKey) 
89-       }  catch  { 
90-         Logger . logError ( " [Settings] Encoding CacheKey failed with error:  \( error) " ) 
91-       } 
92117    } 
93118  } 
94119
95120  /// Removes stored cache
96121  func  removeCache( )  { 
97-     cache. setObject ( nil ,  forKey:  UserDefaultsKeys . forContent) 
98-     cache. setObject ( nil ,  forKey:  UserDefaultsKeys . forCacheKey) 
122+     inMemoryCacheLock. withLock  { 
123+       $0. content =  [ : ] 
124+       $0. key =  nil 
125+     } 
126+     persistenceQueue. async { 
127+       self . userDefaults. setObject ( nil ,  forKey:  UserDefaultsKeys . forContent) 
128+       self . userDefaults. setObject ( nil ,  forKey:  UserDefaultsKeys . forCacheKey) 
129+     } 
99130  } 
100131
101132  func  isExpired( for appInfo:  ApplicationInfoProtocol ,  time:  Date )  ->  Bool  { 
102-     guard  !cacheContent. isEmpty else  { 
133+     let  ( content,  key)  =  inMemoryCacheLock. withLock  {  ( $0. content,  $0. key)  } 
134+ 
135+     guard  !content. isEmpty else  { 
103136      removeCache ( ) 
104137      return  true 
105138    } 
106-     guard  let  cacheKey =  cacheKey  else  { 
139+     guard  let  cacheKey =  key  else  { 
107140      Logger . logError ( " [Settings] Could not load settings cache key " ) 
108141      removeCache ( ) 
109142      return  true 
@@ -126,7 +159,8 @@ final class SettingsCache: SettingsCacheClient {
126159  } 
127160
128161  private  func  cacheDuration( )  ->  TimeInterval  { 
129-     guard  let  duration =  cacheContent [ Self . flagCacheDuration]  as?  Double  else  { 
162+     let  content  =  inMemoryCacheLock. value ( ) . content
163+     guard  let  duration =  content [ Self . flagCacheDuration]  as?  Double  else  { 
130164      return  Self . cacheDurationSecondsDefault
131165    } 
132166    Logger . logDebug ( " [Settings] Cache duration:  \( duration) " ) 
0 commit comments