前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AFNetworking源码探究(十一) —— 数据解析之子类中协议方法的实现

AFNetworking源码探究(十一) —— 数据解析之子类中协议方法的实现

原创
作者头像
conanma
修改2021-09-03 17:58:17
1.2K0
修改2021-09-03 17:58:17
举报
文章被收录于专栏:正则正则

回顾

上一篇我们主要介绍了有关数据解析类和协议,以及实现解析的架构,这一篇就分开讲述各个类是如何实现对应的数据解析的。


AFURLResponseSerialization协议

我们先看一下这个协议的接口

代码语言:javascript
复制
/**
 The `AFURLResponseSerialization` protocol is adopted by an object that decodes data into a more useful object representation, according to details in the server response. Response serializers may additionally perform validation on the incoming response and data.

 For example, a JSON response serializer may check for an acceptable status code (`2XX` range) and content type (`application/json`), decoding a valid JSON response into an object.
 */
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>

/**
 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

根据服务器响应中的细节,AFURLResponseSerialization协议被一个对象采用,该对象将数据解码为更有用的对象表示。 Response序列化器还可以对传入响应和数据执行验证。例如,JSON响应序列化器可以检查可接受的状态码(2XX范围)和内容类型(application / json),将有效的JSON响应解码成对象


AFHTTPResponseSerializer

这个是所有其他解析类的父类,他遵守上面的AFURLResponseSerialization协议。

我们看一下协议在这个类中的实现

代码语言:javascript
复制
- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];

    return data;
}

这里调用了一个方法,进行了指定response和数据的验证。

代码语言:javascript
复制
/**
 Validates the specified response and data.

 In its base implementation, this method checks for an acceptable status code and content type. Subclasses may wish to add other domain-specific checks.

 @param response The response to be validated.
 @param data The data associated with the response.
 @param error The error that occurred while attempting to validate the response.

 @return `YES` if the response is valid, otherwise `NO`.
 */
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
                    data:(nullable NSData *)data
                   error:(NSError * _Nullable __autoreleasing *)error;

在其基本实现中,此方法检查可接受的状态码和内容类型。 子类可能希望添加其他域特定的检查。

下面我们看一下验证过程,主要对应下面这段代码

代码语言:javascript
复制
- (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;
}

这是一个具有返回值类型为BOOL的方法,但是这里对于返回值并没有使用。

(a) 最外层的判断

最外层的判断主要是

代码语言:javascript
复制
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) 

就是如果response不是nil,并且response的类型是NSHTTPURLResponse

(b) 第一个if判断

在上面最外层判断的内部是两个if判断,根据不同的条件判断数据是否有效以及在无效时应该抛出怎样的异常。

主要对应下面这段代码

代码语言:javascript
复制
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;
}

responseIsValid = NO,我们可以看出来,这一定是抛出异常,没有验证通过的,但是为什么抛出异常呢?我们看一下。

如果有接受数据类型,如果不匹配response,而且响应类型不为空,数据长度不为0。接着进行判断,如果数据长度大于0,而且有响应URL,那么就生成mutableUserInfo信息,调用下面的方法生成错误信息。

代码语言:javascript
复制
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
    if (!error) {
        return underlyingError;
    }

    if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
        return error;
    }

    NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
    mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;

    return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}

这里要注意,NSURLResponse中这个MIMEType属性。

代码语言:javascript
复制
/*! 
    @abstract Returns the MIME type of the receiver.
    @discussion The MIME type is based on the information provided
    from an origin source. However, that value may be changed or
    corrected by a protocol implementation if it can be determined
    that the origin server or source reported the information
    incorrectly or imprecisely. An attempt to guess the MIME type may
    be made if the origin source did not report any such information.
    @result The MIME type of the receiver.

     @abstract返回接收者的MIME类型。
     @讨论MIME类型基于提供的信息
     来源。 但是,该值可能会改变或
     如果可以确定原始服务器或来源报告了信息
     不正确或不准确,则由协议实施纠正
     。如果原始资料来源未报告任何此类信息, 
     可以尝试猜测MIME类型
     @result接收者的MIME类型。
*/
@property (nullable, readonly, copy) NSString *MIMEType;

(c) 第二个if判断

主要对应下边这段代码

代码语言:javascript
复制
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;
}

判断自己可接受的状态码,如果和response的状态码不匹配,则进入if块,生成错误和标识。

(d) error和responseIsValid判断

主要是下面一段代码

代码语言:javascript
复制
if (error && !responseIsValid) {
    *error = validationError;
}

这里,如果error不为空,并且responseIsValid == NO,也就是说上面两个if判断至少走过了一个,这时候给error进行了赋值。

代码语言:javascript
复制
*error = validationError;

这个方法就是来判断返回数据与咱们使用的解析器是否匹配,需要解析的状态码是否匹配。

