GULUserDefaults.m 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. // Copyright 2018 Google
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #import "Private/GULUserDefaults.h"
  15. #if SWIFT_PACKAGE
  16. // SwiftPM doesn't work with private headers in a framework.
  17. #import "GoogleUtilities/Logger/Private/GULLogger.h"
  18. #else
  19. #import <GoogleUtilities/GULLogger.h>
  20. #endif
  21. NS_ASSUME_NONNULL_BEGIN
  22. static NSTimeInterval const kGULSynchronizeInterval = 1.0;
  23. static NSString *const kGULLogFormat = @"I-GUL%06ld";
  24. static GULLoggerService kGULLogUserDefaultsService = @"[GoogleUtilities/UserDefaults]";
  25. typedef NS_ENUM(NSInteger, GULUDMessageCode) {
  26. GULUDMessageCodeInvalidKeyGet = 1,
  27. GULUDMessageCodeInvalidKeySet = 2,
  28. GULUDMessageCodeInvalidObjectSet = 3,
  29. GULUDMessageCodeSynchronizeFailed = 4,
  30. };
  31. @interface GULUserDefaults ()
  32. /// Equivalent to the suite name for NSUserDefaults.
  33. @property(readonly) CFStringRef appNameRef;
  34. @property(atomic) BOOL isPreferenceFileExcluded;
  35. @end
  36. @implementation GULUserDefaults {
  37. // The application name is the same with the suite name of the NSUserDefaults, and it is used for
  38. // preferences.
  39. CFStringRef _appNameRef;
  40. }
  41. + (GULUserDefaults *)standardUserDefaults {
  42. static GULUserDefaults *standardUserDefaults;
  43. static dispatch_once_t onceToken;
  44. dispatch_once(&onceToken, ^{
  45. standardUserDefaults = [[GULUserDefaults alloc] init];
  46. });
  47. return standardUserDefaults;
  48. }
  49. - (instancetype)init {
  50. return [self initWithSuiteName:nil];
  51. }
  52. - (instancetype)initWithSuiteName:(nullable NSString *)suiteName {
  53. self = [super init];
  54. NSString *name = [suiteName copy];
  55. if (self) {
  56. // `kCFPreferencesCurrentApplication` maps to the same defaults database as
  57. // `[NSUserDefaults standardUserDefaults]`.
  58. _appNameRef =
  59. name.length ? (__bridge_retained CFStringRef)name : kCFPreferencesCurrentApplication;
  60. }
  61. return self;
  62. }
  63. - (void)dealloc {
  64. // If we're using a custom `_appNameRef` it needs to be released. If it's a constant, it shouldn't
  65. // need to be released since we don't own it.
  66. if (CFStringCompare(_appNameRef, kCFPreferencesCurrentApplication, 0) != kCFCompareEqualTo) {
  67. CFRelease(_appNameRef);
  68. }
  69. [NSObject cancelPreviousPerformRequestsWithTarget:self
  70. selector:@selector(synchronize)
  71. object:nil];
  72. }
  73. - (nullable id)objectForKey:(NSString *)defaultName {
  74. NSString *key = [defaultName copy];
  75. if (![key isKindOfClass:[NSString class]] || !key.length) {
  76. GULLogWarning(@"<GoogleUtilities>", NO,
  77. [NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidKeyGet],
  78. @"Cannot get object for invalid user default key.");
  79. return nil;
  80. }
  81. return (__bridge_transfer id)CFPreferencesCopyAppValue((__bridge CFStringRef)key, _appNameRef);
  82. }
  83. - (void)setObject:(nullable id)value forKey:(NSString *)defaultName {
  84. NSString *key = [defaultName copy];
  85. if (![key isKindOfClass:[NSString class]] || !key.length) {
  86. GULLogWarning(kGULLogUserDefaultsService, NO,
  87. [NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidKeySet],
  88. @"Cannot set object for invalid user default key.");
  89. return;
  90. }
  91. if (!value) {
  92. CFPreferencesSetAppValue((__bridge CFStringRef)key, NULL, _appNameRef);
  93. [self scheduleSynchronize];
  94. return;
  95. }
  96. BOOL isAcceptableValue =
  97. [value isKindOfClass:[NSString class]] || [value isKindOfClass:[NSNumber class]] ||
  98. [value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]] ||
  99. [value isKindOfClass:[NSDate class]] || [value isKindOfClass:[NSData class]];
  100. if (!isAcceptableValue) {
  101. GULLogWarning(kGULLogUserDefaultsService, NO,
  102. [NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeInvalidObjectSet],
  103. @"Cannot set invalid object to user defaults. Must be a string, number, array, "
  104. @"dictionary, date, or data. Value: %@",
  105. value);
  106. return;
  107. }
  108. CFPreferencesSetAppValue((__bridge CFStringRef)key, (__bridge CFStringRef)value, _appNameRef);
  109. [self scheduleSynchronize];
  110. }
  111. - (void)removeObjectForKey:(NSString *)key {
  112. [self setObject:nil forKey:key];
  113. }
  114. #pragma mark - Getters
  115. - (NSInteger)integerForKey:(NSString *)defaultName {
  116. NSNumber *object = [self objectForKey:defaultName];
  117. return object.integerValue;
  118. }
  119. - (float)floatForKey:(NSString *)defaultName {
  120. NSNumber *object = [self objectForKey:defaultName];
  121. return object.floatValue;
  122. }
  123. - (double)doubleForKey:(NSString *)defaultName {
  124. NSNumber *object = [self objectForKey:defaultName];
  125. return object.doubleValue;
  126. }
  127. - (BOOL)boolForKey:(NSString *)defaultName {
  128. NSNumber *object = [self objectForKey:defaultName];
  129. return object.boolValue;
  130. }
  131. - (nullable NSString *)stringForKey:(NSString *)defaultName {
  132. return [self objectForKey:defaultName];
  133. }
  134. - (nullable NSArray *)arrayForKey:(NSString *)defaultName {
  135. return [self objectForKey:defaultName];
  136. }
  137. - (nullable NSDictionary<NSString *, id> *)dictionaryForKey:(NSString *)defaultName {
  138. return [self objectForKey:defaultName];
  139. }
  140. #pragma mark - Setters
  141. - (void)setInteger:(NSInteger)integer forKey:(NSString *)defaultName {
  142. [self setObject:@(integer) forKey:defaultName];
  143. }
  144. - (void)setFloat:(float)value forKey:(NSString *)defaultName {
  145. [self setObject:@(value) forKey:defaultName];
  146. }
  147. - (void)setDouble:(double)doubleNumber forKey:(NSString *)defaultName {
  148. [self setObject:@(doubleNumber) forKey:defaultName];
  149. }
  150. - (void)setBool:(BOOL)boolValue forKey:(NSString *)defaultName {
  151. [self setObject:@(boolValue) forKey:defaultName];
  152. }
  153. #pragma mark - Save data
  154. - (void)synchronize {
  155. if (!CFPreferencesAppSynchronize(_appNameRef)) {
  156. GULLogError(kGULLogUserDefaultsService, NO,
  157. [NSString stringWithFormat:kGULLogFormat, (long)GULUDMessageCodeSynchronizeFailed],
  158. @"Cannot synchronize user defaults to disk");
  159. }
  160. }
  161. #pragma mark - Private methods
  162. - (void)scheduleSynchronize {
  163. // Synchronize data using a timer so that multiple set... calls can be coalesced under one
  164. // synchronize.
  165. [NSObject cancelPreviousPerformRequestsWithTarget:self
  166. selector:@selector(synchronize)
  167. object:nil];
  168. // This method may be called on multiple queues (due to set... methods can be called on any queue)
  169. // synchronize can be scheduled on different queues, so make sure that it does not crash. If this
  170. // instance goes away, self will be released also, no one will retain it and the schedule won't be
  171. // called.
  172. [self performSelector:@selector(synchronize) withObject:nil afterDelay:kGULSynchronizeInterval];
  173. }
  174. @end
  175. NS_ASSUME_NONNULL_END