mirror of
https://github.com/zhigang1992/RestKit.git
synced 2026-01-12 22:51:50 +08:00
368 lines
14 KiB
Objective-C
368 lines
14 KiB
Objective-C
//
|
|
// RKObjectRequestOperation.m
|
|
// RestKit
|
|
//
|
|
// Created by Blake Watters on 8/9/12.
|
|
// Copyright (c) 2012 RestKit. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
//
|
|
|
|
#import "RKObjectRequestOperation.h"
|
|
#import "RKResponseMapperOperation.h"
|
|
#import "RKResponseDescriptor.h"
|
|
#import "RKMIMETypeSerialization.h"
|
|
#import "RKHTTPUtilities.h"
|
|
#import "RKLog.h"
|
|
#import "RKMappingErrors.h"
|
|
|
|
#import <Availability.h>
|
|
|
|
#if __IPHONE_OS_VERSION_MIN_REQUIRED
|
|
#import "AFNetworkActivityIndicatorManager.h"
|
|
#endif
|
|
|
|
// Set Logging Component
|
|
#undef RKLogComponent
|
|
#define RKLogComponent RKlcl_cRestKitNetwork
|
|
|
|
NSString * const RKObjectRequestOperationDidStartNotification = @"RKObjectRequestOperationDidStartNotification";
|
|
NSString * const RKObjectRequestOperationDidFinishNotification = @"RKObjectRequestOperationDidFinishNotification";
|
|
NSString * const RKResponseHasBeenMappedCacheUserInfoKey = @"RKResponseHasBeenMapped";
|
|
|
|
static void RKIncrementNetworkActivityIndicator()
|
|
{
|
|
#if __IPHONE_OS_VERSION_MIN_REQUIRED
|
|
[[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount];
|
|
#endif
|
|
}
|
|
|
|
static void RKDecrementNetworkAcitivityIndicator()
|
|
{
|
|
#if __IPHONE_OS_VERSION_MIN_REQUIRED
|
|
[[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
|
|
#endif
|
|
}
|
|
|
|
static inline NSString *RKDescriptionForRequest(NSURLRequest *request)
|
|
{
|
|
return [NSString stringWithFormat:@"%@ '%@'", request.HTTPMethod, [request.URL absoluteString]];
|
|
}
|
|
|
|
static NSIndexSet *RKAcceptableStatusCodesFromResponseDescriptors(NSArray *responseDescriptors)
|
|
{
|
|
// If there are no response descriptors or any descriptor matches any status code (expressed by `statusCodes` == `nil`) then we want to accept anything
|
|
if ([responseDescriptors count] == 0 || [[responseDescriptors valueForKey:@"statusCodes"] containsObject:[NSNull null]]) return nil;
|
|
|
|
NSMutableIndexSet *acceptableStatusCodes = [NSMutableIndexSet indexSet];
|
|
[responseDescriptors enumerateObjectsUsingBlock:^(RKResponseDescriptor *responseDescriptor, NSUInteger idx, BOOL *stop) {
|
|
[acceptableStatusCodes addIndexes:responseDescriptor.statusCodes];
|
|
}];
|
|
return acceptableStatusCodes;
|
|
}
|
|
|
|
static NSString *RKStringForStateOfObjectRequestOperation(RKObjectRequestOperation *operation)
|
|
{
|
|
if ([operation isExecuting]) {
|
|
return @"Executing";
|
|
} else if ([operation isFinished]) {
|
|
if (operation.error) {
|
|
return @"Failed";
|
|
} else {
|
|
return @"Successful";
|
|
}
|
|
} else {
|
|
return @"Ready";
|
|
}
|
|
}
|
|
|
|
static NSString *RKStringDescribingURLResponseWithData(NSURLResponse *response, NSData *data)
|
|
{
|
|
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
|
NSHTTPURLResponse *HTTPResponse = (NSHTTPURLResponse *)response;
|
|
return [NSString stringWithFormat:@"<%@: %p statusCode=%ld MIMEType=%@ length=%ld>", [response class], response, (long) [HTTPResponse statusCode], [HTTPResponse MIMEType], (long) [data length]];
|
|
} else {
|
|
return [response description];
|
|
}
|
|
}
|
|
|
|
@interface RKObjectRequestOperation ()
|
|
@property (nonatomic, strong, readwrite) RKHTTPRequestOperation *HTTPRequestOperation;
|
|
@property (nonatomic, strong, readwrite) NSArray *responseDescriptors;
|
|
@property (nonatomic, strong, readwrite) RKMappingResult *mappingResult;
|
|
@property (nonatomic, strong, readwrite) NSError *error;
|
|
@property (nonatomic, strong) RKObjectResponseMapperOperation *responseMapperOperation;
|
|
@property (nonatomic, copy) id (^willMapDeserializedResponseBlock)(id deserializedResponseBody);
|
|
@end
|
|
|
|
@implementation RKObjectRequestOperation
|
|
|
|
+ (NSOperationQueue *)responseMappingQueue
|
|
{
|
|
static NSOperationQueue *responseMappingQueue = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
responseMappingQueue = [NSOperationQueue new];
|
|
[responseMappingQueue setName:@"RKObjectRequestOperation Response Mapping Queue" ];
|
|
[responseMappingQueue setMaxConcurrentOperationCount:1];
|
|
});
|
|
|
|
return responseMappingQueue;
|
|
}
|
|
|
|
+ (BOOL)canProcessRequest:(NSURLRequest *)request
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
#if !OS_OBJECT_USE_OBJC
|
|
if (_failureCallbackQueue) dispatch_release(_failureCallbackQueue);
|
|
if (_successCallbackQueue) dispatch_release(_successCallbackQueue);
|
|
#endif
|
|
_failureCallbackQueue = NULL;
|
|
_successCallbackQueue = NULL;
|
|
}
|
|
|
|
// Designated initializer
|
|
- (id)initWithHTTPRequestOperation:(RKHTTPRequestOperation *)requestOperation responseDescriptors:(NSArray *)responseDescriptors
|
|
{
|
|
NSParameterAssert(requestOperation);
|
|
NSParameterAssert(responseDescriptors);
|
|
|
|
self = [self init];
|
|
if (self) {
|
|
self.responseDescriptors = responseDescriptors;
|
|
self.HTTPRequestOperation = requestOperation;
|
|
self.HTTPRequestOperation.acceptableContentTypes = [RKMIMETypeSerialization registeredMIMETypes];
|
|
self.HTTPRequestOperation.acceptableStatusCodes = RKAcceptableStatusCodesFromResponseDescriptors(responseDescriptors);
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (id)initWithRequest:(NSURLRequest *)request responseDescriptors:(NSArray *)responseDescriptors
|
|
{
|
|
NSParameterAssert(request);
|
|
NSParameterAssert(responseDescriptors);
|
|
return [self initWithHTTPRequestOperation:[[RKHTTPRequestOperation alloc] initWithRequest:request] responseDescriptors:responseDescriptors];
|
|
}
|
|
|
|
- (void)setSuccessCallbackQueue:(dispatch_queue_t)successCallbackQueue
|
|
{
|
|
if (successCallbackQueue != _successCallbackQueue) {
|
|
if (_successCallbackQueue) {
|
|
#if !OS_OBJECT_USE_OBJC
|
|
dispatch_release(_successCallbackQueue);
|
|
#endif
|
|
_successCallbackQueue = NULL;
|
|
}
|
|
|
|
if (successCallbackQueue) {
|
|
#if !OS_OBJECT_USE_OBJC
|
|
dispatch_retain(successCallbackQueue);
|
|
#endif
|
|
_successCallbackQueue = successCallbackQueue;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)setFailureCallbackQueue:(dispatch_queue_t)failureCallbackQueue
|
|
{
|
|
if (failureCallbackQueue != _failureCallbackQueue) {
|
|
if (_failureCallbackQueue) {
|
|
#if !OS_OBJECT_USE_OBJC
|
|
dispatch_release(_failureCallbackQueue);
|
|
#endif
|
|
_failureCallbackQueue = NULL;
|
|
}
|
|
|
|
if (failureCallbackQueue) {
|
|
#if !OS_OBJECT_USE_OBJC
|
|
dispatch_retain(failureCallbackQueue);
|
|
#endif
|
|
_failureCallbackQueue = failureCallbackQueue;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Adopted fix for "The Deallocation Problem" from AFN
|
|
- (void)setCompletionBlock:(void (^)(void))block
|
|
{
|
|
if (!block) {
|
|
[super setCompletionBlock:nil];
|
|
} else {
|
|
__unsafe_unretained id weakSelf = self;
|
|
[super setCompletionBlock:^ {
|
|
block();
|
|
[weakSelf setCompletionBlock:nil];
|
|
}];
|
|
}
|
|
}
|
|
|
|
- (void)setWillMapDeserializedResponseBlock:(id (^)(id))block
|
|
{
|
|
if (!block) {
|
|
_willMapDeserializedResponseBlock = nil;
|
|
} else {
|
|
__unsafe_unretained id weakSelf = self;
|
|
_willMapDeserializedResponseBlock = ^id (id deserializedResponse) {
|
|
id result = block(deserializedResponse);
|
|
[weakSelf setWillMapDeserializedResponseBlock:nil];
|
|
return result;
|
|
};
|
|
}
|
|
}
|
|
|
|
- (void)setCompletionBlockWithSuccess:(void (^)(RKObjectRequestOperation *operation, RKMappingResult *mappingResult))success
|
|
failure:(void (^)(RKObjectRequestOperation *operation, NSError *error))failure
|
|
{
|
|
// See above setCompletionBlock:
|
|
#pragma clang diagnostic push
|
|
#pragma clang diagnostic ignored "-Warc-retain-cycles"
|
|
self.completionBlock = ^ {
|
|
if ([self isCancelled]) {
|
|
return;
|
|
}
|
|
|
|
if (self.error) {
|
|
if (failure) {
|
|
dispatch_async(self.failureCallbackQueue ? self.failureCallbackQueue : dispatch_get_main_queue(), ^{
|
|
failure(self, self.error);
|
|
});
|
|
}
|
|
} else {
|
|
if (success) {
|
|
dispatch_async(self.successCallbackQueue ? self.successCallbackQueue : dispatch_get_main_queue(), ^{
|
|
success(self, self.mappingResult);
|
|
});
|
|
}
|
|
}
|
|
};
|
|
#pragma clang diagnostic pop
|
|
}
|
|
|
|
- (RKMappingResult *)performMappingOnResponse:(NSError **)error
|
|
{
|
|
// Spin up an RKObjectResponseMapperOperation
|
|
self.responseMapperOperation = [[RKObjectResponseMapperOperation alloc] initWithRequest:self.HTTPRequestOperation.request
|
|
response:self.HTTPRequestOperation.response
|
|
data:self.HTTPRequestOperation.responseData
|
|
responseDescriptors:self.responseDescriptors];
|
|
self.responseMapperOperation.targetObject = self.targetObject;
|
|
self.responseMapperOperation.mappingMetadata = self.mappingMetadata;
|
|
self.responseMapperOperation.mapperDelegate = self;
|
|
[self.responseMapperOperation setQueuePriority:[self queuePriority]];
|
|
[self.responseMapperOperation setWillMapDeserializedResponseBlock:self.willMapDeserializedResponseBlock];
|
|
[[RKObjectRequestOperation responseMappingQueue] addOperation:self.responseMapperOperation];
|
|
[self.responseMapperOperation waitUntilFinished];
|
|
if ([self isCancelled]) return nil;
|
|
if (self.responseMapperOperation.error) {
|
|
if (error) *error = self.responseMapperOperation.error;
|
|
return nil;
|
|
}
|
|
return self.responseMapperOperation.mappingResult;
|
|
}
|
|
|
|
- (void)willFinish
|
|
{
|
|
// Default implementation does nothing
|
|
}
|
|
|
|
- (void)cancel
|
|
{
|
|
[super cancel];
|
|
[self.HTTPRequestOperation cancel];
|
|
[self.responseMapperOperation cancel];
|
|
}
|
|
|
|
- (void)execute
|
|
{
|
|
// Send the request
|
|
[self.HTTPRequestOperation start];
|
|
[self.HTTPRequestOperation waitUntilFinished];
|
|
|
|
if (self.HTTPRequestOperation.error) {
|
|
RKLogError(@"Object request failed: Underlying HTTP request operation failed with error: %@", self.HTTPRequestOperation.error);
|
|
self.error = self.HTTPRequestOperation.error;
|
|
return;
|
|
}
|
|
|
|
if (self.isCancelled) return;
|
|
|
|
// Map the response
|
|
NSError *error = nil;
|
|
RKMappingResult *mappingResult = [self performMappingOnResponse:&error];
|
|
if (self.isCancelled) {
|
|
return;
|
|
}
|
|
|
|
// If there is no mapping result but no error, there was no mapping to be performed,
|
|
// which we do not treat as an error condition
|
|
if (! mappingResult && error && !([self.HTTPRequestOperation.request.HTTPMethod isEqualToString:@"DELETE"] && error.code == RKMappingErrorNotFound)) {
|
|
self.error = error;
|
|
return;
|
|
}
|
|
self.mappingResult = mappingResult;
|
|
[self willFinish];
|
|
|
|
if (self.error) {
|
|
self.mappingResult = nil;
|
|
} else {
|
|
NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.HTTPRequestOperation.request];
|
|
if (cachedResponse) {
|
|
// We're all done mapping this request. Now we set a flag on the cache entry's userInfo dictionary to indicate that the request
|
|
// corresponding to the cache entry completed successfully, and we can reliably skip mapping if a subsequent request results
|
|
// in the use of this cachedResponse.
|
|
NSMutableDictionary *userInfo = cachedResponse.userInfo ? [cachedResponse.userInfo mutableCopy] : [NSMutableDictionary dictionary];
|
|
[userInfo setObject:@YES forKey:RKResponseHasBeenMappedCacheUserInfoKey];
|
|
NSCachedURLResponse *newCachedResponse = [[NSCachedURLResponse alloc] initWithResponse:cachedResponse.response data:cachedResponse.data userInfo:userInfo storagePolicy:cachedResponse.storagePolicy];
|
|
[[NSURLCache sharedURLCache] storeCachedResponse:newCachedResponse forRequest:self.HTTPRequestOperation.request];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)main
|
|
{
|
|
if (self.isCancelled) return;
|
|
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RKObjectRequestOperationDidStartNotification object:self];
|
|
RKIncrementNetworkActivityIndicator();
|
|
[self execute];
|
|
RKDecrementNetworkAcitivityIndicator();
|
|
[[NSNotificationCenter defaultCenter] postNotificationName:RKObjectRequestOperationDidFinishNotification object:self];
|
|
}
|
|
|
|
- (NSString *)description {
|
|
return [NSString stringWithFormat:@"<%@: %p, state: %@, isCancelled=%@, request: %@, response: %@>",
|
|
NSStringFromClass([self class]), self, RKStringForStateOfObjectRequestOperation(self), [self isCancelled] ? @"YES" : @"NO",
|
|
self.HTTPRequestOperation.request, RKStringDescribingURLResponseWithData(self.HTTPRequestOperation.response, self.HTTPRequestOperation.responseData)];
|
|
}
|
|
|
|
#pragma mark - NSCopying
|
|
|
|
- (id)copyWithZone:(NSZone *)zone {
|
|
RKObjectRequestOperation *operation = [(RKObjectRequestOperation *)[[self class] allocWithZone:zone] initWithHTTPRequestOperation:[self.HTTPRequestOperation copyWithZone:zone] responseDescriptors:self.responseDescriptors];
|
|
operation.targetObject = self.targetObject;
|
|
operation.mappingMetadata = self.mappingMetadata;
|
|
operation.successCallbackQueue = self.successCallbackQueue;
|
|
operation.failureCallbackQueue = self.failureCallbackQueue;
|
|
operation.willMapDeserializedResponseBlock = self.willMapDeserializedResponseBlock;
|
|
operation.completionBlock = self.completionBlock;
|
|
|
|
return operation;
|
|
}
|
|
|
|
@end
|