码迷,mamicode.com
首页 > Web开发 > 详细

AFNetworking 3.0源码阅读 - AFURLRequestSerialization

时间:2018-08-07 16:15:47      阅读:315      评论:0      收藏:0      [点我收藏+]

标签:context   4.4   text   文件中   body   图片   global   nil   nested   

AFURLRequestSerialization模块主要做的两样事情:

1.创建普通NSMUtableURLRequest请求对象
2.创建multipart NSMutableURLRequest请求对象
此外还有比如:
处理查询的URL参数 也就是说这主要实现了请求报文构造器的功能

在AFURLRequestSerialization.h文件中声明了三个类来帮助开发者构造请求报文

1.AFHTTPRequestSerializer

2.AFJSONRequestSerializer

3.AFPropertyListRequestSerializer 其中1为2和3的父类,也就是说1中定义了基本的属性和方法。 

在.h最开始我们会发现这两个方法

技术分享图片

技术分享图片

该方法一般是对查询参数进行处理,至于为什么要对查询参数进行百分号编码处理一般是因为数据安全问题以及中文编码等问题。

技术分享图片

在看这个方法的实现之前我们会发现在.m中定义了这么一个对象

技术分享图片

该对象确定一个查询字符串对field=value

技术分享图片

上面的方法将数据编码后按照格式连接起来

回头接着看AFQueryStringFromParameters方法的实现发现该方法根据这俩个方法实现

技术分享图片

其中AFQueryStringPairsFromKeyAndValue为核心方法返回一组AFQueryStringPair对象

//将一个key-value数组转换成AFQueryStringPair数组
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

    //升序
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    //value为字典
    if ([value isKindOfClass:[NSDictionary class]]) {
        NSDictionary *dictionary = value;
        // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
        for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            id nestedValue = dictionary[nestedKey];
            if (nestedValue) {
                //去到value后重新调用自身以确保最后获得的是AFQueryStringPair对象也就是确保执行到最后的那条else语句
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
            }
        }
    } else if ([value isKindOfClass:[NSArray class]]) {
        NSArray *array = value;
        for (id nestedValue in array) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
        }
    } else if ([value isKindOfClass:[NSSet class]]) {
        NSSet *set = value;
        for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
        }
    } else {
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

    return mutableQueryStringComponents;
}

这里有用到函数迭代的思想

==================== 我是一条分割线 ====================

继续从.h文件阅读

技术分享图片

技术分享图片

这里声明了一个请求的构造器,也就是最上面提到三个请求报文构造器的第一个。他具备以下属性和方法 

技术分享图片 

这里有提到管线化:在HTTP连接中,一般都是一个请求对应一个连接,每次简历tcp连接是需要一定时间的。管线化,允许一次发送一组请求而不必等待响应。但由于目前并不是所有的服务器都支持这项功能,因此这个属性默认是不开启的。管线化使用同一tcp连接完成任务,因此能够大大提交请求的时间。但是响应要和请求的顺序 保持一致才行。使用场景也有,比如说首页要发送很多请求,可以考虑这种技术。但前提是建立连接成功后才可以使用。

技术分享图片

在看以上方法之前我们会看到参数里面有一个AFMultipartFormData,这是一个定义在头文件里面的协议,包含了拼接数据的几个方法

技术分享图片

这里又提到了AFHTTPBodyPart模型,我们在网络请求进行数据传输时一般会将数据放在http的body中,一般body会包含这四个部分:

1.初始边界 2.body头 3.body 4.结束边界  AFHTTPBodyPart就是这样的一个body类,所以我们先来看这个类的声明

技术分享图片 

技术分享图片

- (NSInteger)read:(uint8_t *)buffer

        maxLength:(NSUInteger)length;

这个方法是读取内容到buffer中,这个是NSInputStream中的方法,当我们使用NSInputStream的子类open打开流的时候,就会调用这个方法,还有这个maxLength和buffer有关系,uint8_t在mac 64为上为32768

技术分享图片

将_phaseReadOffset重置为0代表当前阶段数据读取完毕

- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    NSInteger totalNumberOfBytesRead = 0;

    if (_phase == AFEncapsulationBoundaryPhase) {
        NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding];
        totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
    }

    if (_phase == AFHeaderPhase) {
        NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
        totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
    }

    if (_phase == AFBodyPhase) {
        NSInteger numberOfBytesRead = 0;

        numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
        if (numberOfBytesRead == -1) {
            return -1;
        } else {
            totalNumberOfBytesRead += numberOfBytesRead;

            if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) {
                [self transitionToNextPhase];
            }
        }
    }

    if (_phase == AFFinalBoundaryPhase) {
        NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]);
        totalNumberOfBytesRead += [self readData:closingBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
    }

    return totalNumberOfBytesRead;
}

