ASIHTTPRequest backed RestKit object mapping

不问归期 提交于 2019-12-30 11:10:54

问题


We must support some old code that runs using ASIHTTPRequest, but we want the object mapping and core data support provided by RestKit. Does anyone know of any way of "gluing" these two together?

I picture using ASIHTTPRequest for the requests and someone manually forwarding the payload over to RestKit.


回答1:


Ok, so this wasn't too hard after all. Here is a class I wrote just for this (no disclaimers, it works for us and may be useful for someone else). You can use this as a direct replacement to the standard RKObjectLoader class.

.h file

#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 file

#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];
        }
    }
}



回答2:


I do the following in my unit test code to make sure my object mappings are working

   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

You might be able to do somthing similar as well to grab the response data from ASIHTTPRequest and pass it on to RestKit



来源:https://stackoverflow.com/questions/10191714/asihttprequest-backed-restkit-object-mapping

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!