Proyectos de Subversion Iphone Microlearning

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
// Copyright 2019 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
 
15
#import "Crashlytics/Crashlytics/FIRCLSUserDefaults/FIRCLSUserDefaults.h"
16
 
17
#import "Crashlytics/Crashlytics/Components/FIRCLSApplication.h"
18
#import "Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h"
19
 
20
#define CLS_USER_DEFAULTS_SERIAL_DISPATCH_QUEUE "com.crashlytics.CLSUserDefaults.access"
21
#define CLS_USER_DEFAULTS_SYNC_QUEUE "com.crashlytics.CLSUserDefaults.io"
22
 
23
#define CLS_TARGET_CAN_WRITE_TO_DISK !TARGET_OS_TV
24
 
25
// These values are required to stay the same between versions of the SDK so
26
// that when end users upgrade, their crashlytics data is still saved on disk.
27
#if !CLS_TARGET_CAN_WRITE_TO_DISK
28
static NSString *const FIRCLSNSUserDefaultsDataDictionaryKey =
29
    @"com.crashlytics.CLSUserDefaults.user-default-key.data-dictionary";
30
#endif
31
 
32
NSString *const FIRCLSUserDefaultsPathComponent = @"CLSUserDefaults";
33
 
34
/**
35
 * This class is an isolated re-implementation of UserDefaults which isolates our storage
36
 * from that of our customers. This solves a number of issues we have seen in production, firstly
37
 * that customers often delete or clear UserDefaults, unintentionally deleting our data.
38
 * Further, we have seen thread safety issues in production with UserDefaults, as well as a number
39
 * of bugs related to accessing UserDefaults before the device has been unlocked due to the
40
 * FileProtection of UserDefaults.
41
 */
42
@interface FIRCLSUserDefaults ()
43
@property(nonatomic, readwrite) BOOL synchronizeWroteToDisk;
44
#if CLS_TARGET_CAN_WRITE_TO_DISK
45
@property(nonatomic, copy, readonly) NSURL *directoryURL;
46
@property(nonatomic, copy, readonly) NSURL *fileURL;
47
#endif
48
@property(nonatomic, copy, readonly)
49
    NSDictionary *persistedDataDictionary;  // May only be safely accessed on the DictionaryQueue
50
@property(nonatomic, copy, readonly)
51
    NSMutableDictionary *dataDictionary;  // May only be safely accessed on the DictionaryQueue
52
@property(nonatomic, readonly) dispatch_queue_t
53
    serialDictionaryQueue;  // The queue on which all access to the dataDictionary occurs.
54
@property(nonatomic, readonly)
55
    dispatch_queue_t synchronizationQueue;  // The queue on which all disk access occurs.
56
 
57
@end
58
 
59
@implementation FIRCLSUserDefaults
60
 
61
#pragma mark - singleton
62
 
63
+ (instancetype)standardUserDefaults {
64
  static FIRCLSUserDefaults *standardUserDefaults = nil;
65
  static dispatch_once_t onceToken;
66
 
67
  dispatch_once(&onceToken, ^{
68
    standardUserDefaults = [[super allocWithZone:NULL] init];
69
  });
70
 
71
  return standardUserDefaults;
72
}
73
 
74
- (id)copyWithZone:(NSZone *)zone {
75
  return self;
76
}
77
 
78
- (id)init {
79
  if (self = [super init]) {
80
    _serialDictionaryQueue =
81
        dispatch_queue_create(CLS_USER_DEFAULTS_SERIAL_DISPATCH_QUEUE, DISPATCH_QUEUE_SERIAL);
82
    _synchronizationQueue =
83
        dispatch_queue_create(CLS_USER_DEFAULTS_SYNC_QUEUE, DISPATCH_QUEUE_SERIAL);
84
 
85
    dispatch_sync(self.serialDictionaryQueue, ^{
86
#if CLS_TARGET_CAN_WRITE_TO_DISK
87
      self->_directoryURL = [self generateDirectoryURL];
88
      self->_fileURL = [[self->_directoryURL
89
          URLByAppendingPathComponent:FIRCLSUserDefaultsPathComponent
90
                          isDirectory:NO] URLByAppendingPathExtension:@"plist"];
91
#endif
92
      self->_persistedDataDictionary = [self loadDefaults];
93
      if (!self->_persistedDataDictionary) {
94
        self->_persistedDataDictionary = [NSDictionary dictionary];
95
      }
96
      self->_dataDictionary = [self->_persistedDataDictionary mutableCopy];
97
    });
98
  }
99
  return self;
100
}
101
 