- (NSInteger)readData:(NSData *)data
           intoBuffer:(uint8_t *)buffer
            maxLength:(NSUInteger)length
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    // 比较数据和允许的最大长度 选取比较小的那个
    NSRange range = NSMakeRange((NSUInteger)_phaseReadOffset, MIN([data length] - ((NSUInteger)_phaseReadOffset), length));
    
    // copy data中range的数据到buffer
    [data getBytes:buffer range:range];
#pragma clang diagnostic pop

    _phaseReadOffset += range.length;

    if (((NSUInteger)_phaseReadOffset) >= [data length]) {
        [self transitionToNextPhase];
    }

    return (NSInteger)range.length;
}

以上是将数据读取到buffer的核心方法

==================== 我是一条分割线 ====================

接下来来看一下AFMultipartBodyStream,如果说AFHTTPBodyPart是一个个数据单元,那么AFMultipartBodyStream就是一个AFHTTPBodyPart流经的管道,两者都有Stream但是业务不同。

先来看一下它的定义

技术分享图片

可以看到AFMultipartBodyStream是NSInputStream的子类遵循NSStreamDelegate,作为NSInputStream的子类的原因可以看这里

技术分享图片

可以看到通过调用setHTTPBodyStream方法将其传递给了request,而该方法的入参是一个NSInputStream对象。

来看定义的三个方法

技术分享图片

初始化属性

技术分享图片

设置边界,当有多个AFHTTPBodyPart,仅保留一个头部边界和一个结束边界 

技术分享图片

以下为通过body读取数据的核心代码

- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    if ([self streamStatus] == NSStreamStatusClosed) {
        return 0;
    }

    NSInteger totalNumberOfBytesRead = 0;

    //此处循环开始读取数据
    while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
        //所读取对象不存在或没有可用数据
        if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
            //使用枚举器将下一个bodypart赋值给当前bodypart,如果为nil就break
            if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
                break;
            }
        } else { //存在
            //剩余可读
            NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
            //将数据读取到buffer
            NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
            if (numberOfBytesRead == -1) {
                self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
                break;
            } else {
                totalNumberOfBytesRead += numberOfBytesRead;

                if (self.delay > 0.0f) {
                    [NSThread sleepForTimeInterval:self.delay];
                }
            }
        }
    }

    return totalNumberOfBytesRead;
}

技术分享图片

关闭读取和设置是否有可用数据 

技术分享图片

open方法和close方法,NSInputStream的子类必须重写。

技术分享图片

NSStreamDelegate方法 

技术分享图片

返回总大小

==================== 我是一条分割线 ====================

说完AFHTTPBodyPart和AFMultipartBodyStream,我们终于可以谈起最开始提到的AFMultipartFormData协议了。通过遵循该协议实现数据的上传,AFNetworking中又封装了一个AFStreamingMultipartFormData对象,该对象遵循AFMultipartFormData协议。协议内包含的方法翻到文章上面就能看到。基本上是各类获取和拼接数据的方法,但是这里要拿出来说下下面的这个方法

- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
    if ([self.bodyStream isEmpty]) {
        return self.request;
    }

    // Reset the initial and final boundaries to ensure correct Content-Length
    [self.bodyStream setInitialAndFinalBoundaries];
    //这里达成关联
    [self.request setHTTPBodyStream:self.bodyStream];

    [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
    [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"];

    return self.request;
}

这是将数据和请求关联起来的核心方法,通过[self.request setHTTPBodyStream:self.bodyStream]方法设置好请求的bodystream,猜测之所以能上传数据却没有找到相应的inputStream open是因为当上传任务开启后会在NSURLSession的代理回调中判断请求的bodystream内的有无,存在就会开启open进行之前一直在说的流的操作,这只是猜测可能会在之后的阅读中找到答案。最后设置Content-Type和Content-Length。

==================== 我是一条分割线 ====================

最后终于轮到主角AFHTTPRequestSerializer上场了,上面所有讲述的东西都是为主角服务的。AFHTTPRequestSerializer的一些声明可以在文章上面看到 点这里

我们来.m文件中的部分。

技术分享图片

设置需要监听的属性,要想实现当属性变化时,就调用监听方法,就需要我们手动实现监听方法。这也就说明,如果在平时开发中想要监听一个对象中某个自定义的属性时并人为的去控制,只需要手动实现监听方法就行了。如 

技术分享图片

首要先实现上面这个方法,然后再重写对应属性的set方法,在set方法中相应的调用willChange和didChange,像下面这样

技术分享图片

接着我们来看初始化方法

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    self.stringEncoding = NSUTF8StringEncoding;

    self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];

    // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
    /**
     *  传递可接受的语言,q代表对语言的喜好程度,默认是取出前5个的数据,不足5个,取实际的个数
     */
    NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
    [[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        float q = 1.0f - (idx * 0.1f);
        [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
        *stop = q <= 0.5f;
    }];
    
    // 设置请求头
    [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];

    // 获取信息
    NSString *userAgent = nil;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#if TARGET_OS_IOS
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif TARGET_OS_WATCH
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
    userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
#pragma clang diagnostic pop
    if (userAgent) {
        if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
            NSMutableString *mutableUserAgent = [userAgent mutableCopy];
            
            // 转换字符串的方法 http://nshipster.com/cfstringtransform/
            if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
                userAgent = mutableUserAgent;
            }
        }
        [self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
    }

    
    // HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
    self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];

    // 设置监听
    self.mutableObservedChangedKeyPaths = [NSMutableSet set];
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
        }
    }

    return self;
}

- (void)dealloc {
    
    // 取消监听
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            [self removeObserver:self forKeyPath:keyPath context:AFHTTPRequestSerializerObserverContext];
        }
    }
}

技术分享图片

请求头的一些操作 

技术分享图片

对应属性的设置方法,接下来看核心方法。

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    //method和URLString不能为空
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];
    
    //url不能为空
    NSParameterAssert(url);

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;

    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }

    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

    return mutableRequest;
}
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
                                              URLString:(NSString *)URLString
                                             parameters:(NSDictionary *)parameters
                              constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                                                  error:(NSError *__autoreleasing *)error
{
    //method不能为空
    //method不能为GET何HEAD
    NSParameterAssert(method);
    NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);

    NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
    
    //创建AFStreamingMultipartFormData用来处理传输的数据
    __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];

    if (parameters) {
        //遍历根据类型进行数据转换为二进制设置然后拼接
        for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
            NSData *data = nil;
            if ([pair.value isKindOfClass:[NSData class]]) {
                data = pair.value;
            } else if ([pair.value isEqual:[NSNull null]]) {
                data = [NSData data];
            } else {
                data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
            }

            if (data) {
                [formData appendPartWithFormData:data name:[pair.field description]];
            }
        }
    }

    //在外部block也可以进行数据拼接
    if (block) {
        block(formData);
    }

    return [formData requestByFinalizingMultipartFormData];
}
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                             writingStreamContentsToFile:(NSURL *)fileURL
                                       completionHandler:(void (^)(NSError *error))handler
{
    NSParameterAssert(request.HTTPBodyStream);
    NSParameterAssert([fileURL isFileURL]);

    //这里就是讲NSData写入到另一文件中的操作了
    NSInputStream *inputStream = request.HTTPBodyStream;
    NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
    __block NSError *error = nil;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

        [inputStream open];
        [outputStream open];

        while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
            uint8_t buffer[1024];

            NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
            if (inputStream.streamError || bytesRead < 0) {
                error = inputStream.streamError;
                break;
            }

            NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
            if (outputStream.streamError || bytesWritten < 0) {
                error = outputStream.streamError;
                break;
            }

            if (bytesRead == 0 && bytesWritten == 0) {
                break;
            }
        }

        [outputStream close];
        [inputStream close];

        if (handler) {
            dispatch_async(dispatch_get_main_queue(), ^{
                handler(error);
            });
        }
    });

    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    mutableRequest.HTTPBodyStream = nil;

    return mutableRequest;
}

==================== 我是一条分割线 ====================

 此外还有AFJSONRequestSerializer和AFPropertyListRequestSerializer,将参数已json或者属性列表方式传过去就不多说了,以上。 

AFNetworking 3.0源码阅读 - AFURLRequestSerialization

标签:context   4.4   text   文件中   body   图片   global   nil   nested   

原文地址:https://www.cnblogs.com/kaisi/p/9139969.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!