Skip to content

Implemented PFObject.deleteAll through PFObjectBatchController. #124

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 28, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import "PFDataProvider.h"

@class BFTask;
@class PFObject;

NS_ASSUME_NONNULL_BEGIN

Expand All @@ -33,11 +34,18 @@ NS_ASSUME_NONNULL_BEGIN

- (BFTask *)fetchObjectsAsync:(nullable NSArray *)objects withSessionToken:(nullable NSString *)sessionToken;

///--------------------------------------
/// @name Delete
///--------------------------------------

- (BFTask *)deleteObjectsAsync:(nullable NSArray *)objects withSessionToken:(nullable NSString *)sessionToken;

///--------------------------------------
/// @name Utilities
///--------------------------------------

+ (nullable NSArray *)uniqueObjectsArrayFromArray:(nullable NSArray *)objects omitObjectsWithData:(BOOL)omitFetched;
+ (NSArray *)uniqueObjectsArrayFromArray:(NSArray *)objects usingFilter:(BOOL (^)(PFObject *object))filter;

@end

Expand Down
101 changes: 101 additions & 0 deletions Parse/Internal/Object/BatchController/PFObjectBatchController.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

#import "PFObjectBatchController.h"

#import <Bolts/Bolts.h>

#import "BFTask+Private.h"
#import "PFAssert.h"
#import "PFCommandResult.h"
Expand All @@ -19,6 +21,8 @@
#import "PFObjectPrivate.h"
#import "PFQueryPrivate.h"
#import "PFRESTQueryCommand.h"
#import "PFRESTObjectCommand.h"
#import "PFRESTObjectBatchCommand.h"

@implementation PFObjectBatchController

Expand Down Expand Up @@ -98,10 +102,86 @@ - (BFTask *)_processFetchResultAsync:(NSDictionary *)result forObjects:(NSArray
}];
}

///--------------------------------------
#pragma mark - Delete
///--------------------------------------

- (BFTask *)deleteObjectsAsync:(NSArray *)objects withSessionToken:(NSString *)sessionToken {
if (objects.count == 0) {
return [BFTask taskWithResult:objects];
}

@weakify(self);
return [[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{
@strongify(self);
NSArray *objectBatches = [PFInternalUtils arrayBySplittingArray:objects
withMaximumComponentsPerSegment:PFRESTObjectBatchCommandSubcommandsLimit];
NSMutableArray *tasks = [NSMutableArray arrayWithCapacity:objectBatches.count];
for (NSArray *batch in objectBatches) {
PFRESTCommand *command = [self _deleteCommandForObjects:batch withSessionToken:sessionToken];
BFTask *task = [[self.dataSource.commandRunner runCommandAsync:command
withOptions:PFCommandRunningOptionRetryIfFailed] continueWithSuccessBlock:^id(BFTask *task) {
PFCommandResult *result = task.result;
return [self _processDeleteResultsAsync:[result result] forObjects:batch];
}];
[tasks addObject:task];
}
return [[BFTask taskForCompletionOfAllTasks:tasks] continueWithBlock:^id(BFTask *task) {
NSError *taskError = task.error;
if (taskError && [taskError.domain isEqualToString:BFTaskErrorDomain]) {
NSArray *taskErrors = taskError.userInfo[@"errors"];
NSMutableArray *errors = [NSMutableArray array];
for (NSError *error in taskErrors) {
if ([error.domain isEqualToString:BFTaskErrorDomain]) {
[errors addObjectsFromArray:error.userInfo[@"errors"]];
} else {
[errors addObject:error];
}
}
return [BFTask taskWithError:[NSError errorWithDomain:BFTaskErrorDomain
code:kBFMultipleErrorsError
userInfo:@{ @"errors" : errors }]];
}
return task;
}];
}] continueWithSuccessResult:objects];
}

- (PFRESTCommand *)_deleteCommandForObjects:(NSArray *)objects withSessionToken:(NSString *)sessionToken {
NSMutableArray *commands = [NSMutableArray arrayWithCapacity:objects.count];
for (PFObject *object in objects) {
[object checkDeleteParams];
PFRESTCommand *deleteCommand = [PFRESTObjectCommand deleteObjectCommandForObjectState:object._state
withSessionToken:sessionToken];
[commands addObject:deleteCommand];
}
return [PFRESTObjectBatchCommand batchCommandWithCommands:commands sessionToken:sessionToken];
}

