From 57db312cc5d7424f20fb1ff5b772c9a777704bfd Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Sun, 15 Apr 2018 16:40:42 +0800 Subject: [PATCH 1/4] Add `SDWebImageLoader` protocol for custom image loader. Also introduce the `SDWebImageLoadersManager` to manage multiple loaders with priority --- .../SDWebImage Demo/MasterViewController.m | 8 +- SDWebImage.xcodeproj/project.pbxproj | 56 +++++++++ SDWebImage/SDWebImageDownloader.h | 15 ++- SDWebImage/SDWebImageDownloader.m | 49 ++++++++ SDWebImage/SDWebImageLoader.h | 68 +++++++++++ SDWebImage/SDWebImageLoader.m | 9 ++ SDWebImage/SDWebImageLoadersManager.h | 34 ++++++ SDWebImage/SDWebImageLoadersManager.m | 112 ++++++++++++++++++ SDWebImage/SDWebImageManager.h | 5 +- SDWebImage/SDWebImageManager.m | 32 +---- WebImage/SDWebImage.h | 2 + 11 files changed, 352 insertions(+), 38 deletions(-) create mode 100644 SDWebImage/SDWebImageLoader.h create mode 100644 SDWebImage/SDWebImageLoader.m create mode 100644 SDWebImage/SDWebImageLoadersManager.h create mode 100644 SDWebImage/SDWebImageLoadersManager.m diff --git a/Examples/SDWebImage Demo/MasterViewController.m b/Examples/SDWebImage Demo/MasterViewController.m index 8ead3031..45e60953 100644 --- a/Examples/SDWebImage Demo/MasterViewController.m +++ b/Examples/SDWebImage Demo/MasterViewController.m @@ -56,8 +56,10 @@ // HTTP NTLM auth example // Add your NTLM image url to the array below and replace the credentials - [SDWebImageManager sharedManager].imageDownloader.config.username = @"httpwatch"; - [SDWebImageManager sharedManager].imageDownloader.config.password = @"httpwatch01"; + [SDWebImageDownloader sharedDownloader].config.username = @"httpwatch"; + [SDWebImageDownloader sharedDownloader].config.password = @"httpwatch01"; + [[SDWebImageDownloader sharedDownloader] setValue:@"SDWebImage Demo" forHTTPHeaderField:@"AppName"]; + [SDWebImageDownloader sharedDownloader].config.executionOrder = SDWebImageDownloaderLIFOExecutionOrder; self.objects = [NSMutableArray arrayWithObjects: @"http://www.httpwatch.com/httpgallery/authentication/authenticatedimage/default.aspx?0.35786508303135633", // requires HTTP auth, used to demo the NTLM auth @@ -78,8 +80,6 @@ } } - [SDWebImageManager.sharedManager.imageDownloader setValue:@"SDWebImage Demo" forHTTPHeaderField:@"AppName"]; - SDWebImageManager.sharedManager.imageDownloader.config.executionOrder = SDWebImageDownloaderLIFOExecutionOrder; return self; } diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj index 11e46035..9b09d6f3 100644 --- a/SDWebImage.xcodeproj/project.pbxproj +++ b/SDWebImage.xcodeproj/project.pbxproj @@ -51,6 +51,30 @@ 320CAE1E2086F50500CFFC80 /* SDWebImageError.m in Sources */ = {isa = PBXBuildFile; fileRef = 320CAE142086F50500CFFC80 /* SDWebImageError.m */; }; 320CAE1F2086F50500CFFC80 /* SDWebImageError.m in Sources */ = {isa = PBXBuildFile; fileRef = 320CAE142086F50500CFFC80 /* SDWebImageError.m */; }; 320CAE202086F50500CFFC80 /* SDWebImageError.m in Sources */ = {isa = PBXBuildFile; fileRef = 320CAE142086F50500CFFC80 /* SDWebImageError.m */; }; + 321B37812083290E00C0EA77 /* SDWebImageLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B377D2083290D00C0EA77 /* SDWebImageLoader.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 321B37822083290E00C0EA77 /* SDWebImageLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B377D2083290D00C0EA77 /* SDWebImageLoader.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 321B37832083290E00C0EA77 /* SDWebImageLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B377D2083290D00C0EA77 /* SDWebImageLoader.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 321B37842083290E00C0EA77 /* SDWebImageLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B377D2083290D00C0EA77 /* SDWebImageLoader.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 321B37852083290E00C0EA77 /* SDWebImageLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B377D2083290D00C0EA77 /* SDWebImageLoader.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 321B37862083290E00C0EA77 /* SDWebImageLoader.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B377D2083290D00C0EA77 /* SDWebImageLoader.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 321B37872083290E00C0EA77 /* SDWebImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B377E2083290D00C0EA77 /* SDWebImageLoader.m */; }; + 321B37882083290E00C0EA77 /* SDWebImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B377E2083290D00C0EA77 /* SDWebImageLoader.m */; }; + 321B37892083290E00C0EA77 /* SDWebImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B377E2083290D00C0EA77 /* SDWebImageLoader.m */; }; + 321B378A2083290E00C0EA77 /* SDWebImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B377E2083290D00C0EA77 /* SDWebImageLoader.m */; }; + 321B378B2083290E00C0EA77 /* SDWebImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B377E2083290D00C0EA77 /* SDWebImageLoader.m */; }; + 321B378C2083290E00C0EA77 /* SDWebImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B377E2083290D00C0EA77 /* SDWebImageLoader.m */; }; + 321B378D2083290E00C0EA77 /* SDWebImageLoadersManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B377F2083290E00C0EA77 /* SDWebImageLoadersManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 321B378E2083290E00C0EA77 /* SDWebImageLoadersManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B377F2083290E00C0EA77 /* SDWebImageLoadersManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 321B378F2083290E00C0EA77 /* SDWebImageLoadersManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B377F2083290E00C0EA77 /* SDWebImageLoadersManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 321B37902083290E00C0EA77 /* SDWebImageLoadersManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B377F2083290E00C0EA77 /* SDWebImageLoadersManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 321B37912083290E00C0EA77 /* SDWebImageLoadersManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B377F2083290E00C0EA77 /* SDWebImageLoadersManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 321B37922083290E00C0EA77 /* SDWebImageLoadersManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 321B377F2083290E00C0EA77 /* SDWebImageLoadersManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 321B37932083290E00C0EA77 /* SDWebImageLoadersManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B37802083290E00C0EA77 /* SDWebImageLoadersManager.m */; }; + 321B37942083290E00C0EA77 /* SDWebImageLoadersManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B37802083290E00C0EA77 /* SDWebImageLoadersManager.m */; }; + 321B37952083290E00C0EA77 /* SDWebImageLoadersManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B37802083290E00C0EA77 /* SDWebImageLoadersManager.m */; }; + 321B37962083290E00C0EA77 /* SDWebImageLoadersManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B37802083290E00C0EA77 /* SDWebImageLoadersManager.m */; }; + 321B37972083290E00C0EA77 /* SDWebImageLoadersManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B37802083290E00C0EA77 /* SDWebImageLoadersManager.m */; }; + 321B37982083290E00C0EA77 /* SDWebImageLoadersManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 321B37802083290E00C0EA77 /* SDWebImageLoadersManager.m */; }; 321DB3612011D4D70015D2CB /* NSButton+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 321DB35F2011D4D60015D2CB /* NSButton+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; }; 321DB3622011D4D70015D2CB /* NSButton+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 321DB3602011D4D60015D2CB /* NSButton+WebCache.m */; }; 321E60861F38E8C800405457 /* SDWebImageCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 321E60841F38E8C800405457 /* SDWebImageCoder.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -1519,6 +1543,10 @@ 320224BA203979BA00E9F285 /* SDAnimatedImageRep.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDAnimatedImageRep.m; sourceTree = ""; }; 320CAE132086F50500CFFC80 /* SDWebImageError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageError.h; sourceTree = ""; }; 320CAE142086F50500CFFC80 /* SDWebImageError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageError.m; sourceTree = ""; }; + 321B377D2083290D00C0EA77 /* SDWebImageLoader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageLoader.h; sourceTree = ""; }; + 321B377E2083290D00C0EA77 /* SDWebImageLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageLoader.m; sourceTree = ""; }; + 321B377F2083290E00C0EA77 /* SDWebImageLoadersManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageLoadersManager.h; sourceTree = ""; }; + 321B37802083290E00C0EA77 /* SDWebImageLoadersManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageLoadersManager.m; sourceTree = ""; }; 321DB35F2011D4D60015D2CB /* NSButton+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSButton+WebCache.h"; path = "SDWebImage/NSButton+WebCache.h"; sourceTree = ""; }; 321DB3602011D4D60015D2CB /* NSButton+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSButton+WebCache.m"; path = "SDWebImage/NSButton+WebCache.m"; sourceTree = ""; }; 321E60841F38E8C800405457 /* SDWebImageCoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDWebImageCoder.h; sourceTree = ""; }; @@ -2137,6 +2165,10 @@ 32B9B536206ED4230026769D /* SDWebImageDownloaderConfig.m */, 32F21B4F20788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.h */, 32F21B5020788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.m */, + 321B377D2083290D00C0EA77 /* SDWebImageLoader.h */, + 321B377E2083290D00C0EA77 /* SDWebImageLoader.m */, + 321B377F2083290E00C0EA77 /* SDWebImageLoadersManager.h */, + 321B37802083290E00C0EA77 /* SDWebImageLoadersManager.m */, ); name = Downloader; sourceTree = ""; @@ -2346,6 +2378,7 @@ 32B9B53A206ED4230026769D /* SDWebImageDownloaderConfig.h in Headers */, 328BB6AD2081FEE500760D6C /* SDWebImageCacheSerializer.h in Headers */, 80377C4A1F2F666300F89830 /* bit_writer_utils.h in Headers */, + 321B37902083290E00C0EA77 /* SDWebImageLoadersManager.h in Headers */, 4397D2F81D0DF44200BB2784 /* MKAnnotationView+WebCache.h in Headers */, 323F8BE71F38EF770092B609 /* vp8li_enc.h in Headers */, 329A185C1FFF5DFD008C9A2F /* UIImage+WebCache.h in Headers */, @@ -2389,6 +2422,7 @@ 328BB6D02082581100760D6C /* SDMemoryCache.h in Headers */, 321E60891F38E8C800405457 /* SDWebImageCoder.h in Headers */, 00733A721BC4880E00A5A117 /* UIView+WebCacheOperation.h in Headers */, + 321B37842083290E00C0EA77 /* SDWebImageLoader.h in Headers */, 80377C481F2F666300F89830 /* bit_reader_utils.h in Headers */, 80377C511F2F666300F89830 /* huffman_encode_utils.h in Headers */, 32484778201775F600AF9E5A /* SDAnimatedImage.h in Headers */, @@ -2466,6 +2500,7 @@ 4314D16F1D0E0E3B004B36C9 /* NSData+ImageContentType.h in Headers */, 80377C121F2F666300F89830 /* bit_reader_inl_utils.h in Headers */, 4314D1701D0E0E3B004B36C9 /* mux.h in Headers */, + 321B378E2083290E00C0EA77 /* SDWebImageLoadersManager.h in Headers */, 321E60871F38E8C800405457 /* SDWebImageCoder.h in Headers */, 80377EA21F2F66D400F89830 /* vp8i_dec.h in Headers */, 320CAE162086F50500CFFC80 /* SDWebImageError.h in Headers */, @@ -2474,6 +2509,7 @@ 80377C211F2F666300F89830 /* quant_levels_dec_utils.h in Headers */, 4314D1721D0E0E3B004B36C9 /* SDWebImageCompat.h in Headers */, 32484776201775F600AF9E5A /* SDAnimatedImage.h in Headers */, + 321B37822083290E00C0EA77 /* SDWebImageLoader.h in Headers */, 80377C251F2F666300F89830 /* random_utils.h in Headers */, 80377D4F1F2F66A700F89830 /* lossless.h in Headers */, 80377D511F2F66A700F89830 /* msa_macro.h in Headers */, @@ -2528,6 +2564,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + 321B37912083290E00C0EA77 /* SDWebImageLoadersManager.h in Headers */, 80377C791F2F666400F89830 /* utils.h in Headers */, 328BB6A02081FED200760D6C /* SDWebImageCacheKeyFilter.h in Headers */, 323F8B721F38EF770092B609 /* delta_palettization_enc.h in Headers */, @@ -2540,6 +2577,7 @@ 321E608A1F38E8C800405457 /* SDWebImageCoder.h in Headers */, 32484767201775F600AF9E5A /* SDAnimatedImageView+WebCache.h in Headers */, 80377C601F2F666400F89830 /* bit_reader_inl_utils.h in Headers */, + 321B37852083290E00C0EA77 /* SDWebImageLoader.h in Headers */, 329A185D1FFF5DFD008C9A2F /* UIImage+WebCache.h in Headers */, 431BB6DC1D06D2C1006A3455 /* UIButton+WebCache.h in Headers */, 431BB6E11D06D2C1006A3455 /* SDWebImage.h in Headers */, @@ -2634,6 +2672,7 @@ 32B9B53C206ED4230026769D /* SDWebImageDownloaderConfig.h in Headers */, 328BB6AF2081FEE500760D6C /* SDWebImageCacheSerializer.h in Headers */, 4397D2BA1D0DDD8C00BB2784 /* demux.h in Headers */, + 321B37922083290E00C0EA77 /* SDWebImageLoadersManager.h in Headers */, 80377C8F1F2F666400F89830 /* rescaler_utils.h in Headers */, 4397D2BD1D0DDD8C00BB2784 /* types.h in Headers */, 4397D2C01D0DDD8C00BB2784 /* SDWebImage.h in Headers */, @@ -2677,6 +2716,7 @@ 328BB6D22082581100760D6C /* SDMemoryCache.h in Headers */, 323F8BDD1F38EF770092B609 /* vp8i_enc.h in Headers */, 323F8B671F38EF770092B609 /* cost_enc.h in Headers */, + 321B37862083290E00C0EA77 /* SDWebImageLoader.h in Headers */, 80377EE11F2F66D500F89830 /* vp8_dec.h in Headers */, 80377EE41F2F66D500F89830 /* vp8li_dec.h in Headers */, 80377C931F2F666400F89830 /* utils.h in Headers */, @@ -2733,6 +2773,7 @@ 32B9B539206ED4230026769D /* SDWebImageDownloaderConfig.h in Headers */, 328BB6AC2081FEE500760D6C /* SDWebImageCacheSerializer.h in Headers */, 80377C301F2F666300F89830 /* bit_writer_utils.h in Headers */, + 321B378F2083290E00C0EA77 /* SDWebImageLoadersManager.h in Headers */, 431739541CDFC8B70008FEB9 /* types.h in Headers */, 323F8BE61F38EF770092B609 /* vp8li_enc.h in Headers */, 329A185B1FFF5DFD008C9A2F /* UIImage+WebCache.h in Headers */, @@ -2776,6 +2817,7 @@ 328BB6CF2082581100760D6C /* SDMemoryCache.h in Headers */, 321E60881F38E8C800405457 /* SDWebImageCoder.h in Headers */, 4A2CAE371AB4BB7500B6BC39 /* UIView+WebCacheOperation.h in Headers */, + 321B37832083290E00C0EA77 /* SDWebImageLoader.h in Headers */, 80377C2E1F2F666300F89830 /* bit_reader_utils.h in Headers */, 80377C371F2F666300F89830 /* huffman_encode_utils.h in Headers */, 32484777201775F600AF9E5A /* SDAnimatedImage.h in Headers */, @@ -2868,6 +2910,7 @@ 321E60BE1F38E91700405457 /* UIImage+ForceDecode.h in Headers */, 5376131E155AD0D5005750A4 /* SDWebImagePrefetcher.h in Headers */, 32F7C06F2030114C00873181 /* SDWebImageTransformer.h in Headers */, + 321B378D2083290E00C0EA77 /* SDWebImageLoadersManager.h in Headers */, 324DF4B4200A14DC008A84CC /* SDWebImageDefine.h in Headers */, 80377CE11F2F66A100F89830 /* common_sse2.h in Headers */, 80377C0B1F2F665300F89830 /* random_utils.h in Headers */, @@ -2892,6 +2935,7 @@ 323F8BE41F38EF770092B609 /* vp8li_enc.h in Headers */, 320CAE152086F50500CFFC80 /* SDWebImageError.h in Headers */, 323F8B861F38EF770092B609 /* histogram_enc.h in Headers */, + 321B37812083290E00C0EA77 /* SDWebImageLoader.h in Headers */, 323F8BF61F38EF770092B609 /* animi.h in Headers */, 321E60861F38E8C800405457 /* SDWebImageCoder.h in Headers */, 321E60B01F38E90100405457 /* SDWebImageWebPCoder.h in Headers */, @@ -3193,6 +3237,7 @@ 80377DD71F2F66A700F89830 /* lossless_sse2.c in Sources */, 80377DA81F2F66A700F89830 /* alpha_processing_mips_dsp_r2.c in Sources */, 80377DB11F2F66A700F89830 /* cost_mips_dsp_r2.c in Sources */, + 321B37962083290E00C0EA77 /* SDWebImageLoadersManager.m in Sources */, 80377C581F2F666300F89830 /* random_utils.c in Sources */, 323F8B591F38EF770092B609 /* config_enc.c in Sources */, 80377DE91F2F66A700F89830 /* yuv_mips32.c in Sources */, @@ -3252,6 +3297,7 @@ 80377C561F2F666300F89830 /* quant_levels_utils.c in Sources */, 323F8BCF1F38EF770092B609 /* token_enc.c in Sources */, 80377DD11F2F66A700F89830 /* lossless_enc_sse2.c in Sources */, + 321B378A2083290E00C0EA77 /* SDWebImageLoader.m in Sources */, 32484772201775F600AF9E5A /* SDAnimatedImage.m in Sources */, 323F8C1D1F38EF770092B609 /* muxread.c in Sources */, 807A12311F89636300EC2A9B /* SDWebImageCodersManager.m in Sources */, @@ -3395,6 +3441,7 @@ 80377E9C1F2F66D400F89830 /* idec_dec.c in Sources */, 323F8B7B1F38EF770092B609 /* frame_enc.c in Sources */, 80377D211F2F66A700F89830 /* alpha_processing_sse41.c in Sources */, + 321B37942083290E00C0EA77 /* SDWebImageLoadersManager.m in Sources */, 323F8B8D1F38EF770092B609 /* iterator_enc.c in Sources */, 3248475E201775F600AF9E5A /* SDAnimatedImageView.m in Sources */, 80377D481F2F66A700F89830 /* lossless_enc_sse41.c in Sources */, @@ -3455,6 +3502,7 @@ 80377E991F2F66D400F89830 /* buffer_dec.c in Sources */, 80377C201F2F666300F89830 /* quant_levels_dec_utils.c in Sources */, 32F7C07F2030719600873181 /* UIImage+Transform.m in Sources */, + 321B37882083290E00C0EA77 /* SDWebImageLoader.m in Sources */, 80377D471F2F66A700F89830 /* lossless_enc_sse2.c in Sources */, 80377C1E1F2F666300F89830 /* huffman_utils.c in Sources */, ); @@ -3559,6 +3607,7 @@ 80377DF01F2F66A800F89830 /* alpha_processing_sse41.c in Sources */, 80377ECC1F2F66D500F89830 /* idec_dec.c in Sources */, 323F8B7E1F38EF770092B609 /* frame_enc.c in Sources */, + 321B37972083290E00C0EA77 /* SDWebImageLoadersManager.m in Sources */, 80377E171F2F66A800F89830 /* lossless_enc_sse41.c in Sources */, 32484761201775F600AF9E5A /* SDAnimatedImageView.m in Sources */, 323F8B901F38EF770092B609 /* iterator_enc.c in Sources */, @@ -3619,6 +3668,7 @@ 80377EC91F2F66D500F89830 /* buffer_dec.c in Sources */, 80377C6C1F2F666400F89830 /* huffman_utils.c in Sources */, 32F7C0822030719600873181 /* UIImage+Transform.m in Sources */, + 321B378B2083290E00C0EA77 /* SDWebImageLoader.m in Sources */, 80377E161F2F66A800F89830 /* lossless_enc_sse2.c in Sources */, 431BB6C71D06D2C1006A3455 /* UIImageView+HighlightedWebCache.m in Sources */, ); @@ -3707,6 +3757,7 @@ 32F21B5C20788D8C0036B1D5 /* SDWebImageDownloaderRequestModifier.m in Sources */, 32D122292080B2EB003685A3 /* SDWebImageCache.m in Sources */, 80377E3B1F2F66A800F89830 /* cost_mips_dsp_r2.c in Sources */, + 321B378C2083290E00C0EA77 /* SDWebImageLoader.m in Sources */, 4397D29B1D0DDD8C00BB2784 /* SDWebImageDownloader.m in Sources */, 80377E711F2F66A800F89830 /* upsampling.c in Sources */, 328BB6A72081FED200760D6C /* SDWebImageCacheKeyFilter.m in Sources */, @@ -3784,6 +3835,7 @@ 323F8B851F38EF770092B609 /* histogram_enc.c in Sources */, 80377EE51F2F66D500F89830 /* webp_dec.c in Sources */, 4397D2B01D0DDD8C00BB2784 /* SDImageCache.m in Sources */, + 321B37982083290E00C0EA77 /* SDWebImageLoadersManager.m in Sources */, 32F7C07A2030114C00873181 /* SDWebImageTransformer.m in Sources */, 80377E4F1F2F66A800F89830 /* enc_sse41.c in Sources */, 80377E701F2F66A800F89830 /* upsampling_sse2.c in Sources */, @@ -3855,6 +3907,7 @@ 80377D921F2F66A700F89830 /* lossless_sse2.c in Sources */, 80377D631F2F66A700F89830 /* alpha_processing_mips_dsp_r2.c in Sources */, 80377D6C1F2F66A700F89830 /* cost_mips_dsp_r2.c in Sources */, + 321B37952083290E00C0EA77 /* SDWebImageLoadersManager.m in Sources */, 4A2CAE361AB4BB7500B6BC39 /* UIImageView+WebCache.m in Sources */, 323F8B581F38EF770092B609 /* config_enc.c in Sources */, 43C892A21D9D6DDD0022038D /* demux.c in Sources */, @@ -3914,6 +3967,7 @@ 4A2CAE191AB4BB6400B6BC39 /* SDWebImageCompat.m in Sources */, 80377DA11F2F66A700F89830 /* upsampling_sse2.c in Sources */, 323F8BCE1F38EF770092B609 /* token_enc.c in Sources */, + 321B37892083290E00C0EA77 /* SDWebImageLoader.m in Sources */, 32484771201775F600AF9E5A /* SDAnimatedImage.m in Sources */, 80377C3C1F2F666300F89830 /* quant_levels_utils.c in Sources */, 323F8C1C1F38EF770092B609 /* muxread.c in Sources */, @@ -4023,6 +4077,7 @@ 80377CD91F2F66A100F89830 /* alpha_processing_mips_dsp_r2.c in Sources */, 80377CE21F2F66A100F89830 /* cost_mips_dsp_r2.c in Sources */, 5376130B155AD0D5005750A4 /* SDWebImageDownloader.m in Sources */, + 321B37932083290E00C0EA77 /* SDWebImageLoadersManager.m in Sources */, 323F8B561F38EF770092B609 /* config_enc.c in Sources */, 43C8929B1D9D6DD70022038D /* demux.c in Sources */, 80377D1A1F2F66A100F89830 /* yuv_mips32.c in Sources */, @@ -4082,6 +4137,7 @@ 80377D171F2F66A100F89830 /* upsampling_sse2.c in Sources */, 323F8BCC1F38EF770092B609 /* token_enc.c in Sources */, 80377C081F2F665300F89830 /* quant_levels_utils.c in Sources */, + 321B37872083290E00C0EA77 /* SDWebImageLoader.m in Sources */, 3248476F201775F600AF9E5A /* SDAnimatedImage.m in Sources */, 323F8C1A1F38EF770092B609 /* muxread.c in Sources */, 807A122E1F89636300EC2A9B /* SDWebImageCodersManager.m in Sources */, diff --git a/SDWebImage/SDWebImageDownloader.h b/SDWebImage/SDWebImageDownloader.h index 23bc0d66..37050719 100644 --- a/SDWebImage/SDWebImageDownloader.h +++ b/SDWebImage/SDWebImageDownloader.h @@ -12,6 +12,7 @@ #import "SDWebImageOperation.h" #import "SDWebImageDownloaderConfig.h" #import "SDWebImageDownloaderRequestModifier.h" +#import "SDWebImageLoader.h" typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) { /** @@ -86,9 +87,8 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) { FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStartNotification; FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageDownloadStopNotification; -typedef void(^SDWebImageDownloaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL); - -typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished); +typedef SDWebImageLoaderProgressBlock SDWebImageDownloaderProgressBlock; +typedef SDWebImageLoaderCompletedBlock SDWebImageDownloaderCompletedBlock; /** * A token associated with each download. Can be used to cancel a download @@ -245,3 +245,12 @@ typedef void(^SDWebImageDownloaderCompletedBlock)(UIImage * _Nullable image, NSD - (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations; @end + + +/** + SDWebImageDownloader is the built-in image downloader conform to `SDWebImageLoader`. Which provide the HTTP/HTTPS/FTP download, or local file URL using NSURLSession. + However, this downloader does also support customization for advanced users. You can specify `operationClass` in download config to custom download operation, See `SDWebImageDownloaderOperation`. + */ +@interface SDWebImageDownloader (SDWebImageLoader) + +@end diff --git a/SDWebImage/SDWebImageDownloader.m b/SDWebImage/SDWebImageDownloader.m index 1c47878d..d17a6b54 100644 --- a/SDWebImage/SDWebImageDownloader.m +++ b/SDWebImage/SDWebImageDownloader.m @@ -462,3 +462,52 @@ didReceiveResponse:(NSURLResponse *)response } @end + +@implementation SDWebImageDownloader (SDWebImageLoader) + +- (BOOL)canLoadWithURL:(NSURL *)url { + if (!url) { + return NO; + } + Class operationClass = self.config.operationClass; + if (!operationClass || [operationClass isSubclassOfClass:[SDWebImageDownloaderOperation class]]) { + // Built-in download operation class, checking all supported NSURLProtocol + NSURLRequest *request = [NSURLRequest requestWithURL:url]; + NSArray *protocolClasses = self.sessionConfiguration.protocolClasses; + for (Class protocolClass in protocolClasses) { + if ([protocolClass isSubclassOfClass:[NSURLProtocol class]]) { + BOOL canLoad = [protocolClass canInitWithRequest:request]; + if (canLoad) { + return YES; + } + continue; + } + } + return NO; + } + // Custom download operation class may not dependent on NSURLSession, always pass YES. + return YES; +} + +- (id)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageLoaderProgressBlock)progressBlock completed:(SDWebImageLoaderCompletedBlock)completedBlock context:(SDWebImageContext *)context { + SDWebImageDownloaderOptions downloaderOptions = 0; + if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority; + if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload; + if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache; + if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground; + if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies; + if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates; + if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority; + if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages; + + if (options & SDWebImageRefreshCached) { + // force progressive off if image already cached but forced refreshing + downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; + // ignore image read from NSURLCache if image if cached but force refreshing + downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; + } + + return [self downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:completedBlock]; +} + +@end diff --git a/SDWebImage/SDWebImageLoader.h b/SDWebImage/SDWebImageLoader.h new file mode 100644 index 00000000..6c891b6b --- /dev/null +++ b/SDWebImage/SDWebImageLoader.h @@ -0,0 +1,68 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "SDWebImageCompat.h" +#import "SDWebImageDefine.h" +#import "SDWebImageOperation.h" + +typedef void(^SDWebImageLoaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL); +typedef void(^SDWebImageLoaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished); +typedef void(^SDWebImageLoaderDataCompletedBlock)(NSData * _Nullable data, NSError * _Nullable error, BOOL finished); + +/** + A `SDImageCacheType` value to specify the cache type information from manager. `SDWebImageManager` will firstly query cache, then if cache miss or `SDWebImageRefreshCached` is set, it will start image loader to load the image. + This can be a hint for image loader to load the image from network and refresh the image from remote location if needed. (NSNumber) + */ +FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextLoaderCacheType; + +@protocol SDWebImageLoader + +- (BOOL)canLoadWithURL:(nullable NSURL *)url; + +// We provide two ways to allow a image loader to load the image. +// The first one should return the `UIImage` image instance as well as `NSData` image data. This is suitable for the use case such as progressive download from network, or image directlly from Photos framework. +// The second one should return just the `NSData` image data, we will use the common image decoding logic to process the correct image instance, so the image loader itself can concentrate on only data retriving. This is suitable for the use case such as load the data from file. +// Your image loader **MUST** implement at least one of those protocol, or an assert will occur. We will firstlly ask for `loadImageWithURL:options:progress:completed:context:` if you implement it. If this one return nil, we will continue to ask for `loadImageDataWithURL:options:progress:completed:context:` if you implement it. +// @note It's your responsibility to load the image in the desired global queue(to avoid block main queue). We do not dispatch these method call in a global queue but just from the call queue (For `SDWebImageManager`, it typically call from the main queue). + +@optional +/** + Load the image and image data with the given URL and return the image data. You're responsible for producing the image instance. + + @param url The URL represent the image. Note this may not be a HTTP URL + @param options A mask to specify options to use for this request + @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. + @param progressBlock A block called while image is downloading + * @note the progress block is executed on a background queue + @param completedBlock A block called when operation has been completed. + @return An operation which allow the user to cancel the current request. + */ +- (nullable id)loadImageWithURL:(nullable NSURL *)url + options:(SDWebImageOptions)options + context:(nullable SDWebImageContext *)context + progress:(nullable SDWebImageLoaderProgressBlock)progressBlock + completed:(nullable SDWebImageLoaderCompletedBlock)completedBlock; + +/** + Load the image with the given URL and return the image data. We will automatically handler the image decoding stuff for you. + + @param url The URL represent the image. Note this may not be a HTTP URL + @param options A mask to specify options to use for this request + @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. + @param progressBlock A block called while image is downloading + * @note the progress block is executed on a background queue + @param completedBlock A block called when operation has been completed. + @return An operation which allow the user to cancel the current request. + */ +- (nullable id)loadImageDataWithURL:(nullable NSURL *)url + options:(SDWebImageOptions)options + context:(nullable SDWebImageContext *)context + progress:(nullable SDWebImageLoaderProgressBlock)progressBlock + completed:(nullable SDWebImageLoaderDataCompletedBlock)completedBlock; + +@end diff --git a/SDWebImage/SDWebImageLoader.m b/SDWebImage/SDWebImageLoader.m new file mode 100644 index 00000000..2f669be0 --- /dev/null +++ b/SDWebImage/SDWebImageLoader.m @@ -0,0 +1,9 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "SDWebImageLoader.h" diff --git a/SDWebImage/SDWebImageLoadersManager.h b/SDWebImage/SDWebImageLoadersManager.h new file mode 100644 index 00000000..7e070f2c --- /dev/null +++ b/SDWebImage/SDWebImageLoadersManager.h @@ -0,0 +1,34 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "SDWebImageLoader.h" + +@interface SDWebImageLoadersManager : NSObject + +@property (nonatomic, class, readonly, nonnull) SDWebImageLoadersManager *sharedManager; + +/** + All image loaders in manager. The loaders array is a priority queue, which means the later added loader will have the highest priority + */ +@property (nonatomic, strong, readwrite, nullable) NSArray>* loaders; + +/** + Add a new image loader to the end of loaders array. Which has the highest priority. + + @param loader loader + */ +- (void)addLoader:(nonnull id)loader; + +/** + Remove a image loader in the loaders array. + + @param loader loader + */ +- (void)removeLoader:(nonnull id)loader; + +@end diff --git a/SDWebImage/SDWebImageLoadersManager.m b/SDWebImage/SDWebImageLoadersManager.m new file mode 100644 index 00000000..8343b12b --- /dev/null +++ b/SDWebImage/SDWebImageLoadersManager.m @@ -0,0 +1,112 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "SDWebImageLoadersManager.h" +#import "SDWebImageDownloader.h" + +#define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); +#define UNLOCK(lock) dispatch_semaphore_signal(lock); + +@interface SDWebImageLoadersManager () + +@property (strong, nonatomic, nonnull) NSMutableArray> *mutableLoaders; +@property (nonatomic, strong, nonnull) dispatch_semaphore_t loadersLock; + +@end + +@implementation SDWebImageLoadersManager + ++ (SDWebImageLoadersManager *)sharedManager { + static dispatch_once_t onceToken; + static SDWebImageLoadersManager *manager; + dispatch_once(&onceToken, ^{ + manager = [[SDWebImageLoadersManager alloc] init]; + }); + return manager; +} + +- (instancetype)init { + self = [super init]; + if (self) { + // initialize with default image loaders + self.mutableLoaders = [@[[SDWebImageDownloader sharedDownloader]] mutableCopy]; + self.loadersLock = dispatch_semaphore_create(1); + } + return self; +} + +#pragma mark - Loader Property + +- (void)addLoader:(id)loader { + if ([loader conformsToProtocol:@protocol(SDWebImageLoader)]) { + LOCK(self.loadersLock); + [self.mutableLoaders addObject:loader]; + UNLOCK(self.loadersLock); + } +} + +- (void)removeLoader:(id)loader { + LOCK(self.loadersLock); + [self.mutableLoaders removeObject:loader]; + UNLOCK(self.loadersLock); +} + +- (NSArray> *)loaders { + NSArray> *sortedLoaders; + LOCK(self.loadersLock); + sortedLoaders = [[[self.mutableLoaders copy] reverseObjectEnumerator] allObjects]; + UNLOCK(self.loadersLock); + return sortedLoaders; +} + +- (void)setLoaders:(NSArray> *)loaders { + LOCK(self.loadersLock); + self.mutableLoaders = [loaders mutableCopy]; + UNLOCK(self.loadersLock); +} + +#pragma mark - SDWebImageLoader + +- (BOOL)canLoadWithURL:(nullable NSURL *)url { + for (id loader in self.loaders) { + if ([loader canLoadWithURL:url]) { + return YES; + } + } + return NO; +} + +- (id)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDWebImageLoaderProgressBlock)progressBlock completed:(SDWebImageLoaderCompletedBlock)completedBlock { + if (!url) { + return nil; + } + for (id loader in self.loaders) { + if ([loader respondsToSelector:@selector(loadImageWithURL:options:context:progress:completed:)]) { + if ([loader canLoadWithURL:url]) { + return [loader loadImageWithURL:url options:options context:context progress:progressBlock completed:completedBlock]; + } + } + } + return nil; +} + +- (id)loadImageDataWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDWebImageLoaderProgressBlock)progressBlock completed:(SDWebImageLoaderDataCompletedBlock)completedBlock { + if (!url) { + return nil; + } + for (id loader in self.loaders) { + if ([loader respondsToSelector:@selector(loadImageDataWithURL:options:context:progress:completed:)]) { + if ([loader canLoadWithURL:url]) { + return [loader loadImageDataWithURL:url options:options context:context progress:progressBlock completed:completedBlock]; + } + } + } + return nil; +} + +@end diff --git a/SDWebImage/SDWebImageManager.h b/SDWebImage/SDWebImageManager.h index 6f338d95..39c31f3c 100644 --- a/SDWebImage/SDWebImageManager.h +++ b/SDWebImage/SDWebImageManager.h @@ -13,6 +13,7 @@ #import "SDWebImageTransformer.h" #import "SDWebImageCacheKeyFilter.h" #import "SDWebImageCacheSerializer.h" +#import "SDWebImageLoader.h" typedef void(^SDExternalCompletionBlock)(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL); @@ -105,7 +106,7 @@ SDWebImageManager *manager = [SDWebImageManager sharedManager]; * The image downloader used by manager to download image. * @note If you specify a non-shared downloader, don't forget to call `invalidateSessionAndCancel:` at proper time to avoid memory leak. */ -@property (strong, nonatomic, readonly, nonnull) SDWebImageDownloader *imageDownloader; +@property (strong, nonatomic, readonly, nonnull) id imageDownloader; /** The image transformer for manager. It's used for image transform after the image load finished and store the transformed image to cache, see `SDWebImageTransformer`. @@ -175,7 +176,7 @@ SDWebImageManager *manager = [SDWebImageManager sharedManager]; * Allows to specify instance of cache and image downloader used with image manager. * @return new instance of `SDWebImageManager` with specified cache and downloader. */ -- (nonnull instancetype)initWithCache:(nonnull id)cache downloader:(nonnull SDWebImageDownloader *)downloader NS_DESIGNATED_INITIALIZER; +- (nonnull instancetype)initWithCache:(nonnull id)cache downloader:(nonnull id)downloader NS_DESIGNATED_INITIALIZER; /** * Downloads the image at the given URL if not present in cache or return the cached version otherwise. diff --git a/SDWebImage/SDWebImageManager.m b/SDWebImage/SDWebImageManager.m index 84c72340..c35bb07e 100644 --- a/SDWebImage/SDWebImageManager.m +++ b/SDWebImage/SDWebImageManager.m @@ -28,7 +28,7 @@ static SDWebImageDownloader *_defaultImageDownloader; @interface SDWebImageManager () @property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache; -@property (strong, nonatomic, readwrite, nonnull) SDWebImageDownloader *imageDownloader; +@property (strong, nonatomic, readwrite, nonnull) id imageDownloader; @property (strong, nonatomic, nonnull) NSMutableSet *failedURLs; @property (strong, nonatomic, nonnull) NSMutableArray *runningOperations; @@ -79,7 +79,7 @@ static SDWebImageDownloader *_defaultImageDownloader; return [self initWithCache:cache downloader:downloader]; } -- (nonnull instancetype)initWithCache:(nonnull id)cache downloader:(nonnull SDWebImageDownloader *)downloader { +- (nonnull instancetype)initWithCache:(nonnull id)cache downloader:(nonnull id)downloader { if ((self = [super init])) { _imageCache = cache; _imageDownloader = downloader; @@ -201,30 +201,10 @@ static SDWebImageDownloader *_defaultImageDownloader; // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; } - - // download if no image or requested to refresh anyway, and download allowed by delegate - SDWebImageDownloaderOptions downloaderOptions = 0; - if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority; - if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload; - if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache; - if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground; - if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies; - if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates; - if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority; - if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages; - if (options & SDWebImageDecodeFirstFrameOnly) downloaderOptions |= SDWebImageDownloaderDecodeFirstFrameOnly; - if (options & SDWebImagePreloadAllFrames) downloaderOptions |= SDWebImageDownloaderPreloadAllFrames; - - if (cachedImage && options & SDWebImageRefreshCached) { - // force progressive off if image already cached but forced refreshing - downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; - // ignore image read from NSURLCache if image if cached but force refreshing - downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse; - } // `SDWebImageCombinedOperation` -> `SDWebImageDownloadToken` -> `downloadOperationCancelToken`, which is a `SDCallbacksDictionary` and retain the completed block below, so we need weak-strong again to avoid retain cycle __weak typeof(strongOperation) weakSubOperation = strongOperation; - strongOperation.downloadOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { + strongOperation.downloadOperation = [self.imageDownloader loadImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { __strong typeof(weakSubOperation) strongSubOperation = weakSubOperation; if (!strongSubOperation || strongSubOperation.isCancelled) { // Do nothing if the operation was cancelled @@ -264,12 +244,6 @@ static SDWebImageDownloader *_defaultImageDownloader; if (options & SDWebImageCacheMemoryOnly) { storeCacheType = SDImageCacheTypeMemory; } - - // We've done the scale process in SDWebImageDownloader with the shared manager, this is used for custom manager and avoid extra scale. - if (self != [SDWebImageManager sharedManager] && cacheKeyFilter && downloadedImage && ![downloadedImage conformsToProtocol:@protocol(SDAnimatedImage)]) { - downloadedImage = [self scaledImageForKey:key image:downloadedImage]; - } - if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) { // Image refresh hit the NSURLCache cache, do not call the completion block } else if (downloadedImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)) && transformer) { diff --git a/WebImage/SDWebImage.h b/WebImage/SDWebImage.h index a98f0e97..bdc2df13 100644 --- a/WebImage/SDWebImage.h +++ b/WebImage/SDWebImage.h @@ -36,6 +36,8 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[]; #import #import #import +#import +#import #import #import #import From 8292c0c1e9c669c3b8c2dcf139706ce4d9c722da Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 16 Apr 2018 00:23:03 +0800 Subject: [PATCH 2/4] Update the loader protocol to refactor the common image decoding process. --- SDWebImage/SDWebImageDownloader.m | 8 +- SDWebImage/SDWebImageDownloaderOperation.m | 99 ++++--------------- SDWebImage/SDWebImageLoader.h | 41 +++++++- SDWebImage/SDWebImageLoader.m | 106 +++++++++++++++++++++ SDWebImage/SDWebImageManager.m | 12 ++- 5 files changed, 176 insertions(+), 90 deletions(-) diff --git a/SDWebImage/SDWebImageDownloader.m b/SDWebImage/SDWebImageDownloader.m index d17a6b54..f42568c2 100644 --- a/SDWebImage/SDWebImageDownloader.m +++ b/SDWebImage/SDWebImageDownloader.m @@ -489,7 +489,11 @@ didReceiveResponse:(NSURLResponse *)response return YES; } -- (id)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageLoaderProgressBlock)progressBlock completed:(SDWebImageLoaderCompletedBlock)completedBlock context:(SDWebImageContext *)context { +- (id)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDWebImageLoaderProgressBlock)progressBlock completed:(SDWebImageLoaderCompletedBlock)completedBlock { + UIImage *cachedImage; + if ([context valueForKey:SDWebImageContextLoaderCachedImage]) { + cachedImage = [context valueForKey:SDWebImageContextLoaderCachedImage]; + } SDWebImageDownloaderOptions downloaderOptions = 0; if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority; if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload; @@ -500,7 +504,7 @@ didReceiveResponse:(NSURLResponse *)response if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority; if (options & SDWebImageScaleDownLargeImages) downloaderOptions |= SDWebImageDownloaderScaleDownLargeImages; - if (options & SDWebImageRefreshCached) { + if (cachedImage && options & SDWebImageRefreshCached) { // force progressive off if image already cached but forced refreshing downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload; // ignore image read from NSURLCache if image if cached but force refreshing diff --git a/SDWebImage/SDWebImageDownloaderOperation.m b/SDWebImage/SDWebImageDownloaderOperation.m index 511cf8e2..db177345 100644 --- a/SDWebImage/SDWebImageDownloaderOperation.m +++ b/SDWebImage/SDWebImageDownloaderOperation.m @@ -7,12 +7,7 @@ */ #import "SDWebImageDownloaderOperation.h" -#import "SDWebImageManager.h" -#import "NSImage+Additions.h" -#import "UIImage+WebCache.h" #import "SDWebImageCodersManager.h" -#import "SDWebImageCoderHelper.h" -#import "SDAnimatedImage.h" #import "SDWebImageError.h" #define LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); @@ -46,7 +41,6 @@ typedef NSMutableDictionary SDCallbacksDictionary; @property (assign, nonatomic, getter = isFinished) BOOL finished; @property (strong, nonatomic, nullable) NSMutableData *imageData; @property (copy, nonatomic, nullable) NSData *cachedData; // for `SDWebImageDownloaderIgnoreCachedResponse` -@property (copy, nonatomic, nullable) NSString *cacheKey; @property (assign, nonatomic, readwrite) NSUInteger expectedSize; @property (strong, nonatomic, nullable, readwrite) NSURLResponse *response; @@ -357,42 +351,11 @@ didReceiveResponse:(NSURLResponse *)response } } } - [self.progressiveCoder updateIncrementalData:imageData finished:finished]; // progressive decode the image in coder queue dispatch_async(self.coderQueue, ^{ - // check whether we should use `SDAnimatedImage` - UIImage *image; - BOOL decodeFirstFrame = self.options & SDWebImageDownloaderDecodeFirstFrameOnly; - NSNumber *scaleValue = [self.context valueForKey:SDWebImageContextImageScaleFactor]; - CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(self.cacheKey); - if (!decodeFirstFrame) { - // check whether we should use `SDAnimatedImage` - if ([self.context valueForKey:SDWebImageContextAnimatedImageClass]) { - Class animatedImageClass = [self.context valueForKey:SDWebImageContextAnimatedImageClass]; - if ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)] && [self.progressiveCoder conformsToProtocol:@protocol(SDWebImageAnimatedCoder)]) { - image = [[animatedImageClass alloc] initWithAnimatedCoder:(id)self.progressiveCoder scale:scale]; - } - } - } - if (!image) { - image = [self.progressiveCoder incrementalDecodedImageWithOptions:@{SDWebImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDWebImageCoderDecodeScaleFactor : @(scale)}]; - } + UIImage *image = SDWebImageLoaderDecodeProgressiveImageData(data, self.request.URL, finished, self.progressiveCoder, [[self class] imageOptionsFromDownloaderOptions:self.options], [[self class] imageContextFromDownloadContext:self.context]); if (image) { - BOOL shouldDecode = (self.options & SDWebImageDownloaderAvoidDecodeImage) == 0; - if ([image conformsToProtocol:@protocol(SDAnimatedImage)]) { - // `SDAnimatedImage` do not decode - shouldDecode = NO; - } else if (image.sd_isAnimated) { - // animated image do not decode - shouldDecode = NO; - } - if (shouldDecode) { - image = [SDWebImageCoderHelper decodedImageWithImage:image]; - } - // mark the image as progressive (completionBlock one are not mark as progressive) - image.sd_isIncremental = YES; - // We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding. [self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO]; @@ -456,46 +419,7 @@ didReceiveResponse:(NSURLResponse *)response } else { // decode the image in coder queue dispatch_async(self.coderQueue, ^{ - BOOL decodeFirstFrame = self.options & SDWebImageDownloaderDecodeFirstFrameOnly; - NSNumber *scaleValue = [self.context valueForKey:SDWebImageContextImageScaleFactor]; - CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(self.cacheKey); - if (scale < 1) { - scale = 1; - } - UIImage *image; - if (!decodeFirstFrame) { - // check whether we should use `SDAnimatedImage` - if ([self.context valueForKey:SDWebImageContextAnimatedImageClass]) { - Class animatedImageClass = [self.context valueForKey:SDWebImageContextAnimatedImageClass]; - if ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)]) { - image = [[animatedImageClass alloc] initWithData:imageData scale:scale]; - if (self.options & SDWebImageDownloaderPreloadAllFrames && [image respondsToSelector:@selector(preloadAllFrames)]) { - [((id)image) preloadAllFrames]; - } - } - } - } - if (!image) { - image = [[SDWebImageCodersManager sharedManager] decodedImageWithData:imageData options:@{SDWebImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDWebImageCoderDecodeScaleFactor : @(scale)}]; - } - - BOOL shouldDecode = (self.options & SDWebImageDownloaderAvoidDecodeImage) == 0; - if ([image conformsToProtocol:@protocol(SDAnimatedImage)]) { - // `SDAnimatedImage` do not decode - shouldDecode = NO; - } else if (image.sd_isAnimated) { - // animated image do not decode - shouldDecode = NO; - } - - if (shouldDecode) { - BOOL shouldScaleDown = self.options & SDWebImageDownloaderScaleDownLargeImages; - if (shouldScaleDown) { - image = [SDWebImageCoderHelper decodedAndScaledDownImageWithImage:image limitBytes:0]; - } else { - image = [SDWebImageCoderHelper decodedImageWithImage:image]; - } - } + UIImage *image = SDWebImageLoaderDecodeImageData(imageData, self.request.URL, nil, [[self class] imageOptionsFromDownloaderOptions:self.options], [[self class] imageContextFromDownloadContext:self.context]); CGSize imageSize = image.size; if (imageSize.width == 0 || imageSize.height == 0) { [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]]; @@ -546,11 +470,22 @@ didReceiveResponse:(NSURLResponse *)response } #pragma mark Helper methods -- (NSString *)cacheKey { - if (!_cacheKey) { - _cacheKey = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]; ++ (SDWebImageOptions)imageOptionsFromDownloaderOptions:(SDWebImageDownloaderOptions)downloadOptions { + SDWebImageOptions options = 0; + if (downloadOptions & SDWebImageDownloaderScaleDownLargeImages) options |= SDWebImageScaleDownLargeImages; + if (downloadOptions & SDWebImageDownloaderDecodeFirstFrameOnly) options |= SDWebImageDecodeFirstFrameOnly; + if (downloadOptions & SDWebImageDownloaderPreloadAllFrames) options |= SDWebImagePreloadAllFrames; + + return options; +} + ++ (SDWebImageContext *)imageContextFromDownloadContext:(SDWebImageContext *)downloadContext { + SDWebImageContext *context = nil; + if (!downloadContext) { + return context; } - return _cacheKey; + + return context; } - (BOOL)shouldContinueWhenAppEntersBackground { diff --git a/SDWebImage/SDWebImageLoader.h b/SDWebImage/SDWebImageLoader.h index 6c891b6b..59a432b5 100644 --- a/SDWebImage/SDWebImageLoader.h +++ b/SDWebImage/SDWebImageLoader.h @@ -14,19 +14,50 @@ typedef void(^SDWebImageLoaderProgressBlock)(NSInteger receivedSize, NSInteger e typedef void(^SDWebImageLoaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished); typedef void(^SDWebImageLoaderDataCompletedBlock)(NSData * _Nullable data, NSError * _Nullable error, BOOL finished); +@protocol SDWebImageCoder, SDWebImageProgressiveCoder; + /** - A `SDImageCacheType` value to specify the cache type information from manager. `SDWebImageManager` will firstly query cache, then if cache miss or `SDWebImageRefreshCached` is set, it will start image loader to load the image. - This can be a hint for image loader to load the image from network and refresh the image from remote location if needed. (NSNumber) + This is the built-in decoding process for image download from network or local file. + @note If you want to implement your custom loader with `loadImageWithURL:options:context:progress:completed:` API, but also want to keep compatible with SDWebImage's behavior, you'd better use this to produce image. + + @param imageData The image data from the network. Should not be nil + @param imageURL The image URL from the input. Should not be nil + @param coder The image coder. You can pass nil to use the default `SDWebImageCodersManager`. See `SDWebImageCoder` + @param options The options arg from the input + @param context The context arg from the input + @return The decoded image for current image data load from the network */ -FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextLoaderCacheType; +FOUNDATION_EXPORT UIImage * _Nullable SDWebImageLoaderDecodeImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, id _Nullable coder, SDWebImageOptions options, SDWebImageContext * _Nullable context); + +/** + This is the built-in decoding process for image progressive download from network. It's used when `SDWebImageProgressiveDownload` option is set. + @note If you want to implement your custom loader with `loadImageWithURL:options:context:progress:completed:` API, but also want to keep compatible with SDWebImage's behavior, you'd better use this to produce image. + + @param imageData The image data from the network so far. Should not be nil + @param imageURL The image URL from the input. Should not be nil + @param finished Pass NO to specify the download process has not finished. Pass YES when all image data has finished. + @param progressiveCoder The image progressive coder. Should not be nil. You should bind the progressive coder for each of loading operation to avoid conflict. See `SDWebImageProgressiveCoder`. + @param options The options arg from the input + @param context The context arg from the input + @return The decoded progressive image for current image data load from the network + */ +FOUNDATION_EXPORT UIImage * _Nullable SDWebImageLoaderDecodeProgressiveImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, BOOL finished, id _Nonnull progressiveCoder, SDWebImageOptions options, SDWebImageContext * _Nullable context); + +/** + A `UIImage` instance from `SDWebImageManager` when you specify `SDWebImageRefreshCached` and image cache hit. + This can be a hint for image loader to load the image from network and refresh the image from remote location if needed. If the cached image is equal to the remote location one. you should call the completion with all nil args. (UIImage) + */ +FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextLoaderCachedImage; + +#pragma mark - SDWebImageLoader @protocol SDWebImageLoader - (BOOL)canLoadWithURL:(nullable NSURL *)url; // We provide two ways to allow a image loader to load the image. -// The first one should return the `UIImage` image instance as well as `NSData` image data. This is suitable for the use case such as progressive download from network, or image directlly from Photos framework. -// The second one should return just the `NSData` image data, we will use the common image decoding logic to process the correct image instance, so the image loader itself can concentrate on only data retriving. This is suitable for the use case such as load the data from file. +// The first one should return the `UIImage` image instance as well as `NSData` image data. This is suitable for the use case such as image from third-party SDKs, such as image directlly from Photos framework. +// The second one should return just the `NSData` image data, we will use the common image decoding logic to process the correct image instance, so the image loader itself can concentrate on only data retriving. This is suitable for the use case such as load the data from network or local file. // Your image loader **MUST** implement at least one of those protocol, or an assert will occur. We will firstlly ask for `loadImageWithURL:options:progress:completed:context:` if you implement it. If this one return nil, we will continue to ask for `loadImageDataWithURL:options:progress:completed:context:` if you implement it. // @note It's your responsibility to load the image in the desired global queue(to avoid block main queue). We do not dispatch these method call in a global queue but just from the call queue (For `SDWebImageManager`, it typically call from the main queue). diff --git a/SDWebImage/SDWebImageLoader.m b/SDWebImage/SDWebImageLoader.m index 2f669be0..ce00b1d6 100644 --- a/SDWebImage/SDWebImageLoader.m +++ b/SDWebImage/SDWebImageLoader.m @@ -7,3 +7,109 @@ */ #import "SDWebImageLoader.h" +#import "SDWebImageCodersManager.h" +#import "SDWebImageCoderHelper.h" +#import "SDAnimatedImage.h" +#import "UIImage+WebCache.h" + +UIImage * _Nullable SDWebImageLoaderDecodeImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, id _Nullable coder, SDWebImageOptions options, SDWebImageContext * _Nullable context) { + NSCParameterAssert(imageData); + NSCParameterAssert(imageURL); + + UIImage *image; + NSString *cacheKey = imageURL.absoluteString; + BOOL decodeFirstFrame = options & SDWebImageDecodeFirstFrameOnly; + NSNumber *scaleValue = [context valueForKey:SDWebImageContextImageScaleFactor]; + CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey); + if (scale < 1) { + scale = 1; + } + if (!coder) { + coder = [SDWebImageCodersManager sharedManager]; + } + + if (!decodeFirstFrame) { + // check whether we should use `SDAnimatedImage` + if ([context valueForKey:SDWebImageContextAnimatedImageClass]) { + Class animatedImageClass = [context valueForKey:SDWebImageContextAnimatedImageClass]; + if ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)]) { + image = [[animatedImageClass alloc] initWithData:imageData scale:scale]; + if (options & SDWebImagePreloadAllFrames && [image respondsToSelector:@selector(preloadAllFrames)]) { + [((id)image) preloadAllFrames]; + } + } + } + } + if (!image) { + image = [coder decodedImageWithData:imageData options:@{SDWebImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDWebImageCoderDecodeScaleFactor : @(scale)}]; + } + if (image) { + BOOL shouldDecode = YES; + if ([image conformsToProtocol:@protocol(SDAnimatedImage)]) { + // `SDAnimatedImage` do not decode + shouldDecode = NO; + } else if (image.sd_isAnimated) { + // animated image do not decode + shouldDecode = NO; + } + + if (shouldDecode) { + BOOL shouldScaleDown = options & SDWebImageScaleDownLargeImages; + if (shouldScaleDown) { + image = [SDWebImageCoderHelper decodedAndScaledDownImageWithImage:image limitBytes:0]; + } else { + image = [SDWebImageCoderHelper decodedImageWithImage:image]; + } + } + } + + return image; +} + +UIImage * _Nullable SDWebImageLoaderDecodeProgressiveImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, BOOL finished, id _Nonnull progressiveCoder, SDWebImageOptions options, SDWebImageContext * _Nullable context) { + NSCParameterAssert(imageData); + NSCParameterAssert(imageURL); + NSCParameterAssert(progressiveCoder); + + UIImage *image; + NSString *cacheKey = imageURL.absoluteString; + BOOL decodeFirstFrame = options & SDWebImageDecodeFirstFrameOnly; + NSNumber *scaleValue = [context valueForKey:SDWebImageContextImageScaleFactor]; + CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey); + if (scale < 1) { + scale = 1; + } + + [progressiveCoder updateIncrementalData:imageData finished:finished]; + if (!decodeFirstFrame) { + // check whether we should use `SDAnimatedImage` + if ([context valueForKey:SDWebImageContextAnimatedImageClass]) { + Class animatedImageClass = [context valueForKey:SDWebImageContextAnimatedImageClass]; + if ([animatedImageClass isSubclassOfClass:[UIImage class]] && [animatedImageClass conformsToProtocol:@protocol(SDAnimatedImage)] && [progressiveCoder conformsToProtocol:@protocol(SDWebImageAnimatedCoder)]) { + image = [[animatedImageClass alloc] initWithAnimatedCoder:(id)progressiveCoder scale:scale]; + } + } + } + if (!image) { + image = [progressiveCoder incrementalDecodedImageWithOptions:@{SDWebImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDWebImageCoderDecodeScaleFactor : @(scale)}]; + } + if (image) { + BOOL shouldDecode = YES; + if ([image conformsToProtocol:@protocol(SDAnimatedImage)]) { + // `SDAnimatedImage` do not decode + shouldDecode = NO; + } else if (image.sd_isAnimated) { + // animated image do not decode + shouldDecode = NO; + } + if (shouldDecode) { + image = [SDWebImageCoderHelper decodedImageWithImage:image]; + } + // mark the image as progressive (completionBlock one are not mark as progressive) + image.sd_isIncremental = YES; + } + + return image; +} + +SDWebImageContextOption const SDWebImageContextLoaderCachedImage = @"loaderCachedImage"; diff --git a/SDWebImage/SDWebImageManager.m b/SDWebImage/SDWebImageManager.m index c35bb07e..09c9522a 100644 --- a/SDWebImage/SDWebImageManager.m +++ b/SDWebImage/SDWebImageManager.m @@ -196,15 +196,25 @@ static SDWebImageDownloader *_defaultImageDownloader; && (!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]); if (shouldDownload) { + SDWebImageContext *downloadContext = context; if (cachedImage && options & SDWebImageRefreshCached) { // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url]; + // Pass the cached image to the image loader. The image loader should check whether the remote image is equal to the cached image. + SDWebImageMutableContext *mutableContext; + if (downloadContext) { + mutableContext = [downloadContext mutableCopy]; + } else { + mutableContext = [NSMutableDictionary dictionary]; + } + [mutableContext setValue:cachedImage forKey:SDWebImageContextLoaderCachedImage]; + downloadContext = [mutableContext copy]; } // `SDWebImageCombinedOperation` -> `SDWebImageDownloadToken` -> `downloadOperationCancelToken`, which is a `SDCallbacksDictionary` and retain the completed block below, so we need weak-strong again to avoid retain cycle __weak typeof(strongOperation) weakSubOperation = strongOperation; - strongOperation.downloadOperation = [self.imageDownloader loadImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { + strongOperation.downloadOperation = [self.imageDownloader loadImageWithURL:url options:options context:downloadContext progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { __strong typeof(weakSubOperation) strongSubOperation = weakSubOperation; if (!strongSubOperation || strongSubOperation.isCancelled) { // Do nothing if the operation was cancelled From 60759f812a4ac2c178b0a55579c73f6f55917745 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Mon, 16 Apr 2018 17:14:13 +0800 Subject: [PATCH 3/4] Refactor to only use one publish protocol for custom loader. Renaming image downloader description to loader --- SDWebImage/SDWebImageDownloader.h | 5 +- SDWebImage/SDWebImageDownloaderOperation.m | 18 +++---- SDWebImage/SDWebImageLoader.h | 59 +++++++++------------- SDWebImage/SDWebImageLoader.m | 29 +++++++---- SDWebImage/SDWebImageLoadersManager.m | 14 ----- SDWebImage/SDWebImageManager.h | 15 +++--- SDWebImage/SDWebImageManager.m | 41 ++++++++++----- Tests/Tests/SDWebImageManagerTests.m | 2 +- 8 files changed, 88 insertions(+), 95 deletions(-) diff --git a/SDWebImage/SDWebImageDownloader.h b/SDWebImage/SDWebImageDownloader.h index 37050719..03622749 100644 --- a/SDWebImage/SDWebImageDownloader.h +++ b/SDWebImage/SDWebImageDownloader.h @@ -248,8 +248,9 @@ typedef SDWebImageLoaderCompletedBlock SDWebImageDownloaderCompletedBlock; /** - SDWebImageDownloader is the built-in image downloader conform to `SDWebImageLoader`. Which provide the HTTP/HTTPS/FTP download, or local file URL using NSURLSession. - However, this downloader does also support customization for advanced users. You can specify `operationClass` in download config to custom download operation, See `SDWebImageDownloaderOperation`. + SDWebImageDownloader is the built-in image loader conform to `SDWebImageLoader`. Which provide the HTTP/HTTPS/FTP download, or local file URL using NSURLSession. + However, this downloader class itself also support customization for advanced users. You can specify `operationClass` in download config to custom download operation, See `SDWebImageDownloaderOperation`. + If you want to provide some image loader which beyond network or local file, consider to create your own custom class conform to `SDWebImageLoader`. */ @interface SDWebImageDownloader (SDWebImageLoader) diff --git a/SDWebImage/SDWebImageDownloaderOperation.m b/SDWebImage/SDWebImageDownloaderOperation.m index db177345..0c7c1747 100644 --- a/SDWebImage/SDWebImageDownloaderOperation.m +++ b/SDWebImage/SDWebImageDownloaderOperation.m @@ -350,11 +350,15 @@ didReceiveResponse:(NSURLResponse *)response break; } } + // If we can't find any progressive coder, disable progressive download + if (!self.progressiveCoder) { + self.options &= ~SDWebImageDownloaderProgressiveDownload; + } } // progressive decode the image in coder queue dispatch_async(self.coderQueue, ^{ - UIImage *image = SDWebImageLoaderDecodeProgressiveImageData(data, self.request.URL, finished, self.progressiveCoder, [[self class] imageOptionsFromDownloaderOptions:self.options], [[self class] imageContextFromDownloadContext:self.context]); + UIImage *image = SDWebImageLoaderDecodeProgressiveImageData(data, self.request.URL, finished, self.progressiveCoder, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context); if (image) { // We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding. @@ -419,7 +423,7 @@ didReceiveResponse:(NSURLResponse *)response } else { // decode the image in coder queue dispatch_async(self.coderQueue, ^{ - UIImage *image = SDWebImageLoaderDecodeImageData(imageData, self.request.URL, nil, [[self class] imageOptionsFromDownloaderOptions:self.options], [[self class] imageContextFromDownloadContext:self.context]); + UIImage *image = SDWebImageLoaderDecodeImageData(imageData, self.request.URL, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context); CGSize imageSize = image.size; if (imageSize.width == 0 || imageSize.height == 0) { [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]]; @@ -475,19 +479,11 @@ didReceiveResponse:(NSURLResponse *)response if (downloadOptions & SDWebImageDownloaderScaleDownLargeImages) options |= SDWebImageScaleDownLargeImages; if (downloadOptions & SDWebImageDownloaderDecodeFirstFrameOnly) options |= SDWebImageDecodeFirstFrameOnly; if (downloadOptions & SDWebImageDownloaderPreloadAllFrames) options |= SDWebImagePreloadAllFrames; + if (downloadOptions & SDWebImageDownloaderAvoidDecodeImage) options |= SDWebImageAvoidDecodeImage; return options; } -+ (SDWebImageContext *)imageContextFromDownloadContext:(SDWebImageContext *)downloadContext { - SDWebImageContext *context = nil; - if (!downloadContext) { - return context; - } - - return context; -} - - (BOOL)shouldContinueWhenAppEntersBackground { return self.options & SDWebImageDownloaderContinueInBackground; } diff --git a/SDWebImage/SDWebImageLoader.h b/SDWebImage/SDWebImageLoader.h index 59a432b5..dbc9ad02 100644 --- a/SDWebImage/SDWebImageLoader.h +++ b/SDWebImage/SDWebImageLoader.h @@ -10,11 +10,19 @@ #import "SDWebImageDefine.h" #import "SDWebImageOperation.h" +@protocol SDWebImageProgressiveCoder; typedef void(^SDWebImageLoaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL); typedef void(^SDWebImageLoaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished); -typedef void(^SDWebImageLoaderDataCompletedBlock)(NSData * _Nullable data, NSError * _Nullable error, BOOL finished); -@protocol SDWebImageCoder, SDWebImageProgressiveCoder; +#pragma mark - Context + +/** + A `UIImage` instance from `SDWebImageManager` when you specify `SDWebImageRefreshCached` and image cache hit. + This can be a hint for image loader to load the image from network and refresh the image from remote location if needed. If the cached image is equal to the remote location one. you should call the completion with all nil args. (UIImage) + */ +FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextLoaderCachedImage; + +#pragma mark - Helper method /** This is the built-in decoding process for image download from network or local file. @@ -22,15 +30,14 @@ typedef void(^SDWebImageLoaderDataCompletedBlock)(NSData * _Nullable data, NSErr @param imageData The image data from the network. Should not be nil @param imageURL The image URL from the input. Should not be nil - @param coder The image coder. You can pass nil to use the default `SDWebImageCodersManager`. See `SDWebImageCoder` @param options The options arg from the input @param context The context arg from the input @return The decoded image for current image data load from the network */ -FOUNDATION_EXPORT UIImage * _Nullable SDWebImageLoaderDecodeImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, id _Nullable coder, SDWebImageOptions options, SDWebImageContext * _Nullable context); +FOUNDATION_EXPORT UIImage * _Nullable SDWebImageLoaderDecodeImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, SDWebImageOptions options, SDWebImageContext * _Nullable context); /** - This is the built-in decoding process for image progressive download from network. It's used when `SDWebImageProgressiveDownload` option is set. + This is the built-in decoding process for image progressive download from network. It's used when `SDWebImageProgressiveDownload` option is set. (It's not required when your loader does not support progressive image loading) @note If you want to implement your custom loader with `loadImageWithURL:options:context:progress:completed:` API, but also want to keep compatible with SDWebImage's behavior, you'd better use this to produce image. @param imageData The image data from the network so far. Should not be nil @@ -43,25 +50,24 @@ FOUNDATION_EXPORT UIImage * _Nullable SDWebImageLoaderDecodeImageData(NSData * _ */ FOUNDATION_EXPORT UIImage * _Nullable SDWebImageLoaderDecodeProgressiveImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, BOOL finished, id _Nonnull progressiveCoder, SDWebImageOptions options, SDWebImageContext * _Nullable context); -/** - A `UIImage` instance from `SDWebImageManager` when you specify `SDWebImageRefreshCached` and image cache hit. - This can be a hint for image loader to load the image from network and refresh the image from remote location if needed. If the cached image is equal to the remote location one. you should call the completion with all nil args. (UIImage) - */ -FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextLoaderCachedImage; - #pragma mark - SDWebImageLoader +// This is the protocol to specify custom image load process. You can create your own class to conform this protocol and use as a image loader to load image from network or any avaiable remote resources defined by yourself. +// If you want to implement custom loader for image download from network or local file, you just need to concentrate on image data download only. After the download finish, call `SDWebImageLoaderDecodeImageData` or `SDWebImageLoaderDecodeProgressiveImageData` to use the built-in decoding process and produce image (Remember to call in the global queue). And finally callback the completion block. +// If you directlly get the image instance using some third-party SDKs, such as image directlly from Photos framework. You can process the image data and image instance by yourself without that built-in decoding process. And finally callback the completion block. +// @note It's your responsibility to load the image in the desired global queue(to avoid block main queue). We do not dispatch these method call in a global queue but just from the call queue (For `SDWebImageManager`, it typically call from the main queue). + @protocol SDWebImageLoader +/** + Whether current image loader supports to load the provide image URL. + This will be checked everytime a new image request come for loader. If this return NO, we will mark this image load as failed. If return YES, we will start to call `loadImageWithURL:options:context:progress:completed:`. + + @param url The image URL to be loaded. + @return YES to continue download, NO to stop download. + */ - (BOOL)canLoadWithURL:(nullable NSURL *)url; -// We provide two ways to allow a image loader to load the image. -// The first one should return the `UIImage` image instance as well as `NSData` image data. This is suitable for the use case such as image from third-party SDKs, such as image directlly from Photos framework. -// The second one should return just the `NSData` image data, we will use the common image decoding logic to process the correct image instance, so the image loader itself can concentrate on only data retriving. This is suitable for the use case such as load the data from network or local file. -// Your image loader **MUST** implement at least one of those protocol, or an assert will occur. We will firstlly ask for `loadImageWithURL:options:progress:completed:context:` if you implement it. If this one return nil, we will continue to ask for `loadImageDataWithURL:options:progress:completed:context:` if you implement it. -// @note It's your responsibility to load the image in the desired global queue(to avoid block main queue). We do not dispatch these method call in a global queue but just from the call queue (For `SDWebImageManager`, it typically call from the main queue). - -@optional /** Load the image and image data with the given URL and return the image data. You're responsible for producing the image instance. @@ -79,21 +85,4 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextLoader progress:(nullable SDWebImageLoaderProgressBlock)progressBlock completed:(nullable SDWebImageLoaderCompletedBlock)completedBlock; -/** - Load the image with the given URL and return the image data. We will automatically handler the image decoding stuff for you. - - @param url The URL represent the image. Note this may not be a HTTP URL - @param options A mask to specify options to use for this request - @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold. - @param progressBlock A block called while image is downloading - * @note the progress block is executed on a background queue - @param completedBlock A block called when operation has been completed. - @return An operation which allow the user to cancel the current request. - */ -- (nullable id)loadImageDataWithURL:(nullable NSURL *)url - options:(SDWebImageOptions)options - context:(nullable SDWebImageContext *)context - progress:(nullable SDWebImageLoaderProgressBlock)progressBlock - completed:(nullable SDWebImageLoaderDataCompletedBlock)completedBlock; - @end diff --git a/SDWebImage/SDWebImageLoader.m b/SDWebImage/SDWebImageLoader.m index ce00b1d6..c26fda1b 100644 --- a/SDWebImage/SDWebImageLoader.m +++ b/SDWebImage/SDWebImageLoader.m @@ -7,27 +7,30 @@ */ #import "SDWebImageLoader.h" +#import "SDWebImageCacheKeyFilter.h" #import "SDWebImageCodersManager.h" #import "SDWebImageCoderHelper.h" #import "SDAnimatedImage.h" #import "UIImage+WebCache.h" -UIImage * _Nullable SDWebImageLoaderDecodeImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, id _Nullable coder, SDWebImageOptions options, SDWebImageContext * _Nullable context) { +UIImage * _Nullable SDWebImageLoaderDecodeImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, SDWebImageOptions options, SDWebImageContext * _Nullable context) { NSCParameterAssert(imageData); NSCParameterAssert(imageURL); UIImage *image; - NSString *cacheKey = imageURL.absoluteString; + id cacheKeyFilter = [context valueForKey:SDWebImageContextCacheKeyFilter]; + NSString *cacheKey; + if (cacheKeyFilter) { + cacheKey = [cacheKeyFilter cacheKeyForURL:imageURL]; + } else { + cacheKey = imageURL.absoluteString; + } BOOL decodeFirstFrame = options & SDWebImageDecodeFirstFrameOnly; NSNumber *scaleValue = [context valueForKey:SDWebImageContextImageScaleFactor]; CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey); if (scale < 1) { scale = 1; } - if (!coder) { - coder = [SDWebImageCodersManager sharedManager]; - } - if (!decodeFirstFrame) { // check whether we should use `SDAnimatedImage` if ([context valueForKey:SDWebImageContextAnimatedImageClass]) { @@ -41,10 +44,10 @@ UIImage * _Nullable SDWebImageLoaderDecodeImageData(NSData * _Nonnull imageData, } } if (!image) { - image = [coder decodedImageWithData:imageData options:@{SDWebImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDWebImageCoderDecodeScaleFactor : @(scale)}]; + image = [[SDWebImageCodersManager sharedManager] decodedImageWithData:imageData options:@{SDWebImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDWebImageCoderDecodeScaleFactor : @(scale)}]; } if (image) { - BOOL shouldDecode = YES; + BOOL shouldDecode = (options & SDWebImageAvoidDecodeImage) == 0; if ([image conformsToProtocol:@protocol(SDAnimatedImage)]) { // `SDAnimatedImage` do not decode shouldDecode = NO; @@ -72,7 +75,13 @@ UIImage * _Nullable SDWebImageLoaderDecodeProgressiveImageData(NSData * _Nonnull NSCParameterAssert(progressiveCoder); UIImage *image; - NSString *cacheKey = imageURL.absoluteString; + id cacheKeyFilter = [context valueForKey:SDWebImageContextCacheKeyFilter]; + NSString *cacheKey; + if (cacheKeyFilter) { + cacheKey = [cacheKeyFilter cacheKeyForURL:imageURL]; + } else { + cacheKey = imageURL.absoluteString; + } BOOL decodeFirstFrame = options & SDWebImageDecodeFirstFrameOnly; NSNumber *scaleValue = [context valueForKey:SDWebImageContextImageScaleFactor]; CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey); @@ -94,7 +103,7 @@ UIImage * _Nullable SDWebImageLoaderDecodeProgressiveImageData(NSData * _Nonnull image = [progressiveCoder incrementalDecodedImageWithOptions:@{SDWebImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDWebImageCoderDecodeScaleFactor : @(scale)}]; } if (image) { - BOOL shouldDecode = YES; + BOOL shouldDecode = (options & SDWebImageAvoidDecodeImage) == 0; if ([image conformsToProtocol:@protocol(SDAnimatedImage)]) { // `SDAnimatedImage` do not decode shouldDecode = NO; diff --git a/SDWebImage/SDWebImageLoadersManager.m b/SDWebImage/SDWebImageLoadersManager.m index 8343b12b..8b5ea363 100644 --- a/SDWebImage/SDWebImageLoadersManager.m +++ b/SDWebImage/SDWebImageLoadersManager.m @@ -95,18 +95,4 @@ return nil; } -- (id)loadImageDataWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDWebImageLoaderProgressBlock)progressBlock completed:(SDWebImageLoaderDataCompletedBlock)completedBlock { - if (!url) { - return nil; - } - for (id loader in self.loaders) { - if ([loader respondsToSelector:@selector(loadImageDataWithURL:options:context:progress:completed:)]) { - if ([loader canLoadWithURL:url]) { - return [loader loadImageDataWithURL:url options:options context:context progress:progressBlock completed:completedBlock]; - } - } - } - return nil; -} - @end diff --git a/SDWebImage/SDWebImageManager.h b/SDWebImage/SDWebImageManager.h index 39c31f3c..c9112984 100644 --- a/SDWebImage/SDWebImageManager.h +++ b/SDWebImage/SDWebImageManager.h @@ -103,10 +103,9 @@ SDWebImageManager *manager = [SDWebImageManager sharedManager]; @property (strong, nonatomic, readonly, nonnull) id imageCache; /** - * The image downloader used by manager to download image. - * @note If you specify a non-shared downloader, don't forget to call `invalidateSessionAndCancel:` at proper time to avoid memory leak. + * The image loader used by manager to load image. */ -@property (strong, nonatomic, readonly, nonnull) id imageDownloader; +@property (strong, nonatomic, readonly, nonnull) id imageLoader; /** The image transformer for manager. It's used for image transform after the image load finished and store the transformed image to cache, see `SDWebImageTransformer`. @@ -162,10 +161,10 @@ SDWebImageManager *manager = [SDWebImageManager sharedManager]; @property (nonatomic, class, nullable) id defaultImageCache; /** - The default image downloader for manager which is created with no arguments. Such as shared manager or init. + The default image loader for manager which is created with no arguments. Such as shared manager or init. Defaults to nil. Means using `SDWebImageDownloader.sharedDownloader` */ -@property (nonatomic, class, nullable) SDWebImageDownloader *defaultImageDownloader; +@property (nonatomic, class, nullable) id defaultImageLoader; /** * Returns global shared manager instance. @@ -173,10 +172,10 @@ SDWebImageManager *manager = [SDWebImageManager sharedManager]; @property (nonatomic, class, readonly, nonnull) SDWebImageManager *sharedManager; /** - * Allows to specify instance of cache and image downloader used with image manager. - * @return new instance of `SDWebImageManager` with specified cache and downloader. + * Allows to specify instance of cache and image loader used with image manager. + * @return new instance of `SDWebImageManager` with specified cache and loader. */ -- (nonnull instancetype)initWithCache:(nonnull id)cache downloader:(nonnull id)downloader NS_DESIGNATED_INITIALIZER; +- (nonnull instancetype)initWithCache:(nonnull id)cache loader:(nonnull id)loader NS_DESIGNATED_INITIALIZER; /** * Downloads the image at the given URL if not present in cache or return the cached version otherwise. diff --git a/SDWebImage/SDWebImageManager.m b/SDWebImage/SDWebImageManager.m index 09c9522a..3b0e4ef8 100644 --- a/SDWebImage/SDWebImageManager.m +++ b/SDWebImage/SDWebImageManager.m @@ -14,7 +14,7 @@ #import "SDWebImageError.h" static id _defaultImageCache; -static SDWebImageDownloader *_defaultImageDownloader; +static id _defaultImageLoader; @interface SDWebImageCombinedOperation () @@ -28,7 +28,7 @@ static SDWebImageDownloader *_defaultImageDownloader; @interface SDWebImageManager () @property (strong, nonatomic, readwrite, nonnull) SDImageCache *imageCache; -@property (strong, nonatomic, readwrite, nonnull) id imageDownloader; +@property (strong, nonatomic, readwrite, nonnull) id imageLoader; @property (strong, nonatomic, nonnull) NSMutableSet *failedURLs; @property (strong, nonatomic, nonnull) NSMutableArray *runningOperations; @@ -47,15 +47,15 @@ static SDWebImageDownloader *_defaultImageDownloader; _defaultImageCache = defaultImageCache; } -+ (SDWebImageDownloader *)defaultImageDownloader { - return _defaultImageDownloader; ++ (id)defaultImageLoader { + return _defaultImageLoader; } -+ (void)setDefaultImageDownloader:(SDWebImageDownloader *)defaultImageDownloader { - if (defaultImageDownloader && ![defaultImageDownloader isKindOfClass:[SDWebImageDownloader class]]) { ++ (void)setDefaultImageLoader:(id)defaultImageLoader { + if (defaultImageLoader && ![defaultImageLoader conformsToProtocol:@protocol(SDWebImageLoader)]) { return; } - _defaultImageDownloader = defaultImageDownloader; + _defaultImageLoader = defaultImageLoader; } + (nonnull instancetype)sharedManager { @@ -72,17 +72,17 @@ static SDWebImageDownloader *_defaultImageDownloader; if (!cache) { cache = [SDImageCache sharedImageCache]; } - SDWebImageDownloader *downloader = [[self class] defaultImageDownloader]; - if (!downloader) { - downloader = [SDWebImageDownloader sharedDownloader]; + id loader = [[self class] defaultImageLoader]; + if (!loader) { + loader = [SDWebImageDownloader sharedDownloader]; } - return [self initWithCache:cache downloader:downloader]; + return [self initWithCache:cache loader:loader]; } -- (nonnull instancetype)initWithCache:(nonnull id)cache downloader:(nonnull id)downloader { +- (nonnull instancetype)initWithCache:(nonnull id)cache loader:(nonnull id)loader { if ((self = [super init])) { _imageCache = cache; - _imageDownloader = downloader; + _imageLoader = loader; _failedURLs = [NSMutableSet new]; _runningOperations = [NSMutableArray new]; } @@ -195,8 +195,21 @@ static SDWebImageDownloader *_defaultImageDownloader; BOOL shouldDownload = (!(options & SDWebImageFromCacheOnly)) && (!cachedImage || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]); + // Check whether image downloader support target URL + shouldDownload &= [self.imageLoader canLoadWithURL:url]; if (shouldDownload) { SDWebImageContext *downloadContext = context; + if (cacheKeyFilter) { + // Pass the cache key filter to the image loader. + SDWebImageMutableContext *mutableContext; + if (downloadContext) { + mutableContext = [downloadContext mutableCopy]; + } else { + mutableContext = [NSMutableDictionary dictionary]; + } + [mutableContext setValue:cacheKeyFilter forKey:SDWebImageContextCacheKeyFilter]; + downloadContext = [mutableContext copy]; + } if (cachedImage && options & SDWebImageRefreshCached) { // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server. @@ -214,7 +227,7 @@ static SDWebImageDownloader *_defaultImageDownloader; // `SDWebImageCombinedOperation` -> `SDWebImageDownloadToken` -> `downloadOperationCancelToken`, which is a `SDCallbacksDictionary` and retain the completed block below, so we need weak-strong again to avoid retain cycle __weak typeof(strongOperation) weakSubOperation = strongOperation; - strongOperation.downloadOperation = [self.imageDownloader loadImageWithURL:url options:options context:downloadContext progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { + strongOperation.downloadOperation = [self.imageLoader loadImageWithURL:url options:options context:downloadContext progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) { __strong typeof(weakSubOperation) strongSubOperation = weakSubOperation; if (!strongSubOperation || strongSubOperation.isCancelled) { // Do nothing if the operation was cancelled diff --git a/Tests/Tests/SDWebImageManagerTests.m b/Tests/Tests/SDWebImageManagerTests.m index d9887199..3f2e397b 100644 --- a/Tests/Tests/SDWebImageManagerTests.m +++ b/Tests/Tests/SDWebImageManagerTests.m @@ -118,7 +118,7 @@ NSBundle *testBundle = [NSBundle bundleForClass:[self class]]; NSString *testImagePath = [testBundle pathForResource:@"TestImage" ofType:@"jpg"]; transformer.testImage = [[UIImage alloc] initWithContentsOfFile:testImagePath]; - SDWebImageManager *manager = [[SDWebImageManager alloc] initWithCache:[SDImageCache sharedImageCache] downloader:[SDWebImageDownloader sharedDownloader]]; + SDWebImageManager *manager = [[SDWebImageManager alloc] initWithCache:[SDImageCache sharedImageCache] loader:[SDWebImageDownloader sharedDownloader]]; manager.transformer = transformer; [[SDImageCache sharedImageCache] removeImageForKey:kTestJpegURL withCompletion:^{ [manager loadImageWithURL:imageURL options:SDWebImageTransformAnimatedImage progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) { From d5074429f0fe03f441e615841c45d6ceac394cd4 Mon Sep 17 00:00:00 2001 From: DreamPiggy Date: Tue, 17 Apr 2018 21:03:33 +0800 Subject: [PATCH 4/4] Add tests for custom loader protocol, using a test loader to specify loader function --- SDWebImage/SDWebImageLoadersManager.h | 2 +- SDWebImage/SDWebImageLoadersManager.m | 52 +++++++++--------- .../project.pbxproj | 8 +++ Tests/Tests/SDWebImageDownloaderTests.m | 32 +++++++++++ Tests/Tests/SDWebImageTestLoader.h | 16 ++++++ Tests/Tests/SDWebImageTestLoader.m | 53 +++++++++++++++++++ 6 files changed, 136 insertions(+), 27 deletions(-) create mode 100644 Tests/Tests/SDWebImageTestLoader.h create mode 100644 Tests/Tests/SDWebImageTestLoader.m diff --git a/SDWebImage/SDWebImageLoadersManager.h b/SDWebImage/SDWebImageLoadersManager.h index 7e070f2c..7b59b592 100644 --- a/SDWebImage/SDWebImageLoadersManager.h +++ b/SDWebImage/SDWebImageLoadersManager.h @@ -15,7 +15,7 @@ /** All image loaders in manager. The loaders array is a priority queue, which means the later added loader will have the highest priority */ -@property (nonatomic, strong, readwrite, nullable) NSArray>* loaders; +@property (nonatomic, copy, readwrite, nullable) NSArray>* loaders; /** Add a new image loader to the end of loaders array. Which has the highest priority. diff --git a/SDWebImage/SDWebImageLoadersManager.m b/SDWebImage/SDWebImageLoadersManager.m index 8b5ea363..9e5850de 100644 --- a/SDWebImage/SDWebImageLoadersManager.m +++ b/SDWebImage/SDWebImageLoadersManager.m @@ -14,7 +14,6 @@ @interface SDWebImageLoadersManager () -@property (strong, nonatomic, nonnull) NSMutableArray> *mutableLoaders; @property (nonatomic, strong, nonnull) dispatch_semaphore_t loadersLock; @end @@ -34,7 +33,7 @@ self = [super init]; if (self) { // initialize with default image loaders - self.mutableLoaders = [@[[SDWebImageDownloader sharedDownloader]] mutableCopy]; + self.loaders = @[[SDWebImageDownloader sharedDownloader]]; self.loadersLock = dispatch_semaphore_create(1); } return self; @@ -43,37 +42,37 @@ #pragma mark - Loader Property - (void)addLoader:(id)loader { - if ([loader conformsToProtocol:@protocol(SDWebImageLoader)]) { - LOCK(self.loadersLock); - [self.mutableLoaders addObject:loader]; - UNLOCK(self.loadersLock); + if (![loader conformsToProtocol:@protocol(SDWebImageLoader)]) { + return; } + LOCK(self.loadersLock); + NSMutableArray> *mutableLoaders = [self.loaders mutableCopy]; + if (!mutableLoaders) { + mutableLoaders = [NSMutableArray array]; + } + [mutableLoaders addObject:loader]; + self.loaders = [mutableLoaders copy]; + UNLOCK(self.loadersLock); } - (void)removeLoader:(id)loader { + if (![loader conformsToProtocol:@protocol(SDWebImageLoader)]) { + return; + } LOCK(self.loadersLock); - [self.mutableLoaders removeObject:loader]; - UNLOCK(self.loadersLock); -} - -- (NSArray> *)loaders { - NSArray> *sortedLoaders; - LOCK(self.loadersLock); - sortedLoaders = [[[self.mutableLoaders copy] reverseObjectEnumerator] allObjects]; - UNLOCK(self.loadersLock); - return sortedLoaders; -} - -- (void)setLoaders:(NSArray> *)loaders { - LOCK(self.loadersLock); - self.mutableLoaders = [loaders mutableCopy]; + NSMutableArray> *mutableLoaders = [self.loaders mutableCopy]; + [mutableLoaders removeObject:loader]; + self.loaders = [mutableLoaders copy]; UNLOCK(self.loadersLock); } #pragma mark - SDWebImageLoader - (BOOL)canLoadWithURL:(nullable NSURL *)url { - for (id loader in self.loaders) { + LOCK(self.loadersLock); + NSArray> *loaders = self.loaders; + UNLOCK(self.loadersLock); + for (id loader in loaders.reverseObjectEnumerator) { if ([loader canLoadWithURL:url]) { return YES; } @@ -85,11 +84,12 @@ if (!url) { return nil; } - for (id loader in self.loaders) { + LOCK(self.loadersLock); + NSArray> *loaders = self.loaders; + UNLOCK(self.loadersLock); + for (id loader in loaders.reverseObjectEnumerator) { if ([loader respondsToSelector:@selector(loadImageWithURL:options:context:progress:completed:)]) { - if ([loader canLoadWithURL:url]) { - return [loader loadImageWithURL:url options:options context:context progress:progressBlock completed:completedBlock]; - } + return [loader loadImageWithURL:url options:options context:context progress:progressBlock completed:completedBlock]; } } return nil; diff --git a/Tests/SDWebImage Tests.xcodeproj/project.pbxproj b/Tests/SDWebImage Tests.xcodeproj/project.pbxproj index 4fc599f8..5fafa45f 100644 --- a/Tests/SDWebImage Tests.xcodeproj/project.pbxproj +++ b/Tests/SDWebImage Tests.xcodeproj/project.pbxproj @@ -15,6 +15,8 @@ 321259EE1F39E4110096FE0E /* TestImageAnimated.webp in Resources */ = {isa = PBXBuildFile; fileRef = 321259ED1F39E4110096FE0E /* TestImageAnimated.webp */; }; 3226ECBB20754F7700FAFACF /* SDWebImageTestDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3226ECBA20754F7700FAFACF /* SDWebImageTestDownloadOperation.m */; }; 3226ECBC20754F7700FAFACF /* SDWebImageTestDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3226ECBA20754F7700FAFACF /* SDWebImageTestDownloadOperation.m */; }; + 323B8E1F20862322008952BE /* SDWebImageTestLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 323B8E1E20862322008952BE /* SDWebImageTestLoader.m */; }; + 323B8E2020862322008952BE /* SDWebImageTestLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 323B8E1E20862322008952BE /* SDWebImageTestLoader.m */; }; 3254C32020641077008D1022 /* SDWebImageTransformerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3254C31F20641077008D1022 /* SDWebImageTransformerTests.m */; }; 3254C32120641077008D1022 /* SDWebImageTransformerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3254C31F20641077008D1022 /* SDWebImageTransformerTests.m */; }; 3264FF2F205D42CB00F6BD48 /* SDWebImageTestTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3264FF2E205D42CB00F6BD48 /* SDWebImageTestTransformer.m */; }; @@ -70,6 +72,8 @@ 321259ED1F39E4110096FE0E /* TestImageAnimated.webp */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestImageAnimated.webp; sourceTree = ""; }; 3226ECB920754F7700FAFACF /* SDWebImageTestDownloadOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestDownloadOperation.h; sourceTree = ""; }; 3226ECBA20754F7700FAFACF /* SDWebImageTestDownloadOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestDownloadOperation.m; sourceTree = ""; }; + 323B8E1D20862322008952BE /* SDWebImageTestLoader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestLoader.h; sourceTree = ""; }; + 323B8E1E20862322008952BE /* SDWebImageTestLoader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestLoader.m; sourceTree = ""; }; 3254C31F20641077008D1022 /* SDWebImageTransformerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTransformerTests.m; sourceTree = ""; }; 3264FF2D205D42CB00F6BD48 /* SDWebImageTestTransformer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestTransformer.h; sourceTree = ""; }; 3264FF2E205D42CB00F6BD48 /* SDWebImageTestTransformer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestTransformer.m; sourceTree = ""; }; @@ -223,6 +227,8 @@ 32E6F0311F3A1B4700A945E6 /* SDWebImageTestDecoder.m */, 3264FF2D205D42CB00F6BD48 /* SDWebImageTestTransformer.h */, 3264FF2E205D42CB00F6BD48 /* SDWebImageTestTransformer.m */, + 323B8E1D20862322008952BE /* SDWebImageTestLoader.h */, + 323B8E1E20862322008952BE /* SDWebImageTestLoader.m */, ); path = Tests; sourceTree = ""; @@ -470,6 +476,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 323B8E2020862322008952BE /* SDWebImageTestLoader.m in Sources */, 32B99EAC203B36650017FD66 /* SDWebImageDownloaderTests.m in Sources */, 3254C32120641077008D1022 /* SDWebImageTransformerTests.m in Sources */, 328BB6DE20825E9800760D6C /* SDWebImageTestCache.m in Sources */, @@ -492,6 +499,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 323B8E1F20862322008952BE /* SDWebImageTestLoader.m in Sources */, 32E6F0321F3A1B4700A945E6 /* SDWebImageTestDecoder.m in Sources */, 3226ECBB20754F7700FAFACF /* SDWebImageTestDownloadOperation.m in Sources */, 3254C32020641077008D1022 /* SDWebImageTransformerTests.m in Sources */, diff --git a/Tests/Tests/SDWebImageDownloaderTests.m b/Tests/Tests/SDWebImageDownloaderTests.m index c47550dc..2a2d568e 100644 --- a/Tests/Tests/SDWebImageDownloaderTests.m +++ b/Tests/Tests/SDWebImageDownloaderTests.m @@ -11,8 +11,10 @@ #import #import #import +#import #import "SDWebImageTestDownloadOperation.h" #import "SDWebImageTestDecoder.h" +#import "SDWebImageTestLoader.h" /** * Category for SDWebImageDownloader so we can access the operationClass @@ -414,4 +416,34 @@ [self waitForExpectationsWithCommonTimeout]; } +#pragma mark - SDWebImageLoader +- (void)test30CustomImageLoaderWorks { + XCTestExpectation *expectation = [self expectationWithDescription:@"Custom image not works"]; + SDWebImageTestLoader *loader = [[SDWebImageTestLoader alloc] init]; + NSURL *imageURL = [NSURL URLWithString:kTestJpegURL]; + [loader loadImageWithURL:imageURL options:0 context:nil progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { + expect(targetURL).notTo.beNil(); + } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) { + expect(error).to.beNil(); + expect(image).notTo.beNil(); + [expectation fulfill]; + }]; + + [self waitForExpectationsWithCommonTimeout]; +} + +- (void)test31ThatLoadersManagerWorks { + XCTestExpectation *expectation = [self expectationWithDescription:@"Loaders manager not works"]; + NSURL *imageURL = [NSURL URLWithString:kTestJpegURL]; + [[SDWebImageLoadersManager sharedManager] loadImageWithURL:imageURL options:0 context:nil progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { + expect(targetURL).notTo.beNil(); + } completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) { + expect(error).to.beNil(); + expect(image).notTo.beNil(); + [expectation fulfill]; + }]; + + [self waitForExpectationsWithCommonTimeout]; +} + @end diff --git a/Tests/Tests/SDWebImageTestLoader.h b/Tests/Tests/SDWebImageTestLoader.h new file mode 100644 index 00000000..10250240 --- /dev/null +++ b/Tests/Tests/SDWebImageTestLoader.h @@ -0,0 +1,16 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * (c) Matt Galloway + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import +#import + +// A really naive implementation of custom image loader using `NSURLSession` +@interface SDWebImageTestLoader : NSObject + +@end diff --git a/Tests/Tests/SDWebImageTestLoader.m b/Tests/Tests/SDWebImageTestLoader.m new file mode 100644 index 00000000..65165d73 --- /dev/null +++ b/Tests/Tests/SDWebImageTestLoader.m @@ -0,0 +1,53 @@ +/* + * This file is part of the SDWebImage package. + * (c) Olivier Poitrey + * (c) Matt Galloway + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#import "SDWebImageTestLoader.h" +#import + +@interface NSURLSessionTask (SDWebImageOperation) + +@end + +@implementation SDWebImageTestLoader + +- (BOOL)canLoadWithURL:(NSURL *)url { + return YES; +} + +- (id)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context progress:(SDWebImageLoaderProgressBlock)progressBlock completed:(SDWebImageLoaderCompletedBlock)completedBlock { + NSURLRequest *request = [NSURLRequest requestWithURL:url]; + + NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { + if (data) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + UIImage *image = SDWebImageLoaderDecodeImageData(data, url, options, context); + if (completedBlock) { + completedBlock(image, data, nil, YES); + } + }); + } else { + if (completedBlock) { + completedBlock(nil, nil, error, YES); + } + } + }]; + [self.KVOController observe:task keyPath:NSStringFromSelector(@selector(countOfBytesReceived)) options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary * _Nonnull change) { + NSURLSessionTask *sessionTask = object; + NSInteger receivedSize = sessionTask.countOfBytesReceived; + NSInteger expectedSize = sessionTask.countOfBytesExpectedToReceive; + if (progressBlock) { + progressBlock(receivedSize, expectedSize, url); + } + }]; + [task resume]; + + return task; +} + +@end