AFNetWorking(3.0)ソース分析(5)——AFHTTPRequestSerializer&AFHTTPResponseSerializer
19011 ワード
前のいくつかのブログでは,
サーバ応答の解析プロセスは、
AFHTTPResponseSerializer
はいずれもAFHTTPResponseSerializer から継承されている.は、 を書き換える.
その核心は
上のコードはやはり分かりやすいです.では、AFはいつResponse Serializerを呼び出すかの解析方法です
のは?もちろん、サーバがresponseを返すときです.
ベースクラス
応答の中には、
ここでmanagerが
AFは、このようなプロトコルの抽象化によって、解析対象を特定のアプリケーションにおいて任意に置換することができ、例えば、
AFHTTPRequestSerializer
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
AFHTTPResponseSerializer
のstringEncoding
の注釈から分かるように、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
の声明では、AFHTTPResponseSerializer
がAFURLResponseSerialization
に従っていることがわかります.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解析器の実装では,各サブクラスに異なる実装がある.共通点は
@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
- (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;
}
@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
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
/**
`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
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;
では、AFHTTPRequestSerializer
がNSMutableURLRequest
を組み立てるとき、ユーザーがこれらの属性を設定し、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に関する良い応用シーンである.
まとめ
これが私たちがAFHTTPRequestSerializer
とself.mutableObservedChangedKeyPaths
を分析したすべての内容であり、この部分のコードについては、特に複雑ではなく、心を静めて呼び出し経路を分析すれば基本的に理解できる.AFにおけるKVCとKVOの柔軟な応用についても,我々の参考になるところである.
@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
@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;
//
_allowsCellularAccess = YES;
_cachePolicy = NSURLRequestUseProtocolCachePolicy;
...
// request;
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
request.allowsCellularAccess = self.allowsCellularAccess;
request.cachePolicy = self.cachePolicy;
...
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?
}
}
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;
}
- (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
[self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
_allowsCellularAccess = allowsCellularAccess;
[self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
}
- (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];
}
}
}
// 3. mutableRequest 。 KVO
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
これが私たちが
AFHTTPRequestSerializer
とself.mutableObservedChangedKeyPaths
を分析したすべての内容であり、この部分のコードについては、特に複雑ではなく、心を静めて呼び出し経路を分析すれば基本的に理解できる.AFにおけるKVCとKVOの柔軟な応用についても,我々の参考になるところである.