ASIHTTPRequest支持RestKit对象映射

时间:2012-04-17 12:56:05

标签: asihttprequest restkit

我们必须支持一些使用ASIHTTPRequest运行的旧代码,但我们需要RestKit提供的对象映射和核心数据支持。有谁知道将这两者“胶合”在一起的任何方式?

我使用ASIHTTPRequest表示请求,有人手动将有效负载转发到RestKit。

2 个答案:

答案 0 :(得分:4)

好的,毕竟这不是太难。这是我为此写的一个课程(没有免责声明,它适用于我们,可能对其他人有用)。您可以将其用作标准RKObjectLoader类的直接替代。

.h文件

#import <RestKit/RestKit.h>
#import "ASIFormDataRequest.h"

@interface ASIHTTPObjectLoader : ASIFormDataRequest <RKObjectMapperDelegate> {
    RKObjectManager* _objectManager;
    RKObjectMapping* _objectMapping;
    RKObjectMappingResult* _result;
    RKObjectMapping* _serializationMapping;
    NSString* _serializationMIMEType;
    NSObject* _sourceObject;
NSObject* _targetObject;
}

@property (nonatomic, retain) RKObjectMapping* objectMapping;
@property (nonatomic, readonly) RKObjectManager* objectManager;
@property (nonatomic, readonly) RKObjectMappingResult* result;
@property (nonatomic, retain) RKObjectMapping* serializationMapping;
@property (nonatomic, retain) NSString* serializationMIMEType;
@property (nonatomic, retain) NSObject* sourceObject;
@property (nonatomic, retain) NSObject* targetObject;

- (void) setDelegate:(id<RKObjectLoaderDelegate>)delegate;
+ (id)loaderWithResourcePath:(NSString*)resourcePath objectManager:   (RKObjectManager*)objectManager delegate:(id<RKObjectLoaderDelegate>)delegate;
- (id)initWithResourcePath:(NSString*)resourcePath objectManager:(RKObjectManager*)objectManager delegate:(id<RKObjectLoaderDelegate>)delegate;             
- (void)handleResponseError;

@end

.m文件

#import "ASIHTTPObjectLoader.h"

@interface ASIFormDataRequest (here)

- (void) reportFailure;
- (void) reportFinished;

@end

@implementation ASIHTTPObjectLoader
@synthesize objectManager = _objectManager;
@synthesize targetObject = _targetObject, objectMapping = _objectMapping;
@synthesize result = _result;
@synthesize serializationMapping = _serializationMapping;
@synthesize serializationMIMEType = _serializationMIMEType;
@synthesize sourceObject = _sourceObject;

- (void) setDelegate:(id<RKObjectLoaderDelegate>)_delegate {
    [super setDelegate: _delegate];
}

+ (id)loaderWithResourcePath:(NSString*)resourcePath objectManager:(RKObjectManager*)objectManager delegate:(id<RKObjectLoaderDelegate>)_delegate {
    return [[[self alloc] initWithResourcePath:resourcePath objectManager:objectManager delegate:_delegate] autorelease];
}

- (id)initWithResourcePath:(NSString*)resourcePath objectManager:(RKObjectManager*)objectManager delegate:(id<RKObjectLoaderDelegate>)_delegate {

    self = [super initWithURL: [objectManager.client URLForResourcePath: resourcePath]];

    if ( self ) {
        self.delegate = _delegate;
        _objectManager = objectManager;
    }

    return self;
}

- (void)dealloc {
    // Weak reference
    _objectManager = nil;

    [_sourceObject release];
    _sourceObject = nil;
    [_targetObject release];
    _targetObject = nil;
    [_objectMapping release];
    _objectMapping = nil;
    [_result release];
    _result = nil;
    [_serializationMIMEType release];
    [_serializationMapping release];

    [super dealloc];
}

- (void) reset {
    [_result release];
    _result = nil;
}

- (void)finalizeLoad:(BOOL)successful error:(NSError*)_error {
    //_isLoading = NO;

    if (successful) {
        //_isLoaded = YES;
        if ([self.delegate respondsToSelector:@selector(objectLoaderDidFinishLoading:)]) {
            [self.delegate performSelectorOnMainThread:@selector(objectLoaderDidFinishLoading:)
                                                                               withObject:self waitUntilDone:YES];            
        }

        [super reportFinished];

        /*
        NSDictionary* userInfo = [NSDictionary dictionaryWithObject:_response 
                                                             forKey:RKRequestDidLoadResponseNotificationUserInfoResponseKey];
        [[NSNotificationCenter defaultCenter] postNotificationName:RKRequestDidLoadResponseNotification 
                                                            object:self 
                                                          userInfo:userInfo];
         */
    } else {
        NSDictionary* _userInfo = [NSDictionary dictionaryWithObject:(_error ? _error : (NSError*)[NSNull null])
                                                             forKey:RKRequestDidFailWithErrorNotificationUserInfoErrorKey];
        [[NSNotificationCenter defaultCenter] postNotificationName:RKRequestDidFailWithErrorNotification
                                                            object:self
                                                          userInfo:_userInfo];
    }
}

// Invoked on the main thread. Inform the delegate.
- (void)informDelegateOfObjectLoadWithResultDictionary:(NSDictionary*)resultDictionary {
    NSAssert([NSThread isMainThread], @"RKObjectLoaderDelegate callbacks must occur on the main thread");

    RKObjectMappingResult* result = [RKObjectMappingResult mappingResultWithDictionary:resultDictionary];

    if ([self.delegate respondsToSelector:@selector(objectLoader:didLoadObjectDictionary:)]) {
        [self.delegate objectLoader: (RKObjectLoader*)self didLoadObjectDictionary:[result asDictionary]];
    }

    if ([self.delegate respondsToSelector:@selector(objectLoader:didLoadObjects:)]) {
        [self.delegate objectLoader: (RKObjectLoader*)self didLoadObjects:[result asCollection]];
    }

    if ([self.delegate respondsToSelector:@selector(objectLoader:didLoadObject:)]) {
        [self.delegate objectLoader: (RKObjectLoader*)self didLoadObject:[result asObject]];
    }

    [self finalizeLoad:YES error:nil];
}

#pragma mark - Subclass Hooks

/**
 Overloaded by ASIHTTPManagedObjectLoader to serialize/deserialize managed objects
 at thread boundaries. 

 @protected
 */
- (void)processMappingResult:(RKObjectMappingResult*)result {
    NSAssert(isSynchronous || ![NSThread isMainThread], @"Mapping result processing should occur on a background thread");
    [self performSelectorOnMainThread:@selector(informDelegateOfObjectLoadWithResultDictionary:) withObject:[result asDictionary] waitUntilDone:YES];
}

#pragma mark - Response Object Mapping

