Blame view

SDWebImage/SDWebImageDownloader.m 10.3 KB
1
/*
2 3
 * This file is part of the SDWebImage package.
 * (c) Olivier Poitrey <rs@dailymotion.com>
4 5 6 7 8
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */
9
#import "SDWebImageDownloader.h"
10
#import "SDWebImageDownloaderOperation.h"
11 12
#import <ImageIO/ImageIO.h>
13 14 15
NSString *const SDWebImageDownloadStartNotification = @"SDWebImageDownloadStartNotification";
NSString *const SDWebImageDownloadStopNotification = @"SDWebImageDownloadStopNotification";
16 17
static NSString *const kProgressCallbackKey = @"progress";
static NSString *const kCompletedCallbackKey = @"completed";
18
19
@interface SDWebImageDownloader ()
20
21
@property (strong, nonatomic) NSOperationQueue *downloadQueue;
22
@property (weak, nonatomic) NSOperation *lastAddedOperation;
23
@property (strong, nonatomic) NSMutableDictionary *URLCallbacks;
24
@property (strong, nonatomic) NSMutableDictionary *HTTPHeaders;
25
// This queue is used to serialize the handling of the network responses of all the download operation in a single queue
26
@property (SDDispatchQueueSetterSementics, nonatomic) dispatch_queue_t barrierQueue;
27
28
@end
29
30
@implementation SDWebImageDownloader
31
32
+ (void)initialize {
33 34
    // Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
    // To use it, just add #import "SDNetworkActivityIndicator.h" in addition to the SDWebImage import
35
    if (NSClassFromString(@"SDNetworkActivityIndicator")) {
36
37 38
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
39
        id activityIndicator = [NSClassFromString(@"SDNetworkActivityIndicator") performSelector:NSSelectorFromString(@"sharedActivityIndicator")];
40
#pragma clang diagnostic pop
41 42 43 44 45

        // Remove observer in case it was previously added.
        [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStartNotification object:nil];
        [[NSNotificationCenter defaultCenter] removeObserver:activityIndicator name:SDWebImageDownloadStopNotification object:nil];
46 47 48 49 50 51 52
        [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
                                                 selector:NSSelectorFromString(@"startActivity")
                                                     name:SDWebImageDownloadStartNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:activityIndicator
                                                 selector:NSSelectorFromString(@"stopActivity")
                                                     name:SDWebImageDownloadStopNotification object:nil];
    }
53 54
}
55
+ (SDWebImageDownloader *)sharedDownloader {
56 57
    static dispatch_once_t once;
    static id instance;
58
    dispatch_once(&once, ^{
59
        instance = [self new];
60
    });
61
    return instance;
62 63
}
64 65
- (id)init {
    if ((self = [super init])) {
66
        _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
67
        _downloadQueue = [NSOperationQueue new];
68
        _downloadQueue.maxConcurrentOperationCount = 2;
69
        _URLCallbacks = [NSMutableDictionary new];
70
        _HTTPHeaders = [NSMutableDictionary dictionaryWithObject:@"image/webp,image/*;q=0.8" forKey:@"Accept"];
71
        _barrierQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderBarrierQueue", DISPATCH_QUEUE_CONCURRENT);
72
        _downloadTimeout = 15.0;
73
    }
74
    return self;
75
}
76
77
- (void)dealloc {
78
    [self.downloadQueue cancelAllOperations];
79
    SDDispatchQueueRelease(_barrierQueue);
80 81
}
82 83
- (void)setValue:(NSString *)value forHTTPHeaderField:(NSString *)field {
    if (value) {
84 85
        self.HTTPHeaders[field] = value;
    }
86
    else {
87 88 89 90
        [self.HTTPHeaders removeObjectForKey:field];
    }
}
91
- (NSString *)valueForHTTPHeaderField:(NSString *)field {
92 93 94
    return self.HTTPHeaders[field];
}
95
- (void)setMaxConcurrentDownloads:(NSInteger)maxConcurrentDownloads {
96
    _downloadQueue.maxConcurrentOperationCount = maxConcurrentDownloads;
97 98
}
99
- (NSUInteger)currentDownloadCount {
100 101 102
    return _downloadQueue.operationCount;
}
103
- (NSInteger)maxConcurrentDownloads {
104
    return _downloadQueue.maxConcurrentOperationCount;
105 106
}
107
- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(void (^)(NSInteger, NSInteger))progressBlock completed:(void (^)(UIImage *, NSData *, NSError *, BOOL))completedBlock {
108
    __block SDWebImageDownloaderOperation *operation;
109
    __weak SDWebImageDownloader *wself = self;
110
111
    [self addProgressCallback:progressBlock andCompletedBlock:completedBlock forURL:url createCallback:^{
112 113 114 115
        NSTimeInterval timeoutInterval = wself.downloadTimeout;
        if (timeoutInterval == 0.0) {
            timeoutInterval = 15.0;
        }
116
117
        // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise
118
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];
119
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
120
        request.HTTPShouldUsePipelining = YES;
121
        if (wself.headersFilter) {
122 123
            request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);
        }
124
        else {
125 126
            request.allHTTPHeaderFields = wself.HTTPHeaders;
        }
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
        operation = [[SDWebImageDownloaderOperation alloc] initWithRequest:request
                                                                   options:options
                                                                  progress:^(NSInteger receivedSize, NSInteger expectedSize) {
                                                                      if (!wself) return;
                                                                      SDWebImageDownloader *sself = wself;
                                                                      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) {
                                                                     if (!wself) return;
                                                                     SDWebImageDownloader *sself = wself;
                                                                     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:^{
                                                                     if (!wself) return;
                                                                     SDWebImageDownloader *sself = wself;
                                                                     [sself removeCallbacksForURL:url];
                                                                 }];
155 156 157 158
        
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        }
159
160
        [wself.downloadQueue addOperation:operation];
161
        if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
162
            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency
163 164
            [wself.lastAddedOperation addDependency:operation];
            wself.lastAddedOperation = operation;
165
        }
