本篇是AFNetworking 3.0 源码解读的第四篇了。

AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager

AFNetworking 3.0 源码解读(二)之 AFSecurityPolicy

AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization

这次主要讲AFURLResponseSerialization(HTTP响应)这一个类的知识。

这是一个协议,只要遵守这个协议,就要实现NSSecureCoding/NSCopying这两个协议,还要实现

- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

这个方法来返回序列化后的结果。不管是下边的AFHTTPResponseSerializer,还是它的子类,都遵守这个协议,也就是在各自的实现中实现了这个协议,然后返回了属于自身的一个结果。

ps:根据这个协议,我有了一些启发。当我们在设计一个网络框架的时候,因为业务不同,返回的数据也有很多种,通常的一种做法是直接返回服务器响应的数据,由业务人员自己实现业务。但是如果业务繁杂,这样写出的代码也会很乱,我们不妨采用类似这种协议的设计模式,这样做有两个好处:

1. 业务人员和数据人员可以分开。 数据提前约定好名称和内容,写数据人员实现数据部分,写业务人员实现业务部分。

2. 左右的数据转换放到协议实现方法中,出现问题,更容易查找问题。

由于这个类有很多的子类,我们先来看看这些类的组成,然后逐一的对每个子类的代码进行解读。

==================================  分割线 =======================================

我们还是先来看看AFHTTPResponseSerializer头文件的组成部分。

来看看实现部分:

这个初始化,看起来还是很普通的,主要是初始化一些默认值,我们在这里花点篇幅讲解一下NSIndexSet这个集合的知识。

定义:NSIndexSet是一个有序的,唯一的,无符号整数的集合。

我们先看个例子:

    NSMutableIndexSet *indexSetM = [NSMutableIndexSet indexSet];
[indexSetM addIndex:];
[indexSetM addIndex:];
[indexSetM addIndex:];
[indexSetM addIndex:];
[indexSetM addIndexesInRange:NSMakeRange(, )]; [indexSetM enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%lu",idx);
}];

打印结果如下:

-- ::00.826 qikeyunDemo[:]
-- ::00.827 qikeyunDemo[:]
-- ::00.827 qikeyunDemo[:]
-- ::00.827 qikeyunDemo[:]
-- ::00.827 qikeyunDemo[:]
-- ::00.828 qikeyunDemo[:]
-- ::00.828 qikeyunDemo[:]
-- ::00.828 qikeyunDemo[:]
-- ::00.828 qikeyunDemo[:]
-- ::00.828 qikeyunDemo[:]
-- ::00.828 qikeyunDemo[:]
-- ::00.828 qikeyunDemo[:]
-- ::00.828 qikeyunDemo[:]
-- ::00.829 qikeyunDemo[:]

这充分说明了一下几点:

1. 它是一个无符号整数的集合。

2. 使用addIndex方法可以添加单个整数值,使用addIndexesInRange可以添加一个范围,比如NSMakeRange(20, 10) 取20包括20后边十个整数。

3. 唯一性,集合内的整数不会重复出现。

具体用法就不再次做详细的解释了,有兴趣的朋友可以看看这篇文章: NSIndexSet 用法

因此 self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; 这个方法设置了默认接受的状态码范围为200 ~ 299.

 - (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
// 1. 默认responseIsValid == yes
BOOL responseIsValid = YES;
NSError *validationError = nil; // 2. 假如response存在且类型是NSHTTPURLResponse
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) { // 2.1 条件:self.acceptableContentTypes存在且不包含服务器返回的MIMEType且MIMEType和data都不能为空
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
!([response MIMEType] == nil && [data length] == )) { if ([data length] > && [response URL]) { // 2.1.1 生成错误信息
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response, } mutableCopy];
// 2.1.2 包含data错误信息
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
} // 2.1.3 生成NSError
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;
} // 设置错误,通过AFErrorWithUnderlyingError这个函数设置validationError的NSUnderlyingErrorKey
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError); responseIsValid = NO;
}
} // 赋值
if (error && !responseIsValid) {
*error = validationError;
} return responseIsValid;
}

这个方法就是检测响应的有效性的。默认是YES。整个方法中比较值得关注的是对NSError的使用。在这里不对它做详细的介绍,大概解释下最长用的一些东东。

1. 我们关注下它的NSDictionary *userInfo这个属性,错误信息一般都在这个字典中获得。因为是一个字典,所以我们在给NSError的userInfo赋值的时候,会用到key。我们看看系统自带的key和含义有哪些?

当然我们也可以自定义key来操作NSError。

在上边的那个方法中,有可能会出现两个错误,在self.acceptableContentTypes和self.acceptableStatusCodes这两个判断中,如果都出现错误怎么办呢?

这就用到了NSUnderlyingErrorKey 这个字段,它标示一个优先的错误,value为NSErro对象。

通过下边的这个函数进行了转换和赋值:

 #pragma mark - NSSecureCoding

 + (BOOL)supportsSecureCoding {
return YES;
} - (instancetype)initWithCoder:(NSCoder *)decoder {
self = [self init];
if (!self) {
return nil;
} self.acceptableStatusCodes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableStatusCodes))];
self.acceptableContentTypes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableContentTypes))]; return self;
} - (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.acceptableStatusCodes forKey:NSStringFromSelector(@selector(acceptableStatusCodes))];
[coder encodeObject:self.acceptableContentTypes forKey:NSStringFromSelector(@selector(acceptableContentTypes))];
} #pragma mark - NSCopying - (instancetype)copyWithZone:(NSZone *)zone {
AFHTTPResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
serializer.acceptableStatusCodes = [self.acceptableStatusCodes copyWithZone:zone];
serializer.acceptableContentTypes = [self.acceptableContentTypes copyWithZone:zone]; return serializer;
}

这几个是对 NSCopying NSSecureCoding 这两个协议的实现部分,算是固定写法吧。为了节省篇幅 ,在下边的各个分类中,就不对这些代码进行说明了。

==================================  分割线 ==================================

我们来看看这个Json序列化的头文件。

这个选项可以设置json的读取选项,我们点进去可以看到:

  • NSJSONReadingMutableContainers    这个解析json成功后返回一个容器。

  • NSJSONReadingMutableLeaves         返回中的json对象中字符串为NSMutableString

  • NSJSONReadingAllowFragments        允许JSON字符串最外层既不是NSArray也不是NSDictionary,但必须是有效的JSON Fragment。例如使用这个选项可以解析 @“123” 这样的字符串

我们举个例子说明一下NSJSONReadingMutableContainers:

 NSString *str = @"{\"name\":\"zhangsan\"}";

     NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options: error:nil];
// 应用崩溃,不选用NSJSONReadingOptions,则返回的对象是不可变的,NSDictionary
[dict setObject:@"male" forKey:@"sex"]; NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil];
// 没问题,使用NSJSONReadingMutableContainers,则返回的对象是可变的,NSMutableDictionary
[dict setObject:@"male" forKey:@"sex"]; NSLog(@"%@", dict);

如果服务器返回的json的最外层并不是以NSArray 或者 NSDictionary ,而是一个有效的json fragment ,比如 就返回了一个@"abc"。 那么我们使用NSJSONReadingAllowFragments这个选项也能够解析出来。

这个属性用来设置是否过滤NSNull。

通过初始化方法可以看出来,AFNetworking支持的ContentType有:

  • application/json

  • text/json

  • text/javascript

 - (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
// 判空处理,如果验证结果失败,在没有error 或者 错误中code:NSURLErrorCannotDecodeContentData 的情况下,是不能解析数据的,就返回nil
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:]];
if (data.length > && !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; }

json转化的和新方法,这个方法中除了加了一些必要的判断之外,新出现了两个函数,函数的实现也比较好理解,就不做详细介绍了,把代码贴出来:

 // 检测错误或者有限错误中是否匹配code和domain
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) { // error中的domain和code相同,直接返回YES
if ([error.domain isEqualToString:domain] && error.code == code) {
return YES; // 否则检测error中的NSUnderlyingErrorKey
} else if (error.userInfo[NSUnderlyingErrorKey]) {
return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
} return NO;
}
 // 该方法用于删除一个对象中的NUll
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) { // 1.数组
if ([JSONObject isKindOfClass:[NSArray class]]) { //1.1 创建一个可变的数组,为了提高性能,使用Capacity创建
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]]; // 1.2 遍历数组,通过迭代的手段清空数组内的null
for (id value in (NSArray *)JSONObject) {
[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
} // 1.3 readingOptions == NSJSONReadingMutableContainers 返回可变容器,其他返回不可变容器
return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
} // 2. 思想同上
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;
}