- (RKObjectMappingResult*)mapResponseWithMappingProvider:(RKObjectMappingProvider*)mappingProvider toObject:(id)targetObject error:(NSError**)_error {
    NSString* MIMEType = [[self responseHeaders] objectForKey: @"Content-Type"];
    id<RKParser> parser = [[RKParserRegistry sharedRegistry] parserForMIMEType: MIMEType];
    NSAssert1(parser, @"Cannot perform object load without a parser for MIME Type '%@'", MIMEType);

    // Check that there is actually content in the response body for mapping. It is possible to get back a 200 response
    // with the appropriate MIME Type with no content (such as for a successful PUT or DELETE). Make sure we don't generate an error
    // in these cases
    id bodyAsString = [self responseString];
    if (bodyAsString == nil || [[bodyAsString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] length] == 0) {
        RKLogDebug(@"Mapping attempted on empty response body...");
        if (self.targetObject) {
            return [RKObjectMappingResult mappingResultWithDictionary:[NSDictionary dictionaryWithObject:self.targetObject forKey:@""]];
        }

        return [RKObjectMappingResult mappingResultWithDictionary:[NSDictionary dictionary]];
    }

    id parsedData = [parser objectFromString:bodyAsString error:_error];
    if (parsedData == nil && _error) {
        return nil;
    }

    // Allow the delegate to manipulate the data
    if ([self.delegate respondsToSelector:@selector(objectLoader:willMapData:)]) {
        parsedData = [[parsedData mutableCopy] autorelease];
        [self.delegate objectLoader: (RKObjectLoader*)self willMapData:&parsedData];
    }

    RKObjectMapper* mapper = [RKObjectMapper mapperWithObject:parsedData mappingProvider:mappingProvider];
    mapper.targetObject = targetObject;
    mapper.delegate = self;
    RKObjectMappingResult* result = [mapper performMapping];

    // Log any mapping errors
    if (mapper.errorCount > 0) {
        RKLogError(@"Encountered errors during mapping: %@", [[mapper.errors valueForKey:@"localizedDescription"] componentsJoinedByString:@", "]);
    }

    // The object mapper will return a nil result if mapping failed
    if (nil == result) {
        // TODO: Construct a composite error that wraps up all the other errors. Should probably make it performMapping:&error when we have this?
        if (_error) *_error = [mapper.errors lastObject];
        return nil;
    }

    return result;
}

- (RKObjectMappingResult*)performMapping:(NSError**)_error {
    NSAssert( isSynchronous || ![NSThread isMainThread], @"Mapping should occur on a background thread");

    RKObjectMappingProvider* mappingProvider;
    if (self.objectMapping) {
        NSString* rootKeyPath = self.objectMapping.rootKeyPath ? self.objectMapping.rootKeyPath : @"";
        RKLogDebug(@"Found directly configured object mapping, creating temporary mapping provider for keyPath %@", rootKeyPath);
        mappingProvider = [[RKObjectMappingProvider new] autorelease];        
        [mappingProvider setMapping:self.objectMapping forKeyPath:rootKeyPath];
    } else {
        RKLogDebug(@"No object mapping provider, using mapping provider from parent object manager to perform KVC mapping");
        mappingProvider = self.objectManager.mappingProvider;
    }

    return [self mapResponseWithMappingProvider:mappingProvider toObject:self.targetObject error:_error];
}


- (void)performMappingOnBackgroundThread {
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

    NSError* _error = nil;
    _result = [[self performMapping:&_error] retain];
    NSAssert(_result || _error, @"Expected performMapping to return a mapping result or an error.");
    if (self.result) {
        [self processMappingResult:self.result];
    } else if (_error) {
        [self failWithError: _error];
    }

    [pool drain];
}

- (BOOL)canParseMIMEType:(NSString*)MIMEType {
    if ([[RKParserRegistry sharedRegistry] parserForMIMEType: MIMEType]) {
        return YES;
    }

    RKLogWarning(@"Unable to find parser for MIME Type '%@'", MIMEType);
    return NO;
}

- (BOOL)isResponseMappable {
    if ([self responseStatusCode] == 503) {
        [[NSNotificationCenter defaultCenter] postNotificationName:RKServiceDidBecomeUnavailableNotification object:self];
    }

    NSString* MIMEType = [[self responseHeaders] objectForKey: @"Content-Type"];

    if ( error ) {
        [self.delegate objectLoader: (RKObjectLoader*)self didFailWithError: error];

        [self finalizeLoad:NO error: error];

        return NO;
    } else if ([self responseStatusCode] == 204) {
        // The No Content (204) response will never have a message body or a MIME Type. Invoke the delegate with self
        [self informDelegateOfObjectLoadWithResultDictionary:[NSDictionary dictionaryWithObject:self forKey:@""]];
        return NO;
    } else if (NO == [self canParseMIMEType: MIMEType]) {
        // We can't parse the response, it's unmappable regardless of the status code
        RKLogWarning(@"Encountered unexpected response with status code: %ld (MIME Type: %@)", (long) [self responseStatusCode], MIMEType);
        NSError* _error = [NSError errorWithDomain:RKRestKitErrorDomain code:RKObjectLoaderUnexpectedResponseError userInfo:nil];
        if ([self.delegate respondsToSelector:@selector(objectLoaderDidLoadUnexpectedResponse:)]) {
            [self.delegate objectLoaderDidLoadUnexpectedResponse: (RKObjectLoader*)self];
        } else {            
            [self.delegate objectLoader: (RKObjectLoader*)self didFailWithError: _error];
        }

        // NOTE: We skip didFailLoadWithError: here so that we don't send the delegate
        // conflicting messages around unexpected response and failure with error
        [self finalizeLoad:NO error:_error];

        return NO;
    } else if (([self responseStatusCode] >= 400 && [self responseStatusCode] < 500) ||
               ([self responseStatusCode] >= 500 && [self responseStatusCode] < 600) ) {
        // This is an error and we can map the MIME Type of the response
        [self handleResponseError];
        return NO;
    }

    return YES;
}

