【问题标题】:ASIHTTPRequest backed RestKit object mappingASIHTTPRequest 支持的 RestKit 对象映射
【发布时间】:2012-04-17 12:56:04
【问题描述】:

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

我想象使用 ASIHTTPRequest 处理请求,并且有人手动将有效负载转发到 RestKit。

【问题讨论】:

    标签: asihttprequest restkit


    【解决方案1】:

    好的,毕竟这并不太难。这是我为此编写的一个类(没有免责声明,它对我们有用,可能对其他人有用)。您可以将其用作标准 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];
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

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

         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

      【讨论】:

      • 仍在弄清楚 StackOverflow :) 我进行编辑的原因似乎不可见。我将代码更改为使用 RKManagedObjectLoader 并指定对象存储 - 早些时候,我使用的是 RKObjectLoader,并且在我需要实际使用它时无法从对象存储中检索数据(我尝试在我的实际应用程序中做类似的事情而不仅仅是单元测试映射)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-08-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-10-12
      • 2017-12-15
      相关资源
      最近更新 更多