白水

iOS 框架详解—「Serialization 序列化」

引导


AFNetWorking 基本是 iOS 开发中使用网络通信框架的标配,这个框架本身比较庞大,也很复杂,但是使用起来非常非常简单。

SourceCodeToolsClassPublic-Codeidea

本篇文章主要从【Serialization 序列化】学习总结,该模块学习将续更 ~
在「时间 & 知识 」有限内,总结的文章难免有「未全、不足 」的地方,还望各位好友指出,可留言指正或是补充,以提高文章质量@CoderLN著;

Source Code【源码学习】


介绍:【Serialization 序列化】
简单点理解说,就是将对象转换为二进制流,以便存储或传输,且日后也能从二进制流转换回对象。 所以我们今天要说的请求序列化,说到底就是一个可以在网络上传递(被序列化)的、合法可用的请求。

AFHTTPRequestSerializer.h 继承自 NSObject ,遵守了AFURLRequestSerialization 协议。

首先看 .h 文件中定义的属性:

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
/**
The string encoding used to serialize parameters. `NSUTF8StringEncoding` by default.
*/
// 编码类型,字符串编码用于序列化的参数,默认值是NSUTF8StringEncoding
@property (nonatomic, assign) NSStringEncoding stringEncoding;

/**
Whether created requests can use the device’s cellular radio (if present). `YES` by default.

@see NSMutableURLRequest -setAllowsCellularAccess:
*/
// 是否允许使用蜂窝网络,默认YES(set 方法是 setAllowsCellularAccess)
@property (nonatomic, assign) BOOL allowsCellularAccess;

/**
The cache policy of created requests. `NSURLRequestUseProtocolCachePolicy` by default.

@see NSMutableURLRequest -setCachePolicy:
*/
// 缓存策略,创建请求的缓存策略,默认是 NSURLRequestUseProtocolCachePolicy
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;

/**
Whether created requests should use the default cookie handling. `YES` by default.

@see NSMutableURLRequest -setHTTPShouldHandleCookies:
*/
// 是否创建的请求应该使用默认的cookie处理,默认是YES
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;

/**
Whether created requests can continue transmitting data before receiving a response from an earlier transmission. `NO` by default

@see NSMutableURLRequest -setHTTPShouldUsePipelining:
*/
// 是否创建的请求在接收一个来自一个更近的传输源的响应之前可以继续传输数据,默认是不可以NO
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;

/**
The network service type for created requests. `NSURLNetworkServiceTypeDefault` by default.

@see NSMutableURLRequest -setNetworkServiceType:
*/
// 创建的请求的网络服务类型,默认是 NSURLNetworkServiceTypeDefault
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;

/**
The timeout interval, in seconds, for created requests. The default timeout interval is 60 seconds.

@see NSMutableURLRequest -setTimeoutInterval:
*/
// 超时时间间隔,以秒为创建请求。默认超时时间间隔是60秒。
@property (nonatomic, assign) NSTimeInterval timeoutInterval;

具体什么意思,都已经注释在上面了,一目了然 我们接着往下学习。

@name Configuring HTTP Request Headers【配置HTTP请求头】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 序列化 HTTP的请求头,包括`Accept-Language` 和 `User-Agent`
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;

// 初始化方法,返回该类的实例对象
+ (instancetype)serializer;

/**
下面两个方法是对请求头字典操作。
*/
- (void)setValue:(nullable NSString *)value
forHTTPHeaderField:(NSString *)field;

- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;

/**
下面两个方法是关于登录认证
*/
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
password:(NSString *)password;

- (void)clearAuthorizationHeader;

@name Configuring Query String Parameter Serialization【配置查询字符串参数序列化】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 集合对象,它表示序列化时参数被追加在url里的请求方式的集合
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;

/**
设置序列化类型的方法,代表遵循什么样的规则进行queryString转换。
参数是个枚举,但是这个枚举只有一个值 AFHTTPRequestQueryStringDefaultStyle
*/
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;

/**
提供了以 block 形式自定义 queryString 转换的接口,
也就是说可以通过block回调的方式让调用者以自己的方式完成 queryString 的转换。
*/
- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters,
NSError * __autoreleasing *error))block;

最后就剩下三个核心方法了,之前我们都已经介绍了,这里就不再赘余了,提示:代码注释非常好,可以细细看来。


接下来看,实现 AFURLRequestSerialization.m 部分

首先看到的是初始化方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
+ (instancetype)serializer {
return [[self alloc] init];
}

- (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
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;
}];
// init里面先将Accept-Language存到mutableHTTPRequestHeaders里
[self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];

1、首先调用+ serializer进行初始化,里面调用了自己init方法,
init 里面先将 Accept-Language 存到mutableHTTPRequestHeaders


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    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];
if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
userAgent = mutableUserAgent;
}
}
[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
}

