继续上一篇的学习,本篇着重学习对YYWebImageManager、YYWebImageOperation 进一步了解,还是以问题的方式带入。
* NSMutableURLRequest处对request 做了什么特别处理?
* 为什么很多地方使用 @autoreleasepool {}
* 重写里面的一些方法,做了什么处理?
* 锁和线程中在里面的运用?
* NSURLConnectionDelegate代理方法中有没有一些特别的处理?
* 此处用的是苹果已经弃用的NSURLConnection,是否后期会替换?
1、NSMutableURLRequest处对request 做了什么处理?
request.cachePolicy = (options & YYWebImageOptionUseNSURLCache) ?
NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
设置缓存策略,对加载图片模式进行处理。
- NSURLRequestUseProtocolCachePolicy这个是系统默认的缓存策略,缓存不存在,就去重新服务端拉去,如果存在的话,根据下一步请求的Cache-control字段来进行下一步的操作,比如如果cache-control = must-revalidata,那么还会去询问服务端是否有数据更新,有的话就拉取新数据,没有就返回缓存
- NSURLRequestReloadIgnoringLocalCacheData:忽略本地缓存,每次都去请求服务端
当然生成一个YYWebImageOperation对象,才是其核心咯。
2、为什么很多地方使用使用 @autoreleasepool {}?
通过Objective-C Autorelease Pool 的实现原理 和 黑幕背后的Autorelease的了解,一般我们在下列情况会用到@autoreleasepool {}。
- 如果你编写的程序不是基于 UI 框架的,比如说命令行工具;
- 如果你编写的循环中创建了大量的临时对象;
- 如果你创建了一个辅助线程。
我的理解是作者在很多方法里面都用到了它,是为了可以更好的管理内存这块不会出问题,让产生的对象都能在适当的地方释放。
PS:@ autorelease基本注意点:
- 将对象放到一个自动释放池中,结束后对象会返回对象本身;
- 当自动释放池被销毁时, 会对其池子里面的所有对象做一次release 操作。
- 占用内存较大的对象不要随便用 autorelease;占用内存较小的对象使用 autorelease,没有太大影响;
- 系统自带方法里面没有alloc、new、copy,说明返回的对象是autorelease的。
3、重写里面的一些方法,做了什么处理?
以 start 举例, @autoreleasepool,递归锁防止死锁,_cancelOperation方法的使用,但最核心的当然还是依据各种条件状态进行调整。
- (void)start {
@autoreleasepool {
[_lock lock];
self.started = YES;
if ([self isCancelled]) {
[self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
self.finished = YES;
} else if ([self isReady] && ![self isFinished] && ![self isExecuting]) {
if (!_request) {
self.finished = YES;
if (_completion) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{NSLocalizedDescriptionKey:@"request in nil"}];
_completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
}
} else {
self.executing = YES;
[self performSelector:@selector(_startOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
if ((_options & YYWebImageOptionAllowBackgroundTask) && _YYSharedApplication()) {
__weak __typeof__ (self) _self = self;
if (_taskID == UIBackgroundTaskInvalid) {
_taskID = [_YYSharedApplication() beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof (_self) self = _self;
if (self) {
[self cancel];
self.finished = YES;
}
}];
}
}
}
}
[_lock unlock];
}
}
通过给出的条件定制自己的 Operation,此时回过去看看 _startOperation
- (void)_startOperation {
if ([self isCancelled]) return;
@autoreleasepool {
// 如果缓存存在,并且不等于使用NSURLCache,并且不是刷新缓存,则直接通过_cacheKey 获取
if (_cache &&
!(_options & YYWebImageOptionUseNSURLCache) &&
!(_options & YYWebImageOptionRefreshImageCache)) {
UIImage *image = [_cache getImageForKey:_cacheKey withType:YYImageCacheTypeMemory];
if (image) {
// 得到图片
[_lock lock];
if (![self isCancelled]) {
if (_completion) _completion(image, _request.URL, YYWebImageFromMemoryCache, YYWebImageStageFinished, nil);
}
[self _finish];
[_lock unlock];
return;
}
// 判断下载模式
if (!(_options & YYWebImageOptionIgnoreDiskCache)) {
__weak typeof(self) _self = self;
dispatch_async([self.class _imageQueue], ^{
__strong typeof(_self) self = _self;
if (!self || [self isCancelled]) return;
UIImage *image = [self.cache getImageForKey:self.cacheKey withType:YYImageCacheTypeDisk];
if (image) {
[self.cache setImage:image imageData:nil forKey:self.cacheKey withType:YYImageCacheTypeMemory];
[self performSelector:@selector(_didReceiveImageFromDiskCache:) onThread:[self.class _networkThread] withObject:image waitUntilDone:NO];
} else {
// 假如没有图片就到网络线程立刻开始请求
[self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];
}
});
return;
}
}
}
//在网络线程立刻开始请求
[self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];
}
总的说来,在重写系统方法的时候,就是根据自己的条件定制化自己的 operation。
4、锁和线程中在里面的运用?
4-1、互斥锁的使用
[lock lock]; // 上锁
// 处理公共资源
[self dosomething]
[lock unlock]; // 释放锁
以及@synchronized
互斥锁简洁版本的使用。很直接的说明,用来保护同一时间只有一个线程访问数据。
4-2、全局队列
+ (dispatch_queue_t)_imageQueue {
#define MAX_QUEUE_COUNT 16
static int queueCount;
static dispatch_queue_t queues[MAX_QUEUE_COUNT];
static dispatch_once_t onceToken;
static int32_t counter = 0;
dispatch_once(&onceToken, ^{
queueCount = (int)[NSProcessInfo processInfo].activeProcessorCount;
queueCount = queueCount < 1 ? 1 : queueCount > MAX_QUEUE_COUNT ? MAX_QUEUE_COUNT : queueCount;
if ([UIDevice currentDevice].systemVersion.floatValue >= 8.0) {
for (NSUInteger i = 0; i < queueCount; i++) {
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
queues[i] = dispatch_queue_create("com.ibireme.image.decode", attr);
}
} else {
for (NSUInteger i = 0; i < queueCount; i++) {
queues[i] = dispatch_queue_create("com.ibireme.image.decode", DISPATCH_QUEUE_SERIAL);
dispatch_set_target_queue(queues[i], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0));
}
}
});
int32_t cur = OSAtomicIncrement32(&counter);
if (cur < 0) cur = -cur;
return queues[(cur) % queueCount];
#undef MAX_QUEUE_COUNT
}
dispatch_async([self.class _imageQueue], ^{
// do something
});
全局图片线程,用于读取 、解码图片,注意最大线程数的限制,以及iOS 8之后,创建 queues 的不同。
-
dispatch_queue_attr_make_with_qos_class
这个函数可以创建带有优先级的dispatch_queue_attr_t对象。通过这个对象可以自定义queue的优先级。 -
dispatch_set_target_queue
可以设置优先级,也可以设置队列层级体系,比如让多个串行和并行队列在统一一个串行队列里串行执行
5、NSURLConnectionDelegate代理方法中有没有一些特别的处理?
特别要注意一个地方,接收到数据对进度的展示
//收到数据回调
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data ;
/*---------- progressive ----------------------------*/
BOOL progressive = (_options & YYWebImageOptionProgressive) > 0;
BOOL progressiveBlur = (_options & YYWebImageOptionProgressiveBlur) > 0;
if (!_completion || !(progressive || progressiveBlur)) return;
if (data.length <= 16) return;
if (_expectedSize > 0 && data.length >= _expectedSize * 0.99) return;
if (_progressiveIgnored) return;
NSTimeInterval min = progressiveBlur ? MIN_PROGRESSIVE_BLUR_TIME_INTERVAL : MIN_PROGRESSIVE_TIME_INTERVAL;
NSTimeInterval now = CACurrentMediaTime();
if (now - _lastProgressiveDecodeTimestamp < min) return;
//没有解码,初始化一个解码器
if (!_progressiveDecoder) {
_progressiveDecoder = [[YYImageDecoder alloc] initWithScale:[UIScreen mainScreen].scale];
}
//解码器更新数据
[_progressiveDecoder updateData:_data final:NO];
if ([self isCancelled]) return;
//只支持渐进式的JPEG图像和interlanced类型的PNG图像
if (_progressiveDecoder.type == YYImageTypeUnknown ||
_progressiveDecoder.type == YYImageTypeWebP ||
_progressiveDecoder.type == YYImageTypeOther) {
_progressiveDecoder = nil;
_progressiveIgnored = YES;
return;
}
if (progressiveBlur) { // only support progressive JPEG and interlaced PNG
if (_progressiveDecoder.type != YYImageTypeJPEG &&
_progressiveDecoder.type != YYImageTypePNG) {
_progressiveDecoder = nil;
_progressiveIgnored = YES;
return;
}
}
if (_progressiveDecoder.frameCount == 0) return;
//不存在渐进显示的话
if (!progressiveBlur) {
YYImageFrame *frame = [_progressiveDecoder frameAtIndex:0 decodeForDisplay:YES];
if (frame.image) {
[_lock lock];
if (![self isCancelled]) {
_completion(frame.image, _request.URL, YYWebImageFromRemote, YYWebImageStageProgress, nil);
_lastProgressiveDecodeTimestamp = now;
}
[_lock unlock];
}
return;
} else {
//解码之后发现是JPEG格式的
if (_progressiveDecoder.type == YYImageTypeJPEG) {
if (!_progressiveDetected) {
//不是渐进式加载
NSDictionary *dic = [_progressiveDecoder framePropertiesAtIndex:0];
NSDictionary *jpeg = dic[(id)kCGImagePropertyJFIFDictionary];
NSNumber *isProg = jpeg[(id)kCGImagePropertyJFIFIsProgressive];
if (!isProg.boolValue) {
_progressiveIgnored = YES;
_progressiveDecoder = nil;
return;
}
_progressiveDetected = YES;
}
//缩放长度为 接收到数据length - _progressiveScanedLength - 4
NSInteger scanLength = (NSInteger)_data.length - (NSInteger)_progressiveScanedLength - 4;
if (scanLength <= 2) return;
NSRange scanRange = NSMakeRange(_progressiveScanedLength, scanLength);
NSRange markerRange = [_data rangeOfData:JPEGSOSMarker() options:kNilOptions range:scanRange];
_progressiveScanedLength = _data.length;
if (markerRange.location == NSNotFound) return;
if ([self isCancelled]) return;
} else if (_progressiveDecoder.type == YYImageTypePNG) {
if (!_progressiveDetected) {
//从解码中取值,解码,赋值
NSDictionary *dic = [_progressiveDecoder framePropertiesAtIndex:0];
NSDictionary *png = dic[(id)kCGImagePropertyPNGDictionary];
NSNumber *isProg = png[(id)kCGImagePropertyPNGInterlaceType];
if (!isProg.boolValue) {
_progressiveIgnored = YES;
_progressiveDecoder = nil;
return;
}
_progressiveDetected = YES;
}
}
YYImageFrame *frame = [_progressiveDecoder frameAtIndex:0 decodeForDisplay:YES];
UIImage *image = frame.image;
if (!image) return;
if ([self isCancelled]) return;
//最后一个像素没有填充完毕,以为没有下载成功,返回
if (!YYCGImageLastPixelFilled(image.CGImage)) return;
_progressiveDisplayCount++;
CGFloat radius = 32;
if (_expectedSize > 0) {
radius *= 1.0 / (3 * _data.length / (CGFloat)_expectedSize + 0.6) - 0.25;
} else {
radius /= (_progressiveDisplayCount);
}
image = [image yy_imageByBlurRadius:radius tintColor:nil tintMode:0 saturation:1 maskImage:nil];
if (image) {
[_lock lock];
if (![self isCancelled]) {
// block 赋值结束
_completion(image, _request.URL, YYWebImageFromRemote, YYWebImageStageProgress, nil);
_lastProgressiveDecodeTimestamp = now;
}
[_lock unlock];
}
}
6、此处用的是苹果已经弃用的NSURLConnection,是否后期会替换?
随着 iOS 10的到来,在 iOS 9 引入的 ATS 将在来年更加严格。2017 年起,新提交的 app 将不再被允许进行 http 的访问,所有的 app 内的网络请求必须加密,通过 https 完成。所以我想网络这块的NSURLConnection 正式弃用,已经不远了。自从 AFNetworking 也已经更新了 NSURLSession 的情况下,YYWebImage网络这块的更新应该也不远了,虽说目前使用没问题,但这个目前持续的时间不久了。
总的说来,还是没怎么吃透,对于源码中还有很多地方可以推敲学习的,鉴于此这块的学习依然会持续,方式还是应该yi问题持续中。
备注参考:
https://github.com/ibireme/YYWebImage
//www.greatytc.com/p/5a5bea9180e7
//www.greatytc.com/p/9b71c2d94b89