mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-24 07:05:35 +00:00
ASMultiplexImageNode.
Initial open-source release of ASMultiplexImageNode. Documentation and example code forthcoming. Note: ASMultiplexImageNode requires Xcode 6 to compile. Tests are now compiled against the iOS 8 SDK and run on iOS 7.1 and iOS 8.
This commit is contained in:
11
.travis.yml
11
.travis.yml
@@ -3,9 +3,8 @@ before_install:
|
||||
- brew update
|
||||
- brew reinstall xctool
|
||||
- gem update cocoapods
|
||||
xcode_workspace: AsyncDisplayKit.xcworkspace
|
||||
xcode_scheme: AsyncDisplayKit
|
||||
xcode_sdk:
|
||||
- iphonesimulator7.0
|
||||
- iphonesimulator7.1
|
||||
- iphonesimulator8.0
|
||||
- xcrun simctl list
|
||||
env:
|
||||
- TEST_OS=7.1
|
||||
- TEST_OS=8.0
|
||||
script: xctool -workspace AsyncDisplayKit.xcworkspace -scheme AsyncDisplayKit -sdk iphonesimulator8.0 -destination "platform=iOS Simulator,OS=${TEST_OS},name=iPhone 5" build test
|
||||
|
||||
@@ -20,6 +20,9 @@ Pod::Spec.new do |spec|
|
||||
'Base/*.{h,m}'
|
||||
]
|
||||
|
||||
spec.frameworks = 'AssetsLibrary'
|
||||
spec.weak_frameworks = 'Photos'
|
||||
|
||||
# ASDealloc2MainObject must be compiled with MRR
|
||||
spec.requires_arc = true
|
||||
spec.exclude_files = ['AsyncDisplayKit/Details/ASDealloc2MainObject.m']
|
||||
|
||||
@@ -7,6 +7,16 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0515EA211A15769900BA8B9A /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
0515EA221A1576A100BA8B9A /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; };
|
||||
0516FA3C1A15563400B4EBED /* ASAvailability.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3A1A15563400B4EBED /* ASAvailability.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
0516FA3D1A15563400B4EBED /* ASLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3B1A15563400B4EBED /* ASLog.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
0516FA401A1563D200B4EBED /* ASMultiplexImageNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */; };
|
||||
051943131A1575630030A7D0 /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943121A1575630030A7D0 /* AssetsLibrary.framework */; };
|
||||
051943151A1575670030A7D0 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
|
||||
052EE06B1A15A0D8002C6279 /* TestResources in Resources */ = {isa = PBXBuildFile; fileRef = 052EE06A1A15A0D8002C6279 /* TestResources */; };
|
||||
055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
055F1A3519ABD3E3004DAFF1 /* ASTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3319ABD3E3004DAFF1 /* ASTableView.m */; };
|
||||
055F1A3819ABD413004DAFF1 /* ASRangeController.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3619ABD413004DAFF1 /* ASRangeController.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@@ -111,7 +121,6 @@
|
||||
058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
058D0A7C195D05F900B7D73C /* ASImageNode+CGExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
058D0A7D195D05F900B7D73C /* ASImageNode+CGExtras.m in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
058D0A7E195D05F900B7D73C /* ASImageProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A0F195D050800B7D73C /* ASImageProtocols.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
058D0A7F195D05F900B7D73C /* ASSentinel.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A10195D050800B7D73C /* ASSentinel.h */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
058D0A80195D05F900B7D73C /* ASSentinel.m in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A11195D050800B7D73C /* ASSentinel.m */; settings = {ATTRIBUTES = (Private, ); }; };
|
||||
058D0A81195D05F900B7D73C /* ASThread.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A12195D050800B7D73C /* ASThread.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
@@ -120,6 +129,7 @@
|
||||
058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A45195D058D00B7D73C /* ASDisplayNodeExtraIvars.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
05A6D05A19D0EB64002DD95E /* ASDealloc2MainObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
|
||||
05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */ = {isa = PBXBuildFile; fileRef = 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
|
||||
6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; };
|
||||
@@ -148,6 +158,14 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0516FA3A1A15563400B4EBED /* ASAvailability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAvailability.h; sourceTree = "<group>"; };
|
||||
0516FA3B1A15563400B4EBED /* ASLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLog.h; sourceTree = "<group>"; };
|
||||
0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMultiplexImageNode.h; sourceTree = "<group>"; };
|
||||
0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMultiplexImageNode.mm; sourceTree = "<group>"; };
|
||||
051943121A1575630030A7D0 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; };
|
||||
051943141A1575670030A7D0 /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; };
|
||||
052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASMultiplexImageNodeTests.m; sourceTree = "<group>"; };
|
||||
052EE06A1A15A0D8002C6279 /* TestResources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = TestResources; sourceTree = "<group>"; };
|
||||
053011A719B9882B00A9F2D0 /* ASRangeControllerInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASRangeControllerInternal.h; sourceTree = "<group>"; };
|
||||
055F1A3219ABD3E3004DAFF1 /* ASTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableView.h; sourceTree = "<group>"; };
|
||||
055F1A3319ABD3E3004DAFF1 /* ASTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableView.m; sourceTree = "<group>"; };
|
||||
@@ -219,7 +237,6 @@
|
||||
058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeInternal.h; sourceTree = "<group>"; };
|
||||
058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASImageNode+CGExtras.h"; sourceTree = "<group>"; };
|
||||
058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "ASImageNode+CGExtras.m"; sourceTree = "<group>"; };
|
||||
058D0A0F195D050800B7D73C /* ASImageProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageProtocols.h; sourceTree = "<group>"; };
|
||||
058D0A10195D050800B7D73C /* ASSentinel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASSentinel.h; sourceTree = "<group>"; };
|
||||
058D0A11195D050800B7D73C /* ASSentinel.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASSentinel.m; sourceTree = "<group>"; };
|
||||
058D0A12195D050800B7D73C /* ASThread.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASThread.h; sourceTree = "<group>"; };
|
||||
@@ -239,6 +256,7 @@
|
||||
058D0A45195D058D00B7D73C /* ASDisplayNodeExtraIvars.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeExtraIvars.h; sourceTree = "<group>"; };
|
||||
05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASDealloc2MainObject.h; path = ../Details/ASDealloc2MainObject.h; sourceTree = "<group>"; };
|
||||
05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASDealloc2MainObject.m; path = ../Details/ASDealloc2MainObject.m; sourceTree = "<group>"; };
|
||||
05F20AA31A15733C00DCA68A /* ASImageProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageProtocols.h; sourceTree = "<group>"; };
|
||||
3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableViewTests.m; sourceTree = "<group>"; };
|
||||
6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = "<group>"; };
|
||||
D3779BCFF841AD3EB56537ED /* Pods-AsyncDisplayKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-AsyncDisplayKitTests/Pods-AsyncDisplayKitTests.release.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -251,6 +269,8 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
051943151A1575670030A7D0 /* Photos.framework in Frameworks */,
|
||||
051943131A1575630030A7D0 /* AssetsLibrary.framework in Frameworks */,
|
||||
058D09B0195D04C000B7D73C /* Foundation.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -259,6 +279,8 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0515EA221A1576A100BA8B9A /* AssetsLibrary.framework in Frameworks */,
|
||||
0515EA211A15769900BA8B9A /* Photos.framework in Frameworks */,
|
||||
058D09BE195D04C000B7D73C /* XCTest.framework in Frameworks */,
|
||||
058D09C1195D04C000B7D73C /* UIKit.framework in Frameworks */,
|
||||
058D09C4195D04C000B7D73C /* libAsyncDisplayKit.a in Frameworks */,
|
||||
@@ -293,6 +315,8 @@
|
||||
058D09AE195D04C000B7D73C /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
051943141A1575670030A7D0 /* Photos.framework */,
|
||||
051943121A1575630030A7D0 /* AssetsLibrary.framework */,
|
||||
058D09AF195D04C000B7D73C /* Foundation.framework */,
|
||||
058D09BD195D04C000B7D73C /* XCTest.framework */,
|
||||
058D09C0195D04C000B7D73C /* UIKit.framework */,
|
||||
@@ -317,6 +341,8 @@
|
||||
058D09E0195D050800B7D73C /* ASTextNode.mm */,
|
||||
058D09DD195D050800B7D73C /* ASImageNode.h */,
|
||||
058D09DE195D050800B7D73C /* ASImageNode.mm */,
|
||||
0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */,
|
||||
0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */,
|
||||
055F1A3219ABD3E3004DAFF1 /* ASTableView.h */,
|
||||
0574D5E119C110610097DC25 /* ASTableViewProtocols.h */,
|
||||
055F1A3319ABD3E3004DAFF1 /* ASTableView.m */,
|
||||
@@ -347,12 +373,14 @@
|
||||
058D0A30195D057000B7D73C /* ASDisplayNodeTestsHelper.h */,
|
||||
058D0A31195D057000B7D73C /* ASDisplayNodeTestsHelper.m */,
|
||||
058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */,
|
||||
052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */,
|
||||
3C9C128419E616EF00E942A0 /* ASTableViewTests.m */,
|
||||
058D0A33195D057000B7D73C /* ASTextNodeCoreTextAdditionsTests.m */,
|
||||
058D0A34195D057000B7D73C /* ASTextNodeRendererTests.m */,
|
||||
058D0A35195D057000B7D73C /* ASTextNodeShadowerTests.m */,
|
||||
058D0A36195D057000B7D73C /* ASTextNodeTests.m */,
|
||||
058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */,
|
||||
052EE06A1A15A0D8002C6279 /* TestResources */,
|
||||
058D09C6195D04C000B7D73C /* Supporting Files */,
|
||||
);
|
||||
path = AsyncDisplayKitTests;
|
||||
@@ -370,6 +398,7 @@
|
||||
058D09E1195D050800B7D73C /* Details */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
05F20AA31A15733C00DCA68A /* ASImageProtocols.h */,
|
||||
058D09E2195D050800B7D73C /* _ASDisplayLayer.h */,
|
||||
058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */,
|
||||
058D09E4195D050800B7D73C /* _ASDisplayView.h */,
|
||||
@@ -433,7 +462,6 @@
|
||||
058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */,
|
||||
058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */,
|
||||
058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */,
|
||||
058D0A0F195D050800B7D73C /* ASImageProtocols.h */,
|
||||
053011A719B9882B00A9F2D0 /* ASRangeControllerInternal.h */,
|
||||
058D0A10195D050800B7D73C /* ASSentinel.h */,
|
||||
058D0A11195D050800B7D73C /* ASSentinel.m */,
|
||||
@@ -445,8 +473,10 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
058D0A43195D058D00B7D73C /* ASAssert.h */,
|
||||
0516FA3A1A15563400B4EBED /* ASAvailability.h */,
|
||||
058D0A44195D058D00B7D73C /* ASBaseDefines.h */,
|
||||
058D0A45195D058D00B7D73C /* ASDisplayNodeExtraIvars.h */,
|
||||
0516FA3B1A15563400B4EBED /* ASLog.h */,
|
||||
);
|
||||
path = Base;
|
||||
sourceTree = SOURCE_ROOT;
|
||||
@@ -468,6 +498,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
05A6D05A19D0EB64002DD95E /* ASDealloc2MainObject.h in Headers */,
|
||||
0516FA401A1563D200B4EBED /* ASMultiplexImageNode.h in Headers */,
|
||||
058D0A47195D05CB00B7D73C /* ASControlNode.h in Headers */,
|
||||
058D0A48195D05CB00B7D73C /* ASControlNode.m in Headers */,
|
||||
058D0A49195D05CB00B7D73C /* ASControlNode+Subclasses.h in Headers */,
|
||||
@@ -516,8 +547,11 @@
|
||||
058D0A6F195D05EC00B7D73C /* UIView+ASConvenience.h in Headers */,
|
||||
058D0A70195D05EC00B7D73C /* UIView+ASConvenience.m in Headers */,
|
||||
058D0A82195D060300B7D73C /* ASAssert.h in Headers */,
|
||||
0516FA3C1A15563400B4EBED /* ASAvailability.h in Headers */,
|
||||
0516FA3D1A15563400B4EBED /* ASLog.h in Headers */,
|
||||
058D0A83195D060300B7D73C /* ASBaseDefines.h in Headers */,
|
||||
058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */,
|
||||
05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */,
|
||||
058D0A71195D05F800B7D73C /* _AS-objc-internal.h in Headers */,
|
||||
058D0A72195D05F800B7D73C /* _ASCoreAnimationExtras.h in Headers */,
|
||||
058D0A73195D05F800B7D73C /* _ASCoreAnimationExtras.mm in Headers */,
|
||||
@@ -531,7 +565,6 @@
|
||||
058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */,
|
||||
058D0A7C195D05F900B7D73C /* ASImageNode+CGExtras.h in Headers */,
|
||||
058D0A7D195D05F900B7D73C /* ASImageNode+CGExtras.m in Headers */,
|
||||
058D0A7E195D05F900B7D73C /* ASImageProtocols.h in Headers */,
|
||||
058D0A7F195D05F900B7D73C /* ASSentinel.h in Headers */,
|
||||
058D0A80195D05F900B7D73C /* ASSentinel.m in Headers */,
|
||||
058D0A81195D05F900B7D73C /* ASThread.h in Headers */,
|
||||
@@ -611,6 +644,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
052EE06B1A15A0D8002C6279 /* TestResources in Resources */,
|
||||
058D09CA195D04C000B7D73C /* InfoPlist.strings in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -681,6 +715,7 @@
|
||||
05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */,
|
||||
058D0A17195D050800B7D73C /* ASTextNode.mm in Sources */,
|
||||
058D0A27195D050800B7D73C /* _ASPendingState.m in Sources */,
|
||||
0516FA411A1563D200B4EBED /* ASMultiplexImageNode.mm in Sources */,
|
||||
058D0A16195D050800B7D73C /* ASImageNode.mm in Sources */,
|
||||
058D0A29195D050800B7D73C /* ASDisplayNode+DebugTiming.mm in Sources */,
|
||||
058D0A22195D050800B7D73C /* _ASAsyncTransaction.m in Sources */,
|
||||
@@ -698,6 +733,7 @@
|
||||
058D0A3F195D057000B7D73C /* ASTextNodeShadowerTests.m in Sources */,
|
||||
058D0A3B195D057000B7D73C /* ASDisplayNodeTestsHelper.m in Sources */,
|
||||
058D0A3A195D057000B7D73C /* ASDisplayNodeTests.m in Sources */,
|
||||
052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.m in Sources */,
|
||||
058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.m in Sources */,
|
||||
058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */,
|
||||
058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */,
|
||||
|
||||
197
AsyncDisplayKit/ASMultiplexImageNode.h
Normal file
197
AsyncDisplayKit/ASMultiplexImageNode.h
Normal file
@@ -0,0 +1,197 @@
|
||||
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <AsyncDisplayKit/ASImageNode.h>
|
||||
#import <AsyncDisplayKit/ASImageProtocols.h>
|
||||
|
||||
@protocol ASMultiplexImageNodeDelegate;
|
||||
@protocol ASMultiplexImageNodeDataSource;
|
||||
|
||||
extern NSString *const ASMultiplexImageNodeErrorDomain;
|
||||
|
||||
/**
|
||||
* ASMultiplexImageNode error codes.
|
||||
*/
|
||||
typedef NS_ENUM(NSUInteger, ASMultiplexImageNodeErrorCode) {
|
||||
/**
|
||||
* Indicates that the data source didn't provide a source for an image identifier.
|
||||
*/
|
||||
ASMultiplexImageNodeErrorCodeNoSourceForImage = 0,
|
||||
|
||||
/**
|
||||
* Indicates that the best image identifier changed before a download for a worse identifier began.
|
||||
*/
|
||||
ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged,
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @abstract ASMultiplexImageNode is an image node that can load and display multiple versions of an image. For
|
||||
* example, it can display a low-resolution version of an image while the high-resolution version is loading.
|
||||
*
|
||||
* @discussion ASMultiplexImageNode begins loading images when its <imageIdentifiers> property is set. For each image
|
||||
* identifier, the data source can either return a UIImage directly, or a URL the image node should load.
|
||||
*/
|
||||
@interface ASMultiplexImageNode : ASImageNode
|
||||
|
||||
/**
|
||||
* @abstract The designated initializer.
|
||||
* @param cache The object that implements a cache of images for the image node.
|
||||
* @param downloader The object that implements image downloading for the image node.
|
||||
* @discussion If `cache` is nil, the receiver will not attempt to retrieve images from a cache before downloading them.
|
||||
* @returns An initialized ASMultiplexImageNode.
|
||||
*/
|
||||
- (instancetype)initWithCache:(id<ASImageCacheProtocol>)cache downloader:(id<ASImageDownloaderProtocol>)downloader;
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
* @abstract The delegate, which must conform to the <ASMultiplexImageNodeDelegate> protocol.
|
||||
*/
|
||||
@property (nonatomic, readwrite, weak) id <ASMultiplexImageNodeDelegate> delegate;
|
||||
|
||||
/**
|
||||
* @abstract The data source, which must conform to the <ASMultiplexImageNodeDataSource> protocol.
|
||||
* @discussion This value is required for ASMultiplexImageNode to load images.
|
||||
*/
|
||||
@property (nonatomic, readwrite, weak) id <ASMultiplexImageNodeDataSource> dataSource;
|
||||
|
||||
/**
|
||||
* @abstract Whether the receiver should download more than just its highest-quality image. Defaults to NO.
|
||||
*
|
||||
* @discussion ASMultiplexImageNode immediately loads and displays the first image specified in <imageIdentifiers> (its
|
||||
* highest-quality image). If that image is not immediately available or cached, the node can download and display
|
||||
* lesser-quality images. Set `downloadsIntermediateImages` to YES to enable this behaviour.
|
||||
*/
|
||||
@property (nonatomic, readwrite, assign) BOOL downloadsIntermediateImages;
|
||||
|
||||
/**
|
||||
* @abstract An array of identifiers representing various versions of an image for ASMultiplexImageNode to display.
|
||||
*
|
||||
* @discussion An identifier can be any object that conforms to NSObject and NSCopying. The array should be in
|
||||
* decreasing order of image quality -- that is, the first identifier in the array represents the best version.
|
||||
*
|
||||
* @see <downloadsIntermediateImages> for more information on the image loading process.
|
||||
*/
|
||||
@property (nonatomic, readwrite, copy) NSArray *imageIdentifiers;
|
||||
|
||||
/**
|
||||
* @abstract Notify the receiver that its data source has new UIImages or NSURLs available for <imageIdentifiers>.
|
||||
*
|
||||
* @discussion If a higher-quality image than is currently displayed is now available, it will be loaded.
|
||||
*/
|
||||
- (void)reloadImageIdentifierSources;
|
||||
|
||||
/**
|
||||
* @abstract The identifier for the last image that the receiver loaded, or nil.
|
||||
*
|
||||
* @discussion This value may differ from <displayedImageIdentifier> if the image hasn't yet been displayed.
|
||||
*/
|
||||
@property (nonatomic, readonly) id loadedImageIdentifier;
|
||||
|
||||
/**
|
||||
* @abstract The identifier for the image that the receiver is currently displaying, or nil.
|
||||
*/
|
||||
@property (nonatomic, readonly) id displayedImageIdentifier;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark -
|
||||
@protocol ASMultiplexImageNodeDelegate <NSObject>
|
||||
|
||||
@optional
|
||||
/**
|
||||
* @abstract Notification that the image node began downloading an image.
|
||||
* @param imageNode The sender.
|
||||
* @param imageIdentifier The identifier for the image that is downloading.
|
||||
*/
|
||||
- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode didStartDownloadOfImageWithIdentifier:(id)imageIdentifier;
|
||||
|
||||
/**
|
||||
* @abstract Notification that the image node's download progressed.
|
||||
* @param imageNode The sender.
|
||||
* @param downloadProgress The progress of the download. Value is between 0.0 and 1.0.
|
||||
* @param imageIdentifier The identifier for the image that is downloading.
|
||||
*/
|
||||
- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode
|
||||
didUpdateDownloadProgress:(CGFloat)downloadProgress
|
||||
forImageWithIdentifier:(id)imageIdentifier;
|
||||
|
||||
/**
|
||||
* @abstract Notification that the image node's download has finished.
|
||||
* @param imageNode The sender.
|
||||
* @param imageIdentifier The identifier for the image that finished downloading.
|
||||
* @param error The error that occurred while downloading, if one occurred; nil otherwise.
|
||||
*/
|
||||
- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode
|
||||
didFinishDownloadingImageWithIdentifier:(id)imageIdentifier
|
||||
error:(NSError *)error;
|
||||
|
||||
/**
|
||||
* @abstract Notification that the image node's image was updated.
|
||||
* @param imageNode The sender.
|
||||
* @param image The new image, ready for display.
|
||||
* @param imageIdentifier The identifier for `image`.
|
||||
* @param previousImage The old, previously-loaded image.
|
||||
* @param previousImageIdentifier The identifier for `previousImage`.
|
||||
* @note This method does not indicate that `image` has been displayed.
|
||||
* @see <[ASMultiplexImageNodeDelegate multiplexImageNode:didDisplayUpdatedImage:withIdentifier:]>.
|
||||
*/
|
||||
- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode
|
||||
didUpdateImage:(UIImage *)image
|
||||
withIdentifier:(id)imageIdentifier
|
||||
fromImage:(UIImage *)previousImage
|
||||
withIdentifier:(id)previousImageIdentifier;
|
||||
|
||||
/**
|
||||
* @abstract Notification that the image node displayed a new image.
|
||||
* @param imageNode The sender.
|
||||
* @param image The new image, now being displayed.
|
||||
* @param imageIdentifier The identifier for `image`.
|
||||
* @discussion This method is only called when `image` changes, and not on subsequent redisplays of the same image.
|
||||
*/
|
||||
- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode
|
||||
didDisplayUpdatedImage:(UIImage *)image
|
||||
withIdentifier:(id)imageIdentifier;
|
||||
|
||||
/**
|
||||
* @abstract Notification that the image node finished displaying an image.
|
||||
* @param imageNode The sender.
|
||||
* @discussion This method is called every time an image is displayed, whether or not it has changed.
|
||||
*/
|
||||
- (void)multiplexImageNodeDidFinishDisplay:(ASMultiplexImageNode *)imageNode;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#pragma mark -
|
||||
@protocol ASMultiplexImageNodeDataSource <NSObject>
|
||||
|
||||
@optional
|
||||
/**
|
||||
* @abstract An image for the specified identifier.
|
||||
* @param imageNode The sender.
|
||||
* @param imageIdentifier The identifier for the image that should be returned.
|
||||
* @discussion If the image is already available to the data source, this method should be used in lieu of providing the
|
||||
* URL to the image via -multiplexImageNode:URLForImageIdentifier:.
|
||||
* @returns A UIImage corresponding to `imageIdentifier`, or nil if none is available.
|
||||
*/
|
||||
- (UIImage *)multiplexImageNode:(ASMultiplexImageNode *)imageNode imageForImageIdentifier:(id)imageIdentifier;
|
||||
|
||||
/**
|
||||
* @abstract An image URL for the specified identifier.
|
||||
* @param imageNode The sender.
|
||||
* @param imageIdentifier The identifier for the image that will be downloaded.
|
||||
* @discussion Supported URLs include assets-library, Photo framework URLs (ph://), HTTP, HTTPS, and FTP URLs. If the
|
||||
* image is already available to the data source, it should be provided via <[ASMultiplexImageNodeDataSource
|
||||
* multiplexImageNode:imageForImageIdentifier:]> instead.
|
||||
* @returns An NSURL for the image identified by `imageIdentifier`, or nil if none is available.
|
||||
*/
|
||||
- (NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(id)imageIdentifier;
|
||||
|
||||
@end
|
||||
652
AsyncDisplayKit/ASMultiplexImageNode.mm
Normal file
652
AsyncDisplayKit/ASMultiplexImageNode.mm
Normal file
@@ -0,0 +1,652 @@
|
||||
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "ASMultiplexImageNode.h"
|
||||
|
||||
#import <AssetsLibrary/AssetsLibrary.h>
|
||||
|
||||
#import <Photos/Photos.h>
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
#import "ASAvailability.h"
|
||||
#import "ASBaseDefines.h"
|
||||
#import "ASDisplayNode+Subclasses.h"
|
||||
#import "ASLog.h"
|
||||
|
||||
#if !AS_IOS8_SDK_OR_LATER
|
||||
#error ASMultiplexImageNode can be used on iOS 7, but must be linked against the iOS 8 SDK.
|
||||
#endif
|
||||
|
||||
NSString *const ASMultiplexImageNodeErrorDomain = @"ASMultiplexImageNodeErrorDomain";
|
||||
|
||||
static NSString *const kAssetsLibraryURLScheme = @"assets-library";
|
||||
static NSString *const kPHAssetURLScheme = @"ph";
|
||||
static NSString *const kPHAssetURLPrefix = @"ph://";
|
||||
|
||||
/**
|
||||
@abstract Signature for the block to be performed after an image has loaded.
|
||||
@param image The image that was loaded, or nil if no image was loaded.
|
||||
@param imageIdentifier The identifier of the image that was loaded, or nil if no image was loaded.
|
||||
@param error An error describing why an image couldn't be loaded, if it failed to load; nil otherwise.
|
||||
*/
|
||||
typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdentifier, NSError *error);
|
||||
|
||||
@interface ASMultiplexImageNode ()
|
||||
{
|
||||
@private
|
||||
// Core.
|
||||
id<ASImageCacheProtocol> _cache;
|
||||
id<ASImageDownloaderProtocol> _downloader;
|
||||
|
||||
__weak id<ASMultiplexImageNodeDelegate> _delegate;
|
||||
struct {
|
||||
unsigned int downloadStart:1;
|
||||
unsigned int downloadProgress:1;
|
||||
unsigned int downloadFinish:1;
|
||||
unsigned int updatedImageDisplayFinish:1;
|
||||
unsigned int updatedImage:1;
|
||||
unsigned int displayFinish:1;
|
||||
} _delegateFlags;
|
||||
|
||||
__weak id<ASMultiplexImageNodeDataSource> _dataSource;
|
||||
struct {
|
||||
unsigned int image:1;
|
||||
unsigned int URL:1;
|
||||
} _dataSourceFlags;
|
||||
|
||||
// Image flags.
|
||||
BOOL _downloadsIntermediateImages; // Defaults to NO.
|
||||
OSSpinLock _imageIdentifiersLock;
|
||||
NSArray *_imageIdentifiers;
|
||||
id _loadedImageIdentifier;
|
||||
id _loadingImageIdentifier;
|
||||
id _displayedImageIdentifier;
|
||||
|
||||
// Networking.
|
||||
id _downloadIdentifier;
|
||||
BOOL _canceledImageDownload;
|
||||
}
|
||||
|
||||
//! @abstract Read-write redeclaration of property declared in ASMultiplexImageNode.h.
|
||||
@property (nonatomic, readwrite, copy) id loadedImageIdentifier;
|
||||
|
||||
//! @abstract The image identifier that's being loaded by _loadNextImageWithCompletion:.
|
||||
@property (nonatomic, readwrite, copy) id loadingImageIdentifier;
|
||||
|
||||
/**
|
||||
@abstract Indicates whether a change from one array of image identifiers to another should prompt the receiver to update its loaded image.
|
||||
@param loadedIdentifier The image identifier that's currently loaded, or nil if no identifier is loaded yet.
|
||||
@param loadingImageIdentifier The image identifier that's currently loading, or nil if no identifier is loading.
|
||||
@param oldIdentifiers An array of image identifiers that were previously managed, or nil if no identifiers were managed before.
|
||||
@param newIdentifiers The array of new image identifiers.
|
||||
@result YES if the receiver should update its loaded image as a consequence of the newly assigned identifiers; NO otherwise.
|
||||
*/
|
||||
static inline BOOL _shouldUpdateAfterChangedImageIdentifiers(id loadedIdentifier, id loadingImageIdentifier, NSArray *oldIdentifiers, NSArray *newIdentifiers);
|
||||
|
||||
/**
|
||||
@abstract Returns the next image identifier that should be downloaded.
|
||||
@discussion This method obeys and reflects the value of `downloadsIntermediateImages`.
|
||||
@result The next image identifier, from `_imageIdentifiers`, that should be downloaded, or nil if no image should be downloaded next.
|
||||
*/
|
||||
- (id)_nextImageIdentifierToDownload;
|
||||
|
||||
/**
|
||||
@abstract Returns the best image that is immediately available from our datasource without downloading or hitting the cache.
|
||||
@param imageIdentifierOut Upon return, the image identifier for the returned image; nil otherwise.
|
||||
@discussion This method exclusively uses the data source's -multiplexImageNode:imageForIdentifier: method to return images. It does not fetch from the cache or kick off downloading.
|
||||
@result The best UIImage available immediately; nil if no image is immediately available.
|
||||
*/
|
||||
- (UIImage *)_bestImmediatelyAvailableImageFromDataSource:(id *)imageIdentifierOut;
|
||||
|
||||
/**
|
||||
@abstract Loads and displays the next image in the receiver's loading sequence.
|
||||
@discussion This method obeys `downloadsIntermediateImages`. This method has no effect if nothing further should be loaded, as indicated by `_nextImageIdentifierToDownload`. This method will load the next image from the data-source, if possible; otherwise, the session's image cache will be queried for the desired image, and as a last resort, the image will be downloaded.
|
||||
*/
|
||||
- (void)_loadNextImage;
|
||||
|
||||
/**
|
||||
@abstract Fetches the image corresponding to the given imageIdentifier from the given URL from the session's image cache.
|
||||
@param imageIdentifier The identifier for the image to be fetched. May not be nil.
|
||||
@param imageURL The URL of the image to fetch. May not be nil.
|
||||
@param completionBlock The block to be performed when the image has been fetched from the cache, if possible. May not be nil.
|
||||
@param image The image fetched from the cache, if any.
|
||||
@discussion This method queries both the session's in-memory and on-disk caches (with preference for the in-memory cache).
|
||||
*/
|
||||
- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock;
|
||||
|
||||
/**
|
||||
@abstract Loads the image corresponding to the given assetURL from the device's Assets Library.
|
||||
@param imageIdentifier The identifier for the image to be loaded. May not be nil.
|
||||
@param assetURL The assets-library URL (e.g., "assets-library://identifier") of the image to load, from ALAsset. May not be nil.
|
||||
@param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil.
|
||||
@param image The image that was loaded. May be nil if no image could be downloaded.
|
||||
@param error An error describing why the load failed, if it failed; nil otherwise.
|
||||
*/
|
||||
- (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock;
|
||||
|
||||
/**
|
||||
@abstract Loads the image corresponding to the given assetURL from the Photos framework.
|
||||
@param imageIdentifier The identifier for the image to be loaded. May not be nil.
|
||||
@param assetURL The photos framework URL (e.g., "ph://identifier") of the image to load, from PHAsset. May not be nil.
|
||||
@param completionBlock The block to be performed when the image has been loaded, if possible. May not be nil.
|
||||
@param image The image that was loaded. May be nil if no image could be downloaded.
|
||||
@param error An error describing why the load failed, if it failed; nil otherwise.
|
||||
*/
|
||||
- (void)_loadPHAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock;
|
||||
|
||||
/**
|
||||
@abstract Downloads the image corresponding to the given imageIdentifier from the given URL.
|
||||
@param imageIdentifier The identifier for the image to be downloaded. May not be nil.
|
||||
@param imageURL The URL of the image to downloaded. May not be nil.
|
||||
@param completionBlock The block to be performed when the image has been downloaded, if possible. May not be nil.
|
||||
@param image The image that was downloaded. May be nil if no image could be downloaded.
|
||||
@param error An error describing why the download failed, if it failed; nil otherwise.
|
||||
*/
|
||||
- (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image, NSError *error))completionBlock;
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASMultiplexImageNode
|
||||
|
||||
#pragma mark - Getting Started / Tearing Down
|
||||
- (instancetype)initWithCache:(id<ASImageCacheProtocol>)cache downloader:(id<ASImageDownloaderProtocol>)downloader
|
||||
{
|
||||
if (!(self = [super init]))
|
||||
return nil;
|
||||
|
||||
_cache = cache;
|
||||
_downloader = downloader;
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init
|
||||
{
|
||||
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
|
||||
}
|
||||
|
||||
#pragma mark - ASDisplayNode Overrides
|
||||
- (void)didExitHierarchy
|
||||
{
|
||||
[super didExitHierarchy]; // This actually clears the contents, so we need to do this first for our displayedImageIdentifier to be meaningful.
|
||||
[self _setDisplayedImageIdentifier:nil withImage:nil];
|
||||
|
||||
if (_downloadIdentifier) {
|
||||
[_downloader cancelImageDownloadForIdentifier:_downloadIdentifier];
|
||||
_downloadIdentifier = nil;
|
||||
_canceledImageDownload = YES;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)willEnterHierarchy
|
||||
{
|
||||
[super willEnterHierarchy];
|
||||
|
||||
if(_canceledImageDownload) {
|
||||
[self _updatedImageIdentifiers];
|
||||
_canceledImageDownload = NO;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)displayDidFinish
|
||||
{
|
||||
[super displayDidFinish];
|
||||
|
||||
// We may now be displaying the loaded identifier, if they're different.
|
||||
UIImage *displayedImage = self.image;
|
||||
if (displayedImage) {
|
||||
if (![_displayedImageIdentifier isEqual:_loadedImageIdentifier])
|
||||
[self _setDisplayedImageIdentifier:_loadedImageIdentifier withImage:displayedImage];
|
||||
|
||||
// Delegateify
|
||||
if (_delegateFlags.displayFinish) {
|
||||
if ([NSThread isMainThread])
|
||||
[_delegate multiplexImageNodeDidFinishDisplay:self];
|
||||
else {
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
if (!strongSelf)
|
||||
return;
|
||||
[strongSelf.delegate multiplexImageNodeDidFinishDisplay:strongSelf];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - Core
|
||||
|
||||
- (void)setDelegate:(id <ASMultiplexImageNodeDelegate>)delegate
|
||||
{
|
||||
if (_delegate == delegate)
|
||||
return;
|
||||
|
||||
_delegate = delegate;
|
||||
_delegateFlags.downloadStart = [_delegate respondsToSelector:@selector(multiplexImageNode:didStartDownloadOfImageWithIdentifier:)];
|
||||
_delegateFlags.downloadProgress = [_delegate respondsToSelector:@selector(multiplexImageNode:didUpdateDownloadProgress:forImageWithIdentifier:)];
|
||||
_delegateFlags.downloadFinish = [_delegate respondsToSelector:@selector(multiplexImageNode:didFinishDownloadingImageWithIdentifier:error:)];
|
||||
_delegateFlags.updatedImageDisplayFinish = [_delegate respondsToSelector:@selector(multiplexImageNode:didDisplayUpdatedImage:withIdentifier:)];
|
||||
_delegateFlags.updatedImage = [_delegate respondsToSelector:@selector(multiplexImageNode:didUpdateImage:withIdentifier:fromImage:withIdentifier:)];
|
||||
_delegateFlags.displayFinish = [_delegate respondsToSelector:@selector(multiplexImageNodeDidFinishDisplay:)];
|
||||
}
|
||||
|
||||
|
||||
- (void)setDataSource:(id <ASMultiplexImageNodeDataSource>)dataSource
|
||||
{
|
||||
if (_dataSource == dataSource)
|
||||
return;
|
||||
|
||||
_dataSource = dataSource;
|
||||
_dataSourceFlags.image = [_dataSource respondsToSelector:@selector(multiplexImageNode:imageForImageIdentifier:)];
|
||||
_dataSourceFlags.URL = [_dataSource respondsToSelector:@selector(multiplexImageNode:URLForImageIdentifier:)];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSArray *)imageIdentifiers
|
||||
{
|
||||
OSSpinLockLock(&_imageIdentifiersLock);
|
||||
NSArray *imageIdentifiers = [_imageIdentifiers copy];
|
||||
OSSpinLockUnlock(&_imageIdentifiersLock);
|
||||
return imageIdentifiers;
|
||||
}
|
||||
|
||||
- (void)setImageIdentifiers:(NSArray *)imageIdentifiers
|
||||
{
|
||||
OSSpinLockLock(&_imageIdentifiersLock);
|
||||
|
||||
if (_imageIdentifiers == imageIdentifiers) {
|
||||
OSSpinLockUnlock(&_imageIdentifiersLock);
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *oldImageIdentifiers = [[NSArray alloc] initWithArray:_imageIdentifiers];
|
||||
|
||||
_imageIdentifiers = [imageIdentifiers copy];
|
||||
|
||||
// Kick off image loading and display, if the identifiers have substantively changed.
|
||||
BOOL shouldUpdateAfterChangedImageIdentifiers = _shouldUpdateAfterChangedImageIdentifiers(self.loadedImageIdentifier, self.loadingImageIdentifier, oldImageIdentifiers, _imageIdentifiers);
|
||||
|
||||
OSSpinLockUnlock(&_imageIdentifiersLock);
|
||||
|
||||
if (shouldUpdateAfterChangedImageIdentifiers) {
|
||||
[self _updatedImageIdentifiers];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reloadImageIdentifierSources
|
||||
{
|
||||
[self _updatedImageIdentifiers];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
|
||||
#pragma mark - Core Internal
|
||||
- (void)_setDisplayedImageIdentifier:(id)displayedImageIdentifier withImage:(UIImage *)image
|
||||
{
|
||||
if (_displayedImageIdentifier == displayedImageIdentifier)
|
||||
return;
|
||||
|
||||
_displayedImageIdentifier = [displayedImageIdentifier copy];
|
||||
|
||||
// Delegateify.
|
||||
// Note that we're using the params here instead of self.image and _displayedImageIdentifier because those can change before the async block below executes.
|
||||
if (_delegateFlags.updatedImageDisplayFinish) {
|
||||
if ([NSThread isMainThread])
|
||||
[_delegate multiplexImageNode:self didDisplayUpdatedImage:image withIdentifier:displayedImageIdentifier];
|
||||
else {
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
if (!strongSelf)
|
||||
return;
|
||||
[strongSelf.delegate multiplexImageNode:strongSelf didDisplayUpdatedImage:image withIdentifier:displayedImageIdentifier];
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_setDownloadIdentifier:(id)downloadIdentifier
|
||||
{
|
||||
if (_downloadIdentifier == downloadIdentifier)
|
||||
return;
|
||||
|
||||
[_downloader cancelImageDownloadForIdentifier:_downloadIdentifier];
|
||||
_downloadIdentifier = downloadIdentifier;
|
||||
}
|
||||
|
||||
#pragma mark - Image Loading Machinery
|
||||
static inline BOOL _shouldUpdateAfterChangedImageIdentifiers(id loadedIdentifier, id loadingImageIdentifier, NSArray *oldIdentifiers, NSArray *newIdentifiers)
|
||||
{
|
||||
// If we're loading an identifier, we need to update if it isn't permitted to load anymore.
|
||||
if (loadingImageIdentifier)
|
||||
return ![newIdentifiers containsObject:loadingImageIdentifier];
|
||||
|
||||
// We're not loading, so we need to update unless we've already loaded the best identifier.
|
||||
return ![[newIdentifiers firstObject] isEqual:loadedIdentifier];
|
||||
}
|
||||
|
||||
- (void)_updatedImageIdentifiers
|
||||
{
|
||||
// Kill any in-flight downloads.
|
||||
[self _setDownloadIdentifier:nil];
|
||||
|
||||
// Grab the best possible image we can load right now.
|
||||
id bestImmediatelyAvailableImageIdentifier = nil;
|
||||
UIImage *bestImmediatelyAvailableImage = [self _bestImmediatelyAvailableImageFromDataSource:&bestImmediatelyAvailableImageIdentifier];
|
||||
ASMultiplexImageNodeLogDebug(@"[%p] Best immediately available image identifier is %@", self, bestImmediatelyAvailableImageIdentifier);
|
||||
|
||||
// Load it. This kicks off cache fetching/downloading, as appropriate.
|
||||
[self _finishedLoadingImage:bestImmediatelyAvailableImage forIdentifier:bestImmediatelyAvailableImageIdentifier error:nil];
|
||||
}
|
||||
|
||||
- (UIImage *)_bestImmediatelyAvailableImageFromDataSource:(id *)imageIdentifierOut
|
||||
{
|
||||
OSSpinLockLock(&_imageIdentifiersLock);
|
||||
|
||||
// If we don't have any identifiers to load or don't implement the image DS method, bail.
|
||||
if ([_imageIdentifiers count] == 0 || !_dataSourceFlags.image) {
|
||||
OSSpinLockUnlock(&_imageIdentifiersLock);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Grab the best available image from the data source.
|
||||
for (id imageIdentifier in _imageIdentifiers) {
|
||||
UIImage *image = [_dataSource multiplexImageNode:self imageForImageIdentifier:imageIdentifier];
|
||||
if (image) {
|
||||
if (imageIdentifierOut) {
|
||||
*imageIdentifierOut = [imageIdentifier copy];
|
||||
}
|
||||
|
||||
OSSpinLockUnlock(&_imageIdentifiersLock);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
OSSpinLockUnlock(&_imageIdentifiersLock);
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
- (id)_nextImageIdentifierToDownload
|
||||
{
|
||||
OSSpinLockLock(&_imageIdentifiersLock);
|
||||
|
||||
// If we've already loaded the best identifier, we've got nothing else to do.
|
||||
id bestImageIdentifier = ([_imageIdentifiers count] > 0) ? _imageIdentifiers[0] : nil;
|
||||
if (!bestImageIdentifier || [_loadedImageIdentifier isEqual:bestImageIdentifier]) {
|
||||
OSSpinLockUnlock(&_imageIdentifiersLock);
|
||||
return nil;
|
||||
}
|
||||
|
||||
id nextImageIdentifierToDownload = nil;
|
||||
|
||||
// If we're not supposed to download intermediate images, load the best identifier we've got.
|
||||
if (!_downloadsIntermediateImages) {
|
||||
nextImageIdentifierToDownload = bestImageIdentifier;
|
||||
}
|
||||
// Otherwise, load progressively.
|
||||
else {
|
||||
NSUInteger loadedIndex = [_imageIdentifiers indexOfObject:_loadedImageIdentifier];
|
||||
|
||||
// If nothing has loaded yet, load the worst identifier.
|
||||
if (loadedIndex == NSNotFound) {
|
||||
nextImageIdentifierToDownload = [_imageIdentifiers lastObject];
|
||||
}
|
||||
// Otherwise, load the next best identifier (if there is one)
|
||||
else if (loadedIndex > 0) {
|
||||
nextImageIdentifierToDownload = _imageIdentifiers[loadedIndex - 1];
|
||||
}
|
||||
}
|
||||
|
||||
OSSpinLockUnlock(&_imageIdentifiersLock);
|
||||
|
||||
return nextImageIdentifierToDownload;
|
||||
}
|
||||
|
||||
- (void)_loadNextImage
|
||||
{
|
||||
// Determine the next identifier to load (if any).
|
||||
id nextImageIdentifier = [self _nextImageIdentifierToDownload];
|
||||
if (!nextImageIdentifier) {
|
||||
[self _finishedLoadingImage:nil forIdentifier:nil error:nil];
|
||||
return;
|
||||
}
|
||||
|
||||
self.loadingImageIdentifier = nextImageIdentifier;
|
||||
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
ASMultiplexImageLoadCompletionBlock finishedLoadingBlock = ^(UIImage *image, id imageIdentifier, NSError *error) {
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
if (!strongSelf)
|
||||
return;
|
||||
|
||||
// Only nil out the loading identifier if the loading identifier hasn't changed.
|
||||
if ([strongSelf.loadingImageIdentifier isEqual:nextImageIdentifier]) {
|
||||
strongSelf.loadingImageIdentifier = nil;
|
||||
}
|
||||
[strongSelf _finishedLoadingImage:image forIdentifier:imageIdentifier error:error];
|
||||
};
|
||||
|
||||
ASMultiplexImageNodeLogDebug(@"[%p] Loading next image, ident: %@", self, nextImageIdentifier);
|
||||
|
||||
// Ask our data-source if it's got this image.
|
||||
if (_dataSourceFlags.image) {
|
||||
UIImage *image = [_dataSource multiplexImageNode:self imageForImageIdentifier:nextImageIdentifier];
|
||||
if (image) {
|
||||
ASMultiplexImageNodeLogDebug(@"[%p] Acquired next image (%@) from data-source", self, nextImageIdentifier);
|
||||
finishedLoadingBlock(image, nextImageIdentifier, nil);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NSURL *nextImageURL = (_dataSourceFlags.URL) ? [_dataSource multiplexImageNode:self URLForImageIdentifier:nextImageIdentifier] : nil;
|
||||
// If we fail to get a URL for the image, we have no source and can't proceed.
|
||||
if (!nextImageURL) {
|
||||
ASMultiplexImageNodeLogError(@"[%p] Could not acquire URL for next image (%@). Bailing.", self, nextImageIdentifier);
|
||||
finishedLoadingBlock(nil, nil, [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodeNoSourceForImage userInfo:nil]);
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's an assets-library URL, we need to fetch it from the assets library.
|
||||
if ([[nextImageURL scheme] isEqualToString:kAssetsLibraryURLScheme]) {
|
||||
// Load the asset.
|
||||
[self _loadALAssetWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *downloadedImage, NSError *error) {
|
||||
ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from asset library", weakSelf, nextImageIdentifier);
|
||||
finishedLoadingBlock(downloadedImage, nextImageIdentifier, error);
|
||||
}];
|
||||
}
|
||||
// Likewise, if it's a iOS 8 Photo asset, we need to fetch it accordingly.
|
||||
else if (AS_AT_LEAST_IOS8 && [[nextImageURL scheme] isEqualToString:kPHAssetURLScheme]) {
|
||||
[self _loadPHAssetWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *image, NSError *error) {
|
||||
ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from Photos Framework", weakSelf, nextImageIdentifier);
|
||||
finishedLoadingBlock(image, nextImageIdentifier, error);
|
||||
}];
|
||||
}
|
||||
else // Otherwise, it's a web URL that we can download.
|
||||
{
|
||||
// First, check the cache.
|
||||
[self _fetchImageWithIdentifierFromCache:nextImageIdentifier URL:nextImageURL completion:^(UIImage *imageFromCache) {
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
if (!strongSelf)
|
||||
return;
|
||||
|
||||
// If we had a cache-hit, we're done.
|
||||
if (imageFromCache) {
|
||||
ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from cache", strongSelf, nextImageIdentifier);
|
||||
finishedLoadingBlock(imageFromCache, nextImageIdentifier, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the next image to load has changed, bail.
|
||||
if (![[strongSelf _nextImageIdentifierToDownload] isEqual:nextImageIdentifier]) {
|
||||
finishedLoadingBlock(nil, nil, [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged userInfo:nil]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise, we've got to download it.
|
||||
[strongSelf _downloadImageWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *downloadedImage, NSError *error) {
|
||||
ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from download", strongSelf, nextImageIdentifier);
|
||||
finishedLoadingBlock(downloadedImage, nextImageIdentifier, error);
|
||||
}];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_loadALAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock
|
||||
{
|
||||
ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required");
|
||||
ASDisplayNodeAssertNotNil(assetURL, @"assetURL is required");
|
||||
ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required");
|
||||
|
||||
ALAssetsLibrary *assetLibrary = [[ALAssetsLibrary alloc] init];
|
||||
|
||||
[assetLibrary assetForURL:assetURL resultBlock:^(ALAsset *asset) {
|
||||
ALAssetRepresentation *representation = [asset defaultRepresentation];
|
||||
CGImageRef coreGraphicsImage = [representation fullScreenImage];
|
||||
|
||||
UIImage *downloadedImage = (coreGraphicsImage ? [UIImage imageWithCGImage:coreGraphicsImage] : nil);
|
||||
completionBlock(downloadedImage, nil);
|
||||
} failureBlock:^(NSError *error) {
|
||||
completionBlock(nil, error);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_loadPHAssetWithIdentifier:(id)imageIdentifier URL:(NSURL *)assetURL completion:(void (^)(UIImage *image, NSError *error))completionBlock
|
||||
{
|
||||
ASDisplayNodeAssert(AS_AT_LEAST_IOS8, @"PhotosKit is unavailable on iOS 7.");
|
||||
ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required");
|
||||
ASDisplayNodeAssertNotNil(assetURL, @"assetURL is required");
|
||||
ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required");
|
||||
|
||||
// Get the PHAsset itself.
|
||||
ASDisplayNodeAssertTrue([[assetURL absoluteString] hasPrefix:kPHAssetURLPrefix]);
|
||||
NSString *assetIdentifier = [[assetURL absoluteString] substringFromIndex:[kPHAssetURLPrefix length]];
|
||||
PHFetchResult *assetFetchResult = [PHAsset fetchAssetsWithLocalIdentifiers:@[assetIdentifier] options:nil];
|
||||
if ([assetFetchResult count] == 0) {
|
||||
// Error.
|
||||
completionBlock(nil, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the best image we can.
|
||||
PHAsset *imageAsset = [assetFetchResult firstObject];
|
||||
|
||||
PHImageRequestOptions *requestOptions = [[PHImageRequestOptions alloc] init];
|
||||
requestOptions.version = PHImageRequestOptionsVersionCurrent;
|
||||
requestOptions.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat;
|
||||
requestOptions.resizeMode = PHImageRequestOptionsResizeModeNone;
|
||||
|
||||
[[PHImageManager defaultManager] requestImageForAsset:imageAsset
|
||||
targetSize:CGSizeMake(2048.0, 2048.0) // Ideally we would use PHImageManagerMaximumSize and kill the options, but we get back nil when requesting images of video assets. rdar://18447788
|
||||
contentMode:PHImageContentModeDefault
|
||||
options:requestOptions
|
||||
resultHandler:^(UIImage *image, NSDictionary *info) {
|
||||
completionBlock(image, info[PHImageErrorKey]);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_fetchImageWithIdentifierFromCache:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image))completionBlock
|
||||
{
|
||||
ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required");
|
||||
ASDisplayNodeAssertNotNil(imageURL, @"imageURL is required");
|
||||
ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required");
|
||||
|
||||
if (_cache) {
|
||||
[_cache fetchCachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(CGImageRef coreGraphicsImageFromCache) {
|
||||
UIImage *imageFromCache = (coreGraphicsImageFromCache ? [UIImage imageWithCGImage:coreGraphicsImageFromCache] : nil);
|
||||
completionBlock(imageFromCache);
|
||||
}];
|
||||
}
|
||||
// If we don't have a cache, just fail immediately.
|
||||
else {
|
||||
completionBlock(nil);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image, NSError *error))completionBlock
|
||||
{
|
||||
ASDisplayNodeAssertNotNil(imageIdentifier, @"imageIdentifier is required");
|
||||
ASDisplayNodeAssertNotNil(imageURL, @"imageURL is required");
|
||||
ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required");
|
||||
|
||||
// Delegate (start)
|
||||
if (_delegateFlags.downloadStart)
|
||||
[_delegate multiplexImageNode:self didStartDownloadOfImageWithIdentifier:imageIdentifier];
|
||||
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
void (^downloadProgressBlock)(CGFloat) = nil;
|
||||
if (_delegateFlags.downloadProgress) {
|
||||
downloadProgressBlock = ^(CGFloat progress) {
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
if (!strongSelf)
|
||||
return;
|
||||
[strongSelf.delegate multiplexImageNode:strongSelf didUpdateDownloadProgress:progress forImageWithIdentifier:imageIdentifier];
|
||||
};
|
||||
}
|
||||
|
||||
// Download!
|
||||
[self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL
|
||||
callbackQueue:dispatch_get_main_queue()
|
||||
downloadProgressBlock:downloadProgressBlock
|
||||
completion:^(CGImageRef coreGraphicsImage, NSError *error) {
|
||||
// We dereference iVars directly, so we can't have weakSelf going nil on us.
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
if (!strongSelf)
|
||||
return;
|
||||
|
||||
UIImage *downloadedImage = (coreGraphicsImage ? [UIImage imageWithCGImage:coreGraphicsImage] : nil);
|
||||
completionBlock(downloadedImage, error);
|
||||
|
||||
// Delegateify.
|
||||
if (strongSelf->_delegateFlags.downloadFinish)
|
||||
[strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error];
|
||||
}]];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
- (void)_finishedLoadingImage:(UIImage *)image forIdentifier:(id)imageIdentifier error:(NSError *)error
|
||||
{
|
||||
// If we failed to load, we stop the loading process.
|
||||
// Note that if we bailed before we began downloading because the best identifier changed, we don't bail, but rather just begin loading the best image identifier.
|
||||
if (error && error.code != ASMultiplexImageNodeErrorCodeBestImageIdentifierChanged)
|
||||
return;
|
||||
|
||||
OSSpinLockLock(&_imageIdentifiersLock);
|
||||
NSUInteger imageIdentifierCount = [_imageIdentifiers count];
|
||||
OSSpinLockUnlock(&_imageIdentifiersLock);
|
||||
|
||||
// Update our image if we got one, or if we're not supposed to display one at all.
|
||||
// We explicitly perform this check because our datasource often doesn't give back immediately available images, even though we might have downloaded one already.
|
||||
// Because we seed this call with bestImmediatelyAvailableImageFromDataSource, we must be careful not to trample an existing image.
|
||||
if (image || imageIdentifierCount == 0) {
|
||||
ASMultiplexImageNodeLogDebug(@"[%p] loaded -> displaying (%@, %@)", self, imageIdentifier, image);
|
||||
id previousIdentifier = self.loadedImageIdentifier;
|
||||
UIImage *previousImage = self.image;
|
||||
|
||||
self.loadedImageIdentifier = imageIdentifier;
|
||||
self.image = image;
|
||||
|
||||
if (_delegateFlags.updatedImage) {
|
||||
[_delegate multiplexImageNode:self didUpdateImage:image withIdentifier:imageIdentifier fromImage:previousImage withIdentifier:previousIdentifier];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Load our next image, if we have one to load.
|
||||
if ([self _nextImageIdentifierToDownload])
|
||||
[self _loadNextImage];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -13,5 +13,7 @@
|
||||
#import <AsyncDisplayKit/ASImageNode.h>
|
||||
#import <AsyncDisplayKit/ASTextNode.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASMultiplexImageNode.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASTableView.h>
|
||||
#import <AsyncDisplayKit/ASCellNode.h>
|
||||
|
||||
350
AsyncDisplayKitTests/ASMultiplexImageNodeTests.m
Normal file
350
AsyncDisplayKitTests/ASMultiplexImageNodeTests.m
Normal file
@@ -0,0 +1,350 @@
|
||||
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <OCMock/OCMock.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASImageProtocols.h>
|
||||
#import <AsyncDisplayKit/ASMultiplexImageNode.h>
|
||||
|
||||
#import <libkern/OSAtomic.h>
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
@interface ASMultiplexImageNodeTests : XCTestCase
|
||||
{
|
||||
@private
|
||||
id _mockCache;
|
||||
id _mockDownloader;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@implementation ASMultiplexImageNodeTests
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Helpers.
|
||||
|
||||
- (NSURL *)_testImageURL
|
||||
{
|
||||
return [[NSBundle bundleForClass:[self class]] URLForResource:@"logo-square"
|
||||
withExtension:@"png"
|
||||
subdirectory:@"TestResources"];
|
||||
}
|
||||
|
||||
- (UIImage *)_testImage
|
||||
{
|
||||
return [[[UIImage alloc] initWithContentsOfFile:[self _testImageURL].path] autorelease];
|
||||
}
|
||||
|
||||
static BOOL ASInvokeConditionBlockWithBarriers(BOOL (^block)()) {
|
||||
// In case the block does multiple comparisons, ensure it has a consistent view of memory by issuing read-write
|
||||
// barriers on either side of the block.
|
||||
OSMemoryBarrier();
|
||||
BOOL result = block();
|
||||
OSMemoryBarrier();
|
||||
return result;
|
||||
}
|
||||
|
||||
static BOOL ASRunRunLoopUntilBlockIsTrue(BOOL (^block)())
|
||||
{
|
||||
// Time out after 30 seconds.
|
||||
CFTimeInterval timeoutDate = CACurrentMediaTime() + 30.0f;
|
||||
BOOL passed = NO;
|
||||
|
||||
while (true) {
|
||||
passed = ASInvokeConditionBlockWithBarriers(block);
|
||||
|
||||
if (passed) {
|
||||
break;
|
||||
}
|
||||
|
||||
CFTimeInterval now = CACurrentMediaTime();
|
||||
if (now > timeoutDate) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Run 1000 times a second until the poll timeout or until timeoutDate, whichever is first.
|
||||
CFTimeInterval runLoopTimeout = MIN(0.001, timeoutDate - now);
|
||||
CFRunLoopRunInMode(kCFRunLoopDefaultMode, runLoopTimeout, true);
|
||||
}
|
||||
|
||||
return passed;
|
||||
}
|
||||
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark Unit tests.
|
||||
|
||||
// TODO: add tests for delegate display notifications
|
||||
|
||||
- (void)setUp
|
||||
{
|
||||
[super setUp];
|
||||
|
||||
_mockCache = [OCMockObject mockForProtocol:@protocol(ASImageCacheProtocol)];
|
||||
_mockDownloader = [OCMockObject mockForProtocol:@protocol(ASImageDownloaderProtocol)];
|
||||
}
|
||||
|
||||
- (void)testDataSourceImageMethod
|
||||
{
|
||||
ASMultiplexImageNode *imageNode = [[ASMultiplexImageNode alloc] initWithCache:_mockCache downloader:_mockDownloader];
|
||||
|
||||
// Mock the data source.
|
||||
// Note that we're not using a niceMock because we want to assert if the URL data-source method gets hit, as the image
|
||||
// method should be hit first and exclusively if it successfully returns an image.
|
||||
id mockDataSource = [OCMockObject mockForProtocol:@protocol(ASMultiplexImageNodeDataSource)];
|
||||
imageNode.dataSource = mockDataSource;
|
||||
|
||||
NSNumber *imageIdentifier = @1;
|
||||
|
||||
// Expect the image method to be hit, and have it return our test image.
|
||||
UIImage *testImage = [self _testImage];
|
||||
[[[mockDataSource expect] andReturn:testImage] multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier];
|
||||
|
||||
imageNode.imageIdentifiers = @[imageIdentifier];
|
||||
|
||||
[mockDataSource verify];
|
||||
|
||||
// Also expect it to be loaded immediately.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, imageIdentifier, @"imageIdentifier was not loaded");
|
||||
// And for the image to be equivalent to the image we provided.
|
||||
XCTAssertEqualObjects(UIImagePNGRepresentation(imageNode.image),
|
||||
UIImagePNGRepresentation(testImage),
|
||||
@"Loaded image isn't the one we provided");
|
||||
|
||||
imageNode.delegate = nil;
|
||||
imageNode.dataSource = nil;
|
||||
[imageNode release];
|
||||
}
|
||||
|
||||
- (void)testDataSourceURLMethod
|
||||
{
|
||||
ASMultiplexImageNode *imageNode = [[ASMultiplexImageNode alloc] initWithCache:_mockCache downloader:_mockDownloader];
|
||||
|
||||
NSNumber *imageIdentifier = @1;
|
||||
|
||||
// Mock the data source such that we...
|
||||
id mockDataSource = [OCMockObject niceMockForProtocol:@protocol(ASMultiplexImageNodeDataSource)];
|
||||
imageNode.dataSource = mockDataSource;
|
||||
// (a) first expect to be hit for the image directly, and fail to return it.
|
||||
[mockDataSource setExpectationOrderMatters:YES];
|
||||
[[[mockDataSource expect] andReturn:nil] multiplexImageNode:imageNode imageForImageIdentifier:imageIdentifier];
|
||||
// (b) and then expect to be hit for the URL, which we'll return.
|
||||
[[[mockDataSource expect] andReturn:[self _testImageURL]] multiplexImageNode:imageNode URLForImageIdentifier:imageIdentifier];
|
||||
|
||||
// Mock the cache to do a cache-hit for the test image URL.
|
||||
[[[_mockCache stub] andDo:^(NSInvocation *inv) {
|
||||
// Params are URL, callbackQueue, completion
|
||||
NSArray *URL;
|
||||
[inv getArgument:&URL atIndex:2];
|
||||
|
||||
void (^completionBlock)(CGImageRef);
|
||||
[inv getArgument:&completionBlock atIndex:4];
|
||||
|
||||
// Call the completion block with our test image and URL.
|
||||
NSURL *testImageURL = [self _testImageURL];
|
||||
XCTAssertEqualObjects(URL, testImageURL, @"Fetching URL other than test image");
|
||||
completionBlock([self _testImage].CGImage);
|
||||
}] fetchCachedImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] completion:[OCMArg any]];
|
||||
|
||||
// Kick off loading.
|
||||
imageNode.imageIdentifiers = @[imageIdentifier];
|
||||
|
||||
// Verify the data source.
|
||||
[mockDataSource verify];
|
||||
// Also expect it to be loaded immediately.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, imageIdentifier, @"imageIdentifier was not loaded");
|
||||
// And for the image to be equivalent to the image we provided.
|
||||
XCTAssertEqualObjects(UIImagePNGRepresentation(imageNode.image),
|
||||
UIImagePNGRepresentation([self _testImage]),
|
||||
@"Loaded image isn't the one we provided");
|
||||
|
||||
imageNode.delegate = nil;
|
||||
imageNode.dataSource = nil;
|
||||
[imageNode release];
|
||||
}
|
||||
|
||||
- (void)testAddLowerQualityImageIdentifier
|
||||
{
|
||||
// Adding a lower quality image identifier should not cause any loading.
|
||||
ASMultiplexImageNode *imageNode = [[ASMultiplexImageNode alloc] initWithCache:_mockCache downloader:_mockDownloader];
|
||||
|
||||
NSNumber *highResIdentifier = @2;
|
||||
|
||||
// Mock the data source such that we: (a) return the test image, and log whether we get hit for the lower-quality image.
|
||||
id mockDataSource = [OCMockObject mockForProtocol:@protocol(ASMultiplexImageNodeDataSource)];
|
||||
imageNode.dataSource = mockDataSource;
|
||||
__block int dataSourceHits = 0;
|
||||
[[[mockDataSource stub] andDo:^(NSInvocation *inv) {
|
||||
dataSourceHits++;
|
||||
|
||||
// Return the test image.
|
||||
[inv setReturnValue:(void *)[self _testImage]];
|
||||
}] multiplexImageNode:[OCMArg any] imageForImageIdentifier:[OCMArg any]];
|
||||
|
||||
imageNode.imageIdentifiers = @[highResIdentifier];
|
||||
|
||||
// At this point, we should have the high-res identifier loaded and the DS should have been hit once.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, highResIdentifier, @"High res identifier should be loaded.");
|
||||
XCTAssertTrue(dataSourceHits == 1, @"Unexpected DS hit count");
|
||||
|
||||
// Add the low res identifier.
|
||||
NSNumber *lowResIdentifier = @1;
|
||||
imageNode.imageIdentifiers = @[highResIdentifier, lowResIdentifier];
|
||||
|
||||
// At this point the high-res should still be loaded, and the data source should not have been hit.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, highResIdentifier, @"High res identifier should be loaded.");
|
||||
XCTAssertTrue(dataSourceHits == 1, @"Unexpected DS hit count");
|
||||
|
||||
imageNode.delegate = nil;
|
||||
imageNode.dataSource = nil;
|
||||
[imageNode release];
|
||||
}
|
||||
|
||||
- (void)testAddHigherQualityImageIdentifier
|
||||
{
|
||||
// Adding a higher quality image identifier should cause loading.
|
||||
ASMultiplexImageNode *imageNode = [[ASMultiplexImageNode alloc] initWithCache:_mockCache downloader:_mockDownloader];
|
||||
|
||||
NSNumber *lowResIdentifier = @1;
|
||||
|
||||
// Mock the data source such that we: (a) return the test image, and log how many times the DS gets hit.
|
||||
id mockDataSource = [OCMockObject mockForProtocol:@protocol(ASMultiplexImageNodeDataSource)];
|
||||
imageNode.dataSource = mockDataSource;
|
||||
__block int dataSourceHits = 0;
|
||||
[[[mockDataSource stub] andDo:^(NSInvocation *inv) {
|
||||
dataSourceHits++;
|
||||
|
||||
// Return the test image.
|
||||
[inv setReturnValue:(void *)[self _testImage]];
|
||||
}] multiplexImageNode:[OCMArg any] imageForImageIdentifier:[OCMArg any]];
|
||||
|
||||
imageNode.imageIdentifiers = @[lowResIdentifier];
|
||||
|
||||
// At this point, we should have the low-res identifier loaded and the DS should have been hit once.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, lowResIdentifier, @"Low res identifier should be loaded.");
|
||||
XCTAssertTrue(dataSourceHits == 1, @"Unexpected DS hit count");
|
||||
|
||||
// Add the low res identifier.
|
||||
NSNumber *highResIdentifier = @2;
|
||||
imageNode.imageIdentifiers = @[highResIdentifier, lowResIdentifier];
|
||||
|
||||
// At this point the high-res should be loaded, and the data source should been hit twice.
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, highResIdentifier, @"High res identifier should be loaded.");
|
||||
XCTAssertTrue(dataSourceHits == 2, @"Unexpected DS hit count");
|
||||
|
||||
imageNode.delegate = nil;
|
||||
imageNode.dataSource = nil;
|
||||
[imageNode release];
|
||||
}
|
||||
|
||||
- (void)testProgressiveDownloading
|
||||
{
|
||||
ASMultiplexImageNode *imageNode = [[ASMultiplexImageNode alloc] initWithCache:_mockCache downloader:_mockDownloader];
|
||||
imageNode.downloadsIntermediateImages = YES;
|
||||
|
||||
// Set up a few identifiers to load.
|
||||
NSInteger identifierCount = 5;
|
||||
NSMutableArray *imageIdentifiers = [NSMutableArray array];
|
||||
for (NSInteger identifierIndex = 0; identifierIndex < identifierCount; identifierIndex++)
|
||||
[imageIdentifiers insertObject:@(identifierIndex + 1) atIndex:0];
|
||||
|
||||
// Mock the data source to only make the images available progressively.
|
||||
// This is necessary because ASMultiplexImageNode will try to grab the best image immediately, regardless of
|
||||
// `downloadsIntermediateImages`.
|
||||
id mockDataSource = [OCMockObject niceMockForProtocol:@protocol(ASMultiplexImageNodeDataSource)];
|
||||
imageNode.dataSource = mockDataSource;
|
||||
__block NSUInteger loadedImageCount = 0;
|
||||
[[[mockDataSource stub] andDo:^(NSInvocation *inv) {
|
||||
id requestedIdentifier;
|
||||
[inv getArgument:&requestedIdentifier atIndex:3];
|
||||
|
||||
NSInteger requestedIdentifierValue = [requestedIdentifier intValue];
|
||||
|
||||
// If no images are loaded, bail on trying to load anything but the worst image.
|
||||
if (!imageNode.loadedImageIdentifier && requestedIdentifierValue != [[imageIdentifiers lastObject] integerValue])
|
||||
return;
|
||||
|
||||
// Bail if it's trying to load an identifier that's more than one step than what's loaded.
|
||||
NSInteger nextImageIdentifier = [imageNode.loadedImageIdentifier integerValue] + 1;
|
||||
if (requestedIdentifierValue != nextImageIdentifier)
|
||||
return;
|
||||
|
||||
// Return the test image.
|
||||
loadedImageCount++;
|
||||
[inv setReturnValue:(void *)[self _testImage]];
|
||||
}] multiplexImageNode:[OCMArg any] imageForImageIdentifier:[OCMArg any]];
|
||||
|
||||
imageNode.imageIdentifiers = imageIdentifiers;
|
||||
|
||||
XCTAssertTrue(loadedImageCount == identifierCount, @"Expected to load the same number of identifiers we supplied");
|
||||
|
||||
imageNode.delegate = nil;
|
||||
imageNode.dataSource = nil;
|
||||
[imageNode release];
|
||||
}
|
||||
|
||||
- (void)testUncachedDownload
|
||||
{
|
||||
// Mock a cache miss.
|
||||
id mockCache = [OCMockObject mockForProtocol:@protocol(ASImageCacheProtocol)];
|
||||
[[[mockCache stub] andDo:^(NSInvocation *inv) {
|
||||
void (^completion)(CGImageRef imageFromCache);
|
||||
[inv getArgument:&completion atIndex:4];
|
||||
completion(nil);
|
||||
}] fetchCachedImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] completion:[OCMArg any]];
|
||||
|
||||
// Mock a 50%-progress URL download.
|
||||
id mockDownloader = [OCMockObject mockForProtocol:@protocol(ASImageDownloaderProtocol)];
|
||||
const CGFloat mockedProgress = 0.5;
|
||||
[[[mockDownloader stub] andDo:^(NSInvocation *inv) {
|
||||
// Simulate progress.
|
||||
void (^progressBlock)(CGFloat progress);
|
||||
[inv getArgument:&progressBlock atIndex:4];
|
||||
progressBlock(mockedProgress);
|
||||
|
||||
// Simulate completion.
|
||||
void (^completionBlock)(CGImageRef image, NSError *error);
|
||||
[inv getArgument:&completionBlock atIndex:5];
|
||||
completionBlock([self _testImage].CGImage, nil);
|
||||
}] downloadImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] downloadProgressBlock:[OCMArg any] completion:[OCMArg any]];
|
||||
|
||||
ASMultiplexImageNode *imageNode = [[ASMultiplexImageNode alloc] initWithCache:mockCache downloader:mockDownloader];
|
||||
NSNumber *imageIdentifier = @1;
|
||||
|
||||
// Mock the data source to return our test URL.
|
||||
id mockDataSource = [OCMockObject niceMockForProtocol:@protocol(ASMultiplexImageNodeDataSource)];
|
||||
[[[mockDataSource stub] andReturn:[self _testImageURL]] multiplexImageNode:imageNode URLForImageIdentifier:imageIdentifier];
|
||||
imageNode.dataSource = mockDataSource;
|
||||
|
||||
// Mock the delegate to expect start, 50% progress, and completion invocations.
|
||||
id mockDelegate = [OCMockObject mockForProtocol:@protocol(ASMultiplexImageNodeDelegate)];
|
||||
[[mockDelegate expect] multiplexImageNode:imageNode didStartDownloadOfImageWithIdentifier:imageIdentifier];
|
||||
[[mockDelegate expect] multiplexImageNode:imageNode didUpdateDownloadProgress:mockedProgress forImageWithIdentifier:imageIdentifier];
|
||||
[[mockDelegate expect] multiplexImageNode:imageNode didFinishDownloadingImageWithIdentifier:imageIdentifier error:nil];
|
||||
[[mockDelegate expect] multiplexImageNode:imageNode didUpdateImage:[OCMArg any] withIdentifier:imageIdentifier fromImage:nil withIdentifier:nil];
|
||||
imageNode.delegate = mockDelegate;
|
||||
|
||||
// Kick off loading.
|
||||
imageNode.imageIdentifiers = @[imageIdentifier];
|
||||
|
||||
// Wait until the image is loaded.
|
||||
ASRunRunLoopUntilBlockIsTrue(^BOOL{
|
||||
return [imageNode.loadedImageIdentifier isEqual:imageIdentifier];
|
||||
});
|
||||
|
||||
// Verify the delegation.
|
||||
[mockDelegate verify];
|
||||
// Also verify that it's acutally loaded (could be false if we timed out above).
|
||||
XCTAssertEqualObjects(imageNode.loadedImageIdentifier, imageIdentifier, @"Failed to load image");
|
||||
|
||||
[imageNode release];
|
||||
}
|
||||
|
||||
@end
|
||||
BIN
AsyncDisplayKitTests/TestResources/logo-square.png
Executable file
BIN
AsyncDisplayKitTests/TestResources/logo-square.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
40
Base/ASAvailability.h
Normal file
40
Base/ASAvailability.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Availability.h>
|
||||
#import <AvailabilityInternal.h>
|
||||
|
||||
#import <CoreFoundation/CFBase.h>
|
||||
|
||||
#ifndef kCFCoreFoundationVersionNumber_IOS_7_0
|
||||
#define kCFCoreFoundationVersionNumber_IOS_7_0 838.00
|
||||
#endif
|
||||
|
||||
#ifndef kCFCoreFoundationVersionNumber_iOS_7_1
|
||||
#define kCFCoreFoundationVersionNumber_iOS_7_1 847.24
|
||||
#endif
|
||||
|
||||
#ifndef kCFCoreFoundationVersionNumber_iOS_8_0
|
||||
#define kCFCoreFoundationVersionNumber_iOS_8_0 1140.1
|
||||
#endif
|
||||
|
||||
#ifndef __IPHONE_7_0
|
||||
#define __IPHONE_7_0 70000
|
||||
#endif
|
||||
|
||||
#ifndef __IPHONE_8_0
|
||||
#define __IPHONE_8_0 80000
|
||||
#endif
|
||||
|
||||
#ifndef AS_IOS8_SDK_OR_LATER
|
||||
#define AS_IOS8_SDK_OR_LATER __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0
|
||||
#endif
|
||||
|
||||
#define AS_AT_LEAST_IOS7 (kCFCoreFoundationVersionNumber > kCFCoreFoundationVersionNumber_iOS_6_1)
|
||||
#define AS_AT_LEAST_IOS7_1 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_7_1)
|
||||
#define AS_AT_LEAST_IOS8 (kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber_iOS_8_0)
|
||||
15
Base/ASLog.h
Normal file
15
Base/ASLog.h
Normal file
@@ -0,0 +1,15 @@
|
||||
/* Copyright (c) 2014-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define ASMultiplexImageNodeLogDebug NSLog
|
||||
#define ASMultiplexImageNodeCLogDebug NSLog
|
||||
|
||||
#define ASMultiplexImageNodeLogError NSLog
|
||||
#define ASMultiplexImageNodeCLogError NSLog
|
||||
Reference in New Issue
Block a user