ASIHTTPRequest を使用して実行される古いコードをサポートする必要がありますが、RestKit によって提供されるオブジェクト マッピングとコア データのサポートが必要です。これら2つを「接着」する方法を知っている人はいますか?
リクエストに ASIHTTPRequest を使用し、ペイロードを手動で RestKit に転送する人を想像します。
ASIHTTPRequest を使用して実行される古いコードをサポートする必要がありますが、RestKit によって提供されるオブジェクト マッピングとコア データのサポートが必要です。これら2つを「接着」する方法を知っている人はいますか?
リクエストに ASIHTTPRequest を使用し、ペイロードを手動で RestKit に転送する人を想像します。
わかりましたので、結局、これはそれほど難しくありませんでした。これは私がこのためだけに書いたクラスです (免責事項はありません。これは、標準の 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];
}
}
}
オブジェクト マッピングが機能していることを確認するために、単体テスト コードで次のことを行います。
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 に渡すために、同様のことができる場合があります。