102
- (NSURL *)generateDirectoryURL {
103
  NSURL *directoryBaseURL =
104
      [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory
105
                                              inDomains:NSUserDomainMask] lastObject];
106
  NSString *hostAppBundleIdentifier = [self getEscapedAppBundleIdentifier];
107
  return [self generateDirectoryURLForBaseURL:directoryBaseURL
108
                      hostAppBundleIdentifier:hostAppBundleIdentifier];
109
}
110
 
111
- (NSURL *)generateDirectoryURLForBaseURL:(NSURL *)directoryBaseURL
112
                  hostAppBundleIdentifier:(NSString *)hostAppBundleIdentifier {
113
  NSURL *directoryURL = directoryBaseURL;
114
  // On iOS NSApplicationSupportDirectory is contained in the app's bundle. On OSX, it is not (it is
115
  // ~/Library/Application Support/). On OSX we create a directory
116
  // ~/Library/Application Support/<app-identifier>/com.crashlytics/ for storing files.
117
  // Mac App Store review process requires files to be written to
118
  // ~/Library/Application Support/<app-identifier>/,
119
  // so ~/Library/Application Support/com.crashlytics/<app-identifier>/ cannot be used.
120
#if !TARGET_OS_SIMULATOR && !TARGET_OS_EMBEDDED
121
  if (hostAppBundleIdentifier) {
122
    directoryURL = [directoryURL URLByAppendingPathComponent:hostAppBundleIdentifier];
123
  }
124
#endif
125
  directoryURL = [directoryURL URLByAppendingPathComponent:@"com.crashlytics"];
126
  return directoryURL;
127
}
128
 
129
- (NSString *)getEscapedAppBundleIdentifier {
130
  return FIRCLSApplicationGetBundleIdentifier();
131
}
132
 
133
#pragma mark - fetch object
134
 
135
- (id)objectForKey:(NSString *)key {
136
  __block id result;
137
 
138
  dispatch_sync(self.serialDictionaryQueue, ^{
139
    result = [self->_dataDictionary objectForKey:key];
140
  });
141
 
142
  return result;
143
}
144
 
145
- (NSString *)stringForKey:(NSString *)key {
146
  id result = [self objectForKey:key];
147
 
148
  if (result != nil && [result isKindOfClass:[NSString class]]) {
149
    return (NSString *)result;
150
  } else {
151
    return nil;
152
  }
153
}
154
 
155
- (BOOL)boolForKey:(NSString *)key {
156
  id result = [self objectForKey:key];
157
  if (result != nil && [result isKindOfClass:[NSNumber class]]) {
158
    return [(NSNumber *)result boolValue];
159
  } else {
160
    return NO;
161
  }
162
}
163
 
164
// Defaults to 0
165
- (NSInteger)integerForKey:(NSString *)key {
166
  id result = [self objectForKey:key];
167
  if (result && [result isKindOfClass:[NSNumber class]]) {
168
    return [(NSNumber *)result integerValue];
169
  } else {
170
    return 0;
171
  }
172
}
173
 
174
#pragma mark - set object
175
 
176
- (void)setObject:(id)object forKey:(NSString *)key {
177
  dispatch_sync(self.serialDictionaryQueue, ^{
178
    [self->_dataDictionary setValue:object forKey:key];
179
  });
180
}
181
 
182
- (void)setString:(NSString *)string forKey:(NSString *)key {
183
  [self setObject:string forKey:key];
184
}
185
 
