1. 程式人生 > >SDWebImage擴充套件二次載入圖片

SDWebImage擴充套件二次載入圖片

隨著網路安全的意識加重,相信很多同學都遇到過圖片二次載入的需求,即第一次請求獲得圖片的URL,第二次請求獲得圖片。在iOS中圖片的載入庫很多,而SDWebImage絕對是其中的翹楚,也是廣大iOSer們開發時候的首選。這裡簡單介紹一下自己在專案中在SDWebImage基礎上擴充套件的圖片二次載入請求。為了不破壞SDWebImage原有的執行緒控制和載入邏輯,所以獲取圖片的URL的執行緒仍然使用繼承SDWebImageOperation的方式來開展,這裡取名為:SDWebImageFetchURLOperation,程式碼如下:

SDWebImageFetchURLOperation.h

/*
 * This file is part of the SDWebImage package.
 * (c) Olivier Poitrey <
[email protected]
> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ #import <Foundation/Foundation.h> #import "SDWebImageDownloader.h" #import "SDWebImageOperation.h" @interface SDWebImageFetchURLOperation : NSOperation <SDWebImageOperation> /** * The request used by the operation's connection. */ @property (strong, nonatomic) NSMutableURLRequest *request; /** * Whether the URL connection should consult the credential storage for authenticating the connection. `YES` by default. * * This is the value that is returned in the `NSURLConnectionDelegate` method `-connectionShouldUseCredentialStorage:`. */ @property (nonatomic, assign) BOOL shouldUseCredentialStorage; /** * The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`. * * This will be overridden by any shared credentials that exist for the username or password of the request URL, if present. */ @property (nonatomic, strong) NSURLCredential *credential; /** * Initializes a `SDWebImageFetchURLOperation` object * * @see SDWebImageDownloaderOperation * * @param request the URL request * @param completedBlock the block executed when the download is done. * @note the completed block is executed on the main queue for success. If errors are found, there is a chance the block will be executed on a background queue * @param cancelBlock the block executed if the download (operation) is cancelled * * @return the initialized instance */ - (id)initWithRequest:(NSMutableURLRequest *)request completed:(SDWebImageFetchURLCompletedBlock)completedBlock cancelled:(SDWebImageNoParamsBlock)cancelBlock; @end

SDWebImageFetchURLOperation.m