- (void)handleResponseError {
    // Since we are mapping what we know to be an error response, we don't want to map the result back onto our
    // target object
    NSError* _error = nil;
    RKObjectMappingResult* result = [self mapResponseWithMappingProvider:self.objectManager.mappingProvider toObject:nil error:&_error];
    if (result) {
        _error = [result asError];
    } else {
        RKLogError(@"Encountered an error while attempting to map server side errors from payload: %@", [_error localizedDescription]);
    }

    [self.delegate objectLoader: (RKObjectLoader*)self didFailWithError:_error];
    [self finalizeLoad:NO error:_error];    
}

#pragma mark - RKRequest & RKRequestDelegate methods
- (void) reportFailure {
    [self.delegate objectLoader: (RKObjectLoader*)self didFailWithError:error];

    [super reportFailure];
}

- (void)reportFinished {
    NSAssert([NSThread isMainThread], @"RKObjectLoaderDelegate callbacks must occur on the main thread");

    if ([self isResponseMappable]) {
        // Determine if we are synchronous here or not.
        if (isSynchronous) {
            NSError* _error = nil;
            _result = [[self performMapping:&_error] retain];
            if (self.result) {
                [self processMappingResult:self.result];
            } else {
                [self performSelectorInBackground:@selector(failWithError:) withObject:_error];
            }

            [super reportFinished];
        } else {
            [self performSelectorInBackground:@selector(performMappingOnBackgroundThread) withObject:nil];
        }
    }
}

答案 1 :(得分:1)

我在单元测试代码中执行以下操作以确保我的对象映射正常工作

   NSDictionary *headers = [NSDictionary dictionaryWithObjectsAndKeys:@"application/json", @"X-RESTKIT-CACHED-MIME-TYPE",
                             @"200", @"X-RESTKIT-CACHED-RESPONSE-CODE",
                             @"application/json; charset=utf-8", @"Content-Type",
                             nil];

    NSURL *url = [[NSURL alloc] initWithString:@""]; //need a url to create a dummy RKRequest
    RKRequest *request = [RKRequest requestWithURL:url];
    [url release];
    //Create a dummy response with the data payload
    RKResponse *response = [[[RKResponse alloc] initWithRequest:request
                                                           body:myData //myData is NSData loaded from my file on disk in this case
                                                        headers:headers] autorelease];
    RKURL *rkURL = [[RKURL alloc] initWithString:@"https://api.twitter.com"];
    RKManagedObjectLoader *loader = [[RKManagedObjectLoader alloc] initWithURL:rkURL 
                                       mappingProvider:self.objectManager.mappingProvider 
                                       objectStore:self.objectManager.objectStore];
    loader.delegate = self;
    loader.objectMapping = self.objectMapping; //I pass the object mapping to use here.
    [loader didFinishLoad:response]; //Given a response and request, Restkit will parse the response and call the usual delegates

您也可以做类似的事情从ASIHTTPRequest获取响应数据并将其传递给RestKit