AFNetWorking(3.0)ソース分析(5)——AFHTTPRequestSerializer&AFHTTPResponseSerializer

19011 ワード

前のいくつかのブログでは,AFURLSessionMangerdおよびそのサブクラスAFHTTPSessionManagerを解析した.AFの主な2つのクラスについて,比較的包括的な理解を得た.AFHTTPSessionManagerの場合、要求が送信されると、AFHTTPRequestSerializerが要求を組み立てるために呼び出される.リクエストが応答し、解析が必要になると、返されたdataを解析するために対応するresponse serializerが呼び出されます.
サーバ応答の解析プロセスは、AFHTTPSessionManagerにおいて、AFHTTPResponseSerializerによって実現される.

AFHTTPResponseSerializer

AFHTTPResponseSerializerはhttp responseの解析クラスとして,実はベースクラスとして存在し,真のデータ解析には有用なコードを提供するのではなく,サブクラスに実現させる.AFHTTPResponseSerializerでは、responseがどの言語タイプ、どのステータスコードを受け入れるかなど、最も基礎的な構成情報のみが提供される.およびサブクラスのいくつかの共通の機能関数validateResponse:
@interface AFHTTPResponseSerializer : NSObject 

- (instancetype)init;

@property (nonatomic, assign) NSStringEncoding stringEncoding DEPRECATED_MSG_ATTRIBUTE("The string encoding is never used. AFHTTPResponseSerializer only validates status codes and content types but does not try to decode the received data in any way.");

/**
 Creates and returns a serializer with default configuration.
 */
+ (instancetype)serializer;

///-----------------------------------------
/// @name Configuring Response Serialization
///-----------------------------------------

@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;


@property (nonatomic, copy, nullable) NSSet  *acceptableContentTypes;


- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
                    data:(nullable NSData *)data
                   error:(NSError * _Nullable __autoreleasing *)error;

@end
AFHTTPResponseSerializerstringEncoding の注釈から分かるように、AFHTTPResponseSerializerはhttp responseが有効であることを指定したstatus codesおよびcontent typesにすぎず、response dataに対して何の解析も行われていないため、stringEncoding AFHTTPResponseSerializerにおいて使用されていないはずである.
The string encoding is never used. AFHTTPResponseSerializer only validates status codes and content types but does not try to decode the received data in any way. AFHTTPResponseSerializerは、現在のhttp responseが設定されたvalidateResponseおよびstatus codesに適合しているかどうかを判定する親メソッドcontent typesを同時に提供し、この方法は、子クラスにおいて汎用的である.
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
                    data:(NSData *)data
                   error:(NSError * __autoreleasing *)error
{
    BOOL responseIsValid = YES;
    NSError *validationError = nil;

    if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
        if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
            !([response MIMEType] == nil && [data length] == 0)) {

            if ([data length] > 0 && [response URL]) {
                NSMutableDictionary *mutableUserInfo = [@{
                                                          NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
                                                          NSURLErrorFailingURLErrorKey:[response URL],
                                                          AFNetworkingOperationFailingURLResponseErrorKey: response,
                                                        } mutableCopy];
                if (data) {
                    mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
                }

                validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
            }

            responseIsValid = NO;
        }

        if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
            NSMutableDictionary *mutableUserInfo = [@{
                                               NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
                                               NSURLErrorFailingURLErrorKey:[response URL],
                                               AFNetworkingOperationFailingURLResponseErrorKey: response,
                                       } mutableCopy];

            if (data) {
                mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
            }

            validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);

            responseIsValid = NO;
        }
    }

    if (error && !responseIsValid) {
        *error = validationError;
    }

    return responseIsValid;
}

実現は比較的簡単で、私たちはもう分析しません.AFHTTPResponseSerializerの声明では、AFHTTPResponseSerializerAFURLResponseSerialization に従っていることがわかります.AFURLResponseSerialization の定義は次のとおりです.
@protocol AFURLResponseSerialization 

/**
 The response object decoded from the data associated with a specified response.

 @param response The response to be processed.
 @param data The response data to be decoded.
 @param error The error that occurred while attempting to decode the response data.

 @return The object decoded from the specified response data.
 */
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

@end


メソッドは1つのみ定義され、responseObjectForResponse:data:error:です.この方法はrequestリクエストが返すdataを真の解析に用いる.その戻り値はid 、すなわち解析後のmodeオブジェクトであり、彼は任意のタイプであってもよいし、解析要求の拡張を可能にする.
ベースクラスAFHTTPResponseSerializerの場合、そのresponseObjectForResponse:data:error:方法の実装は、次のとおりである.
- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];

    return data;
}
dataを直接返し、解析は行われていないことがわかります.したがって,AFHTTPResponseSerializerを継承し,responseObjectForResponse:data:error:メソッドを書き換える必要があり,応答の真の解析が可能になる.
サブクラスをカスタマイズできますが、AFもデフォルトでいくつかのサブクラスの実装を提供しています.
/**
 `AFJSONResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes JSON responses.

 By default, `AFJSONResponseSerializer` accepts the following MIME types, which includes the official standard, `application/json`, as well as other commonly-used types:

 - `application/json`
 - `text/json`
 - `text/javascript`

 In RFC 7159 - Section 8.1, it states that JSON text is required to be encoded in UTF-8, UTF-16, or UTF-32, and the default encoding is UTF-8. NSJSONSerialization provides support for all the encodings listed in the specification, and recommends UTF-8 for efficiency. Using an unsupported encoding will result in serialization error. See the `NSJSONSerialization` documentation for more details.
 */