- (BFTask *)_processDeleteResultsAsync:(NSArray *)results forObjects:(NSArray *)objects {
NSMutableArray *tasks = [NSMutableArray arrayWithCapacity:results.count];
[results enumerateObjectsUsingBlock:^(NSDictionary *result, NSUInteger idx, BOOL *stop) {
PFObject *object = objects[idx];
NSDictionary *errorResult = result[@"error"];
NSDictionary *successResult = result[@"success"];

id<PFObjectControlling> controller = [[object class] objectController];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we have access to the data source here? Why go through the class dispatch method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Objects can have different object controllers, say UserController or InstallationController, where those have extra logic on top of the bare object controller.

BFTask *task = [controller processDeleteResultAsync:successResult forObject:object];
if (errorResult) {
task = [task continueWithBlock:^id(BFTask *task) {
return [BFTask taskWithError:[PFErrorUtilities errorFromResult:errorResult]];
}];
}
[tasks addObject:task];
}];
return [BFTask taskForCompletionOfAllTasks:tasks];
}

///--------------------------------------
#pragma mark - Utilities
///--------------------------------------

//TODO: (nlutsenko) Convert to use `uniqueObjectsArrayFromArray:usingFilter:`
+ (NSArray *)uniqueObjectsArrayFromArray:(NSArray *)objects omitObjectsWithData:(BOOL)omitFetched {
if (objects.count == 0) {
return objects;
Expand All @@ -115,6 +195,7 @@ + (NSArray *)uniqueObjectsArrayFromArray:(NSArray *)objects omitObjectsWithData:
continue;
}

//TODO: (nlutsenko) Convert to using errors instead of assertions.
PFParameterAssert([className isEqualToString:object.parseClassName],
@"All object should be in the same class.");
PFParameterAssert(object.objectId != nil,
Expand All @@ -126,4 +207,24 @@ + (NSArray *)uniqueObjectsArrayFromArray:(NSArray *)objects omitObjectsWithData:
return [set allObjects];
}

+ (NSArray *)uniqueObjectsArrayFromArray:(NSArray *)objects usingFilter:(BOOL (^)(PFObject *object))filter {
if (objects.count == 0) {
return objects;
}

NSMutableDictionary *uniqueObjects = [NSMutableDictionary dictionary];
for (PFObject *object in objects) {
if (!filter(object)) {
continue;
}

// Use stringWithFormat: in case objectId or parseClassName are nil.
NSString *objectIdentifier = [NSString stringWithFormat:@"%@%@", object.parseClassName, object.objectId];
if (!uniqueObjects[objectIdentifier]) {
uniqueObjects[objectIdentifier] = object;
}
}
return [uniqueObjects allValues];
}

@end
93 changes: 16 additions & 77 deletions Parse/PFObject.m
Original file line number Diff line number Diff line change
Expand Up @@ -394,80 +394,6 @@ - (BOOL)canBeSerializedAfterSaving:(NSMutableArray *)saved withCurrentUser:(PFUs
}
}