两个属性值,一个acceptableContentTypes,一个acceptableStatusCodes,两者在初始化的时候有给默认值,如果给acceptableContentTypes定义了不匹配的类型,那么数据仍旧会解析错误。


AFJSONResponseSerializer

AFJSONResponseSerializerAFHTTPResponseSerializer的一个子类,用于验证和解码JSON响应。

默认情况下,AFJSONResponseSerializer接受以下MIME类型,其中包括官方标准,application / json以及其他常用类型:

  • application / json
  • text / json
  • text / javascript

我们看一下协议在这个类中的实现

代码语言:javascript
复制
- (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;
        }
    }

    id responseObject = nil;
    NSError *serializationError = 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) {
        responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
    } else {
        return nil;
    }

    if (self.removesKeysWithNullValues && responseObject) {
        responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
    }

    if (error) {
        *error = AFErrorWithUnderlyingError(serializationError, *error);
    }

    return responseObject;
}

下面就看一下,这里都做了a什么

(a) 有效性的验证

我们看一下如何进行有效性的验证的。

代码语言:javascript
复制
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
    if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
        return nil;
    }
}

就是调用我们上面解析的,验证有效性的方法。如果无效,进入判断,接着if判断,如果error为空,或者有错误,去函数里判断。

代码语言:javascript
复制
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
    if ([error.domain isEqualToString:domain] && error.code == code) {
        return YES;
    } else if (error.userInfo[NSUnderlyingErrorKey]) {
        return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
    }

    return NO;
}

数据无效后,返回nil。

(b) 几个条件判断

下面就是几个条件判断,满足的话直接序列化对应的JSON数据,不满足的话返回nil。

代码语言:javascript
复制
id responseObject = nil;
NSError *serializationError = 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) {
    responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
} else {
    return nil;
}

if (self.removesKeysWithNullValues && responseObject) {
    responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}

if (error) {
    *error = AFErrorWithUnderlyingError(serializationError, *error);
}
  • 第一组条件判断
代码语言:javascript
复制
BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
if (data.length > 0 && !isSpace) {
    responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
} else {
    return nil;
}

这里首先判断数据是否为空,利用isEqualToData:方法进行判断,如果不为空,并且数据长度大于0,那么就进行JSON数据的序列化。

代码语言:javascript
复制
responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];

如果不满足上面条件就返nil。

  • 第二组条件判断
代码语言:javascript
复制
if (self.removesKeysWithNullValues && responseObject) {
    responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}

这里有一个属性

代码语言:javascript
复制
/**
 Whether to remove keys with `NSNull` values from response JSON. Defaults to `NO`.
 */
@property (nonatomic, assign) BOOL removesKeysWithNullValues;

是否从响应JSON中删除具有NSNull值的键。 默认为NO。如果需要移除这个键并且上面的responseObject已经序列化成功,那么就要调用下面的函数移除具有NSNull值的键。

代码语言:javascript
复制
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
    if ([JSONObject isKindOfClass:[NSArray class]]) {
        NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
        for (id value in (NSArray *)JSONObject) {
            [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
        }

        return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
    } else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
        NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
        for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
            id value = (NSDictionary *)JSONObject[key];
            if (!value || [value isEqual:[NSNull null]]) {
                [mutableDictionary removeObjectForKey:key];
            } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
                mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
            }
        }

        return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
    }

    return JSONObject;
}

这里有一个属性和枚举,一起来看一下

代码语言:javascript
复制
/**
 Options for reading the response JSON data and creating the Foundation objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONReadingOptions". `0` by default.
 */
用于读取响应JSON数据并创建Foundation对象的选项。 有关可能的值,请参阅“NSJSONSerialization”文档部分“NSJSONReadingOptions”。 默认为'0'

@property (nonatomic, assign) NSJSONReadingOptions readingOptions;

typedef NS_OPTIONS(NSUInteger, NSJSONReadingOptions) {
    NSJSONReadingMutableContainers = (1UL << 0),
    NSJSONReadingMutableLeaves = (1UL << 1),
    NSJSONReadingAllowFragments = (1UL << 2)
} API_AVAILABLE(macos(10.7), ios(5.0), watchos(2.0), tvos(9.0));
  • 第三组条件判断
代码语言:javascript
复制
if (error) {
    *error = AFErrorWithUnderlyingError(serializationError, *error);
}

如果error不为空,那么就利用函数AFErrorWithUnderlyingError生成NSError对象并赋值。

后记

本篇讲述了一个AFURLResponseSerialization协议以及AFHTTPResponseSerializerAFJSONResponseSerializer类中父类那个协议方法的实现。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 回顾
  • AFURLResponseSerialization协议
  • AFHTTPResponseSerializer
  • AFJSONResponseSerializer
  • 后记
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档