OLD | NEW |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #import "ios/web/public/crw_browsing_data_store.h" | 5 #import "ios/web/public/crw_browsing_data_store.h" |
6 | 6 |
7 #import <Foundation/Foundation.h> | 7 #import <Foundation/Foundation.h> |
8 | 8 |
9 #include "base/ios/ios_util.h" | 9 #include "base/ios/ios_util.h" |
10 #import "base/ios/weak_nsobject.h" | 10 #import "base/ios/weak_nsobject.h" |
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
54 (web::BrowsingDataTypes)browsingDataTypes; | 54 (web::BrowsingDataTypes)browsingDataTypes; |
55 // Returns the selector that needs to be performed on the | 55 // Returns the selector that needs to be performed on the |
56 // CRWBrowsingDataManagers for the |operationType|. |operationType| cannot be | 56 // CRWBrowsingDataManagers for the |operationType|. |operationType| cannot be |
57 // |NONE|. | 57 // |NONE|. |
58 - (SEL)browsingDataManagerSelectorForOperationType:(OperationType)operationType; | 58 - (SEL)browsingDataManagerSelectorForOperationType:(OperationType)operationType; |
59 // Returns the selector that needs to be performed on the | 59 // Returns the selector that needs to be performed on the |
60 // CRWBrowsingDataManagers for a REMOVE operation. | 60 // CRWBrowsingDataManagers for a REMOVE operation. |
61 - (SEL)browsingDataManagerSelectorForRemoveOperationType; | 61 - (SEL)browsingDataManagerSelectorForRemoveOperationType; |
62 | 62 |
63 // Redefined to be read-write. Must be called from the main thread. | 63 // Redefined to be read-write. Must be called from the main thread. |
64 @property(nonatomic, assign) CRWBrowsingDataStoreMode mode; | 64 @property(nonatomic, assign) web::BrowsingDataStoreMode mode; |
65 // Sets the mode iff there are no more stash or restore operations that are | 65 // Sets the mode iff there are no more stash or restore operations that are |
66 // still pending. |mode| can only be either |ACTIVE| or |INACTIVE|. | 66 // still pending. |mode| can only be either |ACTIVE| or |INACTIVE|. |
67 // |handler| is called immediately (in the same runloop) with a BOOL indicating | 67 // |handler| is called immediately (in the same runloop) with a BOOL indicating |
68 // whether the mode change was successful or not. |handler| can be nil. | 68 // whether the mode change was successful or not. |handler| can be nil. |
69 - (void)finalizeChangeToMode:(CRWBrowsingDataStoreMode)mode | 69 - (void)finalizeChangeToMode:(web::BrowsingDataStoreMode)mode |
70 andCallCompletionHandler:(void (^)(BOOL modeChangeWasSuccessful))handler; | 70 andCallCompletionHandler:(void (^)(BOOL modeChangeWasSuccessful))handler; |
71 | 71 |
72 // Changes the mode of the CRWBrowsingDataStore to |mode|. This is an | 72 // Changes the mode of the CRWBrowsingDataStore to |mode|. This is an |
73 // asynchronous operation and the mode is not changed immediately. | 73 // asynchronous operation and the mode is not changed immediately. |
74 // |completionHandler| can be nil. | 74 // |completionHandler| can be nil. |
75 // |completionHandler| is called on the main thread. This block has no return | 75 // |completionHandler| is called on the main thread. This block has no return |
76 // value and takes a single BOOL argument that indicates whether or not the | 76 // value and takes a single BOOL argument that indicates whether or not the |
77 // mode change was successfully changed to |mode|. | 77 // mode change was successfully changed to |mode|. |
78 - (void)changeMode:(CRWBrowsingDataStoreMode)mode | 78 - (void)changeMode:(web::BrowsingDataStoreMode)mode |
79 completionHandler:(void (^)(BOOL modeChangeWasSuccessful))completionHandler; | 79 completionHandler:(void (^)(BOOL modeChangeWasSuccessful))completionHandler; |
80 | 80 |
81 // The number of stash or restore operations that are still pending. | 81 // The number of stash or restore operations that are still pending. |
82 @property(nonatomic, assign) NSUInteger numberOfPendingStashOrRestoreOperations; | 82 @property(nonatomic, assign) NSUInteger numberOfPendingStashOrRestoreOperations; |
83 | 83 |
84 // Performs operations of type |operationType| on each of the | 84 // Performs operations of type |operationType| on each of the |
85 // |browsingDataManagers|. |operationType| cannot be |NONE|. | 85 // |browsingDataManagers|. |operationType| cannot be |NONE|. |
86 // Precondition: There must be no web views associated with the BrowserState. | 86 // Precondition: There must be no web views associated with the BrowserState. |
87 // |completionHandler| is called on the main thread and cannot be nil. | 87 // |completionHandler| is called on the main thread and cannot be nil. |
88 - (void)performOperationWithType:(OperationType)operationType | 88 - (void)performOperationWithType:(OperationType)operationType |
(...skipping 11 matching lines...) Expand all Loading... |
100 // Enqueues |operation| to be run on |queue|. All operations are serialized to | 100 // Enqueues |operation| to be run on |queue|. All operations are serialized to |
101 // be run one after another. | 101 // be run one after another. |
102 - (void)addOperation:(NSOperation*)operation toQueue:(NSOperationQueue*)queue; | 102 - (void)addOperation:(NSOperation*)operation toQueue:(NSOperationQueue*)queue; |
103 @end | 103 @end |
104 | 104 |
105 @implementation CRWBrowsingDataStore { | 105 @implementation CRWBrowsingDataStore { |
106 web::BrowserState* _browserState; // Weak, owns this object. | 106 web::BrowserState* _browserState; // Weak, owns this object. |
107 // The delegate. | 107 // The delegate. |
108 base::WeakNSProtocol<id<CRWBrowsingDataStoreDelegate>> _delegate; | 108 base::WeakNSProtocol<id<CRWBrowsingDataStoreDelegate>> _delegate; |
109 // The mode of the CRWBrowsingDataStore. | 109 // The mode of the CRWBrowsingDataStore. |
110 CRWBrowsingDataStoreMode _mode; | 110 web::BrowsingDataStoreMode _mode; |
111 // The dictionary that maps a browsing data type to its | 111 // The dictionary that maps a browsing data type to its |
112 // CRWBrowsingDataManager. | 112 // CRWBrowsingDataManager. |
113 base::scoped_nsobject<NSDictionary> _browsingDataTypeMap; | 113 base::scoped_nsobject<NSDictionary> _browsingDataTypeMap; |
114 // The last operation that was enqueued to be run. Can be stash, restore or a | 114 // The last operation that was enqueued to be run. Can be stash, restore or a |
115 // delete operation. | 115 // delete operation. |
116 base::scoped_nsobject<NSOperation> _lastDispatchedOperation; | 116 base::scoped_nsobject<NSOperation> _lastDispatchedOperation; |
117 // The last dispatched stash or restore operation that was enqueued to be run. | 117 // The last dispatched stash or restore operation that was enqueued to be run. |
118 base::scoped_nsobject<NSOperation> _lastDispatchedStashOrRestoreOperation; | 118 base::scoped_nsobject<NSOperation> _lastDispatchedStashOrRestoreOperation; |
119 // The number of stash or restore operations that are still pending. If this | 119 // The number of stash or restore operations that are still pending. If this |
120 // value > 0 the mode of the CRWBrowsingDataStore is SYNCHRONIZING. The mode | 120 // value > 0 the mode of the CRWBrowsingDataStore is |CHANGING|. The mode |
121 // can be made ACTIVE or INACTIVE only be set when this value is 0. | 121 // can be made ACTIVE or INACTIVE only be set when this value is 0. |
122 NSUInteger _numberOfPendingStashOrRestoreOperations; | 122 NSUInteger _numberOfPendingStashOrRestoreOperations; |
123 } | 123 } |
124 | 124 |
125 + (NSOperationQueue*)operationQueueForStashAndRestoreOperations { | 125 + (NSOperationQueue*)operationQueueForStashAndRestoreOperations { |
126 static dispatch_once_t onceToken = 0; | 126 static dispatch_once_t onceToken = 0; |
127 static NSOperationQueue* operationQueueForStashAndRestoreOperations = nil; | 127 static NSOperationQueue* operationQueueForStashAndRestoreOperations = nil; |
128 dispatch_once(&onceToken, ^{ | 128 dispatch_once(&onceToken, ^{ |
129 operationQueueForStashAndRestoreOperations = | 129 operationQueueForStashAndRestoreOperations = |
130 [[NSOperationQueue alloc] init]; | 130 [[NSOperationQueue alloc] init]; |
(...skipping 25 matching lines...) Expand all Loading... |
156 - (instancetype)initWithBrowserState:(web::BrowserState*)browserState { | 156 - (instancetype)initWithBrowserState:(web::BrowserState*)browserState { |
157 self = [super init]; | 157 self = [super init]; |
158 if (self) { | 158 if (self) { |
159 DCHECK([NSThread isMainThread]); | 159 DCHECK([NSThread isMainThread]); |
160 // TODO(shreyasv): Instantiate the necessary CRWBrowsingDataManagers that | 160 // TODO(shreyasv): Instantiate the necessary CRWBrowsingDataManagers that |
161 // are encapsulated within this class. crbug.com/480654. | 161 // are encapsulated within this class. crbug.com/480654. |
162 _browserState = browserState; | 162 _browserState = browserState; |
163 web::ActiveStateManager* activeStateManager = | 163 web::ActiveStateManager* activeStateManager = |
164 web::BrowserState::GetActiveStateManager(browserState); | 164 web::BrowserState::GetActiveStateManager(browserState); |
165 DCHECK(activeStateManager); | 165 DCHECK(activeStateManager); |
166 _mode = activeStateManager->IsActive() ? ACTIVE : INACTIVE; | 166 _mode = activeStateManager->IsActive() ? web::ACTIVE : web::INACTIVE; |
167 // TODO(shreyasv): If the creation of CRWBrowsingDataManagers turns out to | 167 // TODO(shreyasv): If the creation of CRWBrowsingDataManagers turns out to |
168 // be an expensive operations re-visit this with a lazy-evaluation approach. | 168 // be an expensive operations re-visit this with a lazy-evaluation approach. |
169 base::scoped_nsobject<CRWCookieBrowsingDataManager> | 169 base::scoped_nsobject<CRWCookieBrowsingDataManager> |
170 cookieBrowsingDataManager([[CRWCookieBrowsingDataManager alloc] | 170 cookieBrowsingDataManager([[CRWCookieBrowsingDataManager alloc] |
171 initWithBrowserState:browserState]); | 171 initWithBrowserState:browserState]); |
172 _browsingDataTypeMap.reset([@{ | 172 _browsingDataTypeMap.reset([@{ |
173 @(web::BROWSING_DATA_TYPE_COOKIES) : cookieBrowsingDataManager, | 173 @(web::BROWSING_DATA_TYPE_COOKIES) : cookieBrowsingDataManager, |
174 } retain]); | 174 } retain]); |
175 } | 175 } |
176 return self; | 176 return self; |
(...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
225 case RESTORE: | 225 case RESTORE: |
226 return @selector(restoreData); | 226 return @selector(restoreData); |
227 case REMOVE: | 227 case REMOVE: |
228 return [self browsingDataManagerSelectorForRemoveOperationType]; | 228 return [self browsingDataManagerSelectorForRemoveOperationType]; |
229 }; | 229 }; |
230 NOTREACHED(); | 230 NOTREACHED(); |
231 return nullptr; | 231 return nullptr; |
232 } | 232 } |
233 | 233 |
234 - (SEL)browsingDataManagerSelectorForRemoveOperationType { | 234 - (SEL)browsingDataManagerSelectorForRemoveOperationType { |
235 if (self.mode == ACTIVE) { | 235 if (self.mode == web::ACTIVE) { |
236 return @selector(removeDataAtCanonicalPath); | 236 return @selector(removeDataAtCanonicalPath); |
237 } | 237 } |
238 if (self.mode == INACTIVE) { | 238 if (self.mode == web::INACTIVE) { |
239 return @selector(removeDataAtStashPath); | 239 return @selector(removeDataAtStashPath); |
240 } | 240 } |
241 DCHECK(_lastDispatchedStashOrRestoreOperation); | 241 DCHECK(_lastDispatchedStashOrRestoreOperation); |
242 NSString* lastDispatchedStashOrRestoreOperationName = | 242 NSString* lastDispatchedStashOrRestoreOperationName = |
243 [_lastDispatchedStashOrRestoreOperation name]; | 243 [_lastDispatchedStashOrRestoreOperation name]; |
244 if ([lastDispatchedStashOrRestoreOperationName | 244 if ([lastDispatchedStashOrRestoreOperationName |
245 isEqual:kRestoreOperationName]) { | 245 isEqual:kRestoreOperationName]) { |
246 return @selector(removeDataAtCanonicalPath); | 246 return @selector(removeDataAtCanonicalPath); |
247 } | 247 } |
248 if ([lastDispatchedStashOrRestoreOperationName isEqual:kStashOperationName]) { | 248 if ([lastDispatchedStashOrRestoreOperationName isEqual:kStashOperationName]) { |
249 return @selector(removeDataAtStashPath); | 249 return @selector(removeDataAtStashPath); |
250 } | 250 } |
251 NOTREACHED(); | 251 NOTREACHED(); |
252 return nullptr; | 252 return nullptr; |
253 } | 253 } |
254 | 254 |
255 + (BOOL)automaticallyNotifiesObserversForKey:(NSString*)key { | 255 + (BOOL)automaticallyNotifiesObserversForKey:(NSString*)key { |
256 // It is necessary to override this for |mode| because the default KVO | 256 // It is necessary to override this for |mode| because the default KVO |
257 // behavior in NSObject is to fire a notification irrespective of if an actual | 257 // behavior in NSObject is to fire a notification irrespective of if an actual |
258 // change was made to the ivar or not. The |mode| property needs fine grained | 258 // change was made to the ivar or not. The |mode| property needs fine grained |
259 // control over the actual notifications being fired since observers need to | 259 // control over the actual notifications being fired since observers need to |
260 // be notified iff the |mode| actually changed. | 260 // be notified iff the |mode| actually changed. |
261 if ([key isEqual:@"mode"]) { | 261 if ([key isEqual:@"mode"]) { |
262 return NO; | 262 return NO; |
263 } | 263 } |
264 return [super automaticallyNotifiesObserversForKey:(NSString*)key]; | 264 return [super automaticallyNotifiesObserversForKey:(NSString*)key]; |
265 } | 265 } |
266 | 266 |
267 - (CRWBrowsingDataStoreMode)mode { | 267 - (web::BrowsingDataStoreMode)mode { |
268 DCHECK([NSThread isMainThread]); | 268 DCHECK([NSThread isMainThread]); |
269 return _mode; | 269 return _mode; |
270 } | 270 } |
271 | 271 |
272 - (void)setMode:(CRWBrowsingDataStoreMode)mode { | 272 - (void)setMode:(web::BrowsingDataStoreMode)mode { |
273 DCHECK([NSThread isMainThread]); | 273 DCHECK([NSThread isMainThread]); |
274 if (_mode == mode) { | 274 if (_mode == mode) { |
275 return; | 275 return; |
276 } | 276 } |
277 if (mode == ACTIVE || mode == INACTIVE) { | 277 if (mode == web::ACTIVE || mode == web::INACTIVE) { |
278 DCHECK(!self.numberOfPendingStashOrRestoreOperations); | 278 DCHECK(!self.numberOfPendingStashOrRestoreOperations); |
279 } | 279 } |
280 [self willChangeValueForKey:@"mode"]; | 280 [self willChangeValueForKey:@"mode"]; |
281 _mode = mode; | 281 _mode = mode; |
282 [self didChangeValueForKey:@"mode"]; | 282 [self didChangeValueForKey:@"mode"]; |
283 } | 283 } |
284 | 284 |
285 - (void)finalizeChangeToMode:(CRWBrowsingDataStoreMode)mode | 285 - (void)finalizeChangeToMode:(web::BrowsingDataStoreMode)mode |
286 andCallCompletionHandler:(void (^)(BOOL modeChangeWasSuccessful))handler { | 286 andCallCompletionHandler:(void (^)(BOOL modeChangeWasSuccessful))handler { |
287 DCHECK([NSThread isMainThread]); | 287 DCHECK([NSThread isMainThread]); |
288 DCHECK_NE(SYNCHRONIZING, mode); | 288 DCHECK_NE(web::CHANGING, mode); |
289 | 289 |
290 BOOL modeChangeWasSuccessful = NO; | 290 BOOL modeChangeWasSuccessful = NO; |
291 if (!self.numberOfPendingStashOrRestoreOperations) { | 291 if (!self.numberOfPendingStashOrRestoreOperations) { |
292 [self setMode:mode]; | 292 [self setMode:mode]; |
293 modeChangeWasSuccessful = YES; | 293 modeChangeWasSuccessful = YES; |
294 } | 294 } |
295 if (handler) { | 295 if (handler) { |
296 handler(modeChangeWasSuccessful); | 296 handler(modeChangeWasSuccessful); |
297 } | 297 } |
298 } | 298 } |
299 | 299 |
300 - (NSUInteger)numberOfPendingStashOrRestoreOperations { | 300 - (NSUInteger)numberOfPendingStashOrRestoreOperations { |
301 DCHECK([NSThread isMainThread]); | 301 DCHECK([NSThread isMainThread]); |
302 return _numberOfPendingStashOrRestoreOperations; | 302 return _numberOfPendingStashOrRestoreOperations; |
303 } | 303 } |
304 | 304 |
305 - (void)setNumberOfPendingStashOrRestoreOperations: | 305 - (void)setNumberOfPendingStashOrRestoreOperations: |
306 (NSUInteger)numberOfPendingStashOrRestoreOperations { | 306 (NSUInteger)numberOfPendingStashOrRestoreOperations { |
307 DCHECK([NSThread isMainThread]); | 307 DCHECK([NSThread isMainThread]); |
308 _numberOfPendingStashOrRestoreOperations = | 308 _numberOfPendingStashOrRestoreOperations = |
309 numberOfPendingStashOrRestoreOperations; | 309 numberOfPendingStashOrRestoreOperations; |
310 } | 310 } |
311 | 311 |
312 - (void)makeActiveWithCompletionHandler: | 312 - (void)makeActiveWithCompletionHandler: |
313 (void (^)(BOOL success))completionHandler { | 313 (void (^)(BOOL success))completionHandler { |
314 DCHECK([NSThread isMainThread]); | 314 DCHECK([NSThread isMainThread]); |
315 | 315 |
316 [self changeMode:ACTIVE completionHandler:completionHandler]; | 316 [self changeMode:web::ACTIVE completionHandler:completionHandler]; |
317 } | 317 } |
318 | 318 |
319 - (void)makeInactiveWithCompletionHandler: | 319 - (void)makeInactiveWithCompletionHandler: |
320 (void (^)(BOOL success))completionHandler { | 320 (void (^)(BOOL success))completionHandler { |
321 DCHECK([NSThread isMainThread]); | 321 DCHECK([NSThread isMainThread]); |
322 | 322 |
323 [self changeMode:INACTIVE completionHandler:completionHandler]; | 323 [self changeMode:web::INACTIVE completionHandler:completionHandler]; |
324 } | 324 } |
325 | 325 |
326 - (void)changeMode:(CRWBrowsingDataStoreMode)mode | 326 - (void)changeMode:(web::BrowsingDataStoreMode)mode |
327 completionHandler: | 327 completionHandler: |
328 (void (^)(BOOL modeChangeWasSuccessful))completionHandler { | 328 (void (^)(BOOL modeChangeWasSuccessful))completionHandler { |
329 DCHECK([NSThread isMainThread]); | 329 DCHECK([NSThread isMainThread]); |
330 | 330 |
331 ProceduralBlock completionHandlerAfterPerformingOperation = ^{ | 331 ProceduralBlock completionHandlerAfterPerformingOperation = ^{ |
332 [self finalizeChangeToMode:mode andCallCompletionHandler:completionHandler]; | 332 [self finalizeChangeToMode:mode andCallCompletionHandler:completionHandler]; |
333 }; | 333 }; |
334 | 334 |
335 // Already in the desired mode. | 335 // Already in the desired mode. |
336 if (self.mode == mode) { | 336 if (self.mode == mode) { |
337 // As a caller of this API, it is awkward to get the callback before the | 337 // As a caller of this API, it is awkward to get the callback before the |
338 // method call has completed, hence defer it. | 338 // method call has completed, hence defer it. |
339 dispatch_async(dispatch_get_main_queue(), ^{ | 339 dispatch_async(dispatch_get_main_queue(), ^{ |
340 completionHandlerAfterPerformingOperation(); | 340 completionHandlerAfterPerformingOperation(); |
341 }); | 341 }); |
342 return; | 342 return; |
343 } | 343 } |
344 | 344 |
345 OperationType operationType = NONE; | 345 OperationType operationType = NONE; |
346 if (mode == ACTIVE) { | 346 if (mode == web::ACTIVE) { |
347 // By default a |RESTORE| operation is performed when the mode is changed | 347 // By default a |RESTORE| operation is performed when the mode is changed |
348 // to |ACTIVE|. | 348 // to |ACTIVE|. |
349 operationType = RESTORE; | 349 operationType = RESTORE; |
350 web::BrowsingDataStoreMakeActivePolicy makeActivePolicy = | 350 web::BrowsingDataStoreMakeActivePolicy makeActivePolicy = |
351 [_delegate decideMakeActiveOperationPolicyForBrowsingDataStore:self]; | 351 [_delegate decideMakeActiveOperationPolicyForBrowsingDataStore:self]; |
352 operationType = (makeActivePolicy == web::ADOPT) ? REMOVE : RESTORE; | 352 operationType = (makeActivePolicy == web::ADOPT) ? REMOVE : RESTORE; |
353 } else { | 353 } else { |
354 // By default a |STASH| operation is performed when the mode is changed to | 354 // By default a |STASH| operation is performed when the mode is changed to |
355 // |INACTIVE|. | 355 // |INACTIVE|. |
356 operationType = STASH; | 356 operationType = STASH; |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
391 completionHandler:(ProceduralBlock)completionHandler { | 391 completionHandler:(ProceduralBlock)completionHandler { |
392 DCHECK([NSThread isMainThread]); | 392 DCHECK([NSThread isMainThread]); |
393 DCHECK(completionHandler); | 393 DCHECK(completionHandler); |
394 DCHECK_NE(NONE, operationType); | 394 DCHECK_NE(NONE, operationType); |
395 | 395 |
396 SEL selector = | 396 SEL selector = |
397 [self browsingDataManagerSelectorForOperationType:operationType]; | 397 [self browsingDataManagerSelectorForOperationType:operationType]; |
398 DCHECK(selector); | 398 DCHECK(selector); |
399 | 399 |
400 if (operationType == RESTORE || operationType == STASH) { | 400 if (operationType == RESTORE || operationType == STASH) { |
401 [self setMode:SYNCHRONIZING]; | 401 [self setMode:web::CHANGING]; |
402 ++self.numberOfPendingStashOrRestoreOperations; | 402 ++self.numberOfPendingStashOrRestoreOperations; |
403 completionHandler = ^{ | 403 completionHandler = ^{ |
404 --self.numberOfPendingStashOrRestoreOperations; | 404 --self.numberOfPendingStashOrRestoreOperations; |
405 // It is safe to this and does not lead to the block (|completionHandler|) | 405 // It is safe to this and does not lead to the block (|completionHandler|) |
406 // retaining itself. | 406 // retaining itself. |
407 completionHandler(); | 407 completionHandler(); |
408 }; | 408 }; |
409 } | 409 } |
410 | 410 |
411 id callCompletionHandlerOnMainThread = ^{ | 411 id callCompletionHandlerOnMainThread = ^{ |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
464 DCHECK(queue); | 464 DCHECK(queue); |
465 | 465 |
466 if (_lastDispatchedOperation) { | 466 if (_lastDispatchedOperation) { |
467 [operation addDependency:_lastDispatchedOperation]; | 467 [operation addDependency:_lastDispatchedOperation]; |
468 } | 468 } |
469 _lastDispatchedOperation.reset([operation retain]); | 469 _lastDispatchedOperation.reset([operation retain]); |
470 [queue addOperation:operation]; | 470 [queue addOperation:operation]; |
471 } | 471 } |
472 | 472 |
473 @end | 473 @end |
OLD | NEW |