166
    }];
167
168 169 170
    return operation;
}
171
- (void)addProgressCallback:(void (^)(NSInteger, NSInteger))progressBlock andCompletedBlock:(void (^)(UIImage *, NSData *data, NSError *, BOOL))completedBlock forURL:(NSURL *)url createCallback:(void (^)())createCallback {
172
    // The URL will be used as the key to the callbacks dictionary so it cannot be nil. If it is nil immediately call the completed block with no image or data.
173 174
    if (url == nil) {
        if (completedBlock != nil) {
175
            completedBlock(nil, nil, nil, NO);
176 177 178
        }
        return;
    }
179 180

    dispatch_barrier_sync(self.barrierQueue, ^{
181
        BOOL first = NO;
182
        if (!self.URLCallbacks[url]) {
183
            self.URLCallbacks[url] = [NSMutableArray new];
184
            first = YES;
185 186
        }
187
        // Handle single download of simultaneous download request for the same URL
188
        NSMutableArray *callbacksForURL = self.URLCallbacks[url];
189
        NSMutableDictionary *callbacks = [NSMutableDictionary new];
190 191
        if (progressBlock) callbacks[kProgressCallbackKey] = [progressBlock copy];
        if (completedBlock) callbacks[kCompletedCallbackKey] = [completedBlock copy];
192 193 194
        [callbacksForURL addObject:callbacks];
        self.URLCallbacks[url] = callbacksForURL;
195
        if (first) {
196
            createCallback();
197
        }
198
    });
199
}
200
201
- (NSArray *)callbacksForURL:(NSURL *)url {
202
    __block NSArray *callbacksForURL;
203
    dispatch_sync(self.barrierQueue, ^{
204 205
        callbacksForURL = self.URLCallbacks[url];
    });
206
    return [callbacksForURL copy];
207 208
}
209 210
- (void)removeCallbacksForURL:(NSURL *)url {
    dispatch_barrier_async(self.barrierQueue, ^{
211 212 213 214
        [self.URLCallbacks removeObjectForKey:url];
    });
}
215
@end