zoukankan      html  css  js  c++  java
  • AFNetworking 3.0源码阅读

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

    1.创建普通NSMUtableURLRequest请求对象。
    2.创建表单类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。这里设置的bodystream什么时候会用到呢。其实使用表单上传是使用apple的uploadTaskWithStreamedRequest,而文档中指出该方法会使request中的body stream和body data被忽略。需要上传的body data需要在代理方法 URLSession:task:needNewBodyStream:提供。所以AFN在该代理方法的实现内进行request设置好的HTTPBodyStream属性中取出并拷贝。

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

    最后终于轮到主角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;
    }
  • 相关阅读:
    一步步介绍如何给项目添加单元测试
    日期格式化在移动端的问题
    使用VW时,图片的问题
    转:vw适配中使用伪类选择器遇到的问题
    ES6模块的import和export用法总结
    转:如何在Vue项目中使用vw实现移动端适配
    PhpStrom添加调试功能
    小程序商城笔记
    使用TortoiseGit对android studio工程进行代码版本控制
    Android Studio 常见异常解决办法
  • 原文地址:https://www.cnblogs.com/kaisi/p/9139969.html
Copyright © 2011-2022 走看看