2、将mainBundle里面根据使用语言的优先顺序放到acceptLanguagesComponents里面,再用","分隔,存到mutableHTTPRequestHeaders字典里面

3、然后拼接User-Agent,格式为”%@/%@ (%@; iOS %@; Scale/%0.2f)”,里面需要5个参数,第一个参数先获取项目名,如果没有,就用BundleIdentifier,第二个参数先获取短版本号,如果没有就用版本号,第三个参数是当前设备的类型,第四个参数是当前设备的版本号,第五个参数是屏幕的比例

1
2
3
4
5
6
7
8
9
10
11
// 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;

4、然后设置属性的监听,这些属性在头文件里面都可以找到,实现文件里面也实现了set方法。在这里边调用了静态方法AFHTTPRequestSerializerObservedKeyPaths

下面实现了模式键值观察:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
当我们设置了这些HTTP配置属性的值时,就会触发观察回调方法。
通过KVO判断是否是新值,如果是的话,并将该属性字符串放入mutableObservedChangedKeyPaths集合
*/
- (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];
}
}
}

1
2
3
4
5
6
7
8
// 设置验证字段。
- (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"];
}

初始化方法之后调用

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
- (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;

/*
为该HTTP的request设置配置属性
方法AFHTTPRequestSerializerObservedKeyPaths()返回一个数组,代表我们需要关注的HTTP配置属性。
而mutableObservedChangedKeyPaths集合代表我们已设置的配置属性,
*/
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}

/*
将传入的parameters进行编码,添加到request中
*/
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

return mutableRequest;
}

根据url初始化,设置HTTP方法,根据mutableObservedChangedKeyPaths存储的属性,设置到mutableRequest当中,并为其该HTTP请求设置配置属性。

接下来会调用到这个方法:

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
#pragma mark - AFURLRequestSerialization
/**
原来这个方法便是定义在AFURLRequestSerialization协议中的方法。
在不同的子类中有不同的实现,用以实现不同的功能。
*/
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);

NSMutableURLRequest *mutableRequest = [request mutableCopy];
/*
1.为request设置请求头
*/
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
/*
2.参数序列化处理(将参数字典解析为url query)
*/
NSString *query = nil;
if (parameters) {
// 若自定义了queryStringSerialization,那么就使用自定义的queryStringSerialization构建方式
if (self.queryStringSerialization) {
NSError *serializationError;
// 通过queryStringSerialization构建query字符串
query = self.queryStringSerialization(request, parameters, &serializationError);

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

return nil;
}
} else {
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
// 由参数生成url的query部分
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
// HTTPMethodsEncodingParametersInURI是个NSSet,装了"GET"、"HEAD"、"DELETE"请求方式(在该类的初始化方法里初始化了),因为这几个请求方式的query是直接拼接在url后面的。
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else { // 若请求方式为"POST"、"PUT"等,则需要将query设置到http body上
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
// 将query string编码
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}

return mutableRequest;
}

分为两个步骤。
第一步是 为request设置请求头属性;
第二步是 参数序列化处理:先看是否实现自定义的序列化处理block,【若有】则调用自定义的block,让使用者自己实现参数序列化;【若无】则调用AFQueryStringFromParameters()方法,由参数字典转换为query string



本篇文章下:主要从【网络通信序列化 AFURLResponseSerialization】学习总结,

Source Code【源码学习】
这里对【AFURLResponseSerialization】 就不在一一列举了,我下面会有整理的 AFN框架源码注解,里面对于这个类 部分重点地方,都已有注释,如果需要可以下载下来。
PS.

1
2
3
4
5
6
7
8
9
2.Serialization 序列化
<AFURLRequestSerialization> 请求序列化
AFHTTPRequestSerializer
AFJSONRequestSerializer
AFPropertyListRequestSerializer
<AFURLResponseSerialization> 响应者序列化
AFHTTPResponseSerializer 返回原始类型,默认解析方案
AFJSONResponseSerializer 返回JSON类型,JSON解析方案
AFXMLParserResponseSerializer 返回XML类型,XML解析方案

Reading


  • 如果在阅读过程中遇到 Error || New ideas,希望你能 issue 我,我会及时补充谢谢。
  • 熬夜写者不易,喜欢可 赞赏 or Star 一波;点击左上角关注 或 『Public:Codeidea』,在 Demo | 文章 | Rss 更新时收到提醒通知,便捷阅读。
既然阅读完了    就在下面留言吧!

标题:iOS 框架详解—「Serialization 序列化」

作者:白水

链接:http://githubidea.github.io/SourceAnnotations/AFN3.0+SourceCode-Serialization.html

版权声明: 署名-非商业性使用-禁止演绎 4.0 国际 本博客文章除特别声明外均为原创,如需转载请务必保留原链接(可点击进入的链接)和 作者出处。