// Delete all objects in the array.
+ (BFTask *)deleteAllAsync:(NSArray *)objects withSessionToken:(NSString *)sessionToken {
if ([objects count] == 0) {
return [BFTask taskWithResult:@YES];
}

return [[[BFTask taskFromExecutor:[BFExecutor defaultPriorityBackgroundExecutor] withBlock:^id{
return [PFObject _enqueue:^BFTask *(BFTask *toAwait) {
NSMutableSet *uniqueObjects = [NSMutableSet set];
NSMutableArray *commands = [NSMutableArray arrayWithCapacity:[objects count]];
for (PFObject *object in objects) {
@synchronized (object->lock) {
//Just continue, there is no action to be taken here
if (!object.objectId) {
continue;
}

NSString *uniqueCheck = [NSString stringWithFormat:@"%@-%@",
object.parseClassName,
[object objectId]];
if (![uniqueObjects containsObject:uniqueCheck]) {
[object checkDeleteParams];

[commands addObject:[object _currentDeleteCommandWithSessionToken:sessionToken]];
[uniqueObjects addObject:uniqueCheck];
}
}
}

// Batch requests have currently a limit of 50 packaged requests per single request
// This splitting will split the overall array into segments of upto 50 requests
// and execute them concurrently with a wrapper task for all of them.
NSArray *commandBatches = [PFInternalUtils arrayBySplittingArray:commands
withMaximumComponentsPerSegment:PFRESTObjectBatchCommandSubcommandsLimit];
NSMutableArray *tasks = [NSMutableArray arrayWithCapacity:[commandBatches count]];
for (NSArray *commandBatch in commandBatches) {
PFRESTCommand *command = [PFRESTObjectBatchCommand batchCommandWithCommands:commandBatch
sessionToken:sessionToken];
BFTask *task = [[[Parse _currentManager].commandRunner runCommandAsync:command withOptions:0]
continueAsyncWithSuccessBlock:^id(BFTask *task) {
NSArray *results = [task.result result];
for (NSDictionary *result in results) {
NSDictionary *errorResult = result[@"error"];
if (errorResult) {
NSError *error = [PFErrorUtilities errorFromResult:errorResult];
return [BFTask taskWithError:error];
}
}

return task;
}];
[tasks addObject:task];
}
return [BFTask taskForCompletionOfAllTasks:tasks];
} forObjects:objects];
}] continueWithBlock:^id(BFTask *task) {
if (!task.exception) {
return task;
}

// Return the first exception, instead of the aggregated one
// for the sake of compatability with old versions

if ([task.exception.name isEqualToString:BFTaskMultipleExceptionsException]) {
NSException *firstException = [task.exception.userInfo[@"exceptions"] firstObject];
if (firstException) {
return [BFTask taskWithException:firstException];
}
}

return task;
}] continueWithSuccessResult:@YES];
}

// This saves all of the objects and files reachable from the given object.
// It does its work in multiple waves, saving as many as possible in each wave.
// If there's ever an error, it just gives up, sets error, and returns NO;
Expand Down Expand Up @@ -2495,10 +2421,23 @@ + (BOOL)deleteAll:(NSArray *)objects error:(NSError **)error {
}

+ (BFTask *)deleteAllInBackground:(NSArray *)objects {
return [[[self currentUserController] getCurrentUserSessionTokenAsync] continueWithBlock:^id(BFTask *task) {
NSArray *deleteObjects = [objects copy]; // Snapshot the objects.
if (deleteObjects.count == 0) {
return [BFTask taskWithResult:objects];
}
return [[[[self currentUserController] getCurrentUserSessionTokenAsync] continueWithBlock:^id(BFTask *task) {
NSString *sessionToken = task.result;
return [PFObject deleteAllAsync:objects withSessionToken:sessionToken];
}];

NSArray *uniqueObjects = [PFObjectBatchController uniqueObjectsArrayFromArray:deleteObjects usingFilter:^BOOL(PFObject *object) {
return (object.objectId != nil);
}];
[uniqueObjects makeObjectsPerformSelector:@selector(checkDeleteParams)]; // TODO: (nlutsenko) Make it async?
return [self _enqueue:^BFTask *(BFTask *toAwait) {
return [toAwait continueAsyncWithBlock:^id(BFTask *task) {
return [[self objectBatchController] deleteObjectsAsync:uniqueObjects withSessionToken:sessionToken];
}];
} forObjects:uniqueObjects];
}] continueWithSuccessResult:@YES];
}

+ (void)deleteAllInBackground:(NSArray *)objects target:(id)target selector:(SEL)selector {
Expand Down
4 changes: 3 additions & 1 deletion Tests/Other/OCMock/OCMock+Parse.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@

#import <OCMock/OCMock.h>

@class PFRESTCommand;

@interface OCMockObject (PFCommandRunning)

- (void)mockCommandResult:(id)result forCommandsPassingTest:(BOOL (^)(id obj))block;
- (void)mockCommandResult:(id)result forCommandsPassingTest:(BOOL (^)(PFRESTCommand *command))block;

@end
2 changes: 1 addition & 1 deletion Tests/Other/OCMock/OCMock+Parse.m
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

@implementation OCMockObject (PFCOmmandRunning)

- (void)mockCommandResult:(id)result forCommandsPassingTest:(BOOL (^)(id obj))block {
- (void)mockCommandResult:(id)result forCommandsPassingTest:(BOOL (^)(PFRESTCommand *command))block {
PFCommandResult *commandResult = [PFCommandResult commandResultWithResult:result
resultString:nil
httpResponse:nil];
Expand Down
Loading