186
- (void)setBool:(BOOL)boolean forKey:(NSString *)key {
187
  [self setObject:[NSNumber numberWithBool:boolean] forKey:key];
188
}
189
 
190
- (void)setInteger:(NSInteger)integer forKey:(NSString *)key {
191
  [self setObject:[NSNumber numberWithInteger:integer] forKey:key];
192
}
193
 
194
#pragma mark - removing objects
195
 
196
- (void)removeObjectForKey:(NSString *)key {
197
  dispatch_sync(self.serialDictionaryQueue, ^{
198
    [self->_dataDictionary removeObjectForKey:key];
199
  });
200
}
201
 
202
- (void)removeAllObjects {
203
  dispatch_sync(self.serialDictionaryQueue, ^{
204
    [self->_dataDictionary removeAllObjects];
205
  });
206
}
207
 
208
#pragma mark - dictionary representation
209
 
210
- (NSDictionary *)dictionaryRepresentation {
211
  __block NSDictionary *result;
212
 
213
  dispatch_sync(self.serialDictionaryQueue, ^{
214
    result = [self->_dataDictionary copy];
215
  });
216
 
217
  return result;
218
}
219
 
220
#pragma mark - synchronization
221
 
222
- (void)synchronize {
223
  __block BOOL dirty = NO;
224
 
225
  // only write to the disk if the dictionaries have changed
226
  dispatch_sync(self.serialDictionaryQueue, ^{
227
    dirty = ![self->_persistedDataDictionary isEqualToDictionary:self->_dataDictionary];
228
  });
229
 
230
  _synchronizeWroteToDisk = dirty;
231
  if (!dirty) {
232
    return;
233
  }
234
 
235
  NSDictionary *state = [self dictionaryRepresentation];
236
  dispatch_sync(self.synchronizationQueue, ^{
237
#if CLS_TARGET_CAN_WRITE_TO_DISK
238
    BOOL isDirectory = NO;
239
    BOOL pathExists = [[NSFileManager defaultManager] fileExistsAtPath:[self->_directoryURL path]
240
                                                           isDirectory:&isDirectory];
241
 
242
    if (!pathExists) {
243
      NSError *error;
244
      if (![[NSFileManager defaultManager] createDirectoryAtURL:self->_directoryURL
245
                                    withIntermediateDirectories:YES
246
                                                     attributes:nil
247
                                                          error:&error]) {
248
        FIRCLSErrorLog(@"Failed to create directory with error: %@", error);
249
      }
250
    }
251
 
252
    if (![state writeToURL:self->_fileURL atomically:YES]) {
253
      FIRCLSErrorLog(@"Unable to open file for writing at path %@", [self->_fileURL path]);
254
    } else {
255
#if TARGET_OS_IOS
256
      // We disable NSFileProtection on our file in order to allow us to access it even if the
257
      // device is locked.
258
      NSError *error;
259
      if (![[NSFileManager defaultManager]
260
              setAttributes:@{NSFileProtectionKey : NSFileProtectionNone}
261
               ofItemAtPath:[self->_fileURL path]
262
                      error:&error]) {
263
        FIRCLSErrorLog(@"Error setting NSFileProtection: %@", error);
264
      }
265
#endif
266
    }
267
#else
268
        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
269
        [defaults setObject:state forKey:FIRCLSNSUserDefaultsDataDictionaryKey];
270
        [defaults synchronize];
271
#endif
272
  });
273
 
274
  dispatch_sync(self.serialDictionaryQueue, ^{
275
    self->_persistedDataDictionary = [self->_dataDictionary copy];
276
  });
277
}
278
 
