最近回頭一看,發現我們的項目現在對圖片處理都是用YYWebImage 的處理方式方式的,用了不短時間了,卻沒有好好了解下,今天特此學習下。首先然而怎么下手呢?如何提高閱讀源代碼的能力?結合自己,決定在第一篇,帶著一個問題,去簡單了解。
問題:為什么使用下面這個方法去獲取圖片?
- (void)yy_setImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion
先簡單了解下這幾個參數的含義
* imageURL:圖片的URL
* placeholder: 備用圖片
* options: YYWebImageOptions(圖片下載時同時的操作,可以仔細看一下,很強大的
(展示進度,緩存方式,HTPPS的處理,忽略其他,失敗時處理等等))
* progress: 圖片下載的進度(receivedSize/expectedSize)
* transform: 對圖片是否需要處理(大小、圓角之類的添加),為更適應圖片的展示
* completion:完成后,可以了解的信息(url,from,error)
然后進入去看一下詳細的實現
1、圍繞著_YYWebImageSetter進行一系列的判斷。
2、異步中到 YYWebImageManager 的網絡處理
3、同時對 operation 進行處理,在YYWebImageOperation(NSOperation的子類)中。
所以,得先對:YYWebImageManager、YYWebImageOperation 有個大致了解。
YYWebImageManager
用來創建和管理網絡圖片任務的管理器,這個類其實就一個作用,管理生成一個YYWebImageOperation實例
第一,我們從其三個枚舉就可以大致了解它一些東西啦
* YYWebImageOptions //控制圖片請求的模式
* YYWebImageFromType //用來告訴我們圖片來源
* YYWebImageStage //用來告訴我們圖片下載的完成度的
第二,再看看其幾個 Block
* YYWebImageProgressBlock? //從遠程下載完成過程的回調
* YYWebImageTransformBlock //? 圖片從遠程下載完成之前會執行這個block,用來執行一些額外的操作
* YYWebImageCompletionBlock //在當圖片下載完成或者取消的時候調用
第三,看看它的初始化和屬性,有很多,選擇一個最重要的。也就是將上述 枚舉和 block 結合的體現。
- (YYWebImageOperation *)requestImageWithURL:(NSURL *)url
options:(YYWebImageOptions)options
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion;
第四,看這個初始化的實現
- (YYWebImageOperation *)requestImageWithURL:(NSURL *)url
options:(YYWebImageOptions)options
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
//設置request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.timeoutInterval = _timeout;
request.HTTPShouldHandleCookies = (options & YYWebImageOptionHandleCookies) != 0;
request.allHTTPHeaderFields = [self headersForURL:url];
request.HTTPShouldUsePipelining = YES;
// 設置緩存方式
request.cachePolicy = (options & YYWebImageOptionUseNSURLCache) ?
NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
// 生成一個我們需要的YYWebImageOperation對象
YYWebImageOperation *operation = [[YYWebImageOperation alloc] initWithRequest:request
options:options
cache:_cache
cacheKey:[self cacheKeyForURL:url]
progress:progress
transform:transform ? transform : _sharedTransformBlock
completion:completion];
// 如果有用戶名跟密碼,生成系統提供的NSURLCredential
if (_username && _password) {
operation.credential = [NSURLCredential credentialWithUser:_username password:_password persistence:NSURLCredentialPersistenceForSession];
}
// 真正開始操作
if (operation) {
NSOperationQueue *queue = _queue;
if (queue) {
[queue addOperation:operation];
} else {
[operation start];
}
}
return operation;
}
注意 request 各個屬性的設置,特別是緩存策略,后期可以再了解 YYCache,以及NSURLCredential的身份認證。
YYWebImageOperation
YYWebImageOperation 類是NSOperation的子類,用來通過請求獲取圖片。
第一,對operation中一些狀態進行正確的處理
- (void)_finish
- (void)_startOperation
- (void)_startRequest:(id)object
- (void)_cancelOperation
第二,通過不同的方式下載得到圖片進行的處理
//從磁盤緩存中接受圖片
- (void)_didReceiveImageFromDiskCache:(UIImage *)image
// 從網絡下載的圖片
- (void)_didReceiveImageFromWeb:(UIImage *)image
第三,看NSURLColleciton 的代理方法
//即將緩存請求結果
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
//請求已經收到相應
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
//收到數據回調
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
//連接已經結束加載
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
第四,真正重寫的NSOperation的方法,進行處理
- (void)start
- (void)cancel
在此我們可以大致了解作者在對管理下載隊列的時候的想法,以及自定義一個operation時的大致想法。
總的說來,這一篇只是大致的思路,要去看詳細源碼,注意里面一些細節的才是重點,第二篇筆記重點來挖掘細節。
額外問題的發現
1、static 內聯函數的使用 ?
static inline void _yy_dispatch_sync_on_main_queue(void (^block)()) {
if (pthread_main_np()) {
block();
} else {
dispatch_sync(dispatch_get_main_queue(), block);
}
}
簡單理解, 宏一樣的函數,方便使用。深入理解C語言的define和內聯函數
另外也可了解下iOS OC內聯函數 inline。
static 標識此內聯聯函數只能在本文件中使用,限制了內聯函數的作用域。
相對于宏來說,static inline具有和宏同樣級別的開銷,而且還提供了類型安全,沒有長度和格式的具體限制。
Ps: static 函數的用法
static void URLBlacklistInit() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
URLBlacklist = [NSMutableSet new];
URLBlacklistLock = dispatch_semaphore_create(1);
});
}
用 static 這樣表示該函數聲明為內部函數(又叫靜態函數),這樣該函數就只能在其定義所在的文件中使用。如果在不同的文件中有同名的內部函數,則互不干擾。
2、類名和函數名 前面加下劃線的運用 ?
@interface _YYWebImageSetter : NSObject
+ (void)_delaySetActivity:(NSTimer *)timer
- (void)_startOperation
一般是我們是用前綴避免命名空間沖突,如yy_setImageURL:之類的,但是加上下劃線有時個什么情況呢?
而且蘋果公司喜歡單用一個下劃線作為私有的前綴。此時如果我們也這樣想,假如蘋果公司提供的某個類中繼承了某一個子類,那我們在子類里可能會無意間覆寫了父類的同名方法,因此官方推薦是不要但寫一個下劃線做私有方法的前綴。
作者為什么這么寫呢,對于部分變量名,類名還好理解,但是對私有方法的處理,可以仔細看看。
3、objc_getAssociatedObject大量的使用
這個就是為了解決在分類中添加實例變量的快速方法啦。Objective-C Associated Objects 的實現原理
4、@implementation 后直接使用成員變量?
@implementation User : NSObject {
NSString *_name;
}
以前沒有這樣使用過,但是是合法的哦。對比@interface和@implementation
5、自旋鎖 & 遞歸鎖
OSSpinLock(自旋鎖),自旋鎖在保證了多線程同時訪問本類的時候不會導致數據出錯的同時性能高效。
NSRecursiveLock(遞歸鎖),遞歸鎖防止死鎖,因為請求可能是有多個的。
使用案例
static OSSpinLock URLBlacklistLock;//黑名單鎖
/**
*? 把url添加進黑名單
*/
static void URLInBlackListAdd(NSURL *url) {
if (!url || url == (id)[NSNull null]) return;
URLBlacklistInit();
OSSpinLockLock(&URLBlacklistLock);
[URLBlacklist addObject:url];
OSSpinLockUnlock(&URLBlacklistLock);
}
@property (nonatomic, strong) NSRecursiveLock *lock; //遞歸鎖
- (void)dealloc {
[_lock lock];
//
[_lock unlock];
}
了解更多的鎖的信息,可以看看這篇結合 thread 講的 -----Thread基礎知識
備注參考: