Commit 35a5116a authored by 陈业泓's avatar 陈业泓

更新了SDK代码

parent b1011432
...@@ -157,56 +157,56 @@ PODS: ...@@ -157,56 +157,56 @@ PODS:
- MBProgressHUD (1.2.0) - MBProgressHUD (1.2.0)
- MJExtension (3.2.2) - MJExtension (3.2.2)
- MJRefresh (3.5.0) - MJRefresh (3.5.0)
- SDWebImage (5.9.1): - SDWebImage (5.13.2):
- SDWebImage/Core (= 5.9.1) - SDWebImage/Core (= 5.13.2)
- SDWebImage/Core (5.9.1) - SDWebImage/Core (5.13.2)
- SGQRCode (3.0.1) - SGQRCode (3.0.1)
- STCBinder (1.0.0) - STCBinder (1.0.0)
- SYCSDK (0.1.9): - SYCSDK (0.2.0):
- SYCSDK/BRPickerView (= 0.1.9) - SYCSDK/BRPickerView (= 0.2.0)
- SYCSDK/DACircularProgress (= 0.1.9) - SYCSDK/DACircularProgress (= 0.2.0)
- SYCSDK/IQKeyboardManager (= 0.1.9) - SYCSDK/IQKeyboardManager (= 0.2.0)
- SYCSDK/JKCategories (= 0.1.9) - SYCSDK/JKCategories (= 0.2.0)
- SYCSDK/JXCategoryView (= 0.1.9) - SYCSDK/JXCategoryView (= 0.2.0)
- SYCSDK/JXPagingViewPager (= 0.1.9) - SYCSDK/JXPagingViewPager (= 0.2.0)
- SYCSDK/MBProgressHUD (= 0.1.9) - SYCSDK/MBProgressHUD (= 0.2.0)
- SYCSDK/MJExtension (= 0.1.9) - SYCSDK/MJExtension (= 0.2.0)
- SYCSDK/MJRefresh (= 0.1.9) - SYCSDK/MJRefresh (= 0.2.0)
- SYCSDK/SDWebImage (= 0.1.9) - SYCSDK/SDWebImage (= 0.2.0)
- SYCSDK/SGQRCode (= 0.1.9) - SYCSDK/SGQRCode (= 0.2.0)
- SYCSDK/STCBinder (= 0.1.9) - SYCSDK/STCBinder (= 0.2.0)
- SYCSDK/TTGTagCollectionView (= 0.1.9) - SYCSDK/TTGTagCollectionView (= 0.2.0)
- SYCSDK/ViewUtil (= 0.1.9) - SYCSDK/ViewUtil (= 0.2.0)
- SYCSDK/YYText (= 0.1.9) - SYCSDK/YYText (= 0.2.0)
- SYCSDK/BRPickerView (0.1.9): - SYCSDK/BRPickerView (0.2.0):
- BRPickerView (= 2.7.3) - BRPickerView (= 2.7.3)
- SYCSDK/DACircularProgress (0.1.9): - SYCSDK/DACircularProgress (0.2.0):
- DACircularProgress (= 2.3.1) - DACircularProgress (= 2.3.1)
- SYCSDK/IQKeyboardManager (0.1.9): - SYCSDK/IQKeyboardManager (0.2.0):
- IQKeyboardManager (= 6.5.6) - IQKeyboardManager (= 6.5.6)
- SYCSDK/JKCategories (0.1.9): - SYCSDK/JKCategories (0.2.0):
- JKCategories (= 1.9.3) - JKCategories (= 1.9.3)
- SYCSDK/JXCategoryView (0.1.9): - SYCSDK/JXCategoryView (0.2.0):
- JXCategoryView (= 1.5.8) - JXCategoryView (= 1.5.8)
- SYCSDK/JXPagingViewPager (0.1.9): - SYCSDK/JXPagingViewPager (0.2.0):
- JXPagingView/Pager (= 2.1.0) - JXPagingView/Pager (= 2.1.0)
- SYCSDK/MBProgressHUD (0.1.9): - SYCSDK/MBProgressHUD (0.2.0):
- MBProgressHUD (= 1.2.0) - MBProgressHUD (= 1.2.0)
- SYCSDK/MJExtension (0.1.9): - SYCSDK/MJExtension (0.2.0):
- MJExtension (= 3.2.2) - MJExtension (= 3.2.2)
- SYCSDK/MJRefresh (0.1.9): - SYCSDK/MJRefresh (0.2.0):
- MJRefresh (= 3.5.0) - MJRefresh (= 3.5.0)
- SYCSDK/SDWebImage (0.1.9): - SYCSDK/SDWebImage (0.2.0):
- SDWebImage (= 5.9.1) - SDWebImage (= 5.13.2)
- SYCSDK/SGQRCode (0.1.9): - SYCSDK/SGQRCode (0.2.0):
- SGQRCode (= 3.0.1) - SGQRCode (= 3.0.1)
- SYCSDK/STCBinder (0.1.9): - SYCSDK/STCBinder (0.2.0):
- STCBinder (= 1.0.0) - STCBinder (= 1.0.0)
- SYCSDK/TTGTagCollectionView (0.1.9): - SYCSDK/TTGTagCollectionView (0.2.0):
- TTGTagCollectionView (~> 1.11.1) - TTGTagCollectionView (~> 1.11.1)
- SYCSDK/ViewUtil (0.1.9): - SYCSDK/ViewUtil (0.2.0):
- Masonry - Masonry
- SYCSDK/YYText (0.1.9): - SYCSDK/YYText (0.2.0):
- YYText (= 1.0.7) - YYText (= 1.0.7)
- TTGTagCollectionView (1.11.2) - TTGTagCollectionView (1.11.2)
- YYText (1.0.7) - YYText (1.0.7)
...@@ -247,13 +247,13 @@ SPEC CHECKSUMS: ...@@ -247,13 +247,13 @@ SPEC CHECKSUMS:
MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406 MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406
MJExtension: d9b9c74cbdeb724c1e9ecbb157b318276e62e876 MJExtension: d9b9c74cbdeb724c1e9ecbb157b318276e62e876
MJRefresh: 6afc955813966afb08305477dd7a0d9ad5e79a16 MJRefresh: 6afc955813966afb08305477dd7a0d9ad5e79a16
SDWebImage: a990c053fff71e388a10f3357edb0be17929c9c5 SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
SGQRCode: bf02d60e18d6660a9d75ff1c8b9b467f064f054e SGQRCode: bf02d60e18d6660a9d75ff1c8b9b467f064f054e
STCBinder: 439487688e719d92d7690c0b12c0c1dab8ddcb07 STCBinder: 439487688e719d92d7690c0b12c0c1dab8ddcb07
SYCSDK: 8202881550dddcaf55cc38841f4b146b8d3455c3 SYCSDK: a20e69372a159d21543f6f28a6b3088908a51846
TTGTagCollectionView: 949030356ed27954f39dd29d78978c0f5c35a745 TTGTagCollectionView: 949030356ed27954f39dd29d78978c0f5c35a745
YYText: 5c461d709e24d55a182d1441c41dc639a18a4849 YYText: 5c461d709e24d55a182d1441c41dc639a18a4849
PODFILE CHECKSUM: 32da94b045a2579a8e3c39aebfad5abc91c9f0f4 PODFILE CHECKSUM: 32da94b045a2579a8e3c39aebfad5abc91c9f0f4
COCOAPODS: 1.11.2 COCOAPODS: 1.11.3
{ {
"name": "SYCSDK", "name": "SYCSDK",
"version": "0.1.9", "version": "0.2.0",
"summary": "A short description of SYCSDK.", "summary": "A short description of SYCSDK.",
"description": "TODO: common three party.", "description": "TODO: common three party.",
"homepage": "http://www.shengyc.com/", "homepage": "http://www.shengyc.com/",
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
}, },
"source": { "source": {
"git": "http://139.159.244.151:2900/chenyehong/SYCSDK.git", "git": "http://139.159.244.151:2900/chenyehong/SYCSDK.git",
"tag": "0.1.9" "tag": "0.2.0"
}, },
"platforms": { "platforms": {
"ios": "9.0" "ios": "9.0"
...@@ -128,7 +128,7 @@ ...@@ -128,7 +128,7 @@
"name": "SDWebImage", "name": "SDWebImage",
"dependencies": { "dependencies": {
"SDWebImage": [ "SDWebImage": [
"5.9.1" "5.13.2"
] ]
} }
}, },
......
...@@ -157,56 +157,56 @@ PODS: ...@@ -157,56 +157,56 @@ PODS:
- MBProgressHUD (1.2.0) - MBProgressHUD (1.2.0)
- MJExtension (3.2.2) - MJExtension (3.2.2)
- MJRefresh (3.5.0) - MJRefresh (3.5.0)
- SDWebImage (5.9.1): - SDWebImage (5.13.2):
- SDWebImage/Core (= 5.9.1) - SDWebImage/Core (= 5.13.2)
- SDWebImage/Core (5.9.1) - SDWebImage/Core (5.13.2)
- SGQRCode (3.0.1) - SGQRCode (3.0.1)
- STCBinder (1.0.0) - STCBinder (1.0.0)
- SYCSDK (0.1.9): - SYCSDK (0.2.0):
- SYCSDK/BRPickerView (= 0.1.9) - SYCSDK/BRPickerView (= 0.2.0)
- SYCSDK/DACircularProgress (= 0.1.9) - SYCSDK/DACircularProgress (= 0.2.0)
- SYCSDK/IQKeyboardManager (= 0.1.9) - SYCSDK/IQKeyboardManager (= 0.2.0)
- SYCSDK/JKCategories (= 0.1.9) - SYCSDK/JKCategories (= 0.2.0)
- SYCSDK/JXCategoryView (= 0.1.9) - SYCSDK/JXCategoryView (= 0.2.0)
- SYCSDK/JXPagingViewPager (= 0.1.9) - SYCSDK/JXPagingViewPager (= 0.2.0)
- SYCSDK/MBProgressHUD (= 0.1.9) - SYCSDK/MBProgressHUD (= 0.2.0)
- SYCSDK/MJExtension (= 0.1.9) - SYCSDK/MJExtension (= 0.2.0)
- SYCSDK/MJRefresh (= 0.1.9) - SYCSDK/MJRefresh (= 0.2.0)
- SYCSDK/SDWebImage (= 0.1.9) - SYCSDK/SDWebImage (= 0.2.0)
- SYCSDK/SGQRCode (= 0.1.9) - SYCSDK/SGQRCode (= 0.2.0)
- SYCSDK/STCBinder (= 0.1.9) - SYCSDK/STCBinder (= 0.2.0)
- SYCSDK/TTGTagCollectionView (= 0.1.9) - SYCSDK/TTGTagCollectionView (= 0.2.0)
- SYCSDK/ViewUtil (= 0.1.9) - SYCSDK/ViewUtil (= 0.2.0)
- SYCSDK/YYText (= 0.1.9) - SYCSDK/YYText (= 0.2.0)
- SYCSDK/BRPickerView (0.1.9): - SYCSDK/BRPickerView (0.2.0):
- BRPickerView (= 2.7.3) - BRPickerView (= 2.7.3)
- SYCSDK/DACircularProgress (0.1.9): - SYCSDK/DACircularProgress (0.2.0):
- DACircularProgress (= 2.3.1) - DACircularProgress (= 2.3.1)
- SYCSDK/IQKeyboardManager (0.1.9): - SYCSDK/IQKeyboardManager (0.2.0):
- IQKeyboardManager (= 6.5.6) - IQKeyboardManager (= 6.5.6)
- SYCSDK/JKCategories (0.1.9): - SYCSDK/JKCategories (0.2.0):
- JKCategories (= 1.9.3) - JKCategories (= 1.9.3)
- SYCSDK/JXCategoryView (0.1.9): - SYCSDK/JXCategoryView (0.2.0):
- JXCategoryView (= 1.5.8) - JXCategoryView (= 1.5.8)
- SYCSDK/JXPagingViewPager (0.1.9): - SYCSDK/JXPagingViewPager (0.2.0):
- JXPagingView/Pager (= 2.1.0) - JXPagingView/Pager (= 2.1.0)
- SYCSDK/MBProgressHUD (0.1.9): - SYCSDK/MBProgressHUD (0.2.0):
- MBProgressHUD (= 1.2.0) - MBProgressHUD (= 1.2.0)
- SYCSDK/MJExtension (0.1.9): - SYCSDK/MJExtension (0.2.0):
- MJExtension (= 3.2.2) - MJExtension (= 3.2.2)
- SYCSDK/MJRefresh (0.1.9): - SYCSDK/MJRefresh (0.2.0):
- MJRefresh (= 3.5.0) - MJRefresh (= 3.5.0)
- SYCSDK/SDWebImage (0.1.9): - SYCSDK/SDWebImage (0.2.0):
- SDWebImage (= 5.9.1) - SDWebImage (= 5.13.2)
- SYCSDK/SGQRCode (0.1.9): - SYCSDK/SGQRCode (0.2.0):
- SGQRCode (= 3.0.1) - SGQRCode (= 3.0.1)
- SYCSDK/STCBinder (0.1.9): - SYCSDK/STCBinder (0.2.0):
- STCBinder (= 1.0.0) - STCBinder (= 1.0.0)
- SYCSDK/TTGTagCollectionView (0.1.9): - SYCSDK/TTGTagCollectionView (0.2.0):
- TTGTagCollectionView (~> 1.11.1) - TTGTagCollectionView (~> 1.11.1)
- SYCSDK/ViewUtil (0.1.9): - SYCSDK/ViewUtil (0.2.0):
- Masonry - Masonry
- SYCSDK/YYText (0.1.9): - SYCSDK/YYText (0.2.0):
- YYText (= 1.0.7) - YYText (= 1.0.7)
- TTGTagCollectionView (1.11.2) - TTGTagCollectionView (1.11.2)
- YYText (1.0.7) - YYText (1.0.7)
...@@ -247,13 +247,13 @@ SPEC CHECKSUMS: ...@@ -247,13 +247,13 @@ SPEC CHECKSUMS:
MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406 MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406
MJExtension: d9b9c74cbdeb724c1e9ecbb157b318276e62e876 MJExtension: d9b9c74cbdeb724c1e9ecbb157b318276e62e876
MJRefresh: 6afc955813966afb08305477dd7a0d9ad5e79a16 MJRefresh: 6afc955813966afb08305477dd7a0d9ad5e79a16
SDWebImage: a990c053fff71e388a10f3357edb0be17929c9c5 SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
SGQRCode: bf02d60e18d6660a9d75ff1c8b9b467f064f054e SGQRCode: bf02d60e18d6660a9d75ff1c8b9b467f064f054e
STCBinder: 439487688e719d92d7690c0b12c0c1dab8ddcb07 STCBinder: 439487688e719d92d7690c0b12c0c1dab8ddcb07
SYCSDK: 8202881550dddcaf55cc38841f4b146b8d3455c3 SYCSDK: a20e69372a159d21543f6f28a6b3088908a51846
TTGTagCollectionView: 949030356ed27954f39dd29d78978c0f5c35a745 TTGTagCollectionView: 949030356ed27954f39dd29d78978c0f5c35a745
YYText: 5c461d709e24d55a182d1441c41dc639a18a4849 YYText: 5c461d709e24d55a182d1441c41dc639a18a4849
PODFILE CHECKSUM: 32da94b045a2579a8e3c39aebfad5abc91c9f0f4 PODFILE CHECKSUM: 32da94b045a2579a8e3c39aebfad5abc91c9f0f4
COCOAPODS: 1.11.2 COCOAPODS: 1.11.3
...@@ -6671,6 +6671,37 @@ ...@@ -6671,6 +6671,37 @@
}; };
name = Debug; name = Debug;
}; };
6220EF3052B8964FDDCB97127C9FBA03 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 03FC9E66D646BFC925B3D57E0783B7DF /* SDWebImage.release.xcconfig */;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_PREFIX_HEADER = "Target Support Files/SDWebImage/SDWebImage-prefix.pch";
INFOPLIST_FILE = "Target Support Files/SDWebImage/SDWebImage-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MODULEMAP_FILE = "Target Support Files/SDWebImage/SDWebImage.modulemap";
PRODUCT_MODULE_NAME = SDWebImage;
PRODUCT_NAME = SDWebImage;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
63A8B36BADDCE8263FA5C03D412A0D01 /* Debug */ = { 63A8B36BADDCE8263FA5C03D412A0D01 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 03F410873AF9DBC3A8CF3067BD14C976 /* Pods-SYCSDK_Example.debug.xcconfig */; baseConfigurationReference = 03F410873AF9DBC3A8CF3067BD14C976 /* Pods-SYCSDK_Example.debug.xcconfig */;
...@@ -6918,37 +6949,6 @@ ...@@ -6918,37 +6949,6 @@
}; };
name = Debug; name = Debug;
}; };
B17BCC9842F86427766EF15C2427D2BA /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 03FC9E66D646BFC925B3D57E0783B7DF /* SDWebImage.release.xcconfig */;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_PREFIX_HEADER = "Target Support Files/SDWebImage/SDWebImage-prefix.pch";
INFOPLIST_FILE = "Target Support Files/SDWebImage/SDWebImage-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MODULEMAP_FILE = "Target Support Files/SDWebImage/SDWebImage.modulemap";
PRODUCT_MODULE_NAME = SDWebImage;
PRODUCT_NAME = SDWebImage;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
B6B324EF8406D9723FD9A08989EAFC03 /* Debug */ = { B6B324EF8406D9723FD9A08989EAFC03 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 36789CC34571896C1CF56EF0D3023C10 /* TTGTagCollectionView.debug.xcconfig */; baseConfigurationReference = 36789CC34571896C1CF56EF0D3023C10 /* TTGTagCollectionView.debug.xcconfig */;
...@@ -6979,9 +6979,9 @@ ...@@ -6979,9 +6979,9 @@
}; };
name = Debug; name = Debug;
}; };
C0517E1CCB9EE888964F639762020D05 /* Debug */ = { C437DB9E31D4D0C9067317F7DDA15949 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = 5E18277D69CA0D94A7F12D34762C8F74 /* SDWebImage.debug.xcconfig */; baseConfigurationReference = EC7565F90F0A76C51A1DC90FCA3ED1A7 /* Masonry.debug.xcconfig */;
buildSettings = { buildSettings = {
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
...@@ -6991,14 +6991,14 @@ ...@@ -6991,14 +6991,14 @@
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1; DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath"; DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_PREFIX_HEADER = "Target Support Files/SDWebImage/SDWebImage-prefix.pch"; GCC_PREFIX_HEADER = "Target Support Files/Masonry/Masonry-prefix.pch";
INFOPLIST_FILE = "Target Support Files/SDWebImage/SDWebImage-Info.plist"; INFOPLIST_FILE = "Target Support Files/Masonry/Masonry-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MODULEMAP_FILE = "Target Support Files/SDWebImage/SDWebImage.modulemap"; MODULEMAP_FILE = "Target Support Files/Masonry/Masonry.modulemap";
PRODUCT_MODULE_NAME = SDWebImage; PRODUCT_MODULE_NAME = Masonry;
PRODUCT_NAME = SDWebImage; PRODUCT_NAME = Masonry;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
...@@ -7009,9 +7009,9 @@ ...@@ -7009,9 +7009,9 @@
}; };
name = Debug; name = Debug;
}; };
C437DB9E31D4D0C9067317F7DDA15949 /* Debug */ = { C54376061B0B868404C6ABC57F98241E /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = EC7565F90F0A76C51A1DC90FCA3ED1A7 /* Masonry.debug.xcconfig */; baseConfigurationReference = C8AE5A3B8A1DD200FC95FEE283DBFD6F /* SGQRCode.debug.xcconfig */;
buildSettings = { buildSettings = {
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
...@@ -7021,14 +7021,14 @@ ...@@ -7021,14 +7021,14 @@
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1; DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath"; DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_PREFIX_HEADER = "Target Support Files/Masonry/Masonry-prefix.pch"; GCC_PREFIX_HEADER = "Target Support Files/SGQRCode/SGQRCode-prefix.pch";
INFOPLIST_FILE = "Target Support Files/Masonry/Masonry-Info.plist"; INFOPLIST_FILE = "Target Support Files/SGQRCode/SGQRCode-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MODULEMAP_FILE = "Target Support Files/Masonry/Masonry.modulemap"; MODULEMAP_FILE = "Target Support Files/SGQRCode/SGQRCode.modulemap";
PRODUCT_MODULE_NAME = Masonry; PRODUCT_MODULE_NAME = SGQRCode;
PRODUCT_NAME = Masonry; PRODUCT_NAME = SGQRCode;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
...@@ -7039,9 +7039,9 @@ ...@@ -7039,9 +7039,9 @@
}; };
name = Debug; name = Debug;
}; };
C54376061B0B868404C6ABC57F98241E /* Debug */ = { C5BD2E1647D5DF97106129666460C5D8 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
baseConfigurationReference = C8AE5A3B8A1DD200FC95FEE283DBFD6F /* SGQRCode.debug.xcconfig */; baseConfigurationReference = 5E18277D69CA0D94A7F12D34762C8F74 /* SDWebImage.debug.xcconfig */;
buildSettings = { buildSettings = {
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; "CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
...@@ -7051,14 +7051,14 @@ ...@@ -7051,14 +7051,14 @@
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1; DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath"; DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_PREFIX_HEADER = "Target Support Files/SGQRCode/SGQRCode-prefix.pch"; GCC_PREFIX_HEADER = "Target Support Files/SDWebImage/SDWebImage-prefix.pch";
INFOPLIST_FILE = "Target Support Files/SGQRCode/SGQRCode-Info.plist"; INFOPLIST_FILE = "Target Support Files/SDWebImage/SDWebImage-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MODULEMAP_FILE = "Target Support Files/SGQRCode/SGQRCode.modulemap"; MODULEMAP_FILE = "Target Support Files/SDWebImage/SDWebImage.modulemap";
PRODUCT_MODULE_NAME = SGQRCode; PRODUCT_MODULE_NAME = SDWebImage;
PRODUCT_NAME = SGQRCode; PRODUCT_NAME = SDWebImage;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) "; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
...@@ -7619,8 +7619,8 @@ ...@@ -7619,8 +7619,8 @@
BABD4A24CFCBE5B6FE4680AA062FCF04 /* Build configuration list for PBXNativeTarget "SDWebImage" */ = { BABD4A24CFCBE5B6FE4680AA062FCF04 /* Build configuration list for PBXNativeTarget "SDWebImage" */ = {
isa = XCConfigurationList; isa = XCConfigurationList;
buildConfigurations = ( buildConfigurations = (
C0517E1CCB9EE888964F639762020D05 /* Debug */, C5BD2E1647D5DF97106129666460C5D8 /* Debug */,
B17BCC9842F86427766EF15C2427D2BA /* Release */, 6220EF3052B8964FDDCB97127C9FBA03 /* Release */,
); );
defaultConfigurationIsVisible = 0; defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release; defaultConfigurationName = Release;
......
...@@ -37,8 +37,9 @@ This library provides an async image downloader with cache support. For convenie ...@@ -37,8 +37,9 @@ This library provides an async image downloader with cache support. For convenie
## Supported Image Formats ## Supported Image Formats
- Image formats supported by Apple system (JPEG, PNG, TIFF, HEIC, ...), including GIF/APNG/HEIC animation - Image formats supported by Apple system (JPEG, PNG, TIFF, BMP, ...), including [GIF](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#gif-coder)/[APNG](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#apng-coder) animated image
- WebP format, including animated WebP (use the [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder) project). Note iOS 14/macOS 11.0 supports built-in WebP decoding (no encoding). - HEIC format from iOS 11/macOS 10.13, including animated HEIC from iOS 13/macOS 10.15 via [SDWebImageHEICCoder](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#heic-coder). For lower firmware, use coder plugin [SDWebImageHEIFCoder](https://github.com/SDWebImage/SDWebImageHEIFCoder)
- WebP format from iOS 14/macOS 11.0 via [SDWebImageAWebPCoder](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#awebp-coder). For lower firmware, use coder plugin [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder)
- Support extendable coder plugins for new image formats like BPG, AVIF. And vector format like PDF, SVG. See all the list in [Image coder plugin List](https://github.com/SDWebImage/SDWebImage/wiki/Coder-Plugin-List) - Support extendable coder plugins for new image formats like BPG, AVIF. And vector format like PDF, SVG. See all the list in [Image coder plugin List](https://github.com/SDWebImage/SDWebImage/wiki/Coder-Plugin-List)
## Additional modules and Ecosystem ## Additional modules and Ecosystem
...@@ -74,7 +75,7 @@ The new framework introduce two View structs `WebImage` and `AnimatedImage` for ...@@ -74,7 +75,7 @@ The new framework introduce two View structs `WebImage` and `AnimatedImage` for
#### Integration with 3rd party libraries #### Integration with 3rd party libraries
- [SDWebImageLottiePlugin](https://github.com/SDWebImage/SDWebImageLottiePlugin) - plugin to support [Lottie-iOS](https://github.com/airbnb/lottie-ios), vector animation rending with remote JSON files - [SDWebImageLottiePlugin](https://github.com/SDWebImage/SDWebImageLottiePlugin) - plugin to support [Lottie-iOS](https://github.com/airbnb/lottie-ios), vector animation rending with remote JSON files
- [SDWebImageSVGKitPlugin](https://github.com/SDWebImage/SDWebImageLottiePlugin) - plugin to support [SVGKit](https://github.com/SVGKit/SVGKit), SVG rendering using Core Animation, iOS 8+/macOS 10.10+ support - [SDWebImageSVGKitPlugin](https://github.com/SDWebImage/SDWebImageSVGKitPlugin) - plugin to support [SVGKit](https://github.com/SVGKit/SVGKit), SVG rendering using Core Animation, iOS 8+/macOS 10.10+ support
- [SDWebImageFLPlugin](https://github.com/SDWebImage/SDWebImageFLPlugin) - plugin to support [FLAnimatedImage](https://github.com/Flipboard/FLAnimatedImage) as the engine for animated GIFs - [SDWebImageFLPlugin](https://github.com/SDWebImage/SDWebImageFLPlugin) - plugin to support [FLAnimatedImage](https://github.com/Flipboard/FLAnimatedImage) as the engine for animated GIFs
- [SDWebImageYYPlugin](https://github.com/SDWebImage/SDWebImageYYPlugin) - plugin to integrate [YYImage](https://github.com/ibireme/YYImage) & [YYCache](https://github.com/ibireme/YYCache) for image rendering & caching - [SDWebImageYYPlugin](https://github.com/SDWebImage/SDWebImageYYPlugin) - plugin to integrate [YYImage](https://github.com/ibireme/YYImage) & [YYCache](https://github.com/ibireme/YYCache) for image rendering & caching
...@@ -93,17 +94,18 @@ You can use those directly, or create similar components of your own, by using t ...@@ -93,17 +94,18 @@ You can use those directly, or create similar components of your own, by using t
## Requirements ## Requirements
- iOS 8.0 or later - iOS 9.0 or later
- tvOS 9.0 or later - tvOS 9.0 or later
- watchOS 2.0 or later - watchOS 2.0 or later
- macOS 10.10 or later (10.15 for Catalyst) - macOS 10.11 or later (10.15 for Catalyst)
- Xcode 10.0 or later - Xcode 11.0 or later
#### Backwards compatibility #### Backwards compatibility
- For iOS 8, macOS 10.10 or Xcode < 11, use [any 5.x version up to 5.9.5](https://github.com/SDWebImage/SDWebImage/releases/tag/5.9.5)
- For iOS 7, macOS 10.9 or Xcode < 8, use [any 4.x version up to 4.4.6](https://github.com/SDWebImage/SDWebImage/releases/tag/4.4.6) - For iOS 7, macOS 10.9 or Xcode < 8, use [any 4.x version up to 4.4.6](https://github.com/SDWebImage/SDWebImage/releases/tag/4.4.6)
- For macOS 10.8, use [any 4.x version up to 4.3.0](https://github.com/SDWebImage/SDWebImage/releases/tag/4.3.0) - For macOS 10.8, use [any 4.x version up to 4.3.0](https://github.com/SDWebImage/SDWebImage/releases/tag/4.3.0)
- For iOS 5 and 6, use [any 3.x version up to 3.7.6](https://github.com/SDWebImage/SDWebImage/tag/3.7.6) - For iOS 5 and 6, use [any 3.x version up to 3.7.6](https://github.com/SDWebImage/SDWebImage/releases/tag/3.7.6)
- For iOS < 5.0, please use the last [2.0 version](https://github.com/SDWebImage/SDWebImage/tree/2.0-compat). - For iOS < 5.0, please use the last [2.0 version](https://github.com/SDWebImage/SDWebImage/tree/2.0-compat).
## Getting Started ## Getting Started
...@@ -164,7 +166,7 @@ This animated image solution is available for `iOS`/`tvOS`/`macOS`. The `SDAnima ...@@ -164,7 +166,7 @@ This animated image solution is available for `iOS`/`tvOS`/`macOS`. The `SDAnima
The `SDAnimatedImageView` supports the familiar image loading category methods, works like drop-in replacement for `UIImageView/NSImageView`. The `SDAnimatedImageView` supports the familiar image loading category methods, works like drop-in replacement for `UIImageView/NSImageView`.
Don't have UIView (like WatchKit or CALayer)? you can still use `SDAnimatedPlayer` the player engine for advanced playback and rendering. Don't have `UIView` (like `WatchKit` or `CALayer`)? you can still use `SDAnimatedPlayer` the player engine for advanced playback and rendering.
See [Animated Image](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50) for more detailed information. See [Animated Image](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50) for more detailed information.
...@@ -207,9 +209,9 @@ pod 'SDWebImage', '~> 5.0' ...@@ -207,9 +209,9 @@ pod 'SDWebImage', '~> 5.0'
##### Swift and static framework ##### Swift and static framework
Swift project previously have to use `use_frameworks!` to make all Pods into dynamic framework to let CocoaPods works. Swift project previously had to use `use_frameworks!` to make all Pods into dynamic framework to let CocoaPods work.
However, start with `CocoaPods 1.5.0+` (with `Xcode 9+`), which supports to build both Objective-C && Swift code into static framework. You can use modular headers to use SDWebImage as static framework, without the need of `use_frameworks!`: However, starting with `CocoaPods 1.5.0+` (with `Xcode 9+`), which supports to build both Objective-C && Swift code into static framework. You can use modular headers to use SDWebImage as static framework, without the need of `use_frameworks!`:
``` ```
platform :ios, '8.0' platform :ios, '8.0'
...@@ -238,7 +240,7 @@ Podfile example: ...@@ -238,7 +240,7 @@ Podfile example:
pod 'SDWebImage/MapKit' pod 'SDWebImage/MapKit'
``` ```
### Installation with Carthage (iOS 8+) ### Installation with Carthage
[Carthage](https://github.com/Carthage/Carthage) is a lightweight dependency manager for Swift and Objective-C. It leverages CocoaTouch modules and is less invasive than CocoaPods. [Carthage](https://github.com/Carthage/Carthage) is a lightweight dependency manager for Swift and Objective-C. It leverages CocoaTouch modules and is less invasive than CocoaPods.
...@@ -295,6 +297,9 @@ It's also recommend to use the module import syntax, available for CocoaPods(ena ...@@ -295,6 +297,9 @@ It's also recommend to use the module import syntax, available for CocoaPods(ena
At this point your workspace should build without error. If you are having problem, post to the Issue and the At this point your workspace should build without error. If you are having problem, post to the Issue and the
community can help you solve it. community can help you solve it.
## Data Collection Practices
As required by the [App privacy details on the App Store](https://developer.apple.com/app-store/app-privacy-details/), here's SDWebImage's list of [Data Collection Practices](https://sdwebimage.github.io/DataCollection/index.html).
## Author ## Author
- [Olivier Poitrey](https://github.com/rs) - [Olivier Poitrey](https://github.com/rs)
......
...@@ -149,7 +149,7 @@ static NSString * const SDAlternateImageOperationKey = @"NSButtonAlternateImageO ...@@ -149,7 +149,7 @@ static NSString * const SDAlternateImageOperationKey = @"NSButtonAlternateImageO
[self sd_cancelImageLoadOperationWithKey:SDAlternateImageOperationKey]; [self sd_cancelImageLoadOperationWithKey:SDAlternateImageOperationKey];
} }
#pragma mar - Private #pragma mark - Private
- (NSURL *)sd_currentImageURL { - (NSURL *)sd_currentImageURL {
return objc_getAssociatedObject(self, @selector(sd_currentImageURL)); return objc_getAssociatedObject(self, @selector(sd_currentImageURL));
......
...@@ -45,7 +45,7 @@ static const SDImageFormat SDImageFormatSVG = 8; ...@@ -45,7 +45,7 @@ static const SDImageFormat SDImageFormatSVG = 8;
* *
* @param format Format as SDImageFormat * @param format Format as SDImageFormat
* @return The UTType as CFStringRef * @return The UTType as CFStringRef
* @note For unknown format, `kUTTypeImage` abstract type will return * @note For unknown format, `kSDUTTypeImage` abstract type will return
*/ */
+ (nonnull CFStringRef)sd_UTTypeFromImageFormat:(SDImageFormat)format CF_RETURNS_NOT_RETAINED NS_SWIFT_NAME(sd_UTType(from:)); + (nonnull CFStringRef)sd_UTTypeFromImageFormat:(SDImageFormat)format CF_RETURNS_NOT_RETAINED NS_SWIFT_NAME(sd_UTType(from:));
......
...@@ -87,16 +87,16 @@ ...@@ -87,16 +87,16 @@
CFStringRef UTType; CFStringRef UTType;
switch (format) { switch (format) {
case SDImageFormatJPEG: case SDImageFormatJPEG:
UTType = kUTTypeJPEG; UTType = kSDUTTypeJPEG;
break; break;
case SDImageFormatPNG: case SDImageFormatPNG:
UTType = kUTTypePNG; UTType = kSDUTTypePNG;
break; break;
case SDImageFormatGIF: case SDImageFormatGIF:
UTType = kUTTypeGIF; UTType = kSDUTTypeGIF;
break; break;
case SDImageFormatTIFF: case SDImageFormatTIFF:
UTType = kUTTypeTIFF; UTType = kSDUTTypeTIFF;
break; break;
case SDImageFormatWebP: case SDImageFormatWebP:
UTType = kSDUTTypeWebP; UTType = kSDUTTypeWebP;
...@@ -108,14 +108,14 @@ ...@@ -108,14 +108,14 @@
UTType = kSDUTTypeHEIF; UTType = kSDUTTypeHEIF;
break; break;
case SDImageFormatPDF: case SDImageFormatPDF:
UTType = kUTTypePDF; UTType = kSDUTTypePDF;
break; break;
case SDImageFormatSVG: case SDImageFormatSVG:
UTType = kUTTypeScalableVectorGraphics; UTType = kSDUTTypeSVG;
break; break;
default: default:
// default is kUTTypeImage abstract type // default is kUTTypeImage abstract type
UTType = kUTTypeImage; UTType = kSDUTTypeImage;
break; break;
} }
return UTType; return UTType;
...@@ -126,13 +126,13 @@ ...@@ -126,13 +126,13 @@
return SDImageFormatUndefined; return SDImageFormatUndefined;
} }
SDImageFormat imageFormat; SDImageFormat imageFormat;
if (CFStringCompare(uttype, kUTTypeJPEG, 0) == kCFCompareEqualTo) { if (CFStringCompare(uttype, kSDUTTypeJPEG, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatJPEG; imageFormat = SDImageFormatJPEG;
} else if (CFStringCompare(uttype, kUTTypePNG, 0) == kCFCompareEqualTo) { } else if (CFStringCompare(uttype, kSDUTTypePNG, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatPNG; imageFormat = SDImageFormatPNG;
} else if (CFStringCompare(uttype, kUTTypeGIF, 0) == kCFCompareEqualTo) { } else if (CFStringCompare(uttype, kSDUTTypeGIF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatGIF; imageFormat = SDImageFormatGIF;
} else if (CFStringCompare(uttype, kUTTypeTIFF, 0) == kCFCompareEqualTo) { } else if (CFStringCompare(uttype, kSDUTTypeTIFF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatTIFF; imageFormat = SDImageFormatTIFF;
} else if (CFStringCompare(uttype, kSDUTTypeWebP, 0) == kCFCompareEqualTo) { } else if (CFStringCompare(uttype, kSDUTTypeWebP, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatWebP; imageFormat = SDImageFormatWebP;
...@@ -140,9 +140,9 @@ ...@@ -140,9 +140,9 @@
imageFormat = SDImageFormatHEIC; imageFormat = SDImageFormatHEIC;
} else if (CFStringCompare(uttype, kSDUTTypeHEIF, 0) == kCFCompareEqualTo) { } else if (CFStringCompare(uttype, kSDUTTypeHEIF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatHEIF; imageFormat = SDImageFormatHEIF;
} else if (CFStringCompare(uttype, kUTTypePDF, 0) == kCFCompareEqualTo) { } else if (CFStringCompare(uttype, kSDUTTypePDF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatPDF; imageFormat = SDImageFormatPDF;
} else if (CFStringCompare(uttype, kUTTypeScalableVectorGraphics, 0) == kCFCompareEqualTo) { } else if (CFStringCompare(uttype, kSDUTTypeSVG, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatSVG; imageFormat = SDImageFormatSVG;
} else { } else {
imageFormat = SDImageFormatUndefined; imageFormat = SDImageFormatUndefined;
......
...@@ -35,8 +35,8 @@ ...@@ -35,8 +35,8 @@
NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil]; NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil];
CGFloat width = imageRep.size.width; CGFloat width = imageRep.size.width;
CGFloat height = imageRep.size.height; CGFloat height = imageRep.size.height;
NSUInteger pixelWidth = imageRep.pixelsWide; CGFloat pixelWidth = (CGFloat)imageRep.pixelsWide;
NSUInteger pixelHeight = imageRep.pixelsHigh; CGFloat pixelHeight = (CGFloat)imageRep.pixelsHigh;
if (width > 0 && height > 0) { if (width > 0 && height > 0) {
CGFloat widthScale = pixelWidth / width; CGFloat widthScale = pixelWidth / width;
CGFloat heightScale = pixelHeight / height; CGFloat heightScale = pixelHeight / height;
......
...@@ -126,7 +126,7 @@ static CGFloat SDImageScaleFromPath(NSString *string) { ...@@ -126,7 +126,7 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
} }
data = [data copy]; // avoid mutable data data = [data copy]; // avoid mutable data
id<SDAnimatedImageCoder> animatedCoder = nil; id<SDAnimatedImageCoder> animatedCoder = nil;
for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders) { for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) { if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
if ([coder canDecodeFromData:data]) { if ([coder canDecodeFromData:data]) {
if (!options) { if (!options) {
...@@ -207,7 +207,7 @@ static CGFloat SDImageScaleFromPath(NSString *string) { ...@@ -207,7 +207,7 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
} }
CGFloat scale = self.scale; CGFloat scale = self.scale;
id<SDAnimatedImageCoder> animatedCoder = nil; id<SDAnimatedImageCoder> animatedCoder = nil;
for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders) { for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) { if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
if ([coder canDecodeFromData:animatedImageData]) { if ([coder canDecodeFromData:animatedImageData]) {
animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:animatedImageData options:@{SDImageCoderDecodeScaleFactor : @(scale)}]; animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:animatedImageData options:@{SDImageCoderDecodeScaleFactor : @(scale)}];
...@@ -314,6 +314,10 @@ static CGFloat SDImageScaleFromPath(NSString *string) { ...@@ -314,6 +314,10 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
return; return;
} }
- (NSUInteger)sd_imageFrameCount {
return self.animatedImageFrameCount;
}
- (SDImageFormat)sd_imageFormat { - (SDImageFormat)sd_imageFormat {
return self.animatedImageFormat; return self.animatedImageFormat;
} }
......
...@@ -10,6 +10,25 @@ ...@@ -10,6 +10,25 @@
#import "SDWebImageCompat.h" #import "SDWebImageCompat.h"
#import "SDImageCoder.h" #import "SDImageCoder.h"
typedef NS_ENUM(NSUInteger, SDAnimatedImagePlaybackMode) {
/**
* From first to last frame and stop or next loop.
*/
SDAnimatedImagePlaybackModeNormal = 0,
/**
* From last frame to first frame and stop or next loop.
*/
SDAnimatedImagePlaybackModeReverse,
/**
* From first frame to last frame and reverse again, like reciprocating.
*/
SDAnimatedImagePlaybackModeBounce,
/**
* From last frame to first frame and reverse again, like reversed reciprocating.
*/
SDAnimatedImagePlaybackModeReversedBounce,
};
/// A player to control the playback of animated image, which can be used to drive Animated ImageView or any rendering usage, like CALayer/WatchKit/SwiftUI rendering. /// A player to control the playback of animated image, which can be used to drive Animated ImageView or any rendering usage, like CALayer/WatchKit/SwiftUI rendering.
@interface SDAnimatedImagePlayer : NSObject @interface SDAnimatedImagePlayer : NSObject
...@@ -37,6 +56,9 @@ ...@@ -37,6 +56,9 @@
/// `< 0.0` is not supported currently and stop animation. (may support reverse playback in the future) /// `< 0.0` is not supported currently and stop animation. (may support reverse playback in the future)
@property (nonatomic, assign) double playbackRate; @property (nonatomic, assign) double playbackRate;
/// Asynchronous setup animation playback mode. Default mode is SDAnimatedImagePlaybackModeNormal.
@property (nonatomic, assign) SDAnimatedImagePlaybackMode playbackMode;
/// Provide a max buffer size by bytes. This is used to adjust frame buffer count and can be useful when the decoding cost is expensive (such as Animated WebP software decoding). Default is 0. /// Provide a max buffer size by bytes. This is used to adjust frame buffer count and can be useful when the decoding cost is expensive (such as Animated WebP software decoding). Default is 0.
/// `0` means automatically adjust by calculating current memory usage. /// `0` means automatically adjust by calculating current memory usage.
/// `1` means without any buffer cache, each of frames will be decoded and then be freed after rendering. (Lowest Memory and Highest CPU) /// `1` means without any buffer cache, each of frames will be decoded and then be freed after rendering. (Lowest Memory and Highest CPU)
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#import "SDInternalMacros.h" #import "SDInternalMacros.h"
@interface SDAnimatedImagePlayer () { @interface SDAnimatedImagePlayer () {
SD_LOCK_DECLARE(_lock);
NSRunLoopMode _runLoopMode; NSRunLoopMode _runLoopMode;
} }
...@@ -24,9 +25,9 @@ ...@@ -24,9 +25,9 @@
@property (nonatomic, assign) NSTimeInterval currentTime; @property (nonatomic, assign) NSTimeInterval currentTime;
@property (nonatomic, assign) BOOL bufferMiss; @property (nonatomic, assign) BOOL bufferMiss;
@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable; @property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
@property (nonatomic, assign) BOOL shouldReverse;
@property (nonatomic, assign) NSUInteger maxBufferCount; @property (nonatomic, assign) NSUInteger maxBufferCount;
@property (nonatomic, strong) NSOperationQueue *fetchQueue; @property (nonatomic, strong) NSOperationQueue *fetchQueue;
@property (nonatomic, strong) dispatch_semaphore_t lock;
@property (nonatomic, strong) SDDisplayLink *displayLink; @property (nonatomic, strong) SDDisplayLink *displayLink;
@end @end
...@@ -46,6 +47,7 @@ ...@@ -46,6 +47,7 @@
self.totalLoopCount = provider.animatedImageLoopCount; self.totalLoopCount = provider.animatedImageLoopCount;
self.animatedProvider = provider; self.animatedProvider = provider;
self.playbackRate = 1.0; self.playbackRate = 1.0;
SD_LOCK_INIT(_lock);
#if SD_UIKIT #if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif #endif
...@@ -70,7 +72,7 @@ ...@@ -70,7 +72,7 @@
[_fetchQueue cancelAllOperations]; [_fetchQueue cancelAllOperations];
[_fetchQueue addOperationWithBlock:^{ [_fetchQueue addOperationWithBlock:^{
NSNumber *currentFrameIndex = @(self.currentFrameIndex); NSNumber *currentFrameIndex = @(self.currentFrameIndex);
SD_LOCK(self.lock); SD_LOCK(self->_lock);
NSArray *keys = self.frameBuffer.allKeys; NSArray *keys = self.frameBuffer.allKeys;
// only keep the next frame for later rendering // only keep the next frame for later rendering
for (NSNumber * key in keys) { for (NSNumber * key in keys) {
...@@ -78,7 +80,7 @@ ...@@ -78,7 +80,7 @@
[self.frameBuffer removeObjectForKey:key]; [self.frameBuffer removeObjectForKey:key];
} }
} }
SD_UNLOCK(self.lock); SD_UNLOCK(self->_lock);
}]; }];
} }
...@@ -98,13 +100,6 @@ ...@@ -98,13 +100,6 @@
return _frameBuffer; return _frameBuffer;
} }
- (dispatch_semaphore_t)lock {
if (!_lock) {
_lock = dispatch_semaphore_create(1);
}
return _lock;
}
- (SDDisplayLink *)displayLink { - (SDDisplayLink *)displayLink {
if (!_displayLink) { if (!_displayLink) {
_displayLink = [SDDisplayLink displayLinkWithTarget:self selector:@selector(displayDidRefresh:)]; _displayLink = [SDDisplayLink displayLinkWithTarget:self selector:@selector(displayDidRefresh:)];
...@@ -142,7 +137,12 @@ ...@@ -142,7 +137,12 @@
if (self.currentFrameIndex != 0) { if (self.currentFrameIndex != 0) {
return; return;
} }
if ([self.animatedProvider isKindOfClass:[UIImage class]]) { if (self.playbackMode == SDAnimatedImagePlaybackModeReverse ||
self.playbackMode == SDAnimatedImagePlaybackModeReversedBounce) {
self.currentFrameIndex = self.totalFrameCount - 1;
}
if (!self.currentFrame && [self.animatedProvider isKindOfClass:[UIImage class]]) {
UIImage *image = (UIImage *)self.animatedProvider; UIImage *image = (UIImage *)self.animatedProvider;
// Use the poster image if available // Use the poster image if available
#if SD_MAC #if SD_MAC
...@@ -152,37 +152,36 @@ ...@@ -152,37 +152,36 @@
#endif #endif
if (posterFrame) { if (posterFrame) {
self.currentFrame = posterFrame; self.currentFrame = posterFrame;
SD_LOCK(self.lock); SD_LOCK(self->_lock);
self.frameBuffer[@(self.currentFrameIndex)] = self.currentFrame; self.frameBuffer[@(self.currentFrameIndex)] = self.currentFrame;
SD_UNLOCK(self.lock); SD_UNLOCK(self->_lock);
[self handleFrameChange]; [self handleFrameChange];
} }
} }
} }
- (void)resetCurrentFrameIndex { - (void)resetCurrentFrameStatus {
self.currentFrame = nil; // These should not trigger KVO, user don't need to receive an `index == 0, image == nil` callback.
self.currentFrameIndex = 0; _currentFrame = nil;
self.currentLoopCount = 0; _currentFrameIndex = 0;
self.currentTime = 0; _currentLoopCount = 0;
self.bufferMiss = NO; _currentTime = 0;
self.needsDisplayWhenImageBecomesAvailable = NO; _bufferMiss = NO;
[self handleFrameChange]; _needsDisplayWhenImageBecomesAvailable = NO;
} }
- (void)clearFrameBuffer { - (void)clearFrameBuffer {
SD_LOCK(self.lock); SD_LOCK(_lock);
[_frameBuffer removeAllObjects]; [_frameBuffer removeAllObjects];
SD_UNLOCK(self.lock); SD_UNLOCK(_lock);
} }
#pragma mark - Animation Control #pragma mark - Animation Control
- (void)startPlaying { - (void)startPlaying {
[self.displayLink start]; [self.displayLink start];
// Setup frame // Setup frame
if (self.currentFrameIndex == 0 && !self.currentFrame) { [self setupCurrentFrame];
[self setupCurrentFrame];
}
// Calculate max buffer size // Calculate max buffer size
[self calculateMaxBufferCount]; [self calculateMaxBufferCount];
} }
...@@ -191,7 +190,8 @@ ...@@ -191,7 +190,8 @@
[_fetchQueue cancelAllOperations]; [_fetchQueue cancelAllOperations];
// Using `_displayLink` here because when UIImageView dealloc, it may trigger `[self stopAnimating]`, we already release the display link in SDAnimatedImageView's dealloc method. // Using `_displayLink` here because when UIImageView dealloc, it may trigger `[self stopAnimating]`, we already release the display link in SDAnimatedImageView's dealloc method.
[_displayLink stop]; [_displayLink stop];
[self resetCurrentFrameIndex]; // We need to reset the frame status, but not trigger any handle. This can ensure next time's playing status correct.
[self resetCurrentFrameStatus];
} }
- (void)pausePlaying { - (void)pausePlaying {
...@@ -241,17 +241,32 @@ ...@@ -241,17 +241,32 @@
NSUInteger currentFrameIndex = self.currentFrameIndex; NSUInteger currentFrameIndex = self.currentFrameIndex;
NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount; NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
if (self.playbackMode == SDAnimatedImagePlaybackModeReverse) {
nextFrameIndex = currentFrameIndex == 0 ? (totalFrameCount - 1) : (currentFrameIndex - 1) % totalFrameCount;
} else if (self.playbackMode == SDAnimatedImagePlaybackModeBounce ||
self.playbackMode == SDAnimatedImagePlaybackModeReversedBounce) {
if (currentFrameIndex == 0) {
self.shouldReverse = false;
} else if (currentFrameIndex == totalFrameCount - 1) {
self.shouldReverse = true;
}
nextFrameIndex = self.shouldReverse ? (currentFrameIndex - 1) : (currentFrameIndex + 1);
nextFrameIndex %= totalFrameCount;
}
// Check if we need to display new frame firstly // Check if we need to display new frame firstly
BOOL bufferFull = NO; BOOL bufferFull = NO;
if (self.needsDisplayWhenImageBecomesAvailable) { if (self.needsDisplayWhenImageBecomesAvailable) {
UIImage *currentFrame; UIImage *currentFrame;
SD_LOCK(self.lock); SD_LOCK(_lock);
currentFrame = self.frameBuffer[@(currentFrameIndex)]; currentFrame = self.frameBuffer[@(currentFrameIndex)];
SD_UNLOCK(self.lock); SD_UNLOCK(_lock);
// Update the current frame // Update the current frame
if (currentFrame) { if (currentFrame) {
SD_LOCK(self.lock); SD_LOCK(_lock);
// Remove the frame buffer if need // Remove the frame buffer if need
if (self.frameBuffer.count > self.maxBufferCount) { if (self.frameBuffer.count > self.maxBufferCount) {
self.frameBuffer[@(currentFrameIndex)] = nil; self.frameBuffer[@(currentFrameIndex)] = nil;
...@@ -260,7 +275,7 @@ ...@@ -260,7 +275,7 @@
if (self.frameBuffer.count == totalFrameCount) { if (self.frameBuffer.count == totalFrameCount) {
bufferFull = YES; bufferFull = YES;
} }
SD_UNLOCK(self.lock); SD_UNLOCK(_lock);
// Update the current frame immediately // Update the current frame immediately
self.currentFrame = currentFrame; self.currentFrame = currentFrame;
...@@ -321,9 +336,9 @@ ...@@ -321,9 +336,9 @@
// Or, most cases, the decode speed is faster than render speed, we fetch next frame // Or, most cases, the decode speed is faster than render speed, we fetch next frame
NSUInteger fetchFrameIndex = self.bufferMiss? currentFrameIndex : nextFrameIndex; NSUInteger fetchFrameIndex = self.bufferMiss? currentFrameIndex : nextFrameIndex;
UIImage *fetchFrame; UIImage *fetchFrame;
SD_LOCK(self.lock); SD_LOCK(_lock);
fetchFrame = self.bufferMiss? nil : self.frameBuffer[@(nextFrameIndex)]; fetchFrame = self.bufferMiss? nil : self.frameBuffer[@(nextFrameIndex)];
SD_UNLOCK(self.lock); SD_UNLOCK(_lock);
if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) { if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
// Prefetch next frame in background queue // Prefetch next frame in background queue
...@@ -338,9 +353,9 @@ ...@@ -338,9 +353,9 @@
BOOL isAnimating = self.displayLink.isRunning; BOOL isAnimating = self.displayLink.isRunning;
if (isAnimating) { if (isAnimating) {
SD_LOCK(self.lock); SD_LOCK(self->_lock);
self.frameBuffer[@(fetchFrameIndex)] = frame; self.frameBuffer[@(fetchFrameIndex)] = frame;
SD_UNLOCK(self.lock); SD_UNLOCK(self->_lock);
} }
}]; }];
[self.fetchQueue addOperation:operation]; [self.fetchQueue addOperation:operation];
......
...@@ -27,6 +27,12 @@ ...@@ -27,6 +27,12 @@
} }
} }
- (instancetype)copyWithZone:(NSZone *)zone {
SDAnimatedImageRep *imageRep = [super copyWithZone:zone];
CFRetain(imageRep->_imageSource);
return imageRep;
}
// `NSBitmapImageRep`'s `imageRepWithData:` is not designed initializer // `NSBitmapImageRep`'s `imageRepWithData:` is not designed initializer
+ (instancetype)imageRepWithData:(NSData *)data { + (instancetype)imageRepWithData:(NSData *)data {
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data]; SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
...@@ -52,13 +58,13 @@ ...@@ -52,13 +58,13 @@
if (!type) { if (!type) {
return self; return self;
} }
if (CFStringCompare(type, kUTTypeGIF, 0) == kCFCompareEqualTo) { if (CFStringCompare(type, kSDUTTypeGIF, 0) == kCFCompareEqualTo) {
// GIF // GIF
// Fix the `NSBitmapImageRep` GIF loop count calculation issue // Fix the `NSBitmapImageRep` GIF loop count calculation issue
// Which will use 0 when there are no loop count information metadata in GIF data // Which will use 0 when there are no loop count information metadata in GIF data
NSUInteger loopCount = [SDImageGIFCoder imageLoopCountWithSource:imageSource]; NSUInteger loopCount = [SDImageGIFCoder imageLoopCountWithSource:imageSource];
[self setProperty:NSImageLoopCount withValue:@(loopCount)]; [self setProperty:NSImageLoopCount withValue:@(loopCount)];
} else if (CFStringCompare(type, kUTTypePNG, 0) == kCFCompareEqualTo) { } else if (CFStringCompare(type, kSDUTTypePNG, 0) == kCFCompareEqualTo) {
// APNG // APNG
// Do initialize about frame count, current frame/duration and loop count // Do initialize about frame count, current frame/duration and loop count
[self setProperty:NSImageFrameCount withValue:@(frameCount)]; [self setProperty:NSImageFrameCount withValue:@(frameCount)];
...@@ -100,10 +106,10 @@ ...@@ -100,10 +106,10 @@
} }
NSUInteger index = [value unsignedIntegerValue]; NSUInteger index = [value unsignedIntegerValue];
NSTimeInterval frameDuration = 0; NSTimeInterval frameDuration = 0;
if (CFStringCompare(type, kUTTypeGIF, 0) == kCFCompareEqualTo) { if (CFStringCompare(type, kSDUTTypeGIF, 0) == kCFCompareEqualTo) {
// GIF // GIF
frameDuration = [SDImageGIFCoder frameDurationAtIndex:index source:imageSource]; frameDuration = [SDImageGIFCoder frameDurationAtIndex:index source:imageSource];
} else if (CFStringCompare(type, kUTTypePNG, 0) == kCFCompareEqualTo) { } else if (CFStringCompare(type, kSDUTTypePNG, 0) == kCFCompareEqualTo) {
// APNG // APNG
frameDuration = [SDImageAPNGCoder frameDurationAtIndex:index source:imageSource]; frameDuration = [SDImageAPNGCoder frameDurationAtIndex:index source:imageSource];
} else if (CFStringCompare(type, kSDUTTypeHEICS, 0) == kCFCompareEqualTo) { } else if (CFStringCompare(type, kSDUTTypeHEICS, 0) == kCFCompareEqualTo) {
......
...@@ -11,14 +11,21 @@ ...@@ -11,14 +11,21 @@
#if SD_UIKIT || SD_MAC #if SD_UIKIT || SD_MAC
#import "SDAnimatedImage.h" #import "SDAnimatedImage.h"
#import "SDAnimatedImagePlayer.h"
/** /**
A drop-in replacement for UIImageView/NSImageView, you can use this for animated image rendering. A drop-in replacement for UIImageView/NSImageView, you can use this for animated image rendering.
Call `setImage:` with `UIImage(NSImage)` which conform to `SDAnimatedImage` protocol will start animated image rendering. Call with normal UIImage(NSImage) will back to normal UIImageView(NSImageView) rendering Call `setImage:` with `UIImage(NSImage)` which conforms to `SDAnimatedImage` protocol will start animated image rendering. Call with normal UIImage(NSImage) will back to normal UIImageView(NSImageView) rendering
For UIKit: use `-startAnimating`, `-stopAnimating` to control animating. `isAnimating` to check animation state. For UIKit: use `-startAnimating`, `-stopAnimating` to control animating. `isAnimating` to check animation state.
For AppKit: use `-setAnimates:` to control animating, `animates` to check animation state. This view is layer-backed. For AppKit: use `-setAnimates:` to control animating, `animates` to check animation state. This view is layer-backed.
*/ */
@interface SDAnimatedImageView : UIImageView @interface SDAnimatedImageView : UIImageView
/**
The internal animation player.
This property is only used for advanced usage, like inspecting/debugging animation status, control progressive loading, complicated animation frame index control, etc.
@warning Pay attention if you directly update the player's property like `totalFrameCount`, `totalLoopCount`, the same property on `SDAnimatedImageView` may not get synced.
*/
@property (nonatomic, strong, readonly, nullable) SDAnimatedImagePlayer *player;
/** /**
Current display frame image. This value is KVO Compliance. Current display frame image. This value is KVO Compliance.
...@@ -52,6 +59,10 @@ ...@@ -52,6 +59,10 @@
`< 0.0` is not supported currently and stop animation. (may support reverse playback in the future) `< 0.0` is not supported currently and stop animation. (may support reverse playback in the future)
*/ */
@property (nonatomic, assign) double playbackRate; @property (nonatomic, assign) double playbackRate;
/// Asynchronous setup animation playback mode. Default mode is SDAnimatedImagePlaybackModeNormal.
@property (nonatomic, assign) SDAnimatedImagePlaybackMode playbackMode;
/** /**
Provide a max buffer size by bytes. This is used to adjust frame buffer count and can be useful when the decoding cost is expensive (such as Animated WebP software decoding). Default is 0. Provide a max buffer size by bytes. This is used to adjust frame buffer count and can be useful when the decoding cost is expensive (such as Animated WebP software decoding). Default is 0.
`0` means automatically adjust by calculating current memory usage. `0` means automatically adjust by calculating current memory usage.
...@@ -82,7 +93,7 @@ ...@@ -82,7 +93,7 @@
@property (nonatomic, assign) BOOL resetFrameIndexWhenStopped; @property (nonatomic, assign) BOOL resetFrameIndexWhenStopped;
/** /**
If the image has more than one frame, set this value to `YES` will automatically If the image which conforms to `SDAnimatedImage` protocol has more than one frame, set this value to `YES` will automatically
play/stop the animation when the view become visible/invisible. play/stop the animation when the view become visible/invisible.
Default is YES. Default is YES.
*/ */
......
...@@ -10,7 +10,6 @@ ...@@ -10,7 +10,6 @@
#if SD_UIKIT || SD_MAC #if SD_UIKIT || SD_MAC
#import "SDAnimatedImagePlayer.h"
#import "UIImage+Metadata.h" #import "UIImage+Metadata.h"
#import "NSImage+Compatibility.h" #import "NSImage+Compatibility.h"
#import "SDInternalMacros.h" #import "SDInternalMacros.h"
...@@ -24,14 +23,15 @@ ...@@ -24,14 +23,15 @@
NSRunLoopMode _runLoopMode; NSRunLoopMode _runLoopMode;
NSUInteger _maxBufferSize; NSUInteger _maxBufferSize;
double _playbackRate; double _playbackRate;
SDAnimatedImagePlaybackMode _playbackMode;
} }
@property (nonatomic, strong, readwrite) SDAnimatedImagePlayer *player;
@property (nonatomic, strong, readwrite) UIImage *currentFrame; @property (nonatomic, strong, readwrite) UIImage *currentFrame;
@property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex; @property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
@property (nonatomic, assign, readwrite) NSUInteger currentLoopCount; @property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
@property (nonatomic, assign) BOOL shouldAnimate; @property (nonatomic, assign) BOOL shouldAnimate;
@property (nonatomic, assign) BOOL isProgressive; @property (nonatomic, assign) BOOL isProgressive;
@property (nonatomic,strong) SDAnimatedImagePlayer *player; // The animation player.
@property (nonatomic) CALayer *imageViewLayer; // The actual rendering layer. @property (nonatomic) CALayer *imageViewLayer; // The actual rendering layer.
@end @end
...@@ -164,6 +164,9 @@ ...@@ -164,6 +164,9 @@
// Play Rate // Play Rate
self.player.playbackRate = self.playbackRate; self.player.playbackRate = self.playbackRate;
// Play Mode
self.player.playbackMode = self.playbackMode;
// Setup handler // Setup handler
@weakify(self); @weakify(self);
self.player.animationFrameHandler = ^(NSUInteger index, UIImage * frame) { self.player.animationFrameHandler = ^(NSUInteger index, UIImage * frame) {
...@@ -239,6 +242,19 @@ ...@@ -239,6 +242,19 @@
return _playbackRate; return _playbackRate;
} }
- (void)setPlaybackMode:(SDAnimatedImagePlaybackMode)playbackMode {
_playbackMode = playbackMode;
self.player.playbackMode = playbackMode;
}
- (SDAnimatedImagePlaybackMode)playbackMode {
if (!_initFinished) {
return SDAnimatedImagePlaybackModeNormal; // Default mode is normal
}
return _playbackMode;
}
- (BOOL)shouldIncrementalLoad - (BOOL)shouldIncrementalLoad
{ {
if (!_initFinished) { if (!_initFinished) {
...@@ -402,7 +418,8 @@ ...@@ -402,7 +418,8 @@
/// Check if it should be played /// Check if it should be played
- (void)checkPlay - (void)checkPlay
{ {
if (self.autoPlayAnimatedImage) { // Only handle for SDAnimatedImage, leave UIAnimatedImage or animationImages for super implementation control
if (self.player && self.autoPlayAnimatedImage) {
[self updateShouldAnimate]; [self updateShouldAnimate];
if (self.shouldAnimate) { if (self.shouldAnimate) {
[self startAnimating]; [self startAnimating];
......
...@@ -87,7 +87,7 @@ static NSString * const SDDiskCacheExtendedAttributeName = @"com.hackemist.SDDis ...@@ -87,7 +87,7 @@ static NSString * const SDDiskCacheExtendedAttributeName = @"com.hackemist.SDDis
// get cache Path for image key // get cache Path for image key
NSString *cachePathForKey = [self cachePathForKey:key]; NSString *cachePathForKey = [self cachePathForKey:key];
// transform to NSURL // transform to NSURL
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey]; NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey isDirectory:NO];
[data writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil]; [data writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];
......
...@@ -132,7 +132,13 @@ ...@@ -132,7 +132,13 @@
#elif SD_UIKIT #elif SD_UIKIT
CGFloat screenScale = [UIScreen mainScreen].scale; CGFloat screenScale = [UIScreen mainScreen].scale;
#elif SD_MAC #elif SD_MAC
CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor; NSScreen *mainScreen = nil;
if (@available(macOS 10.12, *)) {
mainScreen = [NSScreen mainScreen];
} else {
mainScreen = [NSScreen screens].firstObject;
}
CGFloat screenScale = mainScreen.backingScaleFactor ?: 1.0f;
#endif #endif
self.scale = screenScale; self.scale = screenScale;
self.opaque = NO; self.opaque = NO;
...@@ -166,7 +172,13 @@ ...@@ -166,7 +172,13 @@
#elif SD_UIKIT #elif SD_UIKIT
CGFloat screenScale = [UIScreen mainScreen].scale; CGFloat screenScale = [UIScreen mainScreen].scale;
#elif SD_MAC #elif SD_MAC
CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor; NSScreen *mainScreen = nil;
if (@available(macOS 10.12, *)) {
mainScreen = [NSScreen mainScreen];
} else {
mainScreen = [NSScreen screens].firstObject;
}
CGFloat screenScale = mainScreen.backingScaleFactor ?: 1.0f;
#endif #endif
self.scale = screenScale; self.scale = screenScale;
self.opaque = NO; self.opaque = NO;
......
...@@ -7,20 +7,13 @@ ...@@ -7,20 +7,13 @@
*/ */
#import "SDImageAPNGCoder.h" #import "SDImageAPNGCoder.h"
#import "SDImageIOAnimatedCoderInternal.h"
#if SD_MAC #if SD_MAC
#import <CoreServices/CoreServices.h> #import <CoreServices/CoreServices.h>
#else #else
#import <MobileCoreServices/MobileCoreServices.h> #import <MobileCoreServices/MobileCoreServices.h>
#endif #endif
// iOS 8 Image/IO framework binary does not contains these APNG constants, so we define them. Thanks Apple :)
// We can not use runtime @available check for this issue, because it's a global symbol and should be loaded during launch time by dyld. So hack if the min deployment target version < iOS 9.0, whatever it running on iOS 9+ or not.
#if (__IPHONE_OS_VERSION_MIN_REQUIRED && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0)
const CFStringRef kCGImagePropertyAPNGLoopCount = (__bridge CFStringRef)@"LoopCount";
const CFStringRef kCGImagePropertyAPNGDelayTime = (__bridge CFStringRef)@"DelayTime";
const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef)@"UnclampedDelayTime";
#endif
@implementation SDImageAPNGCoder @implementation SDImageAPNGCoder
+ (instancetype)sharedCoder { + (instancetype)sharedCoder {
...@@ -39,7 +32,7 @@ const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef ...@@ -39,7 +32,7 @@ const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef
} }
+ (NSString *)imageUTType { + (NSString *)imageUTType {
return (__bridge NSString *)kUTTypePNG; return (__bridge NSString *)kSDUTTypePNG;
} }
+ (NSString *)dictionaryProperty { + (NSString *)dictionaryProperty {
......
...@@ -55,6 +55,23 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { ...@@ -55,6 +55,23 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
SDImageCacheMatchAnimatedImageClass = 1 << 7, SDImageCacheMatchAnimatedImageClass = 1 << 7,
}; };
/**
* A token associated with each cache query. Can be used to cancel a cache query
*/
@interface SDImageCacheToken : NSObject <SDWebImageOperation>
/**
Cancel the current cache query.
*/
- (void)cancel;
/**
The query's cache key.
*/
@property (nonatomic, strong, nullable, readonly) NSString *key;
@end
/** /**
* SDImageCache maintains a memory cache and a disk cache. Disk cache write operations are performed * SDImageCache maintains a memory cache and a disk cache. Disk cache write operations are performed
* asynchronous so it doesn’t add unnecessary latency to the UI. * asynchronous so it doesn’t add unnecessary latency to the UI.
...@@ -179,6 +196,17 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { ...@@ -179,6 +196,17 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
toDisk:(BOOL)toDisk toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock; completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/**
* Asynchronously store an image data into disk cache at the given key.
*
* @param imageData The image data to store
* @param key The unique image cache key, usually it's image absolute URL
* @param completionBlock A block executed after the operation is finished
*/
- (void)storeImageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/** /**
* Asynchronously store an image into memory and disk cache at the given key. * Asynchronously store an image into memory and disk cache at the given key.
* *
...@@ -198,7 +226,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { ...@@ -198,7 +226,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
completion:(nullable SDWebImageNoParamsBlock)completionBlock; completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/** /**
* Synchronously store image into memory cache at the given key. * Synchronously store an image into memory cache at the given key.
* *
* @param image The image to store * @param image The image to store
* @param key The unique image cache key, usually it's image absolute URL * @param key The unique image cache key, usually it's image absolute URL
...@@ -207,7 +235,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { ...@@ -207,7 +235,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
forKey:(nullable NSString *)key; forKey:(nullable NSString *)key;
/** /**
* Synchronously store image data into disk cache at the given key. * Synchronously store an image data into disk cache at the given key.
* *
* @param imageData The image data to store * @param imageData The image data to store
* @param key The unique image cache key, usually it's image absolute URL * @param key The unique image cache key, usually it's image absolute URL
...@@ -259,9 +287,9 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { ...@@ -259,9 +287,9 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`. * @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`.
* @param doneBlock The completion block. Will not get called if the operation is cancelled * @param doneBlock The completion block. Will not get called if the operation is cancelled
* *
* @return a NSOperation instance containing the cache op * @return a SDImageCacheToken instance containing the cache operation, will callback immediately when cancelled
*/ */
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDImageCacheQueryCompletionBlock)doneBlock; - (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
/** /**
* Asynchronously queries the cache with operation and call the completion when done. * Asynchronously queries the cache with operation and call the completion when done.
...@@ -270,9 +298,9 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { ...@@ -270,9 +298,9 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @param options A mask to specify options to use for this cache query * @param options A mask to specify options to use for this cache query
* @param doneBlock The completion block. Will not get called if the operation is cancelled * @param doneBlock The completion block. Will not get called if the operation is cancelled
* *
* @return a NSOperation instance containing the cache op * @return a SDImageCacheToken instance containing the cache operation, will callback immediately when cancelled
*/ */
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDImageCacheQueryCompletionBlock)doneBlock; - (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
/** /**
* Asynchronously queries the cache with operation and call the completion when done. * Asynchronously queries the cache with operation and call the completion when done.
...@@ -282,9 +310,9 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { ...@@ -282,9 +310,9 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @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 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 doneBlock The completion block. Will not get called if the operation is cancelled * @param doneBlock The completion block. Will not get called if the operation is cancelled
* *
* @return a NSOperation instance containing the cache op * @return a SDImageCacheToken instance containing the cache operation, will callback immediately when cancellederation, will callback immediately when cancelled
*/ */
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock; - (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
/** /**
* Asynchronously queries the cache with operation and call the completion when done. * Asynchronously queries the cache with operation and call the completion when done.
...@@ -295,9 +323,9 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { ...@@ -295,9 +323,9 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @param queryCacheType Specify where to query the cache from. By default we use `.all`, which means both memory cache and disk cache. You can choose to query memory only or disk only as well. Pass `.none` is invalid and callback with nil immediately. * @param queryCacheType Specify where to query the cache from. By default we use `.all`, which means both memory cache and disk cache. You can choose to query memory only or disk only as well. Pass `.none` is invalid and callback with nil immediately.
* @param doneBlock The completion block. Will not get called if the operation is cancelled * @param doneBlock The completion block. Will not get called if the operation is cancelled
* *
* @return a NSOperation instance containing the cache op * @return a SDImageCacheToken instance containing the cache operation, will callback immediately when cancelled
*/ */
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock; - (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
/** /**
* Synchronously query the memory cache. * Synchronously query the memory cache.
...@@ -341,7 +369,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) { ...@@ -341,7 +369,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @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 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.
* @return The image for the given key, or nil if not found. * @return The image for the given key, or nil if not found.
*/ */
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context;; - (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context;
#pragma mark - Remove Ops #pragma mark - Remove Ops
......
...@@ -57,7 +57,8 @@ typedef NS_ENUM(NSUInteger, SDImageCacheConfigExpireType) { ...@@ -57,7 +57,8 @@ typedef NS_ENUM(NSUInteger, SDImageCacheConfigExpireType) {
/* /*
* The option to control weak memory cache for images. When enable, `SDImageCache`'s memory cache will use a weak maptable to store the image at the same time when it stored to memory, and get removed at the same time. * The option to control weak memory cache for images. When enable, `SDImageCache`'s memory cache will use a weak maptable to store the image at the same time when it stored to memory, and get removed at the same time.
* However when memory warning is triggered, since the weak maptable does not hold a strong reference to image instance, even when the memory cache itself is purged, some images which are held strongly by UIImageViews or other live instances can be recovered again, to avoid later re-query from disk cache or network. This may be helpful for the case, for example, when app enter background and memory is purged, cause cell flashing after re-enter foreground. * However when memory warning is triggered, since the weak maptable does not hold a strong reference to image instance, even when the memory cache itself is purged, some images which are held strongly by UIImageViews or other live instances can be recovered again, to avoid later re-query from disk cache or network. This may be helpful for the case, for example, when app enter background and memory is purged, cause cell flashing after re-enter foreground.
* Defaults to YES. You can change this option dynamically. * When enabling this option, we will sync back the image from weak maptable to strong cache during next time top level `sd_setImage` function call.
* Defaults to NO (YES before 5.12.0 version). You can change this option dynamically.
*/ */
@property (assign, nonatomic) BOOL shouldUseWeakMemoryCache; @property (assign, nonatomic) BOOL shouldUseWeakMemoryCache;
...@@ -67,6 +68,12 @@ typedef NS_ENUM(NSUInteger, SDImageCacheConfigExpireType) { ...@@ -67,6 +68,12 @@ typedef NS_ENUM(NSUInteger, SDImageCacheConfigExpireType) {
*/ */
@property (assign, nonatomic) BOOL shouldRemoveExpiredDataWhenEnterBackground; @property (assign, nonatomic) BOOL shouldRemoveExpiredDataWhenEnterBackground;
/**
* Whether or not to remove the expired disk data when application been terminated. This operation is processed in sync to ensure clean up.
* Defaults to YES.
*/
@property (assign, nonatomic) BOOL shouldRemoveExpiredDataWhenTerminate;
/** /**
* The reading options while reading cache from disk. * The reading options while reading cache from disk.
* Defaults to 0. You can set this to `NSDataReadingMappedIfSafe` to improve performance. * Defaults to 0. You can set this to `NSDataReadingMappedIfSafe` to improve performance.
......
...@@ -27,8 +27,9 @@ static const NSInteger kDefaultCacheMaxDiskAge = 60 * 60 * 24 * 7; // 1 week ...@@ -27,8 +27,9 @@ static const NSInteger kDefaultCacheMaxDiskAge = 60 * 60 * 24 * 7; // 1 week
if (self = [super init]) { if (self = [super init]) {
_shouldDisableiCloud = YES; _shouldDisableiCloud = YES;
_shouldCacheImagesInMemory = YES; _shouldCacheImagesInMemory = YES;
_shouldUseWeakMemoryCache = YES; _shouldUseWeakMemoryCache = NO;
_shouldRemoveExpiredDataWhenEnterBackground = YES; _shouldRemoveExpiredDataWhenEnterBackground = YES;
_shouldRemoveExpiredDataWhenTerminate = YES;
_diskCacheReadingOptions = 0; _diskCacheReadingOptions = 0;
_diskCacheWritingOptions = NSDataWritingAtomic; _diskCacheWritingOptions = NSDataWritingAtomic;
_maxDiskAge = kDefaultCacheMaxDiskAge; _maxDiskAge = kDefaultCacheMaxDiskAge;
...@@ -46,6 +47,7 @@ static const NSInteger kDefaultCacheMaxDiskAge = 60 * 60 * 24 * 7; // 1 week ...@@ -46,6 +47,7 @@ static const NSInteger kDefaultCacheMaxDiskAge = 60 * 60 * 24 * 7; // 1 week
config.shouldCacheImagesInMemory = self.shouldCacheImagesInMemory; config.shouldCacheImagesInMemory = self.shouldCacheImagesInMemory;
config.shouldUseWeakMemoryCache = self.shouldUseWeakMemoryCache; config.shouldUseWeakMemoryCache = self.shouldUseWeakMemoryCache;
config.shouldRemoveExpiredDataWhenEnterBackground = self.shouldRemoveExpiredDataWhenEnterBackground; config.shouldRemoveExpiredDataWhenEnterBackground = self.shouldRemoveExpiredDataWhenEnterBackground;
config.shouldRemoveExpiredDataWhenTerminate = self.shouldRemoveExpiredDataWhenTerminate;
config.diskCacheReadingOptions = self.diskCacheReadingOptions; config.diskCacheReadingOptions = self.diskCacheReadingOptions;
config.diskCacheWritingOptions = self.diskCacheWritingOptions; config.diskCacheWritingOptions = self.diskCacheWritingOptions;
config.maxDiskAge = self.maxDiskAge; config.maxDiskAge = self.maxDiskAge;
......
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
#import "SDWebImageCompat.h" #import "SDWebImageCompat.h"
#import "SDWebImageOperation.h" #import "SDWebImageOperation.h"
#import "SDWebImageDefine.h" #import "SDWebImageDefine.h"
#import "SDImageCoder.h"
/// Image Cache Type /// Image Cache Type
typedef NS_ENUM(NSInteger, SDImageCacheType) { typedef NS_ENUM(NSInteger, SDImageCacheType) {
...@@ -54,6 +55,12 @@ typedef void(^SDImageCacheContainsCompletionBlock)(SDImageCacheType containsCach ...@@ -54,6 +55,12 @@ typedef void(^SDImageCacheContainsCompletionBlock)(SDImageCacheType containsCach
*/ */
FOUNDATION_EXPORT UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context); FOUNDATION_EXPORT UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context);
/// Get the decode options from the loading context options and cache key. This is the built-in translate between the web loading part to the decoding part (which does not depens on).
/// @param context The options arg from the input
/// @param options The context arg from the input
/// @param cacheKey The image cache key from the input. Should not be nil
FOUNDATION_EXPORT SDImageCoderOptions * _Nonnull SDGetDecodeOptionsFromContext(SDWebImageContext * _Nullable context, SDWebImageOptions options, NSString * _Nonnull cacheKey);
/** /**
This is the image cache protocol to provide custom image cache for `SDWebImageManager`. This is the image cache protocol to provide custom image cache for `SDWebImageManager`.
Though the best practice to custom image cache, is to write your own class which conform `SDMemoryCache` or `SDDiskCache` protocol for `SDImageCache` class (See more on `SDImageCacheConfig.memoryCacheClass & SDImageCacheConfig.diskCacheClass`). Though the best practice to custom image cache, is to write your own class which conform `SDMemoryCache` or `SDDiskCache` protocol for `SDImageCache` class (See more on `SDImageCacheConfig.memoryCacheClass & SDImageCacheConfig.diskCacheClass`).
......
...@@ -13,8 +13,7 @@ ...@@ -13,8 +13,7 @@
#import "UIImage+Metadata.h" #import "UIImage+Metadata.h"
#import "SDInternalMacros.h" #import "SDInternalMacros.h"
UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context) { SDImageCoderOptions * _Nonnull SDGetDecodeOptionsFromContext(SDWebImageContext * _Nullable context, SDWebImageOptions options, NSString * _Nonnull cacheKey) {
UIImage *image;
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly); BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor]; NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey); CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
...@@ -38,6 +37,17 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS ...@@ -38,6 +37,17 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
mutableCoderOptions[SDImageCoderWebImageContext] = context; mutableCoderOptions[SDImageCoderWebImageContext] = context;
SDImageCoderOptions *coderOptions = [mutableCoderOptions copy]; SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
return coderOptions;
}
UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
NSCParameterAssert(imageData);
NSCParameterAssert(cacheKey);
UIImage *image;
SDImageCoderOptions *coderOptions = SDGetDecodeOptionsFromContext(context, options, cacheKey);
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
CGFloat scale = [coderOptions[SDImageCoderDecodeScaleFactor] doubleValue];
// Grab the image coder // Grab the image coder
id<SDImageCoder> imageCoder; id<SDImageCoder> imageCoder;
if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) { if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) {
...@@ -79,6 +89,8 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS ...@@ -79,6 +89,8 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
if (shouldDecode) { if (shouldDecode) {
image = [SDImageCoderHelper decodedImageWithImage:image]; image = [SDImageCoderHelper decodedImageWithImage:image];
} }
// assign the decode options, to let manager check whether to re-decode if needed
image.sd_decodeOptions = coderOptions;
} }
return image; return image;
......
...@@ -13,13 +13,12 @@ ...@@ -13,13 +13,12 @@
@interface SDImageCachesManager () @interface SDImageCachesManager ()
@property (nonatomic, strong, nonnull) dispatch_semaphore_t cachesLock; @property (nonatomic, strong, nonnull) NSMutableArray<id<SDImageCache>> *imageCaches;
@end @end
@implementation SDImageCachesManager @implementation SDImageCachesManager {
{ SD_LOCK_DECLARE(_cachesLock);
NSMutableArray<id<SDImageCache>> *_imageCaches;
} }
+ (SDImageCachesManager *)sharedManager { + (SDImageCachesManager *)sharedManager {
...@@ -41,25 +40,25 @@ ...@@ -41,25 +40,25 @@
self.clearOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent; self.clearOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent;
// initialize with default image caches // initialize with default image caches
_imageCaches = [NSMutableArray arrayWithObject:[SDImageCache sharedImageCache]]; _imageCaches = [NSMutableArray arrayWithObject:[SDImageCache sharedImageCache]];
_cachesLock = dispatch_semaphore_create(1); SD_LOCK_INIT(_cachesLock);
} }
return self; return self;
} }
- (NSArray<id<SDImageCache>> *)caches { - (NSArray<id<SDImageCache>> *)caches {
SD_LOCK(self.cachesLock); SD_LOCK(_cachesLock);
NSArray<id<SDImageCache>> *caches = [_imageCaches copy]; NSArray<id<SDImageCache>> *caches = [_imageCaches copy];
SD_UNLOCK(self.cachesLock); SD_UNLOCK(_cachesLock);
return caches; return caches;
} }
- (void)setCaches:(NSArray<id<SDImageCache>> *)caches { - (void)setCaches:(NSArray<id<SDImageCache>> *)caches {
SD_LOCK(self.cachesLock); SD_LOCK(_cachesLock);
[_imageCaches removeAllObjects]; [_imageCaches removeAllObjects];
if (caches.count) { if (caches.count) {
[_imageCaches addObjectsFromArray:caches]; [_imageCaches addObjectsFromArray:caches];
} }
SD_UNLOCK(self.cachesLock); SD_UNLOCK(_cachesLock);
} }
#pragma mark - Cache IO operations #pragma mark - Cache IO operations
...@@ -68,18 +67,18 @@ ...@@ -68,18 +67,18 @@
if (![cache conformsToProtocol:@protocol(SDImageCache)]) { if (![cache conformsToProtocol:@protocol(SDImageCache)]) {
return; return;
} }
SD_LOCK(self.cachesLock); SD_LOCK(_cachesLock);
[_imageCaches addObject:cache]; [_imageCaches addObject:cache];
SD_UNLOCK(self.cachesLock); SD_UNLOCK(_cachesLock);
} }
- (void)removeCache:(id<SDImageCache>)cache { - (void)removeCache:(id<SDImageCache>)cache {
if (![cache conformsToProtocol:@protocol(SDImageCache)]) { if (![cache conformsToProtocol:@protocol(SDImageCache)]) {
return; return;
} }
SD_LOCK(self.cachesLock); SD_LOCK(_cachesLock);
[_imageCaches removeObject:cache]; [_imageCaches removeObject:cache];
SD_UNLOCK(self.cachesLock); SD_UNLOCK(_cachesLock);
} }
#pragma mark - SDImageCache #pragma mark - SDImageCache
......
...@@ -93,7 +93,7 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeEmbedThumb ...@@ -93,7 +93,7 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeEmbedThumb
But this may be useful for some custom coders, because some business logic may dependent on things other than image or image data information only. But this may be useful for some custom coders, because some business logic may dependent on things other than image or image data information only.
See `SDWebImageContext` for more detailed information. See `SDWebImageContext` for more detailed information.
*/ */
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderWebImageContext API_DEPRECATED("The coder component will be seperated from Core subspec in the future. Update your code to not rely on this context option.", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED));; FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderWebImageContext API_DEPRECATED("The coder component will be seperated from Core subspec in the future. Update your code to not rely on this context option.", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED));
#pragma mark - Coder #pragma mark - Coder
/** /**
......
...@@ -10,6 +10,15 @@ ...@@ -10,6 +10,15 @@
#import "SDWebImageCompat.h" #import "SDWebImageCompat.h"
#import "SDImageFrame.h" #import "SDImageFrame.h"
typedef NS_ENUM(NSUInteger, SDImageCoderDecodeSolution) {
/// automatically choose the solution based on image format, hardware, OS version. This keep balance for compatibility and performance. Default after SDWebImage 5.13.0
SDImageCoderDecodeSolutionAutomatic,
/// always use CoreGraphics to draw on bitmap context and trigger decode. Best compatibility. Default before SDWebImage 5.13.0
SDImageCoderDecodeSolutionCoreGraphics,
/// available on iOS/tvOS 15+, use UIKit's new CGImageDecompressor/CMPhoto to decode. Best performance. If failed, will fallback to CoreGraphics as well
SDImageCoderDecodeSolutionUIKit
};
/** /**
Provide some common helper methods for building the image decoder/encoder. Provide some common helper methods for building the image decoder/encoder.
*/ */
...@@ -76,6 +85,7 @@ ...@@ -76,6 +85,7 @@
/** /**
Create a scaled CGImage by the provided CGImage and size. This follows The Create Rule and you are response to call release after usage. Create a scaled CGImage by the provided CGImage and size. This follows The Create Rule and you are response to call release after usage.
It will detect whether the image size matching the scale size, if not, stretch the image to the target size. It will detect whether the image size matching the scale size, if not, stretch the image to the target size.
@note If you need to keep aspect ratio, you can calculate the scale size by using `scaledSizeWithImageSize` first.
@param cgImage The CGImage @param cgImage The CGImage
@param size The scale size in pixel. @param size The scale size in pixel.
...@@ -83,6 +93,16 @@ ...@@ -83,6 +93,16 @@
*/ */
+ (CGImageRef _Nullable)CGImageCreateScaled:(_Nonnull CGImageRef)cgImage size:(CGSize)size CF_RETURNS_RETAINED; + (CGImageRef _Nullable)CGImageCreateScaled:(_Nonnull CGImageRef)cgImage size:(CGSize)size CF_RETURNS_RETAINED;
/** Scale the image size based on provided scale size, whether or not to preserve aspect ratio, whether or not to scale up.
@note For example, if you implements thumnail decoding, pass `shouldScaleUp` to NO to avoid the calculated size larger than image size.
@param imageSize The image size (in pixel or point defined by caller)
@param scaleSize The scale size (in pixel or point defined by caller)
@param preserveAspectRatio Whether or not to preserve aspect ratio
@param shouldScaleUp Whether or not to scale up (or scale down only)
*/
+ (CGSize)scaledSizeWithImageSize:(CGSize)imageSize scaleSize:(CGSize)scaleSize preserveAspectRatio:(BOOL)preserveAspectRatio shouldScaleUp:(BOOL)shouldScaleUp;
/** /**
Return the decoded image by the provided image. This one unlike `CGImageCreateDecoded:`, will not decode the image which contains alpha channel or animated image Return the decoded image by the provided image. This one unlike `CGImageCreateDecoded:`, will not decode the image which contains alpha channel or animated image
@param image The image to be decoded @param image The image to be decoded
...@@ -100,6 +120,12 @@ ...@@ -100,6 +120,12 @@
*/ */
+ (UIImage * _Nullable)decodedAndScaledDownImageWithImage:(UIImage * _Nullable)image limitBytes:(NSUInteger)bytes; + (UIImage * _Nullable)decodedAndScaledDownImageWithImage:(UIImage * _Nullable)image limitBytes:(NSUInteger)bytes;
/**
Control the default force decode solution. Available solutions in `SDImageCoderDecodeSolution`.
@note Defaults to `SDImageCoderDecodeSolutionAutomatic`, which prefers to use UIKit for JPEG/HEIF, and fallback on CoreGraphics. If you want control on your hand, set the other solution.
*/
@property (class, readwrite) SDImageCoderDecodeSolution defaultDecodeSolution;
/** /**
Control the default limit bytes to scale down largest images. Control the default limit bytes to scale down largest images.
This value must be larger than 4 Bytes (at least 1x1 pixel). Defaults to 60MB on iOS/tvOS, 90MB on macOS, 30MB on watchOS. This value must be larger than 4 Bytes (at least 1x1 pixel). Defaults to 60MB on iOS/tvOS, 90MB on macOS, 30MB on watchOS.
......
...@@ -15,13 +15,12 @@ ...@@ -15,13 +15,12 @@
@interface SDImageCodersManager () @interface SDImageCodersManager ()
@property (nonatomic, strong, nonnull) dispatch_semaphore_t codersLock; @property (nonatomic, strong, nonnull) NSMutableArray<id<SDImageCoder>> *imageCoders;
@end @end
@implementation SDImageCodersManager @implementation SDImageCodersManager {
{ SD_LOCK_DECLARE(_codersLock);
NSMutableArray<id<SDImageCoder>> *_imageCoders;
} }
+ (nonnull instancetype)sharedManager { + (nonnull instancetype)sharedManager {
...@@ -37,27 +36,25 @@ ...@@ -37,27 +36,25 @@
if (self = [super init]) { if (self = [super init]) {
// initialize with default coders // initialize with default coders
_imageCoders = [NSMutableArray arrayWithArray:@[[SDImageIOCoder sharedCoder], [SDImageGIFCoder sharedCoder], [SDImageAPNGCoder sharedCoder]]]; _imageCoders = [NSMutableArray arrayWithArray:@[[SDImageIOCoder sharedCoder], [SDImageGIFCoder sharedCoder], [SDImageAPNGCoder sharedCoder]]];
_codersLock = dispatch_semaphore_create(1); SD_LOCK_INIT(_codersLock);
} }
return self; return self;
} }
- (NSArray<id<SDImageCoder>> *)coders - (NSArray<id<SDImageCoder>> *)coders {
{ SD_LOCK(_codersLock);
SD_LOCK(self.codersLock);
NSArray<id<SDImageCoder>> *coders = [_imageCoders copy]; NSArray<id<SDImageCoder>> *coders = [_imageCoders copy];
SD_UNLOCK(self.codersLock); SD_UNLOCK(_codersLock);
return coders; return coders;
} }
- (void)setCoders:(NSArray<id<SDImageCoder>> *)coders - (void)setCoders:(NSArray<id<SDImageCoder>> *)coders {
{ SD_LOCK(_codersLock);
SD_LOCK(self.codersLock);
[_imageCoders removeAllObjects]; [_imageCoders removeAllObjects];
if (coders.count) { if (coders.count) {
[_imageCoders addObjectsFromArray:coders]; [_imageCoders addObjectsFromArray:coders];
} }
SD_UNLOCK(self.codersLock); SD_UNLOCK(_codersLock);
} }
#pragma mark - Coder IO operations #pragma mark - Coder IO operations
...@@ -66,18 +63,18 @@ ...@@ -66,18 +63,18 @@
if (![coder conformsToProtocol:@protocol(SDImageCoder)]) { if (![coder conformsToProtocol:@protocol(SDImageCoder)]) {
return; return;
} }
SD_LOCK(self.codersLock); SD_LOCK(_codersLock);
[_imageCoders addObject:coder]; [_imageCoders addObject:coder];
SD_UNLOCK(self.codersLock); SD_UNLOCK(_codersLock);
} }
- (void)removeCoder:(nonnull id<SDImageCoder>)coder { - (void)removeCoder:(nonnull id<SDImageCoder>)coder {
if (![coder conformsToProtocol:@protocol(SDImageCoder)]) { if (![coder conformsToProtocol:@protocol(SDImageCoder)]) {
return; return;
} }
SD_LOCK(self.codersLock); SD_LOCK(_codersLock);
[_imageCoders removeObject:coder]; [_imageCoders removeObject:coder];
SD_UNLOCK(self.codersLock); SD_UNLOCK(_codersLock);
} }
#pragma mark - SDImageCoder #pragma mark - SDImageCoder
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
*/ */
#import "SDImageGIFCoder.h" #import "SDImageGIFCoder.h"
#import "SDImageIOAnimatedCoderInternal.h"
#if SD_MAC #if SD_MAC
#import <CoreServices/CoreServices.h> #import <CoreServices/CoreServices.h>
#else #else
...@@ -31,7 +32,7 @@ ...@@ -31,7 +32,7 @@
} }
+ (NSString *)imageUTType { + (NSString *)imageUTType {
return (__bridge NSString *)kUTTypeGIF; return (__bridge NSString *)kSDUTTypeGIF;
} }
+ (NSString *)dictionaryProperty { + (NSString *)dictionaryProperty {
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#import "SDImageGraphics.h" #import "SDImageGraphics.h"
#import "NSImage+Compatibility.h" #import "NSImage+Compatibility.h"
#import "SDImageCoderHelper.h"
#import "objc/runtime.h" #import "objc/runtime.h"
#if SD_MAC #if SD_MAC
...@@ -16,17 +17,32 @@ static void *kNSGraphicsContextScaleFactorKey; ...@@ -16,17 +17,32 @@ static void *kNSGraphicsContextScaleFactorKey;
static CGContextRef SDCGContextCreateBitmapContext(CGSize size, BOOL opaque, CGFloat scale) { static CGContextRef SDCGContextCreateBitmapContext(CGSize size, BOOL opaque, CGFloat scale) {
if (scale == 0) { if (scale == 0) {
// Match `UIGraphicsBeginImageContextWithOptions`, reset to the scale factor of the devices main screen if scale is 0. // Match `UIGraphicsBeginImageContextWithOptions`, reset to the scale factor of the devices main screen if scale is 0.
scale = [NSScreen mainScreen].backingScaleFactor; NSScreen *mainScreen = nil;
if (@available(macOS 10.12, *)) {
mainScreen = [NSScreen mainScreen];
} else {
mainScreen = [NSScreen screens].firstObject;
}
scale = mainScreen.backingScaleFactor ?: 1.0f;
} }
size_t width = ceil(size.width * scale); size_t width = ceil(size.width * scale);
size_t height = ceil(size.height * scale); size_t height = ceil(size.height * scale);
if (width < 1 || height < 1) return NULL; if (width < 1 || height < 1) return NULL;
//pre-multiplied BGRA for non-opaque, BGRX for opaque, 8-bits per component, as Apple's doc CGColorSpaceRef space = [SDImageCoderHelper colorSpaceGetDeviceRGB];
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB(); // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
CGImageAlphaInfo alphaInfo = kCGBitmapByteOrder32Host | (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst); // Check #3330 for more detail about why this bitmap is choosen.
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, kCGBitmapByteOrderDefault | alphaInfo); CGBitmapInfo bitmapInfo;
CGColorSpaceRelease(space); if (!opaque) {
// iPhone GPU prefer to use BGRA8888, see: https://forums.raywenderlich.com/t/why-mtlpixelformat-bgra8unorm/53489
// BGRA8888
bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst;
} else {
// BGR888 previously works on iOS 8~iOS 14, however, iOS 15+ will result a black image. FB9958017
// RGB888
bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast;
}
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, bitmapInfo);
if (!context) { if (!context) {
return NULL; return NULL;
} }
...@@ -96,7 +112,13 @@ UIImage * SDGraphicsGetImageFromCurrentImageContext(void) { ...@@ -96,7 +112,13 @@ UIImage * SDGraphicsGetImageFromCurrentImageContext(void) {
} }
if (!scale) { if (!scale) {
// reset to the scale factor of the devices main screen if scale is 0. // reset to the scale factor of the devices main screen if scale is 0.
scale = [NSScreen mainScreen].backingScaleFactor; NSScreen *mainScreen = nil;
if (@available(macOS 10.12, *)) {
mainScreen = [NSScreen mainScreen];
} else {
mainScreen = [NSScreen screens].firstObject;
}
scale = mainScreen.backingScaleFactor ?: 1.0f;
} }
NSImage *image = [[NSImage alloc] initWithCGImage:imageRef scale:scale orientation:kCGImagePropertyOrientationUp]; NSImage *image = [[NSImage alloc] initWithCGImage:imageRef scale:scale orientation:kCGImagePropertyOrientationUp];
CGImageRelease(imageRef); CGImageRelease(imageRef);
......
...@@ -18,8 +18,6 @@ static NSString * kSDCGImagePropertyHEICSUnclampedDelayTime = @"UnclampedDelayTi ...@@ -18,8 +18,6 @@ static NSString * kSDCGImagePropertyHEICSUnclampedDelayTime = @"UnclampedDelayTi
@implementation SDImageHEICCoder @implementation SDImageHEICCoder
+ (void)initialize { + (void)initialize {
#if __IPHONE_13_0 || __TVOS_13_0 || __MAC_10_15 || __WATCHOS_6_0
// Xcode 11
if (@available(iOS 13, tvOS 13, macOS 10.15, watchOS 6, *)) { if (@available(iOS 13, tvOS 13, macOS 10.15, watchOS 6, *)) {
// Use SDK instead of raw value // Use SDK instead of raw value
kSDCGImagePropertyHEICSDictionary = (__bridge NSString *)kCGImagePropertyHEICSDictionary; kSDCGImagePropertyHEICSDictionary = (__bridge NSString *)kCGImagePropertyHEICSDictionary;
...@@ -27,7 +25,6 @@ static NSString * kSDCGImagePropertyHEICSUnclampedDelayTime = @"UnclampedDelayTi ...@@ -27,7 +25,6 @@ static NSString * kSDCGImagePropertyHEICSUnclampedDelayTime = @"UnclampedDelayTi
kSDCGImagePropertyHEICSDelayTime = (__bridge NSString *)kCGImagePropertyHEICSDelayTime; kSDCGImagePropertyHEICSDelayTime = (__bridge NSString *)kCGImagePropertyHEICSDelayTime;
kSDCGImagePropertyHEICSUnclampedDelayTime = (__bridge NSString *)kCGImagePropertyHEICSUnclampedDelayTime; kSDCGImagePropertyHEICSUnclampedDelayTime = (__bridge NSString *)kCGImagePropertyHEICSUnclampedDelayTime;
} }
#endif
} }
+ (instancetype)sharedCoder { + (instancetype)sharedCoder {
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
*/ */
@property (class, readonly) SDImageFormat imageFormat; @property (class, readonly) SDImageFormat imageFormat;
/** /**
The supported image format UTI Type. Such as `kUTTypeGIF`. The supported image format UTI Type. Such as `kSDUTTypeGIF`.
This can be used for cases when we can not detect `SDImageFormat. Such as progressive decoding's hint format `kCGImageSourceTypeIdentifierHint`. This can be used for cases when we can not detect `SDImageFormat. Such as progressive decoding's hint format `kCGImageSourceTypeIdentifierHint`.
@note Subclass override. @note Subclass override.
*/ */
......
...@@ -191,19 +191,12 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination ...@@ -191,19 +191,12 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
// Some options need to pass to `CGImageSourceCopyPropertiesAtIndex` before `CGImageSourceCreateImageAtIndex`, or ImageIO will ignore them because they parse once :) // Some options need to pass to `CGImageSourceCopyPropertiesAtIndex` before `CGImageSourceCreateImageAtIndex`, or ImageIO will ignore them because they parse once :)
// Parse the image properties // Parse the image properties
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options); NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
NSUInteger pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] unsignedIntegerValue]; CGFloat pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] doubleValue];
NSUInteger pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] unsignedIntegerValue]; CGFloat pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] doubleValue];
CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)[properties[(__bridge NSString *)kCGImagePropertyOrientation] unsignedIntegerValue]; CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)[properties[(__bridge NSString *)kCGImagePropertyOrientation] unsignedIntegerValue];
if (!exifOrientation) { if (!exifOrientation) {
exifOrientation = kCGImagePropertyOrientationUp; exifOrientation = kCGImagePropertyOrientationUp;
} }
CFStringRef uttype = CGImageSourceGetType(source);
// Check vector format
BOOL isVector = NO;
if ([NSData sd_imageFormatFromUTType:uttype] == SDImageFormatPDF) {
isVector = YES;
}
NSMutableDictionary *decodingOptions; NSMutableDictionary *decodingOptions;
if (options) { if (options) {
...@@ -214,22 +207,6 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination ...@@ -214,22 +207,6 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
CGImageRef imageRef; CGImageRef imageRef;
BOOL createFullImage = thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height); BOOL createFullImage = thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height);
if (createFullImage) { if (createFullImage) {
if (isVector) {
if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
// Provide the default pixel count for vector images, simply just use the screen size
#if SD_WATCH
thumbnailSize = WKInterfaceDevice.currentDevice.screenBounds.size;
#elif SD_UIKIT
thumbnailSize = UIScreen.mainScreen.bounds.size;
#elif SD_MAC
thumbnailSize = NSScreen.mainScreen.frame.size;
#endif
}
CGFloat maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height);
NSUInteger DPIPerPixel = 2;
NSUInteger rasterizationDPI = maxPixelSize * DPIPerPixel;
decodingOptions[kSDCGImageSourceRasterizationDPI] = @(rasterizationDPI);
}
imageRef = CGImageSourceCreateImageAtIndex(source, index, (__bridge CFDictionaryRef)[decodingOptions copy]); imageRef = CGImageSourceCreateImageAtIndex(source, index, (__bridge CFDictionaryRef)[decodingOptions copy]);
} else { } else {
decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio); decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio);
...@@ -238,9 +215,9 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination ...@@ -238,9 +215,9 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
CGFloat pixelRatio = pixelWidth / pixelHeight; CGFloat pixelRatio = pixelWidth / pixelHeight;
CGFloat thumbnailRatio = thumbnailSize.width / thumbnailSize.height; CGFloat thumbnailRatio = thumbnailSize.width / thumbnailSize.height;
if (pixelRatio > thumbnailRatio) { if (pixelRatio > thumbnailRatio) {
maxPixelSize = thumbnailSize.width; maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.width / pixelRatio);
} else { } else {
maxPixelSize = thumbnailSize.height; maxPixelSize = MAX(thumbnailSize.height, thumbnailSize.height * pixelRatio);
} }
} else { } else {
maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height); maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height);
...@@ -311,11 +288,14 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination ...@@ -311,11 +288,14 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
// Which decode frames in time and reduce memory usage // Which decode frames in time and reduce memory usage
if (thumbnailSize.width == 0 || thumbnailSize.height == 0) { if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data]; SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale); if (imageRep) {
imageRep.size = size; NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
NSImage *animatedImage = [[NSImage alloc] initWithSize:size]; imageRep.size = size;
[animatedImage addRepresentation:imageRep]; NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
return animatedImage; [animatedImage addRepresentation:imageRep];
animatedImage.sd_imageFormat = self.class.imageFormat;
return animatedImage;
}
} }
#endif #endif
...@@ -496,16 +476,18 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination ...@@ -496,16 +476,18 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
maxPixelSize = maxPixelSizeValue.CGSizeValue; maxPixelSize = maxPixelSizeValue.CGSizeValue;
#endif #endif
} }
NSUInteger pixelWidth = CGImageGetWidth(imageRef); CGFloat pixelWidth = (CGFloat)CGImageGetWidth(imageRef);
NSUInteger pixelHeight = CGImageGetHeight(imageRef); CGFloat pixelHeight = (CGFloat)CGImageGetHeight(imageRef);
CGFloat finalPixelSize = 0; CGFloat finalPixelSize = 0;
if (maxPixelSize.width > 0 && maxPixelSize.height > 0 && pixelWidth > maxPixelSize.width && pixelHeight > maxPixelSize.height) { BOOL encodeFullImage = maxPixelSize.width == 0 || maxPixelSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= maxPixelSize.width && pixelHeight <= maxPixelSize.height);
if (!encodeFullImage) {
// Thumbnail Encoding
CGFloat pixelRatio = pixelWidth / pixelHeight; CGFloat pixelRatio = pixelWidth / pixelHeight;
CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height; CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height;
if (pixelRatio > maxPixelSizeRatio) { if (pixelRatio > maxPixelSizeRatio) {
finalPixelSize = maxPixelSize.width; finalPixelSize = MAX(maxPixelSize.width, maxPixelSize.width / pixelRatio);
} else { } else {
finalPixelSize = maxPixelSize.height; finalPixelSize = MAX(maxPixelSize.height, maxPixelSize.height * pixelRatio);
} }
properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize); properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize);
} }
...@@ -655,7 +637,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination ...@@ -655,7 +637,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
return nil; return nil;
} }
image.sd_imageFormat = self.class.imageFormat; image.sd_imageFormat = self.class.imageFormat;
image.sd_isDecoded = YES;; image.sd_isDecoded = YES;
return image; return image;
} }
......
...@@ -13,6 +13,8 @@ ...@@ -13,6 +13,8 @@
#import "UIImage+Metadata.h" #import "UIImage+Metadata.h"
#import "SDImageIOAnimatedCoderInternal.h" #import "SDImageIOAnimatedCoderInternal.h"
// Specify DPI for vector format in CGImageSource, like PDF
static NSString * kSDCGImageSourceRasterizationDPI = @"kCGImageSourceRasterizationDPI";
// Specify File Size for lossy format encoding, like JPEG // Specify File Size for lossy format encoding, like JPEG
static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize"; static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
...@@ -52,6 +54,31 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination ...@@ -52,6 +54,31 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
return coder; return coder;
} }
#pragma mark - Utils
+ (CGRect)boxRectFromPDFFData:(nonnull NSData *)data {
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
if (!provider) {
return CGRectZero;
}
CGPDFDocumentRef document = CGPDFDocumentCreateWithProvider(provider);
CGDataProviderRelease(provider);
if (!document) {
return CGRectZero;
}
// `CGPDFDocumentGetPage` page number is 1-indexed.
CGPDFPageRef page = CGPDFDocumentGetPage(document, 1);
if (!page) {
CGPDFDocumentRelease(document);
return CGRectZero;
}
CGRect boxRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
CGPDFDocumentRelease(document);
return boxRect;
}
#pragma mark - Decode #pragma mark - Decode
- (BOOL)canDecodeFromData:(nullable NSData *)data { - (BOOL)canDecodeFromData:(nullable NSData *)data {
return YES; return YES;
...@@ -88,13 +115,39 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination ...@@ -88,13 +115,39 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
return nil; return nil;
} }
UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil]; CFStringRef uttype = CGImageSourceGetType(source);
SDImageFormat imageFormat = [NSData sd_imageFormatFromUTType:uttype];
// Check vector format
NSDictionary *decodingOptions = nil;
if (imageFormat == SDImageFormatPDF) {
// Use 72 DPI (1:1 inch to pixel) by default, matching Apple's PDFKit behavior
NSUInteger rasterizationDPI = 72;
CGFloat maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height);
if (maxPixelSize > 0) {
// Calculate DPI based on PDF box and pixel size
CGRect boxRect = [self.class boxRectFromPDFFData:data];
CGFloat maxBoxSize = MAX(boxRect.size.width, boxRect.size.height);
if (maxBoxSize > 0) {
rasterizationDPI = rasterizationDPI * (maxPixelSize / maxBoxSize);
}
}
decodingOptions = @{
// This option will cause ImageIO return the pixel size from `CGImageSourceCopyProperties`
// If not provided, it always return 0 size
kSDCGImageSourceRasterizationDPI : @(rasterizationDPI),
};
// Already calculated DPI, avoid re-calculation based on thumbnail information
preserveAspectRatio = YES;
thumbnailSize = CGSizeZero;
}
UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:decodingOptions];
CFRelease(source); CFRelease(source);
if (!image) { if (!image) {
return nil; return nil;
} }
image.sd_imageFormat = [NSData sd_imageFormatForImageData:data]; image.sd_imageFormat = imageFormat;
return image; return image;
} }
...@@ -250,16 +303,18 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination ...@@ -250,16 +303,18 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
maxPixelSize = maxPixelSizeValue.CGSizeValue; maxPixelSize = maxPixelSizeValue.CGSizeValue;
#endif #endif
} }
NSUInteger pixelWidth = CGImageGetWidth(imageRef); CGFloat pixelWidth = (CGFloat)CGImageGetWidth(imageRef);
NSUInteger pixelHeight = CGImageGetHeight(imageRef); CGFloat pixelHeight = (CGFloat)CGImageGetHeight(imageRef);
if (maxPixelSize.width > 0 && maxPixelSize.height > 0 && pixelWidth > maxPixelSize.width && pixelHeight > maxPixelSize.height) { CGFloat finalPixelSize = 0;
BOOL encodeFullImage = maxPixelSize.width == 0 || maxPixelSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= maxPixelSize.width && pixelHeight <= maxPixelSize.height);
if (!encodeFullImage) {
// Thumbnail Encoding
CGFloat pixelRatio = pixelWidth / pixelHeight; CGFloat pixelRatio = pixelWidth / pixelHeight;
CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height; CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height;
CGFloat finalPixelSize;
if (pixelRatio > maxPixelSizeRatio) { if (pixelRatio > maxPixelSizeRatio) {
finalPixelSize = maxPixelSize.width; finalPixelSize = MAX(maxPixelSize.width, maxPixelSize.width / pixelRatio);
} else { } else {
finalPixelSize = maxPixelSize.height; finalPixelSize = MAX(maxPixelSize.height, maxPixelSize.height * pixelRatio);
} }
properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize); properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize);
} }
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#import "SDWebImageCompat.h" #import "SDWebImageCompat.h"
#import "SDWebImageDefine.h" #import "SDWebImageDefine.h"
#import "SDWebImageOperation.h" #import "SDWebImageOperation.h"
#import "SDImageCoder.h"
typedef void(^SDImageLoaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL); typedef void(^SDImageLoaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL);
typedef void(^SDImageLoaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished); typedef void(^SDImageLoaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished);
...@@ -50,6 +51,18 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Non ...@@ -50,6 +51,18 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Non
*/ */
FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, BOOL finished, id<SDWebImageOperation> _Nonnull operation, SDWebImageOptions options, SDWebImageContext * _Nullable context); FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, BOOL finished, id<SDWebImageOperation> _Nonnull operation, SDWebImageOptions options, SDWebImageContext * _Nullable context);
/**
This function get the progressive decoder for current loading operation. If no progressive decoding is happended or decoder is not able to construct, return nil.
@return The progressive decoder associated with the loading operation.
*/
FOUNDATION_EXPORT id<SDProgressiveImageCoder> _Nullable SDImageLoaderGetProgressiveCoder(id<SDWebImageOperation> _Nonnull operation);
/**
This function set the progressive decoder for current loading operation. If no progressive decoding is happended, pass nil.
@param operation The loading operation to associate the progerssive decoder.
*/
FOUNDATION_EXPORT void SDImageLoaderSetProgressiveCoder(id<SDWebImageOperation> _Nonnull operation, id<SDProgressiveImageCoder> _Nullable progressiveCoder);
#pragma mark - SDImageLoader #pragma mark - SDImageLoader
/** /**
...@@ -60,6 +73,7 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NS ...@@ -60,6 +73,7 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NS
*/ */
@protocol SDImageLoader <NSObject> @protocol SDImageLoader <NSObject>
@required
/** /**
Whether current image loader supports to load the provide image URL. Whether current image loader supports to load the provide image URL.
This will be checked every time 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 `requestImageWithURL:options:context:progress:completed:`. This will be checked every time 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 `requestImageWithURL:options:context:progress:completed:`.
...@@ -67,8 +81,23 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NS ...@@ -67,8 +81,23 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NS
@param url The image URL to be loaded. @param url The image URL to be loaded.
@return YES to continue download, NO to stop download. @return YES to continue download, NO to stop download.
*/ */
- (BOOL)canRequestImageForURL:(nullable NSURL *)url; - (BOOL)canRequestImageForURL:(nullable NSURL *)url API_DEPRECATED("Use canRequestImageForURL:options:context: instead", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED));
@optional
/**
Whether current image loader supports to load the provide image URL, with associated options and context.
This will be checked every time 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 `requestImageWithURL:options:context:progress:completed:`.
@param url The image URL to be loaded.
@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.
@return YES to continue download, NO to stop download.
*/
- (BOOL)canRequestImageForURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context;
@required
/** /**
Load the image and image data with the given URL and return the image data. You're responsible for producing the image instance. Load the image and image data with the given URL and return the image data. You're responsible for producing the image instance.
...@@ -96,6 +125,22 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NS ...@@ -96,6 +125,22 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NS
@return Whether to block this url or not. Return YES to mark this URL as failed. @return Whether to block this url or not. Return YES to mark this URL as failed.
*/ */
- (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url - (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url
error:(nonnull NSError *)error; error:(nonnull NSError *)error API_DEPRECATED("Use shouldBlockFailedURLWithURL:error:options:context: instead", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED));
@optional
/**
Whether the error from image loader should be marked indeed un-recoverable or not, with associated options and context.
If this return YES, failed URL which does not using `SDWebImageRetryFailed` will be blocked into black list. Else not.
@param url The URL represent the image. Note this may not be a HTTP URL
@param error The URL's loading error, from previous `requestImageWithURL:options:context:progress:completed:` completedBlock's error.
@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.
@return Whether to block this url or not. Return YES to mark this URL as failed.
*/
- (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url
error:(nonnull NSError *)error
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context;
@end @end
...@@ -13,10 +13,23 @@ ...@@ -13,10 +13,23 @@
#import "SDAnimatedImage.h" #import "SDAnimatedImage.h"
#import "UIImage+Metadata.h" #import "UIImage+Metadata.h"
#import "SDInternalMacros.h" #import "SDInternalMacros.h"
#import "SDImageCacheDefine.h"
#import "objc/runtime.h" #import "objc/runtime.h"
SDWebImageContextOption const SDWebImageContextLoaderCachedImage = @"loaderCachedImage";
static void * SDImageLoaderProgressiveCoderKey = &SDImageLoaderProgressiveCoderKey; static void * SDImageLoaderProgressiveCoderKey = &SDImageLoaderProgressiveCoderKey;
id<SDProgressiveImageCoder> SDImageLoaderGetProgressiveCoder(id<SDWebImageOperation> operation) {
NSCParameterAssert(operation);
return objc_getAssociatedObject(operation, SDImageLoaderProgressiveCoderKey);
}
void SDImageLoaderSetProgressiveCoder(id<SDWebImageOperation> operation, id<SDProgressiveImageCoder> progressiveCoder) {
NSCParameterAssert(operation);
objc_setAssociatedObject(operation, SDImageLoaderProgressiveCoderKey, progressiveCoder, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, SDWebImageOptions options, SDWebImageContext * _Nullable context) { UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
NSCParameterAssert(imageData); NSCParameterAssert(imageData);
NSCParameterAssert(imageURL); NSCParameterAssert(imageURL);
...@@ -29,28 +42,9 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS ...@@ -29,28 +42,9 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
} else { } else {
cacheKey = imageURL.absoluteString; cacheKey = imageURL.absoluteString;
} }
SDImageCoderOptions *coderOptions = SDGetDecodeOptionsFromContext(context, options, cacheKey);
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly); BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor]; CGFloat scale = [coderOptions[SDImageCoderDecodeScaleFactor] doubleValue];
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
NSValue *thumbnailSizeValue;
BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
if (shouldScaleDown) {
CGFloat thumbnailPixels = SDImageCoderHelper.defaultScaleDownLimitBytes / 4;
CGFloat dimension = ceil(sqrt(thumbnailPixels));
thumbnailSizeValue = @(CGSizeMake(dimension, dimension));
}
if (context[SDWebImageContextImageThumbnailPixelSize]) {
thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
}
SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2];
mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame);
mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale);
mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue;
mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue;
mutableCoderOptions[SDImageCoderWebImageContext] = context;
SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
// Grab the image coder // Grab the image coder
id<SDImageCoder> imageCoder; id<SDImageCoder> imageCoder;
...@@ -94,6 +88,8 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS ...@@ -94,6 +88,8 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
if (shouldDecode) { if (shouldDecode) {
image = [SDImageCoderHelper decodedImageWithImage:image]; image = [SDImageCoderHelper decodedImageWithImage:image];
} }
// assign the decode options, to let manager check whether to re-decode if needed
image.sd_decodeOptions = coderOptions;
} }
return image; return image;
...@@ -136,7 +132,7 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im ...@@ -136,7 +132,7 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
SDImageCoderOptions *coderOptions = [mutableCoderOptions copy]; SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
// Grab the progressive image coder // Grab the progressive image coder
id<SDProgressiveImageCoder> progressiveCoder = objc_getAssociatedObject(operation, SDImageLoaderProgressiveCoderKey); id<SDProgressiveImageCoder> progressiveCoder = SDImageLoaderGetProgressiveCoder(operation);
if (!progressiveCoder) { if (!progressiveCoder) {
id<SDProgressiveImageCoder> imageCoder = context[SDWebImageContextImageCoder]; id<SDProgressiveImageCoder> imageCoder = context[SDWebImageContextImageCoder];
// Check the progressive coder if provided // Check the progressive coder if provided
...@@ -152,7 +148,7 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im ...@@ -152,7 +148,7 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
} }
} }
} }
objc_setAssociatedObject(operation, SDImageLoaderProgressiveCoderKey, progressiveCoder, OBJC_ASSOCIATION_RETAIN_NONATOMIC); SDImageLoaderSetProgressiveCoder(operation, progressiveCoder);
} }
// If we can't find any progressive coder, disable progressive download // If we can't find any progressive coder, disable progressive download
if (!progressiveCoder) { if (!progressiveCoder) {
...@@ -190,11 +186,11 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im ...@@ -190,11 +186,11 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
if (shouldDecode) { if (shouldDecode) {
image = [SDImageCoderHelper decodedImageWithImage:image]; image = [SDImageCoderHelper decodedImageWithImage:image];
} }
// mark the image as progressive (completionBlock one are not mark as progressive) // mark the image as progressive (completed one are not mark as progressive)
image.sd_isIncremental = YES; image.sd_isIncremental = !finished;
// assign the decode options, to let manager check whether to re-decode if needed
image.sd_decodeOptions = coderOptions;
} }
return image; return image;
} }
SDWebImageContextOption const SDWebImageContextLoaderCachedImage = @"loaderCachedImage";
...@@ -12,13 +12,12 @@ ...@@ -12,13 +12,12 @@
@interface SDImageLoadersManager () @interface SDImageLoadersManager ()
@property (nonatomic, strong, nonnull) dispatch_semaphore_t loadersLock; @property (nonatomic, strong, nonnull) NSMutableArray<id<SDImageLoader>> *imageLoaders;
@end @end
@implementation SDImageLoadersManager @implementation SDImageLoadersManager {
{ SD_LOCK_DECLARE(_loadersLock);
NSMutableArray<id<SDImageLoader>>* _imageLoaders;
} }
+ (SDImageLoadersManager *)sharedManager { + (SDImageLoadersManager *)sharedManager {
...@@ -35,25 +34,25 @@ ...@@ -35,25 +34,25 @@
if (self) { if (self) {
// initialize with default image loaders // initialize with default image loaders
_imageLoaders = [NSMutableArray arrayWithObject:[SDWebImageDownloader sharedDownloader]]; _imageLoaders = [NSMutableArray arrayWithObject:[SDWebImageDownloader sharedDownloader]];
_loadersLock = dispatch_semaphore_create(1); SD_LOCK_INIT(_loadersLock);
} }
return self; return self;
} }
- (NSArray<id<SDImageLoader>> *)loaders { - (NSArray<id<SDImageLoader>> *)loaders {
SD_LOCK(self.loadersLock); SD_LOCK(_loadersLock);
NSArray<id<SDImageLoader>>* loaders = [_imageLoaders copy]; NSArray<id<SDImageLoader>>* loaders = [_imageLoaders copy];
SD_UNLOCK(self.loadersLock); SD_UNLOCK(_loadersLock);
return loaders; return loaders;
} }
- (void)setLoaders:(NSArray<id<SDImageLoader>> *)loaders { - (void)setLoaders:(NSArray<id<SDImageLoader>> *)loaders {
SD_LOCK(self.loadersLock); SD_LOCK(_loadersLock);
[_imageLoaders removeAllObjects]; [_imageLoaders removeAllObjects];
if (loaders.count) { if (loaders.count) {
[_imageLoaders addObjectsFromArray:loaders]; [_imageLoaders addObjectsFromArray:loaders];
} }
SD_UNLOCK(self.loadersLock); SD_UNLOCK(_loadersLock);
} }
#pragma mark - Loader Property #pragma mark - Loader Property
...@@ -62,27 +61,37 @@ ...@@ -62,27 +61,37 @@
if (![loader conformsToProtocol:@protocol(SDImageLoader)]) { if (![loader conformsToProtocol:@protocol(SDImageLoader)]) {
return; return;
} }
SD_LOCK(self.loadersLock); SD_LOCK(_loadersLock);
[_imageLoaders addObject:loader]; [_imageLoaders addObject:loader];
SD_UNLOCK(self.loadersLock); SD_UNLOCK(_loadersLock);
} }
- (void)removeLoader:(id<SDImageLoader>)loader { - (void)removeLoader:(id<SDImageLoader>)loader {
if (![loader conformsToProtocol:@protocol(SDImageLoader)]) { if (![loader conformsToProtocol:@protocol(SDImageLoader)]) {
return; return;
} }
SD_LOCK(self.loadersLock); SD_LOCK(_loadersLock);
[_imageLoaders removeObject:loader]; [_imageLoaders removeObject:loader];
SD_UNLOCK(self.loadersLock); SD_UNLOCK(_loadersLock);
} }
#pragma mark - SDImageLoader #pragma mark - SDImageLoader
- (BOOL)canRequestImageForURL:(nullable NSURL *)url { - (BOOL)canRequestImageForURL:(nullable NSURL *)url {
return [self canRequestImageForURL:url options:0 context:nil];
}
- (BOOL)canRequestImageForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
NSArray<id<SDImageLoader>> *loaders = self.loaders; NSArray<id<SDImageLoader>> *loaders = self.loaders;
for (id<SDImageLoader> loader in loaders.reverseObjectEnumerator) { for (id<SDImageLoader> loader in loaders.reverseObjectEnumerator) {
if ([loader canRequestImageForURL:url]) { if ([loader respondsToSelector:@selector(canRequestImageForURL:options:context:)]) {
return YES; if ([loader canRequestImageForURL:url options:options context:context]) {
return YES;
}
} else {
if ([loader canRequestImageForURL:url]) {
return YES;
}
} }
} }
return NO; return NO;
......
...@@ -13,12 +13,15 @@ ...@@ -13,12 +13,15 @@
static void * SDMemoryCacheContext = &SDMemoryCacheContext; static void * SDMemoryCacheContext = &SDMemoryCacheContext;
@interface SDMemoryCache <KeyType, ObjectType> () @interface SDMemoryCache <KeyType, ObjectType> () {
#if SD_UIKIT
SD_LOCK_DECLARE(_weakCacheLock); // a lock to keep the access to `weakCache` thread-safe
#endif
}
@property (nonatomic, strong, nullable) SDImageCacheConfig *config; @property (nonatomic, strong, nullable) SDImageCacheConfig *config;
#if SD_UIKIT #if SD_UIKIT
@property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; // strong-weak cache @property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; // strong-weak cache
@property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock; // a lock to keep the access to `weakCache` thread-safe
#endif #endif
@end @end
...@@ -61,7 +64,7 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext; ...@@ -61,7 +64,7 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
#if SD_UIKIT #if SD_UIKIT
self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0]; self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
self.weakCacheLock = dispatch_semaphore_create(1); SD_LOCK_INIT(_weakCacheLock);
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning:) selector:@selector(didReceiveMemoryWarning:)
...@@ -85,9 +88,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext; ...@@ -85,9 +88,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
} }
if (key && obj) { if (key && obj) {
// Store weak cache // Store weak cache
SD_LOCK(self.weakCacheLock); SD_LOCK(_weakCacheLock);
[self.weakCache setObject:obj forKey:key]; [self.weakCache setObject:obj forKey:key];
SD_UNLOCK(self.weakCacheLock); SD_UNLOCK(_weakCacheLock);
} }
} }
...@@ -98,9 +101,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext; ...@@ -98,9 +101,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
} }
if (key && !obj) { if (key && !obj) {
// Check weak cache // Check weak cache
SD_LOCK(self.weakCacheLock); SD_LOCK(_weakCacheLock);
obj = [self.weakCache objectForKey:key]; obj = [self.weakCache objectForKey:key];
SD_UNLOCK(self.weakCacheLock); SD_UNLOCK(_weakCacheLock);
if (obj) { if (obj) {
// Sync cache // Sync cache
NSUInteger cost = 0; NSUInteger cost = 0;
...@@ -120,9 +123,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext; ...@@ -120,9 +123,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
} }
if (key) { if (key) {
// Remove weak cache // Remove weak cache
SD_LOCK(self.weakCacheLock); SD_LOCK(_weakCacheLock);
[self.weakCache removeObjectForKey:key]; [self.weakCache removeObjectForKey:key];
SD_UNLOCK(self.weakCacheLock); SD_UNLOCK(_weakCacheLock);
} }
} }
...@@ -132,9 +135,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext; ...@@ -132,9 +135,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
return; return;
} }
// Manually remove should also remove weak cache // Manually remove should also remove weak cache
SD_LOCK(self.weakCacheLock); SD_LOCK(_weakCacheLock);
[self.weakCache removeAllObjects]; [self.weakCache removeAllObjects];
SD_UNLOCK(self.weakCacheLock); SD_UNLOCK(_weakCacheLock);
} }
#endif #endif
......
...@@ -105,6 +105,8 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) { ...@@ -105,6 +105,8 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
/** /**
* By default, placeholder images are loaded while the image is loading. This flag will delay the loading * By default, placeholder images are loaded while the image is loading. This flag will delay the loading
* of the placeholder image until after the image has finished loading. * of the placeholder image until after the image has finished loading.
* @note This is used to treate placeholder as an **Error Placeholder** but not **Loading Placeholder** by defaults. if the image loading is cancelled or error, the placeholder will be always set.
* @note Therefore, if you want both **Error Placeholder** and **Loading Placeholder** exist, use `SDWebImageAvoidAutoSetImage` to manually set the two placeholders and final loaded image by your hand depends on loading result.
*/ */
SDWebImageDelayPlaceholder = 1 << 8, SDWebImageDelayPlaceholder = 1 << 8,
...@@ -275,18 +277,23 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextStoreC ...@@ -275,18 +277,23 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextStoreC
/** /**
The same behavior like `SDWebImageContextQueryCacheType`, but control the query cache type for the original image when you use image transformer feature. This allows the detail control of cache query for these two images. For example, if you want to query the transformed image from both memory/disk cache, query the original image from disk cache only, use `[.queryCacheType : .all, .originalQueryCacheType : .disk]` The same behavior like `SDWebImageContextQueryCacheType`, but control the query cache type for the original image when you use image transformer feature. This allows the detail control of cache query for these two images. For example, if you want to query the transformed image from both memory/disk cache, query the original image from disk cache only, use `[.queryCacheType : .all, .originalQueryCacheType : .disk]`
If not provide or the value is invalid, we will use `SDImageCacheTypeNone`, which does not query the original image from cache. (NSNumber) If not provide or the value is invalid, we will use `SDImageCacheTypeDisk`, which query the original full image data from disk cache after transformed image cache miss. This is suitable for most common cases to avoid re-downloading the full data for different transform variants. (NSNumber)
@note Which means, if you set this value to not be `.none`, we will query the original image from cache, then do transform with transformer, instead of actual downloading, which can save bandwidth usage. @note Which means, if you set this value to not be `.none`, we will query the original image from cache, then do transform with transformer, instead of actual downloading, which can save bandwidth usage.
*/ */
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOriginalQueryCacheType; FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOriginalQueryCacheType;
/** /**
The same behavior like `SDWebImageContextStoreCacheType`, but control the store cache type for the original image when you use image transformer feature. This allows the detail control of cache storage for these two images. For example, if you want to store the transformed image into both memory/disk cache, store the original image into disk cache only, use `[.storeCacheType : .all, .originalStoreCacheType : .disk]` The same behavior like `SDWebImageContextStoreCacheType`, but control the store cache type for the original image when you use image transformer feature. This allows the detail control of cache storage for these two images. For example, if you want to store the transformed image into both memory/disk cache, store the original image into disk cache only, use `[.storeCacheType : .all, .originalStoreCacheType : .disk]`
If not provide or the value is invalid, we will use `SDImageCacheTypeNone`, which does not store the original image into cache. (NSNumber) If not provide or the value is invalid, we will use `SDImageCacheTypeDisk`, which store the original full image data into disk cache after storing the transformed image. This is suitable for most common cases to avoid re-downloading the full data for different transform variants. (NSNumber)
@note This only store the original image, if you want to use the original image without downloading in next query, specify `SDWebImageContextOriginalQueryCacheType` as well. @note This only store the original image, if you want to use the original image without downloading in next query, specify `SDWebImageContextOriginalQueryCacheType` as well.
*/ */
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOriginalStoreCacheType; FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOriginalStoreCacheType;
/**
A id<SDImageCache> instance which conforms to `SDImageCache` protocol. It's used to control the cache for original image when using the transformer. If you provide one, the original image (full size image) will query and write from that cache instance instead, the transformed image will query and write from the default `SDWebImageContextImageCache` instead. (id<SDImageCache>)
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOriginalImageCache;
/** /**
A Class object which the instance is a `UIImage/NSImage` subclass and adopt `SDAnimatedImage` protocol. We will call `initWithData:scale:options:` to create the instance (or `initWithAnimatedCoder:scale:` when using progressive download) . If the instance create failed, fallback to normal `UIImage/NSImage`. A Class object which the instance is a `UIImage/NSImage` subclass and adopt `SDAnimatedImage` protocol. We will call `initWithData:scale:options:` to create the instance (or `initWithAnimatedCoder:scale:` when using progressive download) . If the instance create failed, fallback to normal `UIImage/NSImage`.
This can be used to improve animated images rendering performance (especially memory usage on big animated images) with `SDAnimatedImageView` (Class). This can be used to improve animated images rendering performance (especially memory usage on big animated images) with `SDAnimatedImageView` (Class).
......
...@@ -28,7 +28,13 @@ inline CGFloat SDImageScaleFactorForKey(NSString * _Nullable key) { ...@@ -28,7 +28,13 @@ inline CGFloat SDImageScaleFactorForKey(NSString * _Nullable key) {
#elif SD_UIKIT #elif SD_UIKIT
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
#elif SD_MAC #elif SD_MAC
if ([[NSScreen mainScreen] respondsToSelector:@selector(backingScaleFactor)]) NSScreen *mainScreen = nil;
if (@available(macOS 10.12, *)) {
mainScreen = [NSScreen mainScreen];
} else {
mainScreen = [NSScreen screens].firstObject;
}
if ([mainScreen respondsToSelector:@selector(backingScaleFactor)])
#endif #endif
{ {
// a@2x.png -> 8 // a@2x.png -> 8
...@@ -131,6 +137,7 @@ SDWebImageContextOption const SDWebImageContextQueryCacheType = @"queryCacheType ...@@ -131,6 +137,7 @@ SDWebImageContextOption const SDWebImageContextQueryCacheType = @"queryCacheType
SDWebImageContextOption const SDWebImageContextStoreCacheType = @"storeCacheType"; SDWebImageContextOption const SDWebImageContextStoreCacheType = @"storeCacheType";
SDWebImageContextOption const SDWebImageContextOriginalQueryCacheType = @"originalQueryCacheType"; SDWebImageContextOption const SDWebImageContextOriginalQueryCacheType = @"originalQueryCacheType";
SDWebImageContextOption const SDWebImageContextOriginalStoreCacheType = @"originalStoreCacheType"; SDWebImageContextOption const SDWebImageContextOriginalStoreCacheType = @"originalStoreCacheType";
SDWebImageContextOption const SDWebImageContextOriginalImageCache = @"originalImageCache";
SDWebImageContextOption const SDWebImageContextAnimatedImageClass = @"animatedImageClass"; SDWebImageContextOption const SDWebImageContextAnimatedImageClass = @"animatedImageClass";
SDWebImageContextOption const SDWebImageContextDownloadRequestModifier = @"downloadRequestModifier"; SDWebImageContextOption const SDWebImageContextDownloadRequestModifier = @"downloadRequestModifier";
SDWebImageContextOption const SDWebImageContextDownloadResponseModifier = @"downloadResponseModifier"; SDWebImageContextOption const SDWebImageContextDownloadResponseModifier = @"downloadResponseModifier";
......
...@@ -157,7 +157,7 @@ typedef SDImageLoaderCompletedBlock SDWebImageDownloaderCompletedBlock; ...@@ -157,7 +157,7 @@ typedef SDImageLoaderCompletedBlock SDWebImageDownloaderCompletedBlock;
/** /**
* Set the response modifier to modify the original download response during image load. * Set the response modifier to modify the original download response during image load.
* This request modifier method will be called for each downloading image response. Return the original response means no modification. Return nil will mark current download as cancelled. * This response modifier method will be called for each downloading image response. Return the original response means no modification. Return nil will mark current download as cancelled.
* Defaults to nil, means does not modify the original download response. * Defaults to nil, means does not modify the original download response.
* @note If you want to modify single response, consider using `SDWebImageContextDownloadResponseModifier` context option. * @note If you want to modify single response, consider using `SDWebImageContextDownloadResponseModifier` context option.
*/ */
......
...@@ -40,15 +40,16 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext; ...@@ -40,15 +40,16 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue; @property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, NSOperation<SDWebImageDownloaderOperation> *> *URLOperations; @property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, NSOperation<SDWebImageDownloaderOperation> *> *URLOperations;
@property (strong, nonatomic, nullable) NSMutableDictionary<NSString *, NSString *> *HTTPHeaders; @property (strong, nonatomic, nullable) NSMutableDictionary<NSString *, NSString *> *HTTPHeaders;
@property (strong, nonatomic, nonnull) dispatch_semaphore_t HTTPHeadersLock; // A lock to keep the access to `HTTPHeaders` thread-safe
@property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock; // A lock to keep the access to `URLOperations` thread-safe
// The session in which data tasks will run // The session in which data tasks will run
@property (strong, nonatomic) NSURLSession *session; @property (strong, nonatomic) NSURLSession *session;
@end @end
@implementation SDWebImageDownloader @implementation SDWebImageDownloader {
SD_LOCK_DECLARE(_HTTPHeadersLock); // A lock to keep the access to `HTTPHeaders` thread-safe
SD_LOCK_DECLARE(_operationsLock); // A lock to keep the access to `URLOperations` thread-safe
}
+ (void)initialize { + (void)initialize {
// Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator ) // Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
...@@ -120,8 +121,8 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext; ...@@ -120,8 +121,8 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
} }
headerDictionary[@"Accept"] = @"image/*,*/*;q=0.8"; headerDictionary[@"Accept"] = @"image/*,*/*;q=0.8";
_HTTPHeaders = headerDictionary; _HTTPHeaders = headerDictionary;
_HTTPHeadersLock = dispatch_semaphore_create(1); SD_LOCK_INIT(_HTTPHeadersLock);
_operationsLock = dispatch_semaphore_create(1); SD_LOCK_INIT(_operationsLock);
NSURLSessionConfiguration *sessionConfiguration = _config.sessionConfiguration; NSURLSessionConfiguration *sessionConfiguration = _config.sessionConfiguration;
if (!sessionConfiguration) { if (!sessionConfiguration) {
sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration]; sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
...@@ -139,11 +140,12 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext; ...@@ -139,11 +140,12 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
} }
- (void)dealloc { - (void)dealloc {
[self.session invalidateAndCancel];
self.session = nil;
[self.downloadQueue cancelAllOperations]; [self.downloadQueue cancelAllOperations];
[self.config removeObserver:self forKeyPath:NSStringFromSelector(@selector(maxConcurrentDownloads)) context:SDWebImageDownloaderContext]; [self.config removeObserver:self forKeyPath:NSStringFromSelector(@selector(maxConcurrentDownloads)) context:SDWebImageDownloaderContext];
// Invalide the URLSession after all operations been cancelled
[self.session invalidateAndCancel];
self.session = nil;
} }
- (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations { - (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations {
...@@ -161,18 +163,18 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext; ...@@ -161,18 +163,18 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
if (!field) { if (!field) {
return; return;
} }
SD_LOCK(self.HTTPHeadersLock); SD_LOCK(_HTTPHeadersLock);
[self.HTTPHeaders setValue:value forKey:field]; [self.HTTPHeaders setValue:value forKey:field];
SD_UNLOCK(self.HTTPHeadersLock); SD_UNLOCK(_HTTPHeadersLock);
} }
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field { - (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
if (!field) { if (!field) {
return nil; return nil;
} }
SD_LOCK(self.HTTPHeadersLock); SD_LOCK(_HTTPHeadersLock);
NSString *value = [self.HTTPHeaders objectForKey:field]; NSString *value = [self.HTTPHeaders objectForKey:field];
SD_UNLOCK(self.HTTPHeadersLock); SD_UNLOCK(_HTTPHeadersLock);
return value; return value;
} }
...@@ -202,14 +204,14 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext; ...@@ -202,14 +204,14 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
return nil; return nil;
} }
SD_LOCK(self.operationsLock); SD_LOCK(_operationsLock);
id downloadOperationCancelToken; id downloadOperationCancelToken;
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url]; NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
// There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`. // There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`.
if (!operation || operation.isFinished || operation.isCancelled) { if (!operation || operation.isFinished || operation.isCancelled) {
operation = [self createDownloaderOperationWithUrl:url options:options context:context]; operation = [self createDownloaderOperationWithUrl:url options:options context:context];
if (!operation) { if (!operation) {
SD_UNLOCK(self.operationsLock); SD_UNLOCK(_operationsLock);
if (completedBlock) { if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}]; NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
completedBlock(nil, nil, error, YES); completedBlock(nil, nil, error, YES);
...@@ -222,9 +224,9 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext; ...@@ -222,9 +224,9 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
if (!self) { if (!self) {
return; return;
} }
SD_LOCK(self.operationsLock); SD_LOCK(self->_operationsLock);
[self.URLOperations removeObjectForKey:url]; [self.URLOperations removeObjectForKey:url];
SD_UNLOCK(self.operationsLock); SD_UNLOCK(self->_operationsLock);
}; };
self.URLOperations[url] = operation; self.URLOperations[url] = operation;
// Add the handlers before submitting to operation queue, avoid the race condition that operation finished before setting handlers. // Add the handlers before submitting to operation queue, avoid the race condition that operation finished before setting handlers.
...@@ -248,7 +250,7 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext; ...@@ -248,7 +250,7 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
} }
} }
} }
SD_UNLOCK(self.operationsLock); SD_UNLOCK(_operationsLock);
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation]; SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url; token.url = url;
...@@ -271,9 +273,9 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext; ...@@ -271,9 +273,9 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval]; NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
mutableRequest.HTTPShouldHandleCookies = SD_OPTIONS_CONTAINS(options, SDWebImageDownloaderHandleCookies); mutableRequest.HTTPShouldHandleCookies = SD_OPTIONS_CONTAINS(options, SDWebImageDownloaderHandleCookies);
mutableRequest.HTTPShouldUsePipelining = YES; mutableRequest.HTTPShouldUsePipelining = YES;
SD_LOCK(self.HTTPHeadersLock); SD_LOCK(_HTTPHeadersLock);
mutableRequest.allHTTPHeaderFields = self.HTTPHeaders; mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
SD_UNLOCK(self.HTTPHeadersLock); SD_UNLOCK(_HTTPHeadersLock);
// Context Option // Context Option
SDWebImageMutableContext *mutableContext; SDWebImageMutableContext *mutableContext;
...@@ -347,6 +349,14 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext; ...@@ -347,6 +349,14 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
operation.minimumProgressInterval = MIN(MAX(self.config.minimumProgressInterval, 0), 1); operation.minimumProgressInterval = MIN(MAX(self.config.minimumProgressInterval, 0), 1);
} }
if ([operation respondsToSelector:@selector(setAcceptableStatusCodes:)]) {
operation.acceptableStatusCodes = self.config.acceptableStatusCodes;
}
if ([operation respondsToSelector:@selector(setAcceptableContentTypes:)]) {
operation.acceptableContentTypes = self.config.acceptableContentTypes;
}
if (options & SDWebImageDownloaderHighPriority) { if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh; operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) { } else if (options & SDWebImageDownloaderLowPriority) {
...@@ -561,6 +571,10 @@ didReceiveResponse:(NSURLResponse *)response ...@@ -561,6 +571,10 @@ didReceiveResponse:(NSURLResponse *)response
@implementation SDWebImageDownloader (SDImageLoader) @implementation SDWebImageDownloader (SDImageLoader)
- (BOOL)canRequestImageForURL:(NSURL *)url { - (BOOL)canRequestImageForURL:(NSURL *)url {
return [self canRequestImageForURL:url options:0 context:nil];
}
- (BOOL)canRequestImageForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
if (!url) { if (!url) {
return NO; return NO;
} }
...@@ -596,6 +610,10 @@ didReceiveResponse:(NSURLResponse *)response ...@@ -596,6 +610,10 @@ didReceiveResponse:(NSURLResponse *)response
} }
- (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error { - (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error {
return [self shouldBlockFailedURLWithURL:url error:error options:0 context:nil];
}
- (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
BOOL shouldBlockFailedURL; BOOL shouldBlockFailedURL;
// Filter the error domain and check error codes // Filter the error domain and check error codes
if ([error.domain isEqualToString:SDWebImageErrorDomain]) { if ([error.domain isEqualToString:SDWebImageErrorDomain]) {
......
...@@ -95,4 +95,19 @@ typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) { ...@@ -95,4 +95,19 @@ typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
*/ */
@property (nonatomic, copy, nullable) NSString *password; @property (nonatomic, copy, nullable) NSString *password;
/**
* Set the acceptable HTTP Response status code. The status code which beyond the range will mark the download operation failed.
* For example, if we config [200, 400) but server response is 503, the download will fail with error code `SDWebImageErrorInvalidDownloadStatusCode`.
* Defaults to [200,400). Nil means no validation at all.
*/
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;
/**
* Set the acceptable HTTP Response content type. The content type beyond the set will mark the download operation failed.
* For example, if we config ["image/png"] but server response is "application/json", the download will fail with error code `SDWebImageErrorInvalidDownloadContentType`.
* Normally you don't need this for image format detection because we use image's data file signature magic bytes: https://en.wikipedia.org/wiki/List_of_file_signatures
* Defaults to nil. Nil means no validation at all.
*/
@property (nonatomic, copy, nullable) NSSet<NSString *> *acceptableContentTypes;
@end @end
...@@ -26,6 +26,7 @@ static SDWebImageDownloaderConfig * _defaultDownloaderConfig; ...@@ -26,6 +26,7 @@ static SDWebImageDownloaderConfig * _defaultDownloaderConfig;
_maxConcurrentDownloads = 6; _maxConcurrentDownloads = 6;
_downloadTimeout = 15.0; _downloadTimeout = 15.0;
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder; _executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
_acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
} }
return self; return self;
} }
...@@ -41,6 +42,8 @@ static SDWebImageDownloaderConfig * _defaultDownloaderConfig; ...@@ -41,6 +42,8 @@ static SDWebImageDownloaderConfig * _defaultDownloaderConfig;
config.urlCredential = self.urlCredential; config.urlCredential = self.urlCredential;
config.username = self.username; config.username = self.username;
config.password = self.password; config.password = self.password;
config.acceptableStatusCodes = self.acceptableStatusCodes;
config.acceptableContentTypes = self.acceptableContentTypes;
return config; return config;
} }
......
...@@ -37,8 +37,12 @@ ...@@ -37,8 +37,12 @@
@optional @optional
@property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask; @property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;
@property (strong, nonatomic, readonly, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)); @property (strong, nonatomic, readonly, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
// These operation-level config was inherited from downloader. See `SDWebImageDownloaderConfig` for documentation.
@property (strong, nonatomic, nullable) NSURLCredential *credential; @property (strong, nonatomic, nullable) NSURLCredential *credential;
@property (assign, nonatomic) double minimumProgressInterval; @property (assign, nonatomic) double minimumProgressInterval;
@property (copy, nonatomic, nullable) NSIndexSet *acceptableStatusCodes;
@property (copy, nonatomic, nullable) NSSet<NSString *> *acceptableContentTypes;
@end @end
...@@ -85,6 +89,21 @@ ...@@ -85,6 +89,21 @@
*/ */
@property (assign, nonatomic) double minimumProgressInterval; @property (assign, nonatomic) double minimumProgressInterval;
/**
* Set the acceptable HTTP Response status code. The status code which beyond the range will mark the download operation failed.
* For example, if we config [200, 400) but server response is 503, the download will fail with error code `SDWebImageErrorInvalidDownloadStatusCode`.
* Defaults to [200,400). Nil means no validation at all.
*/
@property (copy, nonatomic, nullable) NSIndexSet *acceptableStatusCodes;
/**
* Set the acceptable HTTP Response content type. The content type beyond the set will mark the download operation failed.
* For example, if we config ["image/png"] but server response is "application/json", the download will fail with error code `SDWebImageErrorInvalidDownloadContentType`.
* Normally you don't need this for image format detection because we use image's data file signature magic bytes: https://en.wikipedia.org/wiki/List_of_file_signatures
* Defaults to nil. Nil means no validation at all.
*/
@property (copy, nonatomic, nullable) NSSet<NSString *> *acceptableContentTypes;
/** /**
* The options for the receiver. * The options for the receiver.
*/ */
...@@ -128,7 +147,7 @@ ...@@ -128,7 +147,7 @@
context:(nullable SDWebImageContext *)context NS_DESIGNATED_INITIALIZER; context:(nullable SDWebImageContext *)context NS_DESIGNATED_INITIALIZER;
/** /**
* Adds handlers for progress and completion. Returns a tokent that can be passed to -cancel: to cancel this set of * Adds handlers for progress and completion. Returns a token that can be passed to -cancel: to cancel this set of
* callbacks. * callbacks.
* *
* @param progressBlock the block executed when a new chunk of data arrives. * @param progressBlock the block executed when a new chunk of data arrives.
......
...@@ -11,8 +11,12 @@ ...@@ -11,8 +11,12 @@
FOUNDATION_EXPORT NSErrorDomain const _Nonnull SDWebImageErrorDomain; FOUNDATION_EXPORT NSErrorDomain const _Nonnull SDWebImageErrorDomain;
/// The response instance for invalid download response (NSURLResponse *)
FOUNDATION_EXPORT NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadResponseKey;
/// The HTTP status code for invalid download response (NSNumber *) /// The HTTP status code for invalid download response (NSNumber *)
FOUNDATION_EXPORT NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadStatusCodeKey; FOUNDATION_EXPORT NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadStatusCodeKey;
/// The HTTP MIME content type for invalid download response (NSString *)
FOUNDATION_EXPORT NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadContentTypeKey;
/// SDWebImage error domain and codes /// SDWebImage error domain and codes
typedef NS_ERROR_ENUM(SDWebImageErrorDomain, SDWebImageError) { typedef NS_ERROR_ENUM(SDWebImageErrorDomain, SDWebImageError) {
...@@ -24,4 +28,5 @@ typedef NS_ERROR_ENUM(SDWebImageErrorDomain, SDWebImageError) { ...@@ -24,4 +28,5 @@ typedef NS_ERROR_ENUM(SDWebImageErrorDomain, SDWebImageError) {
SDWebImageErrorInvalidDownloadStatusCode = 2001, // The image download response a invalid status code. You can check the status code in error's userInfo under `SDWebImageErrorDownloadStatusCodeKey` SDWebImageErrorInvalidDownloadStatusCode = 2001, // The image download response a invalid status code. You can check the status code in error's userInfo under `SDWebImageErrorDownloadStatusCodeKey`
SDWebImageErrorCancelled = 2002, // The image loading operation is cancelled before finished, during either async disk cache query, or waiting before actual network request. For actual network request error, check `NSURLErrorDomain` error domain and code. SDWebImageErrorCancelled = 2002, // The image loading operation is cancelled before finished, during either async disk cache query, or waiting before actual network request. For actual network request error, check `NSURLErrorDomain` error domain and code.
SDWebImageErrorInvalidDownloadResponse = 2003, // When using response modifier, the modified download response is nil and marked as failed. SDWebImageErrorInvalidDownloadResponse = 2003, // When using response modifier, the modified download response is nil and marked as failed.
SDWebImageErrorInvalidDownloadContentType = 2004, // The image download response a invalid content type. You can check the MIME content type in error's userInfo under `SDWebImageErrorDownloadContentTypeKey`
}; };
...@@ -10,4 +10,7 @@ ...@@ -10,4 +10,7 @@
#import "SDWebImageError.h" #import "SDWebImageError.h"
NSErrorDomain const _Nonnull SDWebImageErrorDomain = @"SDWebImageErrorDomain"; NSErrorDomain const _Nonnull SDWebImageErrorDomain = @"SDWebImageErrorDomain";
NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadResponseKey = @"SDWebImageErrorDownloadResponseKey";
NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadStatusCodeKey = @"SDWebImageErrorDownloadStatusCodeKey"; NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadStatusCodeKey = @"SDWebImageErrorDownloadStatusCodeKey";
NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadContentTypeKey = @"SDWebImageErrorDownloadContentTypeKey";
...@@ -12,16 +12,7 @@ ...@@ -12,16 +12,7 @@
#if SD_MAC #if SD_MAC
#import <QuartzCore/QuartzCore.h> #import <QuartzCore/QuartzCore.h>
#endif #import <CoreImage/CIFilter.h>
#if SD_UIKIT
#if __IPHONE_13_0 || __TVOS_13_0 || __MAC_10_15
// Xcode 11
#else
// Supports Xcode 10 users, for those users, define these enum
static NSInteger UIActivityIndicatorViewStyleMedium = 100;
static NSInteger UIActivityIndicatorViewStyleLarge = 101;
#endif
#endif #endif
#pragma mark - Activity Indicator #pragma mark - Activity Indicator
......
...@@ -11,8 +11,14 @@ ...@@ -11,8 +11,14 @@
/// A protocol represents cancelable operation. /// A protocol represents cancelable operation.
@protocol SDWebImageOperation <NSObject> @protocol SDWebImageOperation <NSObject>
/// Cancel the operation
- (void)cancel; - (void)cancel;
@optional
/// Whether the operation has been cancelled.
@property (nonatomic, assign, readonly, getter=isCancelled) BOOL cancelled;
@end @end
/// NSOperation conform to `SDWebImageOperation`. /// NSOperation conform to `SDWebImageOperation`.
......
...@@ -22,8 +22,8 @@ ...@@ -22,8 +22,8 @@
unsigned long _totalCount; unsigned long _totalCount;
// Used to ensure NSPointerArray thread safe // Used to ensure NSPointerArray thread safe
dispatch_semaphore_t _prefetchOperationsLock; SD_LOCK_DECLARE(_prefetchOperationsLock);
dispatch_semaphore_t _loadOperationsLock; SD_LOCK_DECLARE(_loadOperationsLock);
} }
@property (nonatomic, copy, readwrite) NSArray<NSURL *> *urls; @property (nonatomic, copy, readwrite) NSArray<NSURL *> *urls;
...@@ -268,8 +268,8 @@ ...@@ -268,8 +268,8 @@
- (instancetype)init { - (instancetype)init {
self = [super init]; self = [super init];
if (self) { if (self) {
_prefetchOperationsLock = dispatch_semaphore_create(1); SD_LOCK_INIT(_prefetchOperationsLock);
_loadOperationsLock = dispatch_semaphore_create(1); SD_LOCK_INIT(_loadOperationsLock);
} }
return self; return self;
} }
......
...@@ -89,6 +89,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio ...@@ -89,6 +89,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)fadeTransitionWithDuration:(NSTimeInterval)duration { + (SDWebImageTransition *)fadeTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new]; SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT #if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction; transition.animationOptions = UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction;
#else #else
...@@ -103,6 +104,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio ...@@ -103,6 +104,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)flipFromLeftTransitionWithDuration:(NSTimeInterval)duration { + (SDWebImageTransition *)flipFromLeftTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new]; SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT #if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionAllowUserInteraction; transition.animationOptions = UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionAllowUserInteraction;
#else #else
...@@ -117,6 +119,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio ...@@ -117,6 +119,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)flipFromRightTransitionWithDuration:(NSTimeInterval)duration { + (SDWebImageTransition *)flipFromRightTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new]; SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT #if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionAllowUserInteraction; transition.animationOptions = UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionAllowUserInteraction;
#else #else
...@@ -131,6 +134,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio ...@@ -131,6 +134,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)flipFromTopTransitionWithDuration:(NSTimeInterval)duration { + (SDWebImageTransition *)flipFromTopTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new]; SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT #if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromTop | UIViewAnimationOptionAllowUserInteraction; transition.animationOptions = UIViewAnimationOptionTransitionFlipFromTop | UIViewAnimationOptionAllowUserInteraction;
#else #else
...@@ -145,6 +149,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio ...@@ -145,6 +149,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)flipFromBottomTransitionWithDuration:(NSTimeInterval)duration { + (SDWebImageTransition *)flipFromBottomTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new]; SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT #if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromBottom | UIViewAnimationOptionAllowUserInteraction; transition.animationOptions = UIViewAnimationOptionTransitionFlipFromBottom | UIViewAnimationOptionAllowUserInteraction;
#else #else
...@@ -159,6 +164,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio ...@@ -159,6 +164,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)curlUpTransitionWithDuration:(NSTimeInterval)duration { + (SDWebImageTransition *)curlUpTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new]; SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT #if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionCurlUp | UIViewAnimationOptionAllowUserInteraction; transition.animationOptions = UIViewAnimationOptionTransitionCurlUp | UIViewAnimationOptionAllowUserInteraction;
#else #else
...@@ -173,6 +179,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio ...@@ -173,6 +179,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)curlDownTransitionWithDuration:(NSTimeInterval)duration { + (SDWebImageTransition *)curlDownTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new]; SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT #if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionCurlDown | UIViewAnimationOptionAllowUserInteraction; transition.animationOptions = UIViewAnimationOptionTransitionCurlDown | UIViewAnimationOptionAllowUserInteraction;
#else #else
......
...@@ -9,12 +9,30 @@ ...@@ -9,12 +9,30 @@
#import "UIImage+ForceDecode.h" #import "UIImage+ForceDecode.h"
#import "SDImageCoderHelper.h" #import "SDImageCoderHelper.h"
#import "objc/runtime.h" #import "objc/runtime.h"
#import "NSImage+Compatibility.h"
@implementation UIImage (ForceDecode) @implementation UIImage (ForceDecode)
- (BOOL)sd_isDecoded { - (BOOL)sd_isDecoded {
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_isDecoded)); NSNumber *value = objc_getAssociatedObject(self, @selector(sd_isDecoded));
return value.boolValue; if (value != nil) {
return value.boolValue;
} else {
// Assume only CGImage based can use lazy decoding
CGImageRef cgImage = self.CGImage;
if (cgImage) {
CFStringRef uttype = CGImageGetUTType(self.CGImage);
if (uttype) {
// Only ImageIO can set `com.apple.ImageIO.imageSourceTypeIdentifier`
return NO;
} else {
// Thumbnail or CGBitmapContext drawn image
return YES;
}
}
}
// Assume others as non-decoded
return NO;
} }
- (void)setSd_isDecoded:(BOOL)sd_isDecoded { - (void)setSd_isDecoded:(BOOL)sd_isDecoded {
......
...@@ -20,7 +20,8 @@ FOUNDATION_STATIC_INLINE NSUInteger SDMemoryCacheCostForImage(UIImage *image) { ...@@ -20,7 +20,8 @@ FOUNDATION_STATIC_INLINE NSUInteger SDMemoryCacheCostForImage(UIImage *image) {
#if SD_MAC #if SD_MAC
frameCount = 1; frameCount = 1;
#elif SD_UIKIT || SD_WATCH #elif SD_UIKIT || SD_WATCH
frameCount = image.images.count > 0 ? image.images.count : 1; // Filter the same frame in `_UIAnimatedImage`.
frameCount = image.images.count > 1 ? [NSSet setWithArray:image.images].count : 1;
#endif #endif
NSUInteger cost = bytesPerFrame * frameCount; NSUInteger cost = bytesPerFrame * frameCount;
return cost; return cost;
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#import "SDWebImageCompat.h" #import "SDWebImageCompat.h"
#import "NSData+ImageContentType.h" #import "NSData+ImageContentType.h"
#import "SDImageCoder.h"
/** /**
UIImage category for image metadata, including animation, loop count, format, incremental, etc. UIImage category for image metadata, including animation, loop count, format, incremental, etc.
...@@ -20,12 +21,23 @@ ...@@ -20,12 +21,23 @@
* For animated image format, 0 means infinite looping. * For animated image format, 0 means infinite looping.
* Note that because of the limitations of categories this property can get out of sync if you create another instance with CGImage or other methods. * Note that because of the limitations of categories this property can get out of sync if you create another instance with CGImage or other methods.
* AppKit: * AppKit:
* NSImage currently only support animated via GIF imageRep unlike UIImage. * NSImage currently only support animated via `NSBitmapImageRep`(GIF) or `SDAnimatedImageRep`(APNG/GIF/WebP) unlike UIImage.
* The getter of this property will get the loop count from GIF imageRep * The getter of this property will get the loop count from animated imageRep
* The setter of this property will set the loop count from GIF imageRep * The setter of this property will set the loop count from animated imageRep
*/ */
@property (nonatomic, assign) NSUInteger sd_imageLoopCount; @property (nonatomic, assign) NSUInteger sd_imageLoopCount;
/**
* UIKit:
* Returns the `images`'s count by unapply the patch for the different frame durations. Which matches the real visible frame count when displaying on UIImageView.
* See more in `SDImageCoderHelper.animatedImageWithFrames`.
* Returns 1 for static image.
* AppKit:
* Returns the underlaying `NSBitmapImageRep` or `SDAnimatedImageRep` frame count.
* Returns 1 for static image.
*/
@property (nonatomic, assign, readonly) NSUInteger sd_imageFrameCount;
/** /**
* UIKit: * UIKit:
* Check the `images` array property. * Check the `images` array property.
...@@ -54,4 +66,12 @@ ...@@ -54,4 +66,12 @@
*/ */
@property (nonatomic, assign) BOOL sd_isIncremental; @property (nonatomic, assign) BOOL sd_isIncremental;
/**
A dictionary value contains the decode options when decoded from SDWebImage loading system (say, `SDImageCacheDecodeImageData/SDImageLoaderDecode[Progressive]ImageData`)
It may not always available and only image decoding related options will be saved. (including [.decodeScaleFactor, .decodeThumbnailPixelSize, .decodePreserveAspectRatio, .decodeFirstFrameOnly])
@note This is used to identify and check the image from downloader when multiple different request (which want different image thumbnail size, image class, etc) share the same URLOperation.
@warning This API exist only because of current SDWebImageDownloader bad design which does not callback the context we call it. There will be refactory in future (API break) and you SHOULD NOT rely on this property at all.
*/
@property (nonatomic, copy) SDImageCoderOptions *sd_decodeOptions;
@end @end
...@@ -29,6 +29,32 @@ ...@@ -29,6 +29,32 @@
objc_setAssociatedObject(self, @selector(sd_imageLoopCount), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(self, @selector(sd_imageLoopCount), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} }
- (NSUInteger)sd_imageFrameCount {
NSArray<UIImage *> *animatedImages = self.images;
if (!animatedImages || animatedImages.count <= 1) {
return 1;
}
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_imageFrameCount));
if ([value isKindOfClass:[NSNumber class]]) {
return [value unsignedIntegerValue];
}
__block NSUInteger frameCount = 1;
__block UIImage *previousImage = animatedImages.firstObject;
[animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
// ignore first
if (idx == 0) {
return;
}
if (![image isEqual:previousImage]) {
frameCount++;
}
previousImage = image;
}];
objc_setAssociatedObject(self, @selector(sd_imageFrameCount), @(frameCount), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return frameCount;
}
- (BOOL)sd_isAnimated { - (BOOL)sd_isAnimated {
return (self.images != nil); return (self.images != nil);
} }
...@@ -87,6 +113,19 @@ ...@@ -87,6 +113,19 @@
} }
} }
- (NSUInteger)sd_imageFrameCount {
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil];
NSBitmapImageRep *bitmapImageRep;
if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
bitmapImageRep = (NSBitmapImageRep *)imageRep;
}
if (bitmapImageRep) {
return [[bitmapImageRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
}
return 1;
}
- (BOOL)sd_isAnimated { - (BOOL)sd_isAnimated {
BOOL isAnimated = NO; BOOL isAnimated = NO;
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height); NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
...@@ -127,10 +166,8 @@ ...@@ -127,10 +166,8 @@
return imageFormat; return imageFormat;
} }
// Check CGImage's UTType, may return nil for non-Image/IO based image // Check CGImage's UTType, may return nil for non-Image/IO based image
if (@available(iOS 9.0, tvOS 9.0, macOS 10.11, watchOS 2.0, *)) { CFStringRef uttype = CGImageGetUTType(self.CGImage);
CFStringRef uttype = CGImageGetUTType(self.CGImage); imageFormat = [NSData sd_imageFormatFromUTType:uttype];
imageFormat = [NSData sd_imageFormatFromUTType:uttype];
}
return imageFormat; return imageFormat;
} }
...@@ -147,4 +184,16 @@ ...@@ -147,4 +184,16 @@
return value.boolValue; return value.boolValue;
} }
- (void)setSd_decodeOptions:(SDImageCoderOptions *)sd_decodeOptions {
objc_setAssociatedObject(self, @selector(sd_decodeOptions), sd_decodeOptions, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (SDImageCoderOptions *)sd_decodeOptions {
SDImageCoderOptions *value = objc_getAssociatedObject(self, @selector(sd_decodeOptions));
if ([value isKindOfClass:NSDictionary.class]) {
return value;
}
return nil;
}
@end @end
...@@ -57,7 +57,89 @@ static inline CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageS ...@@ -57,7 +57,89 @@ static inline CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageS
return rect; return rect;
} }
static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitmapInfo) { static inline UIColor * SDGetColorFromGrayscale(Pixel_88 pixel, CGBitmapInfo bitmapInfo) {
// Get alpha info, byteOrder info
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
CGFloat w = 0, a = 1;
BOOL byteOrderNormal = NO;
switch (byteOrderInfo) {
case kCGBitmapByteOrderDefault: {
byteOrderNormal = YES;
} break;
case kCGBitmapByteOrder32Little: {
} break;
case kCGBitmapByteOrder32Big: {
byteOrderNormal = YES;
} break;
default: break;
}
switch (alphaInfo) {
case kCGImageAlphaPremultipliedFirst:
case kCGImageAlphaFirst: {
if (byteOrderNormal) {
// AW
a = pixel[0] / 255.0;
w = pixel[1] / 255.0;
} else {
// WA
w = pixel[0] / 255.0;
a = pixel[1] / 255.0;
}
}
break;
case kCGImageAlphaPremultipliedLast:
case kCGImageAlphaLast: {
if (byteOrderNormal) {
// WA
w = pixel[0] / 255.0;
a = pixel[1] / 255.0;
} else {
// AW
a = pixel[0] / 255.0;
w = pixel[1] / 255.0;
}
}
break;
case kCGImageAlphaNone: {
// W
w = pixel[0] / 255.0;
}
break;
case kCGImageAlphaNoneSkipLast: {
if (byteOrderNormal) {
// WX
w = pixel[0] / 255.0;
} else {
// XW
a = pixel[1] / 255.0;
}
}
break;
case kCGImageAlphaNoneSkipFirst: {
if (byteOrderNormal) {
// XW
a = pixel[1] / 255.0;
} else {
// WX
a = pixel[0] / 255.0;
}
}
break;
case kCGImageAlphaOnly: {
// A
a = pixel[0] / 255.0;
}
break;
default:
break;
}
return [UIColor colorWithWhite:w alpha:a];
}
static inline UIColor * SDGetColorFromRGBA(Pixel_8888 pixel, CGBitmapInfo bitmapInfo) {
// Get alpha info, byteOrder info // Get alpha info, byteOrder info
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask; CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask; CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
...@@ -470,18 +552,34 @@ static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull ...@@ -470,18 +552,34 @@ static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull
size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef); size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
CFRange range = CFRangeMake(bytesPerRow * point.y + components * point.x, 4); CFRange range = CFRangeMake(bytesPerRow * point.y + components * point.x, components);
if (CFDataGetLength(data) < range.location + range.length) { if (CFDataGetLength(data) < range.location + range.length) {
CFRelease(data); CFRelease(data);
CGImageRelease(imageRef); CGImageRelease(imageRef);
return nil; return nil;
} }
Pixel_8888 pixel = {0}; // greyscale
CFDataGetBytes(data, range, pixel); if (components == 2) {
CFRelease(data); Pixel_88 pixel = {0};
CGImageRelease(imageRef); CFDataGetBytes(data, range, pixel);
// Convert to color CFRelease(data);
return SDGetColorFromPixel(pixel, bitmapInfo); CGImageRelease(imageRef);
// Convert to color
return SDGetColorFromGrayscale(pixel, bitmapInfo);
} else if (components == 3 || components == 4) {
// RGB/RGBA
Pixel_8888 pixel = {0};
CFDataGetBytes(data, range, pixel);
CFRelease(data);
CGImageRelease(imageRef);
// Convert to color
return SDGetColorFromRGBA(pixel, bitmapInfo);
} else {
NSLog(@"Unsupported components: %zu", components);
CFRelease(data);
CGImageRelease(imageRef);
return nil;
}
} }
- (nullable NSArray<UIColor *> *)sd_colorsWithRect:(CGRect)rect { - (nullable NSArray<UIColor *> *)sd_colorsWithRect:(CGRect)rect {
...@@ -539,17 +637,32 @@ static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull ...@@ -539,17 +637,32 @@ static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull
// Convert to color // Convert to color
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef); CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
NSMutableArray<UIColor *> *colors = [NSMutableArray arrayWithCapacity:CGRectGetWidth(rect) * CGRectGetHeight(rect)]; NSMutableArray<UIColor *> *colors = [NSMutableArray arrayWithCapacity:CGRectGetWidth(rect) * CGRectGetHeight(rect)];
for (size_t index = start; index < end; index += 4) { for (size_t index = start; index < end; index += components) {
if (index >= row * bytesPerRow + col * components) { if (index >= row * bytesPerRow + col * components) {
// Index beyond the end of current row, go next row // Index beyond the end of current row, go next row
row++; row++;
index = row * bytesPerRow + CGRectGetMinX(rect) * components; index = row * bytesPerRow + CGRectGetMinX(rect) * components;
index -= 4; index -= components;
continue; continue;
} }
Pixel_8888 pixel = {pixels[index], pixels[index+1], pixels[index+2], pixels[index+3]}; UIColor *color;
UIColor *color = SDGetColorFromPixel(pixel, bitmapInfo); if (components == 2) {
[colors addObject:color]; Pixel_88 pixel = {pixels[index], pixel[index+1]};
color = SDGetColorFromGrayscale(pixel, bitmapInfo);
} else {
if (components == 3) {
Pixel_8888 pixel = {pixels[index], pixels[index+1], pixels[index+2], 0};
color = SDGetColorFromRGBA(pixel, bitmapInfo);
} else if (components == 4) {
Pixel_8888 pixel = {pixels[index], pixels[index+1], pixels[index+2], pixels[index+3]};
color = SDGetColorFromRGBA(pixel, bitmapInfo);
} else {
NSLog(@"Unsupported components: %zu", components);
}
}
if (color) {
[colors addObject:color];
}
} }
CFRelease(data); CFRelease(data);
CGImageRelease(imageRef); CGImageRelease(imageRef);
...@@ -609,7 +722,7 @@ static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull ...@@ -609,7 +722,7 @@ static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull
.bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, //requests a BGRA buffer. .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, //requests a BGRA buffer.
.version = 0, .version = 0,
.decode = NULL, .decode = NULL,
.renderingIntent = kCGRenderingIntentDefault .renderingIntent = CGImageGetRenderingIntent(imageRef)
}; };
vImage_Error err; vImage_Error err;
......
...@@ -71,14 +71,15 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima ...@@ -71,14 +71,15 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima
* block is called a last time with the full image and the last parameter set to YES. * block is called a last time with the full image and the last parameter set to YES.
* *
* The last parameter is the original image URL * The last parameter is the original image URL
* @return The returned operation for cancelling cache and download operation, typically type is `SDWebImageCombinedOperation`
*/ */
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url - (nullable id<SDWebImageOperation>)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock; completed:(nullable SDInternalCompletionBlock)completedBlock;
/** /**
* Cancel the current image load * Cancel the current image load
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#import "SDWebImageError.h" #import "SDWebImageError.h"
#import "SDInternalMacros.h" #import "SDInternalMacros.h"
#import "SDWebImageTransitionInternal.h" #import "SDWebImageTransitionInternal.h"
#import "SDImageCache.h"
const int64_t SDWebImageProgressUnitCountUnknown = 1LL; const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
...@@ -46,13 +47,13 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; ...@@ -46,13 +47,13 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
objc_setAssociatedObject(self, @selector(sd_imageProgress), sd_imageProgress, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(self, @selector(sd_imageProgress), sd_imageProgress, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} }
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url - (nullable id<SDWebImageOperation>)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock { completed:(nullable SDInternalCompletionBlock)completedBlock {
if (context) { if (context) {
// copy to avoid mutable object // copy to avoid mutable object
context = [context copy]; context = [context copy];
...@@ -71,12 +72,35 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; ...@@ -71,12 +72,35 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
[self sd_cancelImageLoadOperationWithKey:validOperationKey]; [self sd_cancelImageLoadOperationWithKey:validOperationKey];
self.sd_imageURL = url; self.sd_imageURL = url;
SDWebImageManager *manager = context[SDWebImageContextCustomManager];
if (!manager) {
manager = [SDWebImageManager sharedManager];
} else {
// remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager)
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextCustomManager] = nil;
context = [mutableContext copy];
}
BOOL shouldUseWeakCache = NO;
if ([manager.imageCache isKindOfClass:SDImageCache.class]) {
shouldUseWeakCache = ((SDImageCache *)manager.imageCache).config.shouldUseWeakMemoryCache;
}
if (!(options & SDWebImageDelayPlaceholder)) { if (!(options & SDWebImageDelayPlaceholder)) {
if (shouldUseWeakCache) {
NSString *key = [manager cacheKeyForURL:url context:context];
// call memory cache to trigger weak cache sync logic, ignore the return value and go on normal query
// this unfortunately will cause twice memory cache query, but it's fast enough
// in the future the weak cache feature may be re-design or removed
[((SDImageCache *)manager.imageCache) imageFromMemoryCacheForKey:key];
}
dispatch_main_async_safe(^{ dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url]; [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
}); });
} }
id <SDWebImageOperation> operation = nil;
if (url) { if (url) {
// reset the progress // reset the progress
NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress)); NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
...@@ -90,15 +114,6 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; ...@@ -90,15 +114,6 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
[self sd_startImageIndicator]; [self sd_startImageIndicator];
id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator; id<SDWebImageIndicator> imageIndicator = self.sd_imageIndicator;
#endif #endif
SDWebImageManager *manager = context[SDWebImageContextCustomManager];
if (!manager) {
manager = [SDWebImageManager sharedManager];
} else {
// remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager)
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextCustomManager] = nil;
context = [mutableContext copy];
}
SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) { SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
if (imageProgress) { if (imageProgress) {
...@@ -122,7 +137,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; ...@@ -122,7 +137,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
} }
}; };
@weakify(self); @weakify(self);
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
@strongify(self); @strongify(self);
if (!self) { return; } if (!self) { return; }
// if the progress not been updated, mark it to complete state // if the progress not been updated, mark it to complete state
...@@ -141,7 +156,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; ...@@ -141,7 +156,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage); BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) || BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder))); (!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{ SDWebImageNoParamsBlock callCompletedBlockClosure = ^{
if (!self) { return; } if (!self) { return; }
if (!shouldNotSetImage) { if (!shouldNotSetImage) {
[self sd_setNeedsLayout]; [self sd_setNeedsLayout];
...@@ -155,7 +170,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; ...@@ -155,7 +170,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
// OR // OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set // case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
if (shouldNotSetImage) { if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure); dispatch_main_async_safe(callCompletedBlockClosure);
return; return;
} }
...@@ -206,7 +221,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; ...@@ -206,7 +221,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
#else #else
[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL]; [self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif #endif
callCompletedBlockClojure(); callCompletedBlockClosure();
}); });
}]; }];
[self sd_setImageLoadOperation:operation forKey:validOperationKey]; [self sd_setImageLoadOperation:operation forKey:validOperationKey];
...@@ -221,6 +236,8 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; ...@@ -221,6 +236,8 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
} }
}); });
} }
return operation;
} }
- (void)sd_cancelCurrentImageLoad { - (void)sd_cancelCurrentImageLoad {
...@@ -272,18 +289,20 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; ...@@ -272,18 +289,20 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
#endif #endif
if (transition) { if (transition) {
NSString *originalOperationKey = view.sd_latestOperationKey;
#if SD_UIKIT #if SD_UIKIT
[UIView transitionWithView:view duration:0 options:0 animations:^{ [UIView transitionWithView:view duration:0 options:0 animations:^{
if (!view.sd_latestOperationKey) { if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) {
return; return;
} }
// 0 duration to let UIKit render placeholder and prepares block // 0 duration to let UIKit render placeholder and prepares block
if (transition.prepares) { if (transition.prepares) {
transition.prepares(view, image, imageData, cacheType, imageURL); transition.prepares(view, image, imageData, cacheType, imageURL);
} }
} completion:^(BOOL finished) { } completion:^(BOOL tempFinished) {
[UIView transitionWithView:view duration:transition.duration options:transition.animationOptions animations:^{ [UIView transitionWithView:view duration:transition.duration options:transition.animationOptions animations:^{
if (!view.sd_latestOperationKey) { if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) {
return; return;
} }
if (finalSetImageBlock && !transition.avoidAutoSetImage) { if (finalSetImageBlock && !transition.avoidAutoSetImage) {
...@@ -293,7 +312,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; ...@@ -293,7 +312,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
transition.animations(view, image); transition.animations(view, image);
} }
} completion:^(BOOL finished) { } completion:^(BOOL finished) {
if (!view.sd_latestOperationKey) { if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) {
return; return;
} }
if (transition.completion) { if (transition.completion) {
...@@ -303,7 +322,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; ...@@ -303,7 +322,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
}]; }];
#elif SD_MAC #elif SD_MAC
[NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull prepareContext) { [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull prepareContext) {
if (!view.sd_latestOperationKey) { if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) {
return; return;
} }
// 0 duration to let AppKit render placeholder and prepares block // 0 duration to let AppKit render placeholder and prepares block
...@@ -313,7 +332,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; ...@@ -313,7 +332,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
} }
} completionHandler:^{ } completionHandler:^{
[NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) { [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
if (!view.sd_latestOperationKey) { if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) {
return; return;
} }
context.duration = transition.duration; context.duration = transition.duration;
...@@ -337,7 +356,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL; ...@@ -337,7 +356,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
transition.animations(view, image); transition.animations(view, image);
} }
} completionHandler:^{ } completionHandler:^{
if (!view.sd_latestOperationKey) { if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) {
return; return;
} }
if (transition.completion) { if (transition.completion) {
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment