繼續(xù)上一篇的學(xué)習(xí)风科,本篇著重學(xué)習(xí)對YYWebImageManager浅萧、YYWebImageOperation 進(jìn)一步了解穿剖,還是以問題的方式帶入。
* NSMutableURLRequest處對request 做了什么特別處理欧穴?
* 為什么很多地方使用 @autoreleasepool {}
* 重寫里面的一些方法民逼,做了什么處理?
* 鎖和線程中在里面的運(yùn)用涮帘?
* NSURLConnectionDelegate代理方法中有沒有一些特別的處理拼苍?
* 此處用的是蘋果已經(jīng)棄用的NSURLConnection,是否后期會替換调缨?
1疮鲫、NSMutableURLRequest處對request 做了什么處理?
request.cachePolicy = (options & YYWebImageOptionUseNSURLCache) ?
NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
設(shè)置緩存策略,對加載圖片模式進(jìn)行處理弦叶。
- NSURLRequestUseProtocolCachePolicy這個是系統(tǒng)默認(rèn)的緩存策略,緩存不存在,就去重新服務(wù)端拉去,如果存在的話,根據(jù)下一步請求的Cache-control字段來進(jìn)行下一步的操作,比如如果cache-control = must-revalidata,那么還會去詢問服務(wù)端是否有數(shù)據(jù)更新,有的話就拉取新數(shù)據(jù),沒有就返回緩存
- NSURLRequestReloadIgnoringLocalCacheData:忽略本地緩存,每次都去請求服務(wù)端
當(dāng)然生成一個YYWebImageOperation對象俊犯,才是其核心咯。
2伤哺、為什么很多地方使用使用 @autoreleasepool {}?
通過Objective-C Autorelease Pool 的實(shí)現(xiàn)原理 和 黑幕背后的Autorelease的了解燕侠,一般我們在下列情況會用到@autoreleasepool {}。
- 如果你編寫的程序不是基于 UI 框架的默责,比如說命令行工具贬循;
- 如果你編寫的循環(huán)中創(chuàng)建了大量的臨時對象咸包;
- 如果你創(chuàng)建了一個輔助線程桃序。
我的理解是作者在很多方法里面都用到了它,是為了可以更好的管理內(nèi)存這塊不會出問題烂瘫,讓產(chǎn)生的對象都能在適當(dāng)?shù)牡胤结尫拧?/p>
PS:@ autorelease基本注意點(diǎn):
- 將對象放到一個自動釋放池中媒熊,結(jié)束后對象會返回對象本身奇适;
- 當(dāng)自動釋放池被銷毀時, 會對其池子里面的所有對象做一次release 操作芦鳍。
- 占用內(nèi)存較大的對象不要隨便用 autorelease嚷往;占用內(nèi)存較小的對象使用 autorelease,沒有太大影響柠衅;
- 系統(tǒng)自帶方法里面沒有alloc皮仁、new、copy菲宴,說明返回的對象是autorelease的贷祈。
3、重寫里面的一些方法喝峦,做了什么處理势誊?
以 start 舉例, @autoreleasepool谣蠢,遞歸鎖防止死鎖粟耻,_cancelOperation方法的使用,但最核心的當(dāng)然還是依據(jù)各種條件狀態(tài)進(jìn)行調(diào)整眉踱。
- (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 {
// 假如沒有圖片就到網(wǎng)絡(luò)線程立刻開始請求
[self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];
}
});
return;
}
}
}
//在網(wǎng)絡(luò)線程立刻開始請求
[self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];
}
總的說來谈喳,在重寫系統(tǒng)方法的時候饭玲,就是根據(jù)自己的條件定制化自己的 operation。
4叁执、鎖和線程中在里面的運(yùn)用茄厘?
4-1、互斥鎖的使用
[lock lock]; // 上鎖
// 處理公共資源
[self dosomething]
[lock unlock]; // 釋放鎖
以及@synchronized
互斥鎖簡潔版本的使用谈宛。很直接的說明次哈,用來保護(hù)同一時間只有一個線程訪問數(shù)據(jù)。
4-2吆录、全局隊(duì)列
+ (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
});
全局圖片線程,用于讀取 窑滞、解碼圖片,注意最大線程數(shù)的限制,以及iOS 8之后喜爷,創(chuàng)建 queues 的不同府蔗。
-
dispatch_queue_attr_make_with_qos_class
這個函數(shù)可以創(chuàng)建帶有優(yōu)先級的dispatch_queue_attr_t對象。通過這個對象可以自定義queue的優(yōu)先級此改。 -
dispatch_set_target_queue
可以設(shè)置優(yōu)先級,也可以設(shè)置隊(duì)列層級體系侄柔,比如讓多個串行和并行隊(duì)列在統(tǒng)一一個串行隊(duì)列里串行執(zhí)行
5共啃、NSURLConnectionDelegate代理方法中有沒有一些特別的處理占调?
特別要注意一個地方,接收到數(shù)據(jù)對進(jìn)度的展示
//收到數(shù)據(jù)回調(diào)
- (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];
}
//解碼器更新數(shù)據(jù)
[_progressiveDecoder updateData:_data final:NO];
if ([self isCancelled]) return;
//只支持漸進(jìn)式的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;
//不存在漸進(jìn)顯示的話
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 {
//解碼之后發(fā)現(xiàn)是JPEG格式的
if (_progressiveDecoder.type == YYImageTypeJPEG) {
if (!_progressiveDetected) {
//不是漸進(jìn)式加載
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;
}
//縮放長度為 接收到數(shù)據(jù)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 賦值結(jié)束
_completion(image, _request.URL, YYWebImageFromRemote, YYWebImageStageProgress, nil);
_lastProgressiveDecodeTimestamp = now;
}
[_lock unlock];
}
}
6移剪、此處用的是蘋果已經(jīng)棄用的NSURLConnection究珊,是否后期會替換?
隨著 iOS 10的到來纵苛,在 iOS 9 引入的 ATS 將在來年更加嚴(yán)格剿涮。2017 年起,新提交的 app 將不再被允許進(jìn)行 http 的訪問攻人,所有的 app 內(nèi)的網(wǎng)絡(luò)請求必須加密幔虏,通過 https 完成。所以我想網(wǎng)絡(luò)這塊的NSURLConnection 正式棄用贝椿,已經(jīng)不遠(yuǎn)了想括。自從 AFNetworking 也已經(jīng)更新了 NSURLSession 的情況下,YYWebImage網(wǎng)絡(luò)這塊的更新應(yīng)該也不遠(yuǎn)了烙博,雖說目前使用沒問題瑟蜈,但這個目前持續(xù)的時間不久了。
總的說來渣窜,還是沒怎么吃透铺根,對于源碼中還有很多地方可以推敲學(xué)習(xí)的,鑒于此這塊的學(xué)習(xí)依然會持續(xù)乔宿,方式還是應(yīng)該yi問題持續(xù)中位迂。
備注參考:
https://github.com/ibireme/YYWebImage
http://www.reibang.com/p/5a5bea9180e7
http://www.reibang.com/p/9b71c2d94b89