GULUserDefaults.m 7.0 KB

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