279
- (NSDictionary *)loadDefaults {
280
  __block NSDictionary *state = nil;
281
  dispatch_sync(self.synchronizationQueue, ^{
282
#if CLS_TARGET_CAN_WRITE_TO_DISK
283
    BOOL isDirectory = NO;
284
    BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:[self->_fileURL path]
285
                                                           isDirectory:&isDirectory];
286
 
287
    if (fileExists && !isDirectory) {
288
      state = [NSDictionary dictionaryWithContentsOfURL:self->_fileURL];
289
      if (nil == state) {
290
        FIRCLSErrorLog(@"Failed to read existing UserDefaults file");
291
      }
292
    } else if (!fileExists) {
293
      // No file found. This is expected on first launch.
294
    } else if (fileExists && isDirectory) {
295
      FIRCLSErrorLog(@"Found directory where file expected. Removing conflicting directory");
296
 
297
      NSError *error;
298
      if (![[NSFileManager defaultManager] removeItemAtURL:self->_fileURL error:&error]) {
299
        FIRCLSErrorLog(@"Error removing conflicting directory: %@", error);
300
      }
301
    }
302
#else
303
        state = [[NSUserDefaults standardUserDefaults] dictionaryForKey:FIRCLSNSUserDefaultsDataDictionaryKey];
304
#endif
305
  });
306
  return state;
307
}
308
 
309
#pragma mark - migration
310
 
311
// This method migrates all keys specified from UserDefaults to FIRCLSUserDefaults
312
// To do so, we copy all known key-value pairs into FIRCLSUserDefaults, synchronize it, then
313
// remove the keys from UserDefaults and synchronize it.
314
- (void)migrateFromNSUserDefaults:(NSArray *)keysToMigrate {
315
  BOOL didFindKeys = NO;
316
 
317
  // First, copy all of the keysToMigrate which are stored UserDefaults
318
  for (NSString *key in keysToMigrate) {
319
    id oldValue = [[NSUserDefaults standardUserDefaults] objectForKey:(NSString *)key];
320
    if (nil != oldValue) {
321
      didFindKeys = YES;
322
      [self setObject:oldValue forKey:key];
323
    }
324
  }
325
 
326
  if (didFindKeys) {
327
    // First synchronize FIRCLSUserDefaults such that all keysToMigrate in UserDefaults are stored
328
    // in FIRCLSUserDefaults. At this point, data is duplicated.
329
    [[FIRCLSUserDefaults standardUserDefaults] synchronize];
330
 
331
    for (NSString *key in keysToMigrate) {
332
      [[NSUserDefaults standardUserDefaults] removeObjectForKey:(NSString *)key];
333
    }
334
 
335
    // This should be our last interaction with UserDefaults. All data is migrated into
336
    // FIRCLSUserDefaults
337
    [[NSUserDefaults standardUserDefaults] synchronize];
338
  }
339
}
340
 
341
// This method first queries FIRCLSUserDefaults to see if the key exist, and upon failure,
342
// searches for the key in UserDefaults, and migrates it if found.
343
- (id)objectForKeyByMigratingFromNSUserDefaults:(NSString *)keyToMigrateOrNil {
344
  if (!keyToMigrateOrNil) {
345
    return nil;
346
  }
347
 
348
  id clsUserDefaultsValue = [self objectForKey:keyToMigrateOrNil];
349
  if (clsUserDefaultsValue != nil) {
350
    return clsUserDefaultsValue;  // if the value exists in FIRCLSUserDefaults, return it.
351
  }
352
 
353
  id oldNSUserDefaultsValue =
354
      [[NSUserDefaults standardUserDefaults] objectForKey:keyToMigrateOrNil];
355
  if (!oldNSUserDefaultsValue) {
356
    return nil;  // if the value also does not exist in UserDefaults, return nil.
357
  }
358
 
359
  // Otherwise, the key exists in UserDefaults. Migrate it to FIRCLSUserDefaults
360
  // and then return the associated value.
361
 
362
  // First store it in FIRCLSUserDefaults so in the event of a crash, data is not lost.
363
  [self setObject:oldNSUserDefaultsValue forKey:keyToMigrateOrNil];
364
  [[FIRCLSUserDefaults standardUserDefaults] synchronize];
365
 
366
  [[NSUserDefaults standardUserDefaults] removeObjectForKey:keyToMigrateOrNil];
367
  [[NSUserDefaults standardUserDefaults] synchronize];
368
 
369
  return oldNSUserDefaultsValue;
370
}
371
 
372
@end