================================ 分割线 ====================================

这个分类是解析XML数据的。

支持的ContentType:

  • application/xml

  • text/xml

================================  分割线 =============================

注: 这个子类只在mac os x上使用

支持的ContentType:

  • application/xml

  • text/xml

=============================== 分隔线 =================================

这个分类用来把json解析成PropertyList:

支持的ContentType:

  • application/x-plist

=================================  分割线 ===============================

这是一个UIImage分类,添加了一个安全的NData转UIImage的方法;那么什么叫安全的呢?当我们访问或者操纵数据的时候,由于数据还可能被别人操纵,这就有可能出现不安全的情况,为了解决这个问题,引入NSLock这个锁对象 。

使用方法:

  • 创建一个单例NSLock *lock
  • [lock lock]
  • do something...
  • [lock unlock]

这个私有方法,返回一个按照scale收缩的图片。这就使用到了上边的那个安全转换数据的方法了。但看这个方法我们应该知道下边两点知识:

  • image.images   images这个属性的用法,下边我们举例说明
  • - (instancetype)initWithCGImage:(CGImageRef)cgImage scale:(CGFloat)scale orientation:(UIImageOrientation)orientation NS_AVAILABLE_IOS(4_0);   这个是创建UIImage的一个方法,稍微注意下需要哪些参数。

 UIImage *image0 = [UIImage imageNamed:@"SenderVoiceNodePlaying001"];
UIImage *image1 = [UIImage imageNamed:@"SenderVoiceNodePlaying002"];
UIImage *image2 = [UIImage imageNamed:@"SenderVoiceNodePlaying003"]; self.imageView.image = [UIImage animatedImageWithImages:@[image0,image1,image2] duration:1.5];

效果图如下:

那么这个images属性就可以应用到很多地方了,我们可以使用这个属性来生成一个类似gif的效果或者简单的动画是最常用的场景。

 // 根据响应结果和scale返回一张图片
static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) { // 1. 判空
if (!data || [data length] == ) {
return nil;
} // 2. 但凡带CG开头的,标示是CoreGraphics
CGImageRef imageRef = NULL; // 3. CGDataProviderRef 可以细节为CoreGraphics中对data的包装
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data); // 判断响应返回的MIMEType类型,
if ([response.MIMEType isEqualToString:@"image/png"]) {
imageRef = CGImageCreateWithPNGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
} else if ([response.MIMEType isEqualToString:@"image/jpeg"]) {
imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault); if (imageRef) {
CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef);
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace); // CGImageCreateWithJPEGDataProvider does not properly handle CMKY, so fall back to AFImageWithDataAtScale
if (imageColorSpaceModel == kCGColorSpaceModelCMYK) {
CGImageRelease(imageRef);
imageRef = NULL;
}
}
} CGDataProviderRelease(dataProvider); UIImage *image = AFImageWithDataAtScale(data, scale);
if (!imageRef) {
if (image.images || !image) {
return image;
} imageRef = CGImageCreateCopy([image CGImage]);
if (!imageRef) {
return nil;
}
} // 代码到了这里,这个imageRef肯定有值了,有可能是response.MIMEType得到的,也有可能是根据image得到的 size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef); // 这行代码不太明白什么意思。。
if (width * height > * || bitsPerComponent > ) {
CGImageRelease(imageRef); return image;
} // CGImageGetBytesPerRow() calculates incorrectly in iOS 5.0, so defer to CGBitmapContextCreate
size_t bytesPerRow = ;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); if (colorSpaceModel == kCGColorSpaceModelRGB) {
uint32_t alpha = (bitmapInfo & kCGBitmapAlphaInfoMask);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wassign-enum"
if (alpha == kCGImageAlphaNone) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaNoneSkipFirst;
} else if (!(alpha == kCGImageAlphaNoneSkipFirst || alpha == kCGImageAlphaNoneSkipLast)) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaPremultipliedFirst;
}
#pragma clang diagnostic pop
} CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo); CGColorSpaceRelease(colorSpace); if (!context) {
CGImageRelease(imageRef); return image;
} CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), imageRef);
CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context); CGContextRelease(context); UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation]; CGImageRelease(inflatedImageRef);
CGImageRelease(imageRef); return inflatedImage;
}

上边的这个方法设计到了CoreGraphics的知识,这个还不是特别了解。稍后会补一补这方面的知识。

上边的代码有一点不明白的地方,:

   // 这行代码不太明白什么意思。。
if (width * height > * || bitsPerComponent > ) {
CGImageRelease(imageRef); return image;
}

后来看到了一篇文章 把原话粘贴出来

AFJSONResponseSerializer使用系统内置的NSJSONSerialization解析json,NSJSON只支持解析UTF8编码的数据(还有UTF-16LE之类的,都不常用),所以要先把返回的数据转成UTF8格式。这里会尝试用HTTP返回的编码类型和自己设置的stringEncoding去把数据解码转成字符串NSString,再把NSString用UTF8编码转成NSData,再用NSJSONSerialization解析成对象返回。

上述过程是NSData->NSString->NSData->NSObject,这里有个问题,如果你能确定服务端返回的是UTF8编码的json数据,那NSData->NSString->NSData这两步就是无意义的,而且这两步进行了两次编解码,很浪费性能,所以如果确定服务端返回utf8编码数据,就建议自己再写个JSONResponseSerializer,跳过这两个步骤。

此外AFJSONResponseSerializer专门写了个方法去除NSNull,直接把对象里值是NSNull的键去掉,还蛮贴心,若不去掉,上层很容易忽略了这个数据类型,判断了数据是否nil没判断是否NSNull,进行了错误的调用导致core。

图片解压

当我们调用UIImage的方法imageWithData:方法把数据转成UIImage对象后,其实这时UIImage对象还没准备好需要渲染到屏幕的数据,现在的网络图像PNG和JPG都是压缩格式,需要把它们解压转成bitmap后才能渲染到屏幕上,如果不做任何处理,当你把UIImage赋给UIImageView,在渲染之前底层会判断到UIImage对象未解压,没有bitmap数据,这时会在主线程对图片进行解压操作,再渲染到屏幕上。这个解压操作是比较耗时的,如果任由它在主线程做,可能会导致速度慢UI卡顿的问题。

AFImageResponseSerializer除了把返回数据解析成UIImage外,还会把图像数据解压,这个处理是在子线程(AFNetworking专用的一条线程,详见AFURLConnectionOperation),处理后上层使用返回的UIImage在主线程渲染时就不需要做解压这步操作,主线程减轻了负担,减少了UI卡顿问题。

具体实现上在AFInflatedImageFromResponseWithDataAtScale里,创建一个画布,把UIImage画在画布上,再把这个画布保存成UIImage返回给上层。只有JPG和PNG才会尝试去做解压操作,期间如果解压失败,或者遇到CMKY颜色格式的jpg,或者图像太大(解压后的bitmap太占内存,一个像素3-4字节,搞不好内存就爆掉了),就直接返回未解压的图像。

另外在代码里看到iOS才需要这样手动解压,MacOS上已经有封装好的对象NSBitmapImageRep可以做这个事。

关于图片解压,还有几个问题不清楚:

.本来以为调用imageWithData方法只是持有了数据,没有做解压相关的事,后来看到调用堆栈发现已经做了一些解压操作,从调用名字看进行了huffman解码,不知还会继续做到解码jpg的哪一步。
UIImage_jpg .以上图片手动解压方式都是在CPU进行的,如果不进行手动解压,把图片放进layer里,让底层自动做这个事,是会用GPU进行的解压的。不知用GPU解压与用CPU解压速度会差多少,如果GPU速度很快,就算是在主线程做解压,也变得可以接受了,就不需要手动解压这样的优化了,不过目前没找到方法检测GPU解压的速度。

原来做这个转化的目的是为了尽量避免在主线程解压数据,因图像太大造成内存崩溃的问题。

============================== 分割线 ==================================

支持的ContentType:

  • image/tiff

  • image/jpeg

  • image/gif

  • image/png

  • image/ico

  • image/x-icon

  • image/bmp

  • image/x-bmp

  • image/x-xbitmap

  • image/x-win-bitmap

===========================  分割线 ==============================

这个复合型的子类有一个数组属性,里边装着多种序列化类型。看其实现方法也是,遍历数组中的内容,只要能转化成功,就返回数据。