/*
 * This file is part of the SDWebImage package.
 * (c) Olivier Poitrey <[email protected]>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

#import "SDWebImageFetchURLOperation.h"
#import "SDWebImageDecoder.h"
#import "UIImage+MultiFormat.h"
#import <ImageIO/ImageIO.h>
#import "SDWebImageManager.h"

@interface SDWebImageFetchURLOperation () <NSURLConnectionDataDelegate>

@property (copy, nonatomic) SDWebImageFetchURLCompletedBlock completedBlock;
@property (copy, nonatomic) SDWebImageNoParamsBlock cancelBlock;

@property (assign, nonatomic, getter = isExecuting) BOOL executing;
@property (assign, nonatomic, getter = isFinished) BOOL finished;
@property (assign, nonatomic) NSInteger expectedSize;
@property (strong, nonatomic) NSMutableData *urlData;
@property (strong, nonatomic) NSURLConnection *connection;
@property (strong, atomic) NSThread *thread;

#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif

@end

@implementation SDWebImageFetchURLOperation {
//    size_t width, height;
    UIImageOrientation orientation;
    BOOL responseFromCached;
}

@synthesize executing = _executing;
@synthesize finished = _finished;

- (id)initWithRequest:(NSMutableURLRequest *)request
            completed:(SDWebImageFetchURLCompletedBlock)completedBlock
            cancelled:(SDWebImageNoParamsBlock)cancelBlock {
    if ((self = [super init])) {
        self.request = request;
        _shouldUseCredentialStorage = YES;
        _completedBlock = [completedBlock copy];
        _cancelBlock = [cancelBlock copy];
        _executing = NO;
        _finished = NO;
        _expectedSize = 0;
        responseFromCached = YES; // Initially wrong until `connection:willCacheResponse:` is called or not called
    }
    return self;
}

- (void)start {

    @synchronized (self) {
        if (self.isCancelled) {
            self.finished = YES;
            [self reset];
            return;
        }
        
        self.executing = YES;
        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
        self.thread = [NSThread currentThread];
    }
    
    [self.connection start];
    
    if (self.connection) {

        [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];
        
        if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
            // Make sure to run the runloop in our background thread so it can process downloaded data
            // Note: we use a timeout to work around an issue with NSURLConnection cancel under iOS 5
            //       not waking up the runloop, leading to dead threads (see https://github.com/rs/SDWebImage/issues/466)
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
        }
        else {
            CFRunLoopRun();
        }
        
        if (!self.isFinished) {
            [self.connection cancel];
            [self connection:self.connection didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorTimedOut userInfo:@{NSURLErrorFailingURLErrorKey : self.request.URL}]];
        }
    }
    else {
        if (self.completedBlock) {
            self.completedBlock(nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);
        }
    }
    
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
        [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId];
        self.backgroundTaskId = UIBackgroundTaskInvalid;
    }
#endif
}

- (void)cancel {
    @synchronized (self) {
        if (self.thread) {
            [self performSelector:@selector(cancelInternalAndStop) onThread:self.thread withObject:nil waitUntilDone:NO];
        }
        else {
            [self cancelInternal];
        }
    }
}

- (void)cancelInternalAndStop {
    if (self.isFinished) return;
    [self cancelInternal];
    CFRunLoopStop(CFRunLoopGetCurrent());
}

- (void)cancelInternal {
    if (self.isFinished) return;
    [super cancel];
    if (self.cancelBlock) self.cancelBlock();
    
    if (self.connection) {
        [self.connection cancel];
        [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
        
        // As we cancelled the connection, its callback won't be called and thus won't
        // maintain the isFinished and isExecuting flags.
        if (self.isExecuting) self.executing = NO;
        if (!self.isFinished) self.finished = YES;
    }
    
    [self reset];
}

- (void)done {
    self.finished = YES;
    self.executing = NO;
    [self reset];
}

- (void)reset {
    self.cancelBlock = nil;
    self.completedBlock = nil;
    self.connection = nil;
    self.urlData = nil;
    self.thread = nil;
}

- (void)setFinished:(BOOL)finished {
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
}

- (void)setExecuting:(BOOL)executing {
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
}

- (BOOL)isConcurrent {
    return YES;
}

#pragma mark NSURLConnection (delegate)

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    if (![response respondsToSelector:@selector(statusCode)] || [((NSHTTPURLResponse *)response) statusCode] < 400) {
        NSInteger expected = response.expectedContentLength > 0 ? (NSInteger)response.expectedContentLength : 0;
        self.expectedSize = expected;
        
        self.urlData = [[NSMutableData alloc] initWithCapacity:expected];
    }
    else {
        [self.connection cancel];
        
        [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:nil];
        
        if (self.completedBlock) {
            self.completedBlock(nil, [NSError errorWithDomain:NSURLErrorDomain code:[((NSHTTPURLResponse *)response) statusCode] userInfo:nil], YES);
        }
        CFRunLoopStop(CFRunLoopGetCurrent());
        [self done];
    }
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    [self.urlData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
    SDWebImageFetchURLCompletedBlock completionBlock = self.completedBlock;
    @synchronized(self) {
        CFRunLoopStop(CFRunLoopGetCurrent());
        self.thread = nil;
        self.connection = nil;
        [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:nil];
    }
    
    if (![[NSURLCache sharedURLCache] cachedResponseForRequest:_request]) {
        responseFromCached = NO;
    }
    
    if (completionBlock)
    {
            NSString *url = [[NSString alloc] initWithData:self.urlData encoding:NSUTF8StringEncoding];
            if (!url) {
                completionBlock(nil, [NSError errorWithDomain:@"SDWebImageErrorDomain" code:0 userInfo:@{NSLocalizedDescriptionKey : @"fetch image url failed"}], YES);
            }
            else {
                completionBlock(url, nil, YES);
            }
    }
    self.completionBlock = nil;
    [self done];
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    CFRunLoopStop(CFRunLoopGetCurrent());
    [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:nil];
    
    if (self.completedBlock) {
        self.completedBlock(nil, error, YES);
    }
    
    [self done];
}

- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse {
    responseFromCached = NO; // If this method is called, it means the response wasn't read from cache
    if (self.request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData) {
        // Prevents caching of responses
        return nil;
    }
    else {
        return cachedResponse;
    }
}

- (BOOL)shouldContinueWhenAppEntersBackground {
    return SDWebImageDownloaderContinueInBackground;
}

- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection {
    return self.shouldUseCredentialStorage;
}

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
    } else {
        if ([challenge previousFailureCount] == 0) {
            if (self.credential) {
                [[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge];
            } else {
                [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
            }
        } else {
            [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
        }
    }
}

@end

其實這部分程式碼基本屬於比葫蘆畫瓢,就是模仿SDWebImageDownloaderOperation來寫的。寫好了獲取圖片URL的Operation,就要處理邏輯方面的東西了。這部分的邏輯主要在SDWebImageDownloader中做修改,修改思路就是在每個下載圖片的operation前面都新增一個獲取圖片的operation,然後讓下載的operation依賴於獲取圖片URL的operation,但是相信大家使用SDWebImage的情形大部分還是在UITabView等列表中,而且圖片下載的資料傳輸遠遠大於獲取一個URL的資料量,所以按照上面的思路來做的話很可能造成大量的獲取圖片URL的operation佔用資源造成圖片下載的不及時,所以這裡把獲取圖片URL的operation優先順序降低,把圖片下載operation的優先順序提高,而圖片下載又依賴於圖片URL的獲取,這樣就基本符合原始SDWebImage的載入邏輯了。下面是邏輯處理程式碼。不對的地方歡迎指正交流。

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
    __block SDWebImageDownloaderOperation *operation;
    __block SDWebImageFetchURLOperation *fetchOperation;
    __weak SDWebImageDownloader *wself = self;
    
    [self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^{
        
        NSTimeInterval timeoutInterval = wself.downloadTimeout;
        if (timeoutInterval == 0.0) {
            timeoutInterval = 15.0;
        }
        
        __block NSURL *mURL = [url copy];
        
        if ([mURL.description hasPrefix:@"xxxxxxxxxxxxxxx"]) {//這裡判斷是不是需要進行二次載入的URL
            NSMutableURLRequest *prerequest = [[NSMutableURLRequest alloc] initWithURL:mURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:5.0];
            [prerequest setHTTPMethod:@"GET"];
            
            fetchOperation = [[SDWebImageFetchURLOperation alloc] initWithRequest:prerequest completed:^(NSString *url, NSError *error, BOOL finished) {
                if (!url) {
                    return ;
                }
                if (operation) {
                    [operation changeURL:[NSURL URLWithString:url]];
                }
            } cancelled:^{
                return ;
            }];
            fetchOperation.queuePriority = NSOperationQueuePriorityLow;//降低獲取URL執行緒優先順序
        }
        
        // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:mURL cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        request.HTTPShouldUsePipelining = YES;
        if (wself.headersFilter) {
            request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
        }
        else {
            request.allHTTPHeaderFields = wself.HTTPHeaders;
        }
        
        
        operation = [[SDWebImageDownloaderOperation alloc] initWithRequest:request
                                                                   options:options
                                                                  progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                                                                      SDWebImageDownloader *sself = wself;
                                                                      if (!sself) return;
                                                                      NSArray *callbacksForURL = [sself callbacksForURL:url];
                                                                      for (NSDictionary *callbacks in callbacksForURL) {
                                                                          SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];
                                                                          if (callback) callback(receivedSize, expectedSize);
                                                                      }
                                                                  }
                                                                 completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
                                                                     SDWebImageDownloader *sself = wself;
                                                                     if (!sself) return;
                                                                     NSArray *callbacksForURL = [sself callbacksForURL:url];
                                                                     if (finished) {
                                                                         [sself removeCallbacksForURL:url];
                                                                     }
                                                                     for (NSDictionary *callbacks in callbacksForURL) {
                                                                         SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];
                                                                         if (callback) callback(image, data, error, finished);
                                                                     }
                                                                 }
                                                                 cancelled:^{
                                                                     SDWebImageDownloader *sself = wself;
                                                                     if (!sself) return;
                                                                     [sself removeCallbacksForURL:url];
                                                                 }];
        
        if (wself.username && wself.password) {
            operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];
        }
        
//        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;//提高下載優先順序
//        } else if (options & SDWebImageDownloaderLowPriority) {
//            operation.queuePriority = NSOperationQueuePriorityLow;
//        }
        
        if (fetchOperation) {//如果有二次載入的operation存在,那麼設定下載和獲取operation的依賴關係並加入執行緒佇列
            [operation addDependency:fetchOperation];
            [wself.downloadQueue addOperation:operation];
            [wself.downloadQueue addOperation:fetchOperation];
        }else{
            [wself.downloadQueue addOperation:operation];
        }
        if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
            if (fetchOperation) {
                [wself.lastAddedOperation addDependency:operation];
                wself.lastAddedOperation = fetchOperation;
            }else{
                [wself.lastAddedOperation addDependency:operation];
                wself.lastAddedOperation = operation;
            }
        }
    }];
    
    return operation;
}


相關推薦

SDWebImage擴充套件載入圖片

隨著網路安全的意識加重,相信很多同學都遇到過圖片二次載入的需求,即第一次請求獲得圖片的URL,第二次請求獲得圖片。在iOS中圖片的載入庫很多,而SDWebImage絕對是其中的翹楚,也是廣大iOSer們開發時候的首選。這裡簡單介紹一下自己在專案中在SDWebImage基礎上

引用百度地圖api載入地圖錯位

所做的網頁需要有百度地圖的功能。 於是引用了百度地圖,但是在初始化地圖的時候遇到了一個問題。 初始化的地圖中心點不正確。所標記的定位如果是第一次載入的話,定位是正確的。 但如果沒有清快取就進行第二次載入的話,這個定位一開始雖然是在地圖的最中間,但是由於初始定位不正確,一開始標記的定位也不

取樣圖片

Mantivity主頁面: package com.bw.ymy.ymy1107; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import

ueditor載入(getEditor)渲染失敗(載入失敗)的原因解決方案

大家自己看看官方的js檔案ueditor.all.js有以下的程式碼 /** * @name getEditor * @since 1.2.4+ * @grammar UE.getEditor(id,[opt]) => E

完美解決ViewPager+Fragment載入空白問題

ViewPager+Fragment使用的還是比較頻繁的,但是當我開啟應用第一次進入時很正常,然而第二次進入的時候卻顯示的是空白,當時感覺很是迷茫,可是仔細一查,原來是第二次載入的時候重複呼叫了onCreateView()這個方法,重新new了一個pageadapter導致子

jQuery DataTables大資料非同步載入渲染及initComplete事件bug

我們在使用dataTables進行資料統計時,不可避免的會碰到對大資料的統計。當進行伺服器端大資料讀取時,毫無疑問的會佔用大量載入時間,拖慢頁面載入速度。為優化頁面載入速度問題,我們便要在將請求中最耗時的部分在頁面載入完成之後,進行二次載入,渲染入資料。 之前

Android-Universal-Image-Loader 學習筆記(載入圖片原理

public void displayImage(String uri, ImageAware imageAware, DisplayImageOptions options, ImageLoadingListener listener, ImageLoadingProgressListener pro

canvas載入圖片需要重新整理的問題

如題:此問題我也經在百度問問上進行了解答。https://zhidao.baidu.com/question/1048045241465845579.html 好吧,難怪現在百度那麼坑人。下面是js程式碼,親測有用。 //canvas載入要修改的圖片function loadSketch(){ v

SDWebImage載入圖片URL第一失敗,後面圖片URL存在不重新整理的問題

業務需求,有時候會首先出現圖片的網路URL地址,但是並沒有顯示出來,使用SDWebImage顯示圖片如下 self.itemImageView sd_setImageWithURL:<#(nullable NSURL *)#>]; 但是發現,後面

微信分享不顯示摘要和圖片的解決方法

conf eight sage 接口 所有 微信公眾平臺 取消 onf split 微信二次分享不顯示摘要和圖片的解決方法 解決不顯示摘要和圖片的問題,需要調用微信公眾號的js-sdk的api ,需要前端和後臺的配合, 後臺需要返回 appid (公眾號的appid )

Revit開發之載入

return mes 刪除 開發 urn com tar 失敗 commit 載入族 此方法載入族無法覆蓋原有族,即若存在相同名稱的族則會載入失敗 1 Family family = null; //族 2 3 Transaction transact

jquery開發之擴充套件物件基元

  (function (window, $, undefined) { var _Core = function () { var eventarr = []; var _OnPageLoad = undefined; ///

圖片採集和壓縮 ---》需要清單檔案宣告註冊讀寫許可權

MainActivity package com.example.renzhili20181107; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitma

圖片取樣

MainActivity頁面 package com.example.image; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import

側滑+fragment切換頁面+fragment巢狀+取樣+輪播圖+gridview展示圖片+網路請求資料+資料庫

全域性配置Appliction 所需要的依賴有:implementation ‘com.google.code.gson:gson:2.8.5’ implementation ‘com.nostra13.universalimageloader:universal-image-loader:

PullToRefreshListView上拉和下拉+輪播圖多條目+fragment巢狀fragment+取樣+側拉點選切換fragment+PullToRefreshGritView圖片展示

側拉 程式碼 1提取的基類 1.1Activity的基類 package com.example.zonghelianxi02.ui.activity; import android.os.Bundle; import android.support.annotation.Nulla

圖片取樣///////////子執行緒

1.提取的基類 在這裡插入程式碼片package com.example.imager_er; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7

fragment中的ImagView+Text多條目,點選ImageView取樣切換相簿圖片

##Fragmentd的 XML: <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android=“http://schemas.and

【CAD開發】-ObjectARX-擴充套件資料 (Xdata)

基本思路: (1)建立一個新專案,命名為Xdata.註冊一個命令AddXData. 實現程式碼為: static void AAAMyGroupAddXData() { // 提示使用者選擇所要新增擴充套件資料的圖形物件 AcDbEntity *pEnt

織夢安裝在二級目錄圖片不顯示的原因

第一種:批量修改域名下所有文章內的圖片路徑。 1、進後臺-核心-批量維護-資料庫內容替換 2、選擇表 dede_addonarticle 3、欄位 body 4、被替換內容: src=”/uploads/ 5、替換為:src=”http://你的域名/uploads