@interface AFJSONResponseSerializer : AFHTTPResponseSerializer

/**
 `AFXMLParserResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLParser` objects.

 By default, `AFXMLParserResponseSerializer` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types:

 - `application/xml`
 - `text/xml`
 */
@interface AFXMLParserResponseSerializer : AFHTTPResponseSerializer

/**
 `AFXMLDocumentResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLDocument` objects.

 By default, `AFXMLDocumentResponseSerializer` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types:

 - `application/xml`
 - `text/xml`
 */
@interface AFXMLDocumentResponseSerializer : AFHTTPResponseSerializer

/**
 `AFPropertyListResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLDocument` objects.

 By default, `AFPropertyListResponseSerializer` accepts the following MIME types:

 - `application/x-plist`
 */
@interface AFPropertyListResponseSerializer : AFHTTPResponseSerializer

/**
 `AFImageResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes image responses.

 By default, `AFImageResponseSerializer` accepts the following MIME types, which correspond to the image formats supported by UIImage or NSImage:

 - `image/tiff`
 - `image/jpeg`
 - `image/gif`
 - `image/png`
 - `image/ico`
 - `image/x-icon`
 - `image/bmp`
 - `image/x-bmp`
 - `image/x-xbitmap`
 - `image/x-win-bitmap`
 */
@interface AFImageResponseSerializer : AFHTTPResponseSerializer


/**
 `AFCompoundSerializer` is a subclass of `AFHTTPResponseSerializer` that delegates the response serialization to the first `AFHTTPResponseSerializer` object that returns an object for `responseObjectForResponse:data:error:`, falling back on the default behavior of `AFHTTPResponseSerializer`. This is useful for supporting multiple potential types and structures of server responses with a single serializer.
 */
@interface AFCompoundResponseSerializer : AFHTTPResponseSerializer


注意してください.最後のAFCompoundResponseSerializerは、実際にはいくつかのResponseSerializerを追加することができます.これは、response typeが未知である場合に、対応する解析器があるか否かを組合せ式ResponseSerializerで試みることを防止するためである.

AFJSONResponseSerializer


response解析器の実装では,各サブクラスに異なる実装がある.共通点は
  • はいずれもAFHTTPResponseSerializer
  • から継承されている.
  • は、responseObjectForResponse:data:error:メソッド
  • を書き換える.AFJSONResponseSerializerを単独で持ち出して、responseObjectForResponse:data:error:の方法をどのように書き換えるかを見てみましょう.
    - (id)responseObjectForResponse:(NSURLResponse *)response
                               data:(NSData *)data
                              error:(NSError *__autoreleasing *)error
    {
        if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
            if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
                return nil;
            }
        }
    
        // Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
        // See https://github.com/rails/rails/issues/1742
        BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
        
        if (data.length == 0 || isSpace) {
            return nil;
        }
        
        NSError *serializationError = nil;
        
        id responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
    
        if (!responseObject)
        {
            if (error) {
                *error = AFErrorWithUnderlyingError(serializationError, *error);
            }
            return nil;
        }
        
        if (self.removesKeysWithNullValues) {
            return AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
        }
    
        return responseObject;
    }
    

    その核心はNSJSONSerializationのメソッドを呼び出し、JSON形式の解析を行うことである.
    上のコードはやはり分かりやすいです.では、AFはいつResponse Serializerを呼び出すかの解析方法です
    - (id)responseObjectForResponse:(NSURLResponse *)response
                               data:(NSData *)data
                              error:(NSError *__autoreleasing *)error
    

    のは?もちろん、サーバがresponseを返すときです.
    ベースクラスAFURLSessionManagerでは、Session taskの応答ごとにAFURLSessionManagerが割り当てられていることを覚えています.ここで、システムAFURLSessionManagerTaskDelegateに対する処理が含まれる.NSURLSessionTaskDelegate
    
    - (void)URLSession:(__unused NSURLSession *)session
                  task:(NSURLSessionTask *)task
    didCompleteWithError:(NSError *)error
    

    応答の中には、NSURLSessionTaskDelegateというセグメントがあります.
    else {
            dispatch_async(url_session_manager_processing_queue(), ^{
                NSError *serializationError = nil;
                responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
    	。。。
    }
    
    

    ここでmanagerがAFURLSessionManagerTaskDelegateを呼び出して応答を解析しているのがわかります.ここでのresponseSerializerは、もちろん特定のクラスではなく、responseSerializerのidタイプに合致しています.
    id  responseSerializer;
    

    AFは、このようなプロトコルの抽象化によって、解析対象を特定のアプリケーションにおいて任意に置換することができ、例えば、AFURLResponseSerialization に適合しているため、上述したAFJSONResponseSerializerに置換することができる.

    AFHTTPRequestSerializer


    AFではrequestのシーケンス化についても抽象プロトコル方式が用いられる.
    
    @protocol AFURLRequestSerialization 
    
    /**
     Returns a request with the specified parameters encoded into a copy of the original request.
    
     @param request The original request.
     @param parameters The parameters to be encoded.
     @param error The error that occurred while attempting to encode the request parameters.
    
     @return A serialized request.
     */
    - (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(nullable id)parameters
                                            error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
    
    @end
    

    前章では,AFURLResponseSerialization などのmethodをどのようにサポートするかを解析した.
    ここでは、残りの実装を分析するのではなく、AFHTTPRequestSerializerで使用されている小さなテクニックに注目します.
    AFのGET/PUT/HEAD/POST/DELETEでは、AFHTTPRequestSerializerの場合、URLやhttp bodyの設定に加えて、要求タイムアウトtimeoutIntervalの設定、cacheポリシーの設定など、ユーザに設定させる制御属性が多数存在するという問題を解決する必要がある.
    これに対応して、AFHTTPRequestSerializerには、ユーザ設定NSMutableURLRequestを実行するために、これらの対応する属性が自然に追加される.
    @interface AFHTTPRequestSerializer : NSObject 
    
    @property (nonatomic, assign) BOOL allowsCellularAccess;
    
    @property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
    
    @property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
    
    @property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
    
    @property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
    
    @property (nonatomic, assign) NSTimeInterval timeoutInterval;
    

    では、AFHTTPRequestSerializerNSMutableURLRequestを組み立てるとき、ユーザーがこれらの属性を設定し、AFHTTPRequestSerializerに設定したことをどのように知っていますか.
    一般的に、このプロパティごとにシステムのデフォルト値と等しい初期値を設定し、requestを組み立てるときにNSMutableURLRequestにすべての値を割り当てますか?次のようにします.
    //  
    _allowsCellularAccess = YES;
    _cachePolicy = NSURLRequestUseProtocolCachePolicy;
    ...
    
    //  request;
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
    request.allowsCellularAccess = self.allowsCellularAccess;
    request.cachePolicy = self.cachePolicy;
    ...
    

    上のようなやり方も間違いありませんが、少し挫折しているのではないでしょうか.多くの属性が割り当てられていますが、ユーザーは1つまたは複数の属性を変更しただけで変更していない可能性があります.
    良い方法は、ユーザーがそれらの属性を変更したことを知っておく必要があります.そして、ユーザーが変更した属性だけを設定します.AFも確かにそうしていて、しかも比較的巧みな方法を使っています.
    まず、NSMutableURLRequestには、KVOリスニングが登録されている.
    
        self.mutableObservedChangedKeyPaths = [NSMutableSet set];
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { //  KVO , 
                [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext]; // AFHTTPRequestSerializerObserverContext  context?
            }
        }
    
    NSMutableURLRequestが傍受するKey配列は、次のように定義される.
    static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
        static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            _AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
        });
    
        return _AFHTTPRequestSerializerObservedKeyPaths;
    }
    

    対応する応答属性のSetメソッドを書き換え、KVOをトリガする.
    - (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
        [self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
        _allowsCellularAccess = allowsCellularAccess;
        [self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
    }
    

    KVOの応答関数を見てみましょう.
    - (void)observeValueForKeyPath:(NSString *)keyPath
                          ofObject:(__unused id)object
                            change:(NSDictionary *)change
                           context:(void *)context
    {
        if (context == AFHTTPRequestSerializerObserverContext) {
            //  , , request , 
            if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
                [self.mutableObservedChangedKeyPaths removeObject:keyPath];
            } else {
                [self.mutableObservedChangedKeyPaths addObject:keyPath];
            }
        }
    }
    

    対応する属性を変更すると、KVOは変更されたkeyPathを通知し、AFにはAFHTTPRequestSerializerというsetで変更された属性の名前が記録されることがわかる.
    最後にrequestを構成する場合は、このkeyPathセットに基づいてKVCを使用して対応するプロパティを取り出し、値を割り当てるだけです.
        // 3. mutableRequest  。 KVO 
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
                [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
            }
        }
    

    これはAFにおけるKVOとKVCに関する良い応用シーンである.

    まとめ


    これが私たちがAFHTTPRequestSerializerself.mutableObservedChangedKeyPathsを分析したすべての内容であり、この部分のコードについては、特に複雑ではなく、心を静めて呼び出し経路を分析すれば基本的に理解できる.AFにおけるKVCとKVOの柔軟な応用についても,我々の参考になるところである.