mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-04-23 20:31:13 +08:00
Add APIs to RKObjectManager for use in firing batches of operations.
This commit is contained in:
@@ -406,6 +406,37 @@ RKMappingResult, RKRequestDescriptor, RKResponseDescriptor;
|
||||
*/
|
||||
- (void)cancelAllObjectRequestOperationsWithMethod:(RKRequestMethod)method matchingPathPattern:(NSString *)pathPattern;
|
||||
|
||||
///--------------------------------------------------
|
||||
/// @name Managing Batches of Enqueued Object Request Operations
|
||||
///--------------------------------------------------
|
||||
|
||||
/**
|
||||
Enqueues a set of `RKObjectRequestOperation`, derived from the provided RKRoute and objects, to the object manager's operation queue.
|
||||
|
||||
@param route The RKRoute to apply to all provided objects.
|
||||
@param objects The set of objects that should be turned into operations using the provided route.
|
||||
@param progress A block object to be executed when an object request operation completes. This block has no return value and takes two arguments: the number of finished operations and the total number of operations initially executed.
|
||||
@param completion A block object to be executed when the object request operations complete. This block has no return value and takes one argument: the list of operations executed.
|
||||
|
||||
@see [RKObjectManager enqueueBatchOfObjectRequestOperations:progress:completion]
|
||||
*/
|
||||
- (void)enqueueBatchOfObjectRequestOperationsWithRoute:(RKRoute *)route
|
||||
objects:(NSArray *)objects
|
||||
progress:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progress
|
||||
completion:(void (^)(NSArray *operations))completion;
|
||||
|
||||
/**
|
||||
Enqueues a set of `RKObjectRequestOperation` to the object manager's operation queue.
|
||||
|
||||
@param operations The set of object request operations to be enqueued.
|
||||
@param progress A block object to be executed when an object request operation completes. This block has no return value and takes two arguments: the number of finished operations and the total number of operations initially executed.
|
||||
@param completion A block object to be executed when the object request operations complete. This block has no return value and takes one argument: the list of operations executed.
|
||||
|
||||
*/
|
||||
- (void)enqueueBatchOfObjectRequestOperations:(NSArray *)operations
|
||||
progress:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progress
|
||||
completion:(void (^)(NSArray *operations))completion;
|
||||
|
||||
///-------------------------------------
|
||||
/// @name Making Object Requests by Path
|
||||
///-------------------------------------
|
||||
|
||||
@@ -109,24 +109,6 @@ static BOOL RKDoesArrayOfResponseDescriptorsContainEntityMapping(NSArray *respon
|
||||
return NO;
|
||||
}
|
||||
|
||||
static char kRKBaseURLAssociatedObjectKey;
|
||||
|
||||
void RKAssociateBaseURLWithURL(NSURL *baseURL, NSURL *URL)
|
||||
{
|
||||
NSCAssert(baseURL, @"baseURL cannot be nil");
|
||||
NSCAssert(URL, @"URL cannot be nil");
|
||||
objc_setAssociatedObject(URL,
|
||||
&kRKBaseURLAssociatedObjectKey,
|
||||
baseURL,
|
||||
OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
}
|
||||
|
||||
NSURL *RKBaseURLAssociatedWithURL(NSURL *URL)
|
||||
{
|
||||
NSCAssert(URL, @"URL cannot be nil");
|
||||
return objc_getAssociatedObject(URL, &kRKBaseURLAssociatedObjectKey);
|
||||
}
|
||||
|
||||
///////////////////////////////////
|
||||
|
||||
@interface RKObjectManager ()
|
||||
@@ -533,6 +515,70 @@ NSURL *RKBaseURLAssociatedWithURL(NSURL *URL)
|
||||
}
|
||||
}
|
||||
|
||||
- (void)enqueueBatchOfObjectRequestOperationsWithRoute:(RKRoute *)route
|
||||
objects:(NSArray *)objects
|
||||
progress:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progress
|
||||
completion:(void (^)(NSArray *operations))completion {
|
||||
NSMutableArray *operations = [[NSMutableArray alloc] initWithCapacity:objects.count];
|
||||
for (id object in objects) {
|
||||
RKObjectRequestOperation *operation = nil;
|
||||
NSURL *URL = [self.router URLForRoute:route object:object];
|
||||
NSAssert(URL, @"Failed to generate URL for route %@ with object %@", route, object);
|
||||
if ([route isClassRoute]) {
|
||||
operation = [self appropriateObjectRequestOperationWithObject:object method:RKRequestMethodGET path:[URL relativeString] parameters:nil];
|
||||
} else {
|
||||
operation = [self appropriateObjectRequestOperationWithObject:nil method:RKRequestMethodGET path:[URL relativeString] parameters:nil];
|
||||
}
|
||||
[operations addObject:operation];
|
||||
}
|
||||
return [self enqueueBatchOfObjectRequestOperations:operations progress:progress completion:completion];
|
||||
}
|
||||
|
||||
- (void)enqueueBatchOfObjectRequestOperations:(NSArray *)operations
|
||||
progress:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progress
|
||||
completion:(void (^)(NSArray *operations))completion {
|
||||
|
||||
__block dispatch_group_t dispatchGroup = dispatch_group_create();
|
||||
NSBlockOperation *batchedOperation = [NSBlockOperation blockOperationWithBlock:^{
|
||||
dispatch_group_notify(dispatchGroup, dispatch_get_main_queue(), ^{
|
||||
if (completion) {
|
||||
completion(operations);
|
||||
}
|
||||
});
|
||||
}];
|
||||
|
||||
for (RKObjectRequestOperation *operation in operations) {
|
||||
void (^originalCompletionBlock)(void) = [operation.completionBlock copy];
|
||||
[operation setCompletionBlock:^{
|
||||
dispatch_queue_t queue = operation.successCallbackQueue ?: dispatch_get_main_queue();
|
||||
dispatch_group_async(dispatchGroup, queue, ^{
|
||||
if (originalCompletionBlock) {
|
||||
originalCompletionBlock();
|
||||
}
|
||||
|
||||
__block NSUInteger numberOfFinishedOperations = 0;
|
||||
[operations enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
|
||||
if ([(NSOperation *)obj isFinished]) {
|
||||
numberOfFinishedOperations++;
|
||||
}
|
||||
}];
|
||||
|
||||
if (progress) {
|
||||
progress(numberOfFinishedOperations, [operations count]);
|
||||
}
|
||||
|
||||
dispatch_group_leave(dispatchGroup);
|
||||
});
|
||||
}];
|
||||
|
||||
dispatch_group_enter(dispatchGroup);
|
||||
[batchedOperation addDependency:operation];
|
||||
|
||||
[self enqueueObjectRequestOperation:operation];
|
||||
}
|
||||
[self.operationQueue addOperation:batchedOperation];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NSString *RKStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus networkReachabilityStatus)
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
#import "RKHTTPUtilities.h"
|
||||
|
||||
@class RKRouteSet;
|
||||
@class RKRoute;
|
||||
|
||||
void RKAssociateBaseURLWithURL(NSURL *baseURL, NSURL *URL);
|
||||
NSURL *RKBaseURLAssociatedWithURL(NSURL *URL);
|
||||
|
||||
/**
|
||||
An `RKRouter` instance is responsible for generating `NSURL` objects with a given base URL and a route set. It is used to centralize the knowledge about the URL's that are used by the application.
|
||||
@@ -106,6 +110,8 @@
|
||||
*/
|
||||
- (NSURL *)URLForRelationship:(NSString *)relationshipName ofObject:(id)object method:(RKRequestMethod)method;
|
||||
|
||||
- (NSURL *)URLForRoute:(RKRoute *)route object:(id)object;
|
||||
|
||||
///---------------------------------------------
|
||||
/// @name Configuring the Base URL and Route Set
|
||||
///---------------------------------------------
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#import "RKRouteSet.h"
|
||||
#import "RKRoute.h"
|
||||
#import "RKPathMatcher.h"
|
||||
#import <objc/runtime.h>
|
||||
|
||||
|
||||
@interface RKRouter ()
|
||||
@@ -29,6 +30,23 @@
|
||||
@property (nonatomic, strong, readwrite) RKRouteSet *routeSet;
|
||||
@end
|
||||
|
||||
static char kRKBaseURLAssociatedObjectKey;
|
||||
|
||||
void RKAssociateBaseURLWithURL(NSURL *baseURL, NSURL *URL)
|
||||
{
|
||||
NSCAssert(baseURL, @"baseURL cannot be nil");
|
||||
NSCAssert(URL, @"URL cannot be nil");
|
||||
objc_setAssociatedObject(URL,
|
||||
&kRKBaseURLAssociatedObjectKey,
|
||||
baseURL,
|
||||
OBJC_ASSOCIATION_COPY_NONATOMIC);
|
||||
}
|
||||
|
||||
NSURL *RKBaseURLAssociatedWithURL(NSURL *URL)
|
||||
{
|
||||
NSCAssert(URL, @"URL cannot be nil");
|
||||
return objc_getAssociatedObject(URL, &kRKBaseURLAssociatedObjectKey);
|
||||
}
|
||||
|
||||
@implementation RKRouter
|
||||
|
||||
@@ -54,23 +72,32 @@
|
||||
- (NSURL *)URLForRouteNamed:(NSString *)routeName method:(out RKRequestMethod *)method object:(id)object
|
||||
{
|
||||
RKRoute *route = [self.routeSet routeForName:routeName];
|
||||
if (! route) return nil;
|
||||
if (method) *method = route.method;
|
||||
return [NSURL URLWithString:[self pathFromRoute:route forObject:object] relativeToURL:self.baseURL];
|
||||
return [self URLForRoute:route object:object];
|
||||
}
|
||||
|
||||
- (NSURL *)URLForObject:(id)object method:(RKRequestMethod)method
|
||||
{
|
||||
RKRoute *route = [self.routeSet routeForObject:object method:method];
|
||||
if (! route) return nil;
|
||||
return [NSURL URLWithString:[self pathFromRoute:route forObject:object] relativeToURL:self.baseURL];
|
||||
return [self URLForRoute:route object:object];
|
||||
}
|
||||
|
||||
- (NSURL *)URLForRelationship:(NSString *)relationshipName ofObject:(id)object method:(RKRequestMethod)method
|
||||
{
|
||||
RKRoute *route = [self.routeSet routeForRelationship:relationshipName ofClass:[object class] method:method];
|
||||
if (! route) return nil;
|
||||
return [NSURL URLWithString:[self pathFromRoute:route forObject:object] relativeToURL:self.baseURL];
|
||||
return [self URLForRoute:route object:object];
|
||||
}
|
||||
|
||||
- (NSURL *)URLForRoute:(RKRoute *)route object:(id)object
|
||||
{
|
||||
NSParameterAssert(route);
|
||||
NSURL *URL = [NSURL URLWithString:[self pathFromRoute:route forObject:object] relativeToURL:self.baseURL];
|
||||
|
||||
/**
|
||||
Associate our baseURL with the URL of the `NSURLRequest` object. This enables us to match response descriptors by path.
|
||||
*/
|
||||
RKAssociateBaseURLWithURL(self.baseURL, URL);
|
||||
return URL;
|
||||
}
|
||||
|
||||
- (NSString *)pathFromRoute:(RKRoute *)route forObject:(id)object
|
||||
|
||||
@@ -32,6 +32,10 @@ NSURL *RKBaseURLAssociatedWithURL(NSURL *URL);
|
||||
@interface RKObjectManagerTest : RKTestCase
|
||||
|
||||
@property (nonatomic, strong) RKObjectManager *objectManager;
|
||||
@property (nonatomic, strong) RKRoute *humanGETRoute;
|
||||
@property (nonatomic, strong) RKRoute *humanPOSTRoute;
|
||||
@property (nonatomic, strong) RKRoute *humanCatsRoute;
|
||||
@property (nonatomic, strong) RKRoute *humansCollectionRoute;
|
||||
|
||||
@end
|
||||
|
||||
@@ -77,7 +81,16 @@ NSURL *RKBaseURLAssociatedWithURL(NSURL *URL);
|
||||
RKObjectMapping *humanSerialization = [RKObjectMapping mappingForClass:[NSDictionary class]];
|
||||
[humanSerialization addPropertyMapping:[RKAttributeMapping attributeMappingFromKeyPath:@"name" toKeyPath:@"name"]];
|
||||
[self.objectManager addRequestDescriptor:[RKRequestDescriptor requestDescriptorWithMapping:humanSerialization objectClass:[RKHuman class] rootKeyPath:@"human"]];
|
||||
[self.objectManager.router.routeSet addRoute:[RKRoute routeWithClass:[RKHuman class] pathPattern:@"/humans" method:RKRequestMethodPOST]];
|
||||
|
||||
self.humanPOSTRoute = [RKRoute routeWithClass:[RKHuman class] pathPattern:@"/humans" method:RKRequestMethodPOST];
|
||||
self.humanGETRoute = [RKRoute routeWithClass:[RKHuman class] pathPattern:@"/humans/:railsID" method:RKRequestMethodGET];
|
||||
self.humanCatsRoute = [RKRoute routeWithRelationshipName:@"cats" objectClass:[RKHuman class] pathPattern:@"/humans/:railsID/cats" method:RKRequestMethodGET];
|
||||
self.humansCollectionRoute = [RKRoute routeWithName:@"humans" pathPattern:@"/humans" method:RKRequestMethodGET];
|
||||
|
||||
[self.objectManager.router.routeSet addRoute:self.humanPOSTRoute];
|
||||
[self.objectManager.router.routeSet addRoute:self.humanGETRoute];
|
||||
[self.objectManager.router.routeSet addRoute:self.humanCatsRoute];
|
||||
[self.objectManager.router.routeSet addRoute:self.humansCollectionRoute];
|
||||
}
|
||||
|
||||
- (void)tearDown
|
||||
@@ -168,6 +181,60 @@ NSURL *RKBaseURLAssociatedWithURL(NSURL *URL);
|
||||
assertThatBool([operation isCancelled], is(equalToBool(NO)));
|
||||
}
|
||||
|
||||
- (void)testShouldProperlyFireABatchOfOperations
|
||||
{
|
||||
RKHuman *temporaryHuman = [NSEntityDescription insertNewObjectForEntityForName:@"RKHuman" inManagedObjectContext:_objectManager.managedObjectStore.primaryManagedObjectContext];
|
||||
temporaryHuman.name = @"My Name";
|
||||
|
||||
RKManagedObjectRequestOperation *successfulGETOperation = [_objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodGET path:nil parameters:nil];
|
||||
RKManagedObjectRequestOperation *successfulPOSTOperation = [_objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodPOST path:nil parameters:nil];
|
||||
RKManagedObjectRequestOperation *failedPOSTOperation = [_objectManager appropriateObjectRequestOperationWithObject:temporaryHuman method:RKRequestMethodPOST path:@"/humans/fail" parameters:nil];
|
||||
|
||||
__block NSUInteger progressCallbackCount = 0;
|
||||
__block NSUInteger completionBlockOperationCount = 0;
|
||||
[_objectManager enqueueBatchOfObjectRequestOperations:@[successfulGETOperation, successfulPOSTOperation, failedPOSTOperation] progress:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
|
||||
progressCallbackCount++;
|
||||
} completion:^(NSArray *operations) {
|
||||
completionBlockOperationCount = operations.count;
|
||||
}];
|
||||
assertThat(_objectManager.operationQueue, is(notNilValue()));
|
||||
[_objectManager.operationQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
// Spin the run loop to allow completion blocks to fire after operations have completed
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
|
||||
|
||||
assertThatInteger(progressCallbackCount, is(equalToInteger(3)));
|
||||
assertThatInteger(completionBlockOperationCount, is(equalToInteger(3)));
|
||||
}
|
||||
|
||||
- (void)testShouldProperlyFireABatchOfOperationsFromRoute
|
||||
{
|
||||
RKHuman *dan = [NSEntityDescription insertNewObjectForEntityForName:@"RKHuman" inManagedObjectContext:_objectManager.managedObjectStore.primaryManagedObjectContext];
|
||||
dan.name = @"Dan";
|
||||
|
||||
RKHuman *blake = [NSEntityDescription insertNewObjectForEntityForName:@"RKHuman" inManagedObjectContext:_objectManager.managedObjectStore.primaryManagedObjectContext];
|
||||
blake.name = @"Blake";
|
||||
|
||||
RKHuman *jeff = [NSEntityDescription insertNewObjectForEntityForName:@"RKHuman" inManagedObjectContext:_objectManager.managedObjectStore.primaryManagedObjectContext];
|
||||
jeff.name = @"Jeff";
|
||||
|
||||
__block NSUInteger progressCallbackCount = 0;
|
||||
__block NSUInteger completionBlockOperationCount = 0;
|
||||
[_objectManager enqueueBatchOfObjectRequestOperationsWithRoute:self.humanPOSTRoute objects:@[dan, blake, jeff] progress:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
|
||||
progressCallbackCount++;
|
||||
} completion:^(NSArray *operations) {
|
||||
completionBlockOperationCount = operations.count;
|
||||
}];
|
||||
assertThat(_objectManager.operationQueue, is(notNilValue()));
|
||||
[_objectManager.operationQueue waitUntilAllOperationsAreFinished];
|
||||
|
||||
// Spin the run loop to allow completion blocks to fire after operations have completed
|
||||
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
|
||||
|
||||
assertThatInteger(progressCallbackCount, is(equalToInteger(3)));
|
||||
assertThatInteger(completionBlockOperationCount, is(equalToInteger(3)));
|
||||
}
|
||||
|
||||
// TODO: Move to Core Data specific spec file...
|
||||
//- (void)testShouldLoadAHuman
|
||||
//{
|
||||
|
||||
Reference in New Issue
Block a user