============================= 分割线 =================================

好了,这篇就到此为止了,我们学到了

  • 通过一个协议来得到不同转换的结果。
  • 知道了AFNetworking响应结果支持的各种类型。
  • 大体了解了NSIndexSet的使用方法
  • 如果创建一个NSError 和 带有优先错误的NSUnderlyingErrorKey
  • 服务器返回的图片是压缩格式,要进行解压
  • 使用images来实现gif效果

AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization的更多相关文章

  1. AFNetworking 3.0 源码解读 总结(干货)(下)

    承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...

  2. AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking

    AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结. 前言 上一篇我们总结了 UIActivityIndicatorVie ...

  3. AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking

    我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...

  4. AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager

    让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心. 前言 AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控 ...

  5. AFNetworking 3.0 源码解读(八)之 AFImageDownloader

    AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义.在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋 ...

  6. AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache

    这篇我们就要介绍AFAutoPurgingImageCache这个类了.这个类给了我们临时管理图片内存的能力. 前言 假如说我们要写一个通用的网络框架,除了必备的请求数据的方法外,必须提供一个下载器来 ...

  7. AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager

    AFHTTPSessionManager相对来说比较好理解,代码也比较短.但却是我们平时可能使用最多的类. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilit ...

  8. AFNetworking 3.0 源码解读(五)之 AFURLSessionManager

    本篇是AFNetworking 3.0 源码解读的第五篇了. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager AFNetworking 3 ...

  9. AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization

    这篇就讲到了跟请求相关的类了 关于AFNetworking 3.0 源码解读 的文章篇幅都会很长,因为不仅仅要把代码进行详细的的解释,还会大概讲解和代码相关的知识点. 上半篇: URI编码的知识 关于 ...

随机推荐

  1. nohup程序后台执行

    Linux常用命令,用于不挂断的执行程序. nohup命令:如果你正在运行一个进程,而且你觉得在退出帐户时该进程还不会结束,那么可以使用nohup命令.该命令可以在你退出帐户/关闭终端之后继续运行相应 ...

  2. ABP源码分析一:整体项目结构及目录

    ABP是一套非常优秀的web应用程序架构,适合用来搭建集中式架构的web应用程序. 整个Abp的Infrastructure是以Abp这个package为核心模块(core)+15个模块(module ...

  3. Android数据存储之Android 6.0运行时权限下文件存储的思考

    前言: 在我们做App开发的过程中基本上都会用到文件存储,所以文件存储对于我们来说是相当熟悉了,不过自从Android 6.0发布之后,基于运行时权限机制访问外置sdcard是需要动态申请权限,所以以 ...

  4. C#创建dll类库

    类库让我们的代码可复用,我们只需要在类库中声明变量一次,就能在接下来的过程中无数次地使用,而无需在每次使用前都要声明它.这样一来,就节省了我们的内存空间.而想要在类库添加什么类,还需取决于类库要实现哪 ...

  5. Spring的数据库开发

                                Spring JDBC框架操作mysql数据库 Spring中的JDBC为我们省去连接和关闭数据库的代码,我们着重关注对数据库的操作.Sprin ...

  6. Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/commons/logging/LogFactory

    学习架构探险,从零开始写Java Web框架时,在学习到springAOP时遇到一个异常: "C:\Program Files\Java\jdk1.7.0_40\bin\java" ...

  7. Configure a bridged network interface for KVM using RHEL 5.4 or later?

    environment Red Hat Enterprise Linux 5.4 or later Red Hat Enterprise Linux 6.0 or later KVM virtual ...

  8. IP报头

      位字段的值设置为二进制的0100表示IP版本4(IPv4).设置为0110表示IP版本6(IPv6)   位,它表示32位字长的IP报头长度,设计报头长度的原因是数据包可选字段大小会发生变化.IP ...

  9. Orcale 三层嵌套分页代码

    select * from( select emp.*,rownum a from ( select * from emp ) emp where rownum<7) where a>3

  10. logstash服务启动脚本

    logstash服务启动脚本 最近在弄ELK,发现logstash没有sysv类型的服务启动脚本,于是按照网上一个老外提供的模板自己进行修改 #添加用户 useradd logstash -M -s ...