1 概述
AFURLRequestSerialization
主要实现了根据不同情况和参数初始化NSURLRequest
对象的功能。只有AFHTTPSessionManager
有requestSerialization,默认是AFHTTPRequestSerializer
对象。尤其是我们使用MultipartForm
请求的时候,可以使用它帮我们完成繁杂的请求头拼接过程,这个是最值得推荐的。
在阅读源码之前,一定要对multipart/form-data
非常熟悉,不然会有很多地方看不懂。具体可以看AFNetWorking源码之AFHTTPSessionManager关于它的那部分。
2 AFURLRequestSerialization的api分析
AFURLRequestSerialization
包含了四个部分:
- 全局方法:
AFPercentEscapedStringFromString
和AFQueryStringFromParameters
。
- 协议
AFURLRequestSerialization
提供了一个序列化parameters
参数的方法。我们可以把参数转换为查询字符串、HTTP请求体、设置恰当的请求头等。
AFHTTPRequestSerializer
继承自AFURLRequestSerialization
协议。提供了查询字符串/URL格式的参数序列化、默认请求头处理。同时以提供HTTP状态码和返回数据的验证等工作。
_ AFMultipartFormData
协议。主要用于添加multipart/form-data
请求的Content-Disposition: file; filename=#{generated filename}; name=#{name}"
和 Content-Type: #{generated mimeType}
的请求体域。
- 类型
AFJSONRequestSerializer
和AFPropertyListRequestSerializer
。主要针对JSON和Plist类型的序列化优化。
AFPercentEscapedStringFromString
返回一个字符串的百分号编码格式的字符串。因为url只有普通英文字符和数字,特殊字符$-_.+!*’()还有保留字符。所以很多字符都需要编码,非ASCII编码的字符串先转换为ASCII编码,然后再转换为百分号编码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
NSString * AFPercentEscapedStringFromString(NSString *string) { static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;="; NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy]; [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];
static NSUInteger const batchSize = 50; NSUInteger index = 0; NSMutableString *escaped = @"".mutableCopy; while (index < string.length) { NSUInteger length = MIN(string.length - index, batchSize); NSRange range = NSMakeRange(index, length); range = [string rangeOfComposedCharacterSequencesForRange:range]; NSString *substring = [string substringWithRange:range]; NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet]; [escaped appendString:encoded]; index += range.length; } return escaped; }
|
私有类AFQueryStringPair
的主要功能就是把一个key和vaue的键值对转换为百分号编码格式的键值对并且用=链接起来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| @interface AFQueryStringPair : NSObject @property (readwrite, nonatomic, strong) id field; @property (readwrite, nonatomic, strong) id value; - (instancetype)initWithField:(id)field value:(id)value; - (NSString *)URLEncodedStringValue; @end @implementation AFQueryStringPair - (instancetype)initWithField:(id)field value:(id)value { self = [super init]; if (!self) { return nil; } self.field = field; self.value = value; return self; }
- (NSString *)URLEncodedStringValue { if (!self.value || [self.value isEqual:[NSNull null]]) { return AFPercentEscapedStringFromString([self.field description]); } else { return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])]; } } @end
|
方法AFQueryStringPairsFromDictionary
和AFQueryStringPairsFromKeyAndValue
分别把一个字典或者key、value键值对转换为url的query参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
|
NSString * AFQueryStringFromParameters(NSDictionary *parameters) { NSMutableArray *mutablePairs = [NSMutableArray array]; for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { [mutablePairs addObject:[pair URLEncodedStringValue]]; } return [mutablePairs componentsJoinedByString:@"&"]; } NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) { return AFQueryStringPairsFromKeyAndValue(nil, dictionary); }
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) { NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)]; if ([value isKindOfClass:[NSDictionary class]]) { NSDictionary *dictionary = value; for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { id nestedValue = dictionary[nestedKey]; if (nestedValue) { [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; }
|
AFHTTPRequestSerializerObservedKeyPaths
全局方法指定了request请求序列化要观察的属性列表、是一个数组,里面有对蜂窝数据、缓存策略、cookie、管道、网络状态、超时这几个元素。
1 2 3 4 5 6 7 8 9
| 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; }
|
2.1 AFHTTPRequestSerializer
的解析
AFHTTPRequestSerializer
主要实现了大部分request拼接转化功能。比如通用请求头的添加如userAgent
、request属性的KVO观察、手动指定请求头序列化的Block、负责具体的request对象的初始化等。
1 AFHTTPRequestSerializer
的属性和初始化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| @interface AFHTTPRequestSerializer ()
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;
@property (readwrite, nonatomic, strong) dispatch_queue_t requestHeaderModificationQueue; @property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle;
@property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization; @end
- (instancetype)init { self = [super init]; if (!self) { return nil; } self.stringEncoding = NSUTF8StringEncoding; self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary]; self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);
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; 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]]; if (userAgent) {
if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) { NSMutableString *mutableUserAgent = [userAgent mutableCopy]; if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) { userAgent = mutableUserAgent; } } [self setValue:userAgent forHTTPHeaderField:@"User-Agent"]; }
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; }
|
2 AFHTTPRequestSerializer
的各种setter方法
首先通过automaticallyNotifiesObserversForKey
方法来阻止一些属性的KVO机制的触发,然后我们通过重写蜂窝数据、缓存策略、cookie、管道、网络状态、超时的观察。可以用于测试这些属性变化是否崩溃等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) { return NO; } return [super automaticallyNotifiesObserversForKey:key]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(__unused id)object change:(NSDictionary *)change context:(void *)context { if (context == AFHTTPRequestSerializerObserverContext) { if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) { [self.mutableObservedChangedKeyPaths removeObject:keyPath]; } else { [self.mutableObservedChangedKeyPaths addObject:keyPath]; } } }
|
通过重写属性的setter方法来手动触发kvo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| #pragma mark - 手动触发蜂窝数据、缓存策略、cookie、管道、网络状态、超时的观察。可以用于测试这些属性变化是否崩溃等。 - (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess { [self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))]; _allowsCellularAccess = allowsCellularAccess; [self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))]; }
- (void)setCachePolicy:(NSURLRequestCachePolicy)cachePolicy { [self willChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))]; _cachePolicy = cachePolicy; [self didChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))]; }
- (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies { [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))]; _HTTPShouldHandleCookies = HTTPShouldHandleCookies; [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))]; }
- (void)setHTTPShouldUsePipelining:(BOOL)HTTPShouldUsePipelining { [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))]; _HTTPShouldUsePipelining = HTTPShouldUsePipelining; [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))]; }
- (void)setNetworkServiceType:(NSURLRequestNetworkServiceType)networkServiceType { [self willChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))]; _networkServiceType = networkServiceType; [self didChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))]; }
- (void)setTimeoutInterval:(NSTimeInterval)timeoutInterval { [self willChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))]; _timeoutInterval = timeoutInterval; [self didChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))]; }
|
3 AFHTTPRequestSerializer
的各种请求头域处理方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
|
- (NSDictionary *)HTTPRequestHeaders { NSDictionary __block *value; dispatch_sync(self.requestHeaderModificationQueue, ^{ value = [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders]; }); return value; }
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field { dispatch_barrier_async(self.requestHeaderModificationQueue, ^{ [self.mutableHTTPRequestHeaders setValue:value forKey:field]; }); }
- (NSString *)valueForHTTPHeaderField:(NSString *)field { NSString __block *value; dispatch_sync(self.requestHeaderModificationQueue, ^{ value = [self.mutableHTTPRequestHeaders valueForKey:field]; }); return value; }
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username password:(NSString *)password { NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding]; NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]; [self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"]; }
- (void)clearAuthorizationHeader { dispatch_barrier_async(self.requestHeaderModificationQueue, ^{ [self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"]; }); }
|
4 AFHTTPRequestSerializer
的各种创建NSMutableURLRequest
的方法
通过下面这三种方法处理不同类型的request对象的初始化和参数序列化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
|
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(method); NSParameterAssert(URLString); NSURL *url = [NSURL URLWithString:URLString]; 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 { NSParameterAssert(method); NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);
NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
__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]]; } } } 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]); 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; }
|
首先,我们看几个全局方法。下面几个方法用于拼接multipart/form-data
的分隔符和文件的MIMEType
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
static NSString * AFCreateMultipartFormBoundary() { return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()]; }
static NSString * const kAFMultipartFormCRLF = @"\r\n";
static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) { return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF]; }
static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) { return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF]; }
static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) { return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF]; }
static inline NSString * AFContentTypeForPathExtension(NSString *extension) { NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL); NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType); if (!contentType) { return @"application/octet-stream"; } else { return contentType; } }
|
AFStreamingMultipartFormData
负责multipart/form-data
的Body的具体构建。比如boundary的指定、请求体数据的拼接等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
| - (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest stringEncoding:(NSStringEncoding)encoding { self = [super init]; if (!self) { return nil; } self.request = urlRequest; self.stringEncoding = encoding; self.boundary = AFCreateMultipartFormBoundary(); self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding]; return self; }
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name error:(NSError * __autoreleasing *)error { NSParameterAssert(fileURL); NSParameterAssert(name); NSString *fileName = [fileURL lastPathComponent]; NSString *mimeType = AFContentTypeForPathExtension([fileURL pathExtension]);
return [self appendPartWithFileURL:fileURL name:name fileName:fileName mimeType:mimeType error:error]; }
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType error:(NSError * __autoreleasing *)error { NSParameterAssert(fileURL); NSParameterAssert(name); NSParameterAssert(fileName); NSParameterAssert(mimeType);
if (![fileURL isFileURL]) { NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"Expected URL to be a file URL", @"AFNetworking", nil)}; if (error) { *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo]; } return NO; } else if ([fileURL checkResourceIsReachableAndReturnError:error] == NO) { NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"File URL not reachable.", @"AFNetworking", nil)}; if (error) { *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo]; } return NO; } NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error]; if (!fileAttributes) { return NO; } NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; bodyPart.stringEncoding = self.stringEncoding; bodyPart.headers = mutableHeaders; bodyPart.boundary = self.boundary; bodyPart.body = fileURL; bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue]; [self.bodyStream appendHTTPBodyPart:bodyPart];
return YES; }
- (void)appendPartWithInputStream:(NSInputStream *)inputStream name:(NSString *)name fileName:(NSString *)fileName length:(int64_t)length mimeType:(NSString *)mimeType { NSParameterAssert(name); NSParameterAssert(fileName); NSParameterAssert(mimeType); NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; bodyPart.stringEncoding = self.stringEncoding; bodyPart.headers = mutableHeaders; bodyPart.boundary = self.boundary; bodyPart.body = inputStream; bodyPart.bodyContentLength = (unsigned long long)length; [self.bodyStream appendHTTPBodyPart:bodyPart]; }
- (void)appendPartWithFileData:(NSData *)data name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType { NSParameterAssert(name); NSParameterAssert(fileName); NSParameterAssert(mimeType);
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; [self appendPartWithHeaders:mutableHeaders body:data]; }
- (void)appendPartWithFormData:(NSData *)data name:(NSString *)name { NSParameterAssert(name);
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"]; [self appendPartWithHeaders:mutableHeaders body:data]; }
- (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body { NSParameterAssert(body);
AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; bodyPart.stringEncoding = self.stringEncoding; bodyPart.headers = headers; bodyPart.boundary = self.boundary; bodyPart.bodyContentLength = [body length]; bodyPart.body = body;
[self.bodyStream appendHTTPBodyPart:bodyPart]; }
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes delay:(NSTimeInterval)delay { self.bodyStream.numberOfBytesInPacket = numberOfBytes; self.bodyStream.delay = delay; }
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData { if ([self.bodyStream isEmpty]) { return self.request; } [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; }
|
4 AFJSONRequestSerializer
和AFPropertyListRequestSerializer
这两个类继承自AFHTTPRequestSerializer
。他们的基本实现都是继承自父类。但是也根据自身不同情况,做了处理。
对于AFJSONRequestSerializer
。需要把Content-Type
指定为"application/json
。同时HTTPBody
需要使用JSON序列化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(request);
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { return [super requestBySerializingRequest:request withParameters:parameters error:error]; } NSMutableURLRequest *mutableRequest = [request mutableCopy]; [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (![request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }]; if (parameters) { if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; }
if (![NSJSONSerialization isValidJSONObject:parameters]) { if (error) { NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"The `parameters` argument is not valid JSON.", @"AFNetworking", nil)}; *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo]; } return nil; } NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]; if (!jsonData) { return nil; } [mutableRequest setHTTPBody:jsonData]; } return mutableRequest; }
|
对于AFPropertyListRequestSerializer
也是同样的道理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(request);
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { return [super requestBySerializingRequest:request withParameters:parameters error:error]; } NSMutableURLRequest *mutableRequest = [request mutableCopy]; [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (![request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }]; if (parameters) { if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"]; } NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error]; if (!plistData) { return nil; } [mutableRequest setHTTPBody:plistData]; } return mutableRequest; }
|
5 总结
这个类主要实现了对于不同情况的请求的request对象的封装。尤其是对于multipart/form-data
类型的request的封装,简化了我们自己封装过程的痛苦。如果我们要使用multipart/form-data
类型的请求。强烈推荐使用AFHTTPSessionManager
对象的AFHTTPRequestSerialization
来处理参数的序列化过程。下面就是使用AFHTTPRequestSerailization
序列化和自己拼装的不同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| - (IBAction)updatePic:(id)sender { NSDictionary *dic = @{ @"businessType":@"CC_USER_CENTER", @"fileType":@"image", @"file":@"img.jpeg" }; NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"1.png"]); NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:url]]; [request setHTTPMethod:@"POST"]; AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; NSURLSessionDataTask *task = [manager POST:url parameters:dic constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { NSDictionary *bodyDic = @{ @"Content-Disposition":@"form-data;name=\"file\";filename=\"img.jpeg\"", @"Content-Type":@"image/png", }; [formData appendPartWithHeaders:bodyDic body:imageData]; } progress:^(NSProgress * _Nonnull uploadProgress) { NSLog(@"下载进度"); } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { NSLog(@"下载成功:%@",responseObject); } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { NSLog(@"下载失败%@",error); }]; [task resume]; } - (IBAction)multipartformPost3:(id)sender { NSDictionary *dic = @{ @"businessType":@"CC_USER_CENTER", @"fileType":@"image", @"file":@"img.jpeg" }; NSString *boundaryString = @"xxxxx"; NSMutableString *str = [NSMutableString string]; [dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { [str appendFormat:@"--%@\r\n",boundaryString]; [str appendFormat:@"%@name=\"%@\"\r\n\r\n",@"Content-Disposition: form-data;",key]; [str appendFormat:@"%@\r\n",obj]; }]; NSMutableData *requestMutableData=[NSMutableData data]; [str appendFormat:@"--%@\r\n",boundaryString]; [str appendFormat:@"%@:%@",@"Content-Disposition",@"form-data;"]; [str appendFormat:@"%@=\"%@\";",@"name",@"file"]; [str appendFormat:@"%@=\"%@\"\r\n",@"filename",@"img1.jpeg"]; [str appendFormat:@"%@:%@\r\n\r\n",@"Content-Type",@"image/png"]; [requestMutableData appendData:[str dataUsingEncoding:NSUTF8StringEncoding]]; NSData *imageData = UIImagePNGRepresentation([UIImage imageNamed:@"1.png"]); [requestMutableData appendData:imageData]; [requestMutableData appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",boundaryString] dataUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:[NSURL URLWithString:url]]; [request setHTTPMethod:@"POST"]; [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundaryString] forHTTPHeaderField:@"Content-Type"]; request.HTTPBody = requestMutableData; NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]]; NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { NSString *result = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"%@",result); }]; [task resume];
}
|
最后原文地址,demo地址。