diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index e1d2962524..4e7bbcaed0 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |spec| spec.name = 'AsyncDisplayKit' - spec.version = '1.9.2' + spec.version = '1.9.3' spec.license = { :type => 'BSD' } spec.homepage = 'http://asyncdisplaykit.org' spec.authors = { 'Scott Goodson' => 'scottgoodson@gmail.com', 'Ryan Nystrom' => 'rnystrom@fb.com' } spec.summary = 'Smooth asynchronous user interfaces for iOS apps.' - spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.2' } + spec.source = { :git => 'https://github.com/facebook/AsyncDisplayKit.git', :tag => '1.9.3' } spec.documentation_url = 'http://asyncdisplaykit.org/appledoc/' @@ -22,7 +22,7 @@ Pod::Spec.new do |spec| ] spec.frameworks = 'AssetsLibrary' - spec.weak_frameworks = 'Photos' + spec.weak_frameworks = 'Photos','MapKit' # ASDealloc2MainObject must be compiled with MRR spec.requires_arc = true diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 8708d2a031..4269a60b2e 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -255,7 +255,20 @@ 509E68641B3AEDB7009B9150 /* ASCollectionViewLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */; }; 509E68651B3AEDC5009B9150 /* CGRect+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 205F0E1F1B376416007741D0 /* CGRect+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; }; 509E68661B3AEDD7009B9150 /* CGRect+ASConvenience.m in Sources */ = {isa = PBXBuildFile; fileRef = 205F0E201B376416007741D0 /* CGRect+ASConvenience.m */; }; + 68B0277A1C1A79CC0041016B /* ASDisplayNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */ = {isa = PBXBuildFile; fileRef = 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 68EE0DBD1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */; }; + 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */ = {isa = PBXBuildFile; fileRef = 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */; }; + 68EE0DBF1C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */; }; + 68EE0DC01C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */ = {isa = PBXBuildFile; fileRef = 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */; }; 6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; + 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; + 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */; }; + 92DD2FE81BF4D0A80074C9DD /* ASMapNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 92DD2FE91BF4D4870074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; + 92DD2FEA1BF4D49B0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */; }; 9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */; }; 9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */ = {isa = PBXBuildFile; fileRef = 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */; }; 9C49C36F1B853957000B0DD5 /* ASStackLayoutable.h in Headers */ = {isa = PBXBuildFile; fileRef = 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -441,6 +454,8 @@ D785F6621A74327E00291744 /* ASScrollNode.h in Headers */ = {isa = PBXBuildFile; fileRef = D785F6601A74327E00291744 /* ASScrollNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; D785F6631A74327E00291744 /* ASScrollNode.m in Sources */ = {isa = PBXBuildFile; fileRef = D785F6611A74327E00291744 /* ASScrollNode.m */; }; DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; }; + DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; + DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */ = {isa = PBXBuildFile; fileRef = DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */; }; DECBD6E71BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E81BE56E1900CF4905 /* ASButtonNode.h in Headers */ = {isa = PBXBuildFile; fileRef = DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; DECBD6E91BE56E1900CF4905 /* ASButtonNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */; }; @@ -522,7 +537,7 @@ 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASControlNode+Subclasses.h"; sourceTree = ""; }; 058D09D8195D050800B7D73C /* ASDisplayNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = ASDisplayNode.h; sourceTree = ""; }; 058D09D9195D050800B7D73C /* ASDisplayNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; lineEnding = 0; path = ASDisplayNode.mm; sourceTree = ""; }; - 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "ASDisplayNode+Subclasses.h"; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; + 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = "ASDisplayNode+Subclasses.h"; sourceTree = ""; }; 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeExtras.h; sourceTree = ""; }; 058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASDisplayNodeExtras.mm; sourceTree = ""; }; 058D09DD195D050800B7D73C /* ASImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageNode.h; sourceTree = ""; }; @@ -650,7 +665,13 @@ 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASFlowLayoutController.h; sourceTree = ""; }; 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASFlowLayoutController.mm; sourceTree = ""; }; 4640521D1A3F83C40061C0BA /* ASLayoutController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutController.h; sourceTree = ""; }; - 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; path = AsyncDisplayKit.h; sourceTree = ""; }; + 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+Beta.h"; sourceTree = ""; }; + 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMainSerialQueue.h; sourceTree = ""; }; + 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMainSerialQueue.mm; sourceTree = ""; }; + 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = ""; }; + 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASMapNode.h; sourceTree = ""; }; + 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMapNode.mm; sourceTree = ""; }; + 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; 9C49C36E1B853957000B0DD5 /* ASStackLayoutable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASStackLayoutable.h; path = AsyncDisplayKit/Layout/ASStackLayoutable.h; sourceTree = ""; }; 9C5586671BD549CB00B50E3A /* ASAsciiArtBoxCreator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASAsciiArtBoxCreator.h; path = AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.h; sourceTree = ""; }; 9C5586681BD549CB00B50E3A /* ASAsciiArtBoxCreator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASAsciiArtBoxCreator.m; path = AsyncDisplayKit/Layout/ASAsciiArtBoxCreator.m; sourceTree = ""; }; @@ -725,6 +746,7 @@ 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 = ""; }; D785F6601A74327E00291744 /* ASScrollNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASScrollNode.h; sourceTree = ""; }; D785F6611A74327E00291744 /* ASScrollNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASScrollNode.m; sourceTree = ""; }; + DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ASDisplayNode+FrameworkPrivate.h"; sourceTree = ""; }; DECBD6E51BE56E1900CF4905 /* ASButtonNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASButtonNode.h; sourceTree = ""; }; DECBD6E61BE56E1900CF4905 /* ASButtonNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASButtonNode.mm; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -743,6 +765,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 92DD2FE91BF4D4870074C9DD /* MapKit.framework in Frameworks */, 051943151A1575670030A7D0 /* Photos.framework in Frameworks */, 051943131A1575630030A7D0 /* AssetsLibrary.framework in Frameworks */, 058D09B0195D04C000B7D73C /* Foundation.framework in Frameworks */, @@ -753,6 +776,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 92DD2FEA1BF4D49B0074C9DD /* MapKit.framework in Frameworks */, 0515EA221A1576A100BA8B9A /* AssetsLibrary.framework in Frameworks */, 0515EA211A15769900BA8B9A /* Photos.framework in Frameworks */, 058D09BE195D04C000B7D73C /* XCTest.framework in Frameworks */, @@ -767,6 +791,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 92DD2FE61BF4D05E0074C9DD /* MapKit.framework in Frameworks */, B350625F1B0111800018CF92 /* Foundation.framework in Frameworks */, B350625E1B0111780018CF92 /* AssetsLibrary.framework in Frameworks */, B350625D1B0111740018CF92 /* Photos.framework in Frameworks */, @@ -826,6 +851,7 @@ 058D09AE195D04C000B7D73C /* Frameworks */ = { isa = PBXGroup; children = ( + 92DD2FE51BF4D05E0074C9DD /* MapKit.framework */, 051943141A1575670030A7D0 /* Photos.framework */, 051943121A1575630030A7D0 /* AssetsLibrary.framework */, 058D09AF195D04C000B7D73C /* Foundation.framework */, @@ -839,6 +865,8 @@ 058D09B1195D04C000B7D73C /* AsyncDisplayKit */ = { isa = PBXGroup; children = ( + 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */, + 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */, 055F1A3A19ABD43F004DAFF1 /* ASCellNode.h */, AC6456071B0A335000CF11B8 /* ASCellNode.m */, 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */, @@ -853,6 +881,7 @@ 058D09D7195D050800B7D73C /* ASControlNode+Subclasses.h */, 058D09D8195D050800B7D73C /* ASDisplayNode.h */, 058D09D9195D050800B7D73C /* ASDisplayNode.mm */, + 68B027791C1A79CC0041016B /* ASDisplayNode+Beta.h */, 058D09DA195D050800B7D73C /* ASDisplayNode+Subclasses.h */, 058D09DB195D050800B7D73C /* ASDisplayNodeExtras.h */, 058D09DC195D050800B7D73C /* ASDisplayNodeExtras.mm */, @@ -950,10 +979,9 @@ 058D09E1195D050800B7D73C /* Details */ = { isa = PBXGroup; children = ( + 25B171EA1C12242700508A7A /* Data Controller */, CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */, CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */, - 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */, - 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */, 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */, 251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */, 251B8EF61BBB3D690087C538 /* ASDataController+Subclasses.h */, @@ -969,10 +997,6 @@ 299DA1A81A828D2900162D41 /* ASBatchContext.mm */, 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */, 205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */, - 464052191A3F83C40061C0BA /* ASDataController.h */, - 4640521A1A3F83C40061C0BA /* ASDataController.mm */, - AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */, - AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */, 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */, 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */, 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */, @@ -984,6 +1008,8 @@ 05F20AA31A15733C00DCA68A /* ASImageProtocols.h */, 4640521D1A3F83C40061C0BA /* ASLayoutController.h */, 292C59991A956527007E5DD6 /* ASLayoutRangeType.h */, + 68EE0DBB1C1B4ED300BA1B99 /* ASMainSerialQueue.h */, + 68EE0DBC1C1B4ED300BA1B99 /* ASMainSerialQueue.mm */, 058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */, 058D09E9195D050800B7D73C /* ASMutableAttributedStringBuilder.m */, 055F1A3619ABD413004DAFF1 /* ASRangeController.h */, @@ -1045,6 +1071,7 @@ 058D0A09195D050800B7D73C /* ASDisplayNode+DebugTiming.h */, 058D0A0A195D050800B7D73C /* ASDisplayNode+DebugTiming.mm */, 058D0A0B195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm */, + DE6EA3211C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h */, 058D0A0C195D050800B7D73C /* ASDisplayNodeInternal.h */, 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */, 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */, @@ -1110,6 +1137,19 @@ name = TextKit; sourceTree = ""; }; + 25B171EA1C12242700508A7A /* Data Controller */ = { + isa = PBXGroup; + children = ( + 251B8EF21BBB3D690087C538 /* ASCollectionDataController.h */, + 251B8EF31BBB3D690087C538 /* ASCollectionDataController.mm */, + 464052191A3F83C40061C0BA /* ASDataController.h */, + 4640521A1A3F83C40061C0BA /* ASDataController.mm */, + AC026B671BD57D6F00BBC17E /* ASChangeSetDataController.h */, + AC026B681BD57D6F00BBC17E /* ASChangeSetDataController.m */, + ); + name = "Data Controller"; + sourceTree = ""; + }; AC6456051B0A333200CF11B8 /* Layout */ = { isa = PBXGroup; children = ( @@ -1184,6 +1224,7 @@ buildActionMask = 2147483647; files = ( 257754C21BEE458E00737CA5 /* ASTextKitCoreTextAdditions.h in Headers */, + 92DD2FE31BF4B97E0074C9DD /* ASMapNode.h in Headers */, AC026B691BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */, 058D0A71195D05F800B7D73C /* _AS-objc-internal.h in Headers */, 058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */, @@ -1196,6 +1237,7 @@ 058D0A74195D05F800B7D73C /* _ASPendingState.h in Headers */, 9C5586691BD549CB00B50E3A /* ASAsciiArtBoxCreator.h in Headers */, 058D0A76195D05F900B7D73C /* _ASScopeTimer.h in Headers */, + 68EE0DBD1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */, 257754B31BEE44CD00737CA5 /* ASTextKitTailTruncater.h in Headers */, 205F0E191B37339C007741D0 /* ASAbstractLayoutController.h in Headers */, 058D0A82195D060300B7D73C /* ASAssert.h in Headers */, @@ -1228,9 +1270,11 @@ 058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */, AC7A2C171BDE11DF0093FE1A /* ASTableViewInternal.h in Headers */, 058D0A4D195D05CB00B7D73C /* ASDisplayNodeExtras.h in Headers */, + 68B0277A1C1A79CC0041016B /* ASDisplayNode+Beta.h in Headers */, 257754B11BEE44CD00737CA5 /* ASTextKitShadower.h in Headers */, 058D0A7B195D05F900B7D73C /* ASDisplayNodeInternal.h in Headers */, 0587F9BD1A7309ED00AFF0BA /* ASEditableTextNode.h in Headers */, + DE6EA3221C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */, 1950C4491A3BB5C1005C8279 /* ASEqualityHelpers.h in Headers */, 257754A81BEE44CD00737CA5 /* ASTextKitContext.h in Headers */, 464052221A3F83C40061C0BA /* ASFlowLayoutController.h in Headers */, @@ -1315,6 +1359,7 @@ B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */, B350620F1B010EFD0018CF92 /* _ASDisplayLayer.h in Headers */, B35062111B010EFD0018CF92 /* _ASDisplayView.h in Headers */, + 68EE0DBE1C1B4ED300BA1B99 /* ASMainSerialQueue.h in Headers */, B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */, 9C55866C1BD54A3000B50E3A /* ASAsciiArtBoxCreator.h in Headers */, B350624D1B010EFD0018CF92 /* _ASScopeTimer.h in Headers */, @@ -1340,6 +1385,7 @@ 254C6B791BF94DF4003EC431 /* ASTextKitEntityAttribute.h in Headers */, 509E68631B3AEDB4009B9150 /* ASCollectionViewLayoutController.h in Headers */, B35061F71B010EFD0018CF92 /* ASCollectionViewProtocols.h in Headers */, + DE6EA3231C14000600183B10 /* ASDisplayNode+FrameworkPrivate.h in Headers */, B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */, B35061F81B010EFD0018CF92 /* ASControlNode.h in Headers */, B35062171B010EFD0018CF92 /* ASDataController.h in Headers */, @@ -1388,6 +1434,7 @@ 34EFC76E1B701CF400AD841F /* ASRatioLayoutSpec.h in Headers */, 34EFC7651B701CCC00AD841F /* ASRelativeSize.h in Headers */, 254C6B741BF94DF4003EC431 /* ASTextNodeWordKerner.h in Headers */, + 68B0277B1C1A79D60041016B /* ASDisplayNode+Beta.h in Headers */, B350622D1B010EFD0018CF92 /* ASScrollDirection.h in Headers */, 254C6B751BF94DF4003EC431 /* ASTextKitHelpers.h in Headers */, B35062081B010EFD0018CF92 /* ASScrollNode.h in Headers */, @@ -1400,6 +1447,7 @@ 254C6B761BF94DF4003EC431 /* ASTextNodeTypes.h in Headers */, 34EFC7711B701CFF00AD841F /* ASStackLayoutSpec.h in Headers */, 2767E9411BB19BD600EA9B77 /* ASViewController.h in Headers */, + 92DD2FE81BF4D0A80074C9DD /* ASMapNode.h in Headers */, 044284FE1BAA387800D16268 /* ASStackLayoutSpecUtilities.h in Headers */, 34EFC7751B701D2400AD841F /* ASStackPositionedLayout.h in Headers */, 34EFC7771B701D2D00AD841F /* ASStackUnpositionedLayout.h in Headers */, @@ -1618,6 +1666,7 @@ AC026B711BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */, 257754BF1BEE458E00737CA5 /* ASTextKitCoreTextAdditions.m in Sources */, 058D0A18195D050800B7D73C /* _ASDisplayLayer.mm in Sources */, + 68EE0DBF1C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */, 058D0A19195D050800B7D73C /* _ASDisplayView.mm in Sources */, 9C55866A1BD549CB00B50E3A /* ASAsciiArtBoxCreator.m in Sources */, 058D0A27195D050800B7D73C /* _ASPendingState.m in Sources */, @@ -1628,6 +1677,7 @@ AC6456091B0A335000CF11B8 /* ASCellNode.m in Sources */, ACF6ED1D1B17843500DA7C62 /* ASCenterLayoutSpec.mm in Sources */, 18C2ED801B9B7DE800F627B3 /* ASCollectionNode.m in Sources */, + 92DD2FE41BF4B97E0074C9DD /* ASMapNode.mm in Sources */, AC3C4A521A1139C100143C57 /* ASCollectionView.mm in Sources */, 205F0E1E1B373A2C007741D0 /* ASCollectionViewLayoutController.mm in Sources */, 058D0A13195D050800B7D73C /* ASControlNode.m in Sources */, @@ -1736,6 +1786,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 92DD2FE71BF4D0850074C9DD /* ASMapNode.mm in Sources */, 9B92C8861BC2EB7600EE46B2 /* ASCollectionViewFlowLayoutInspector.m in Sources */, 9B92C8851BC2EB6E00EE46B2 /* ASCollectionDataController.mm in Sources */, B350623D1B010EFD0018CF92 /* _ASAsyncTransaction.m in Sources */, @@ -1743,6 +1794,7 @@ AC026B721BD57DBF00BBC17E /* _ASHierarchyChangeSet.m in Sources */, B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */, B350624A1B010EFD0018CF92 /* _ASCoreAnimationExtras.mm in Sources */, + 68EE0DC01C1B4ED300BA1B99 /* ASMainSerialQueue.mm in Sources */, 2767E9421BB19BD600EA9B77 /* ASViewController.m in Sources */, B35062101B010EFD0018CF92 /* _ASDisplayLayer.mm in Sources */, 9C55866B1BD54A1900B50E3A /* ASAsciiArtBoxCreator.m in Sources */, diff --git a/AsyncDisplayKit/ASCellNode.m b/AsyncDisplayKit/ASCellNode.m index 27aa2bb916..ae73d5c1e6 100644 --- a/AsyncDisplayKit/ASCellNode.m +++ b/AsyncDisplayKit/ASCellNode.m @@ -25,7 +25,7 @@ if (!(self = [super init])) return nil; - // use UITableViewCell defaults + // Use UITableViewCell defaults _selectionStyle = UITableViewCellSelectionStyleDefault; self.clipsToBounds = YES; diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index aac9507693..ffd1c66005 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -6,19 +6,16 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import "ASCollectionView.h" - #import "ASAssert.h" -#import "ASCollectionViewLayoutController.h" -#import "ASRangeController.h" -#import "ASCollectionDataController.h" #import "ASBatchFetching.h" -#import "UICollectionViewLayout+ASConvenience.h" -#import "ASInternalHelpers.h" +#import "ASCollectionView.h" +#import "ASCollectionDataController.h" +#import "ASCollectionViewLayoutController.h" #import "ASCollectionViewFlowLayoutInspector.h" - -// FIXME: Temporary nonsense import until method names are finalized and exposed -#import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNode+FrameworkPrivate.h" +#import "ASInternalHelpers.h" +#import "ASRangeController.h" +#import "UICollectionViewLayout+ASConvenience.h" static const NSUInteger kASCollectionViewAnimationNone = UITableViewRowAnimationNone; static const ASSizeRange kInvalidSizeRange = {CGSizeZero, CGSizeZero}; @@ -394,15 +391,19 @@ static BOOL _isInterceptedSelector(SEL sel) { NSArray *indexPaths = [self indexPathsForVisibleItems]; NSMutableArray *visibleNodes = [[NSMutableArray alloc] init]; - - [indexPaths enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - ASCellNode *visibleNode = [self nodeForItemAtIndexPath:obj]; - [visibleNodes addObject:visibleNode]; - }]; - + + for (NSIndexPath *indexPath in indexPaths) { + ASCellNode *node = [self nodeForItemAtIndexPath:indexPath]; + if (node) { + // It is possible for UICollectionView to return indexPaths before the node is completed. + [visibleNodes addObject:node]; + } + } + return visibleNodes; } + #pragma mark Assertions. - (void)performBatchAnimated:(BOOL)animated updates:(void (^)())updates completion:(void (^)(BOOL))completion @@ -661,6 +662,8 @@ static BOOL _isInterceptedSelector(SEL sel) - (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath { ASCellNode *node = [_asyncDataSource collectionView:self nodeForItemAtIndexPath:indexPath]; + [node enterHierarchyState:ASHierarchyStateRangeManaged]; + ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); if (node.layoutDelegate == nil) { node.layoutDelegate = self; diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h new file mode 100644 index 0000000000..608f7be71b --- /dev/null +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -0,0 +1,19 @@ +/* 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. + */ + +@interface ASDisplayNode (Beta) + +/** @name Layout */ + + +/** + * @abstract Recursively ensures node and all subnodes are displayed. + */ +- (void)recursivelyEnsureDisplay; + +@end diff --git a/AsyncDisplayKit/ASDisplayNode+Subclasses.h b/AsyncDisplayKit/ASDisplayNode+Subclasses.h index d113beb494..1e23628635 100644 --- a/AsyncDisplayKit/ASDisplayNode+Subclasses.h +++ b/AsyncDisplayKit/ASDisplayNode+Subclasses.h @@ -414,10 +414,8 @@ NS_ASSUME_NONNULL_BEGIN */ - (nullable UIImage *)placeholderImage; - /** @name Description */ - /** * @abstract Return a description of the node * @@ -427,48 +425,6 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface ASDisplayNode (ASDisplayNodePrivate) -/** - * This method has proven helpful in a few rare scenarios, similar to a category extension on UIView, - * but it's considered private API for now and its use should not be encouraged. - * @param checkViewHierarchy If YES, and no supernode can be found, method will walk up from `self.view` to find a supernode. - * If YES, this method must be called on the main thread and the node must not be layer-backed. - */ -- (nullable ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy; - -// The two methods below will eventually be exposed, but their names are subject to change. -/** - * @abstract Ensure that all rendering is complete for this node and its descendents. - * - * @discussion Calling this method on the main thread after a node is added to the view heirarchy will ensure that - * placeholder states are never visible to the user. It is used by ASTableView, ASCollectionView, and ASViewController - * to implement their respective ".neverShowPlaceholders" option. - * - * If all nodes have layer.contents set and/or their layer does not have -needsDisplay set, the method will return immediately. - * - * This method is capable of handling a mixed set of nodes, with some not having started display, some in progress on an - * asynchronous display operation, and some already finished. - * - * In order to guarantee against deadlocks, this method should only be called on the main thread. - * It may block on the private queue, [_ASDisplayLayer displayQueue] - */ -- (void)recursivelyEnsureDisplay; - -/** - * @abstract Allows a node to bypass all ensureDisplay passes. Defaults to NO. - * - * @discussion Nodes that are expensive to draw and expected to have placeholder even with - * .neverShowPlaceholders enabled should set this to YES. - * - * ASImageNode uses the default of NO, as it is often used for UI images that are expected to synchronize with ensureDisplay. - * - * ASNetworkImageNode and ASMultiplexImageNode set this to YES, because they load data from a database or server, - * and are expected to support a placeholder state given that display is often blocked on slow data fetching. - */ -@property (nonatomic, assign) BOOL shouldBypassEnsureDisplay; - -@end - #define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created") #define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created") diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index 2c68b3b7e4..0ca022172e 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -7,8 +7,9 @@ */ #import "ASDisplayNode.h" -#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNode+FrameworkPrivate.h" #import "ASLayoutOptionsPrivate.h" #import @@ -38,6 +39,9 @@ @end +//#define LOG(...) NSLog(__VA_ARGS__) +#define LOG(...) + // Conditionally time these scopes to our debug ivars (only exist in debug/profile builds) #if TIME_DISPLAYNODE_OPS #define TIME_SCOPED(outVar) ASDN::ScopeTimer t(outVar) @@ -323,17 +327,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) #pragma mark - Core -- (void)__tearDown:(BOOL)tearDown subnodesOfNode:(ASDisplayNode *)node -{ - for (ASDisplayNode *subnode in node.subnodes) { - if (tearDown) { - [subnode __unloadNode]; - } else { - [subnode __loadNode]; - } - } -} - - (void)__unloadNode { ASDisplayNodeAssertThreadAffinity(self); @@ -357,22 +350,9 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) [self layer]; } -- (ASDisplayNode *)__rasterizedContainerNode -{ - ASDisplayNode *node = self.supernode; - while (node) { - if (node.shouldRasterizeDescendants) { - return node; - } - node = node.supernode; - } - - return nil; -} - - (BOOL)__shouldLoadViewOrLayer { - return ![self __rasterizedContainerNode]; + return !(_hierarchyState & ASHierarchyStateRasterized); } - (BOOL)__shouldSize @@ -645,28 +625,58 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) { ASDisplayNodeAssertThreadAffinity(self); ASDN::MutexLocker l(_propertyLock); + ASDisplayNodeAssert(!((_hierarchyState & ASHierarchyStateRasterized) && _flags.shouldRasterizeDescendants), + @"Subnode of a rasterized node should not have redundant shouldRasterizeDescendants enabled"); return _flags.shouldRasterizeDescendants; } -- (void)setShouldRasterizeDescendants:(BOOL)flag +- (void)setShouldRasterizeDescendants:(BOOL)shouldRasterize { ASDisplayNodeAssertThreadAffinity(self); - ASDN::MutexLocker l(_propertyLock); - - if (_flags.shouldRasterizeDescendants == flag) - return; - - _flags.shouldRasterizeDescendants = flag; + { + ASDN::MutexLocker l(_propertyLock); + + if (_flags.shouldRasterizeDescendants == shouldRasterize) + return; + + _flags.shouldRasterizeDescendants = shouldRasterize; + } if (self.isNodeLoaded) { - //recursively tear down or build up subnodes + // Recursively tear down or build up subnodes. + // TODO: When disabling rasterization, preserve rasterized backing store as placeholderImage + // while the newly materialized subtree finishes rendering. Then destroy placeholderImage to save memory. [self recursivelyClearContents]; - [self __tearDown:flag subnodesOfNode:self]; - if (flag == NO) { + + ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode *node) { + if (shouldRasterize) { + [node enterHierarchyState:ASHierarchyStateRasterized]; + [node __unloadNode]; + } else { + [node exitHierarchyState:ASHierarchyStateRasterized]; + [node __loadNode]; + } + }); + if (!shouldRasterize) { + // At this point all of our subnodes have their layers or views recreated, but we haven't added + // them to ours yet. This is because our node is already loaded, and the above recursion + // is only performed on our subnodes -- not self. [self _addSubnodeViewsAndLayers]; } - [self recursivelyDisplayImmediately]; + if (self.interfaceState & ASInterfaceStateVisible) { + // TODO: Change this to recursivelyEnsureDisplay - but need a variant that does not skip + // nodes that have shouldBypassEnsureDisplay set (such as image nodes) so they are rasterized. + [self recursivelyDisplayImmediately]; + } + } else { + ASDisplayNodePerformBlockOnEverySubnode(self, ^(ASDisplayNode *node) { + if (shouldRasterize) { + [node enterHierarchyState:ASHierarchyStateRasterized]; + } else { + [node exitHierarchyState:ASHierarchyStateRasterized]; + } + }); } } @@ -968,8 +978,8 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD [oldSubnode removeFromSupernode]; [_subnodes insertObject:subnode atIndex:subnodeIndex]; - // Don't bother inserting the view/layer if in a rasterized subtree, becuase there are no layers in the hierarchy and none of this could possibly work. - if (!_flags.shouldRasterizeDescendants && ![self __rasterizedContainerNode]) { + // Don't bother inserting the view/layer if in a rasterized subtree, because there are no layers in the hierarchy and none of this could possibly work. + if (!_flags.shouldRasterizeDescendants && [self __shouldLoadViewOrLayer]) { if (_layer) { ASDisplayNodeCAssertMainThread(); @@ -1089,8 +1099,8 @@ static NSInteger incrementIfFound(NSInteger i) { NSInteger aboveSubnodeIndex = [_subnodes indexOfObjectIdenticalTo:above]; NSInteger aboveSublayerIndex = NSNotFound; - // Don't bother figuring out the sublayerIndex if in a rasterized subtree, becuase there are no layers in the hierarchy and none of this could possibly work. - if (!_flags.shouldRasterizeDescendants && ![self __rasterizedContainerNode]) { + // Don't bother figuring out the sublayerIndex if in a rasterized subtree, because there are no layers in the hierarchy and none of this could possibly work. + if (!_flags.shouldRasterizeDescendants && [self __shouldLoadViewOrLayer]) { if (_layer) { aboveSublayerIndex = [_layer.sublayers indexOfObjectIdenticalTo:above.layer]; ASDisplayNodeAssert(aboveSublayerIndex != NSNotFound, @"Somehow above's supernode is self, yet we could not find it in our layers to replace"); @@ -1342,10 +1352,35 @@ static NSInteger incrementIfFound(NSInteger i) { return _supernode; } -- (void)__setSupernode:(ASDisplayNode *)supernode +- (void)__setSupernode:(ASDisplayNode *)newSupernode { - ASDN::MutexLocker l(_propertyLock); - _supernode = supernode; + BOOL supernodeDidChange = NO; + ASDisplayNode *oldSupernode = nil; + { + ASDN::MutexLocker l(_propertyLock); + if (_supernode != newSupernode) { + oldSupernode = _supernode; // Access supernode properties outside of lock to avoid remote chance of deadlock, + // in case supernode implementation must access one of our properties. + _supernode = newSupernode; + supernodeDidChange = YES; + } + } + + if (supernodeDidChange) { + ASHierarchyState stateToEnterOrExit = (newSupernode ? newSupernode.hierarchyState + : oldSupernode.hierarchyState); + + BOOL parentWasOrIsRasterized = (newSupernode ? newSupernode.shouldRasterizeDescendants + : oldSupernode.shouldRasterizeDescendants); + if (parentWasOrIsRasterized) { + stateToEnterOrExit |= ASHierarchyStateRasterized; + } + if (newSupernode) { + [self enterHierarchyState:stateToEnterOrExit]; + } else { + [self exitHierarchyState:stateToEnterOrExit]; + } + } } // Track that a node will be displayed as part of the current node hierarchy. @@ -1583,7 +1618,7 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) ASDisplayNodeAssert(_flags.isEnteringHierarchy, @"You should never call -willEnterHierarchy directly. Appearance is automatically managed by ASDisplayNode"); ASDisplayNodeAssert(!_flags.isExitingHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - if (![self supportsInterfaceState]) { + if (![self supportsRangeManagedInterfaceState]) { self.interfaceState = ASInterfaceStateInHierarchy; } } @@ -1594,7 +1629,7 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) ASDisplayNodeAssert(_flags.isExitingHierarchy, @"You should never call -didExitHierarchy directly. Appearance is automatically managed by ASDisplayNode"); ASDisplayNodeAssert(!_flags.isEnteringHierarchy, @"ASDisplayNode inconsistency. __enterHierarchy and __exitHierarchy are mutually exclusive"); - if (![self supportsInterfaceState]) { + if (![self supportsRangeManagedInterfaceState]) { self.interfaceState = ASInterfaceStateNone; } } @@ -1645,17 +1680,12 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) } /** - * We currently only set interface state on nodes - * in table/collection views. For other nodes, if they are - * in the hierarchy we return `Unknown`, otherwise we return `None`. - * - * TODO: Avoid traversing up node hierarchy due to possible deadlock. - * @see https://github.com/facebook/AsyncDisplayKit/issues/900 - * Possible solution is to push `isInCellNode` state downward on `addSubnode`/`removeFromSupernode`. + * We currently only set interface state on nodes in table/collection views. For other nodes, if they are + * in the hierarchy we enable all ASInterfaceState types with `ASInterfaceStateInHierarchy`, otherwise `None`. */ -- (BOOL)supportsInterfaceState { - return ([self isKindOfClass:ASCellNode.class] - || [self _supernodeWithClass:ASCellNode.class checkViewHierarchy:NO] != nil); +- (BOOL)supportsRangeManagedInterfaceState +{ + return (_hierarchyState & ASHierarchyStateRangeManaged); } - (ASInterfaceState)interfaceState @@ -1664,54 +1694,57 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) return _interfaceState; } -- (void)setInterfaceState:(ASInterfaceState)interfaceState +- (void)setInterfaceState:(ASInterfaceState)newState { - ASInterfaceState oldValue; + ASInterfaceState oldState = ASInterfaceStateNone; { ASDN::MutexLocker l(_propertyLock); - oldValue = _interfaceState; - _interfaceState = interfaceState; + if (_interfaceState == newState) { + return; + } + oldState = _interfaceState; + _interfaceState = newState; } - if (interfaceState != oldValue) { - if ((interfaceState & ASInterfaceStateMeasureLayout) != (oldValue & ASInterfaceStateMeasureLayout)) { - // Trigger asynchronous measurement if it is not already cached or being calculated. - } - - // Entered or exited data loading state. - if ((interfaceState & ASInterfaceStateFetchData) != (oldValue & ASInterfaceStateFetchData)) { - if (interfaceState & ASInterfaceStateFetchData) { - [self fetchData]; - } else { - [self clearFetchedData]; - } + if ((newState & ASInterfaceStateMeasureLayout) != (oldState & ASInterfaceStateMeasureLayout)) { + // Trigger asynchronous measurement if it is not already cached or being calculated. + } + + // Entered or exited data loading state. + if ((newState & ASInterfaceStateFetchData) != (oldState & ASInterfaceStateFetchData)) { + if (newState & ASInterfaceStateFetchData) { + [self fetchData]; + } else { + [self clearFetchedData]; } + } - // Entered or exited contents rendering state. - if ((interfaceState & ASInterfaceStateDisplay) != (oldValue & ASInterfaceStateDisplay)) { - if (interfaceState & ASInterfaceStateDisplay) { - // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. - [self setDisplaySuspended:NO]; - } else { - [self setDisplaySuspended:YES]; - [self clearContents]; - } + // Entered or exited contents rendering state. + if ((newState & ASInterfaceStateDisplay) != (oldState & ASInterfaceStateDisplay)) { + if (newState & ASInterfaceStateDisplay) { + // Once the working window is eliminated (ASRangeHandlerRender), trigger display directly here. + [self setDisplaySuspended:NO]; + } else { + [self setDisplaySuspended:YES]; + [self clearContents]; } + } - // Entered or exited data loading state. - if ((interfaceState & ASInterfaceStateVisible) != (oldValue & ASInterfaceStateVisible)) { - if (interfaceState & ASInterfaceStateVisible) { - // Consider providing a -didBecomeVisible. - } else { - // Consider providing a -didBecomeInvisible. - } + // Entered or exited data loading state. + if ((newState & ASInterfaceStateVisible) != (oldState & ASInterfaceStateVisible)) { + if (newState & ASInterfaceStateVisible) { + // Consider providing a -didBecomeVisible. + } else { + // Consider providing a -didBecomeInvisible. } - } } - (void)enterInterfaceState:(ASInterfaceState)interfaceState { + if (interfaceState == ASInterfaceStateNone) { + return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. + } ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { node.interfaceState |= interfaceState; }); @@ -1719,11 +1752,57 @@ void recursivelyEnsureDisplayForLayer(CALayer *layer) - (void)exitInterfaceState:(ASInterfaceState)interfaceState { + if (interfaceState == ASInterfaceStateNone) { + return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. + } ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { node.interfaceState &= (~interfaceState); }); } +- (ASHierarchyState)hierarchyState +{ + ASDN::MutexLocker l(_propertyLock); + return _hierarchyState; +} + +- (void)setHierarchyState:(ASHierarchyState)newState +{ + ASHierarchyState oldState = ASHierarchyStateNormal; + { + ASDN::MutexLocker l(_propertyLock); + if (_hierarchyState == newState) { + return; + } + oldState = _hierarchyState; + _hierarchyState = newState; + } + + if (newState != oldState) { + LOG(@"setHierarchyState: oldState = %lu, newState = %lu", (unsigned long)oldState, (unsigned long)newState); + } +} + +- (void)enterHierarchyState:(ASHierarchyState)hierarchyState +{ + if (hierarchyState == ASHierarchyStateNormal) { + return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. + } + ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { + node.hierarchyState |= hierarchyState; + }); +} + +- (void)exitHierarchyState:(ASHierarchyState)hierarchyState +{ + if (hierarchyState == ASHierarchyStateNormal) { + return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing. + } + ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) { + node.hierarchyState &= (~hierarchyState); + }); +} + - (void)layout { ASDisplayNodeAssertMainThread(); diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.h b/AsyncDisplayKit/ASDisplayNodeExtras.h index b387b374c7..e8e72c7391 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.h +++ b/AsyncDisplayKit/ASDisplayNodeExtras.h @@ -39,6 +39,12 @@ extern ASDisplayNode *ASDisplayNodeUltimateParentOfNode(ASDisplayNode *node); */ extern void ASDisplayNodePerformBlockOnEveryNode(CALayer * _Nullable layer, ASDisplayNode * _Nullable node, void(^block)(ASDisplayNode *node)); +/** + Identical to ASDisplayNodePerformBlockOnEveryNode, except it does not run the block on the + node provided directly to the function call - only on all descendants. + */ +extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, void(^block)(ASDisplayNode *node)); + /** Given a display node, traverses up the layer tree hierarchy, returning the first display node that passes block. */ diff --git a/AsyncDisplayKit/ASDisplayNodeExtras.mm b/AsyncDisplayKit/ASDisplayNodeExtras.mm index f34bd83a38..709f3c1843 100644 --- a/AsyncDisplayKit/ASDisplayNodeExtras.mm +++ b/AsyncDisplayKit/ASDisplayNodeExtras.mm @@ -7,8 +7,8 @@ */ #import "ASDisplayNodeExtras.h" - #import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" extern ASDisplayNode *ASLayerToDisplayNode(CALayer *layer) { @@ -46,6 +46,13 @@ extern void ASDisplayNodePerformBlockOnEveryNode(CALayer *layer, ASDisplayNode * } } +extern void ASDisplayNodePerformBlockOnEverySubnode(ASDisplayNode *node, void(^block)(ASDisplayNode *node)) +{ + for (ASDisplayNode *subnode in node.subnodes) { + ASDisplayNodePerformBlockOnEveryNode(nil, subnode, block); + } +} + id ASDisplayNodeFind(ASDisplayNode *node, BOOL (^block)(ASDisplayNode *node)) { CALayer *layer = node.layer; diff --git a/AsyncDisplayKit/ASEditableTextNode.mm b/AsyncDisplayKit/ASEditableTextNode.mm index d8da46498b..13554d88e5 100644 --- a/AsyncDisplayKit/ASEditableTextNode.mm +++ b/AsyncDisplayKit/ASEditableTextNode.mm @@ -124,7 +124,6 @@ textView.opaque = NO; } textView.textContainerInset = self.textContainerInset; - textView.clipsToBounds = NO; // We don't want selection handles cut off. }; // Create and configure the placeholder text view. diff --git a/AsyncDisplayKit/ASMapNode.h b/AsyncDisplayKit/ASMapNode.h new file mode 100644 index 0000000000..e5fcbd4921 --- /dev/null +++ b/AsyncDisplayKit/ASMapNode.h @@ -0,0 +1,43 @@ +/* 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 +#import + +@interface ASMapNode : ASImageNode + +- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate NS_DESIGNATED_INITIALIZER; + +/** + This is the MKMapView that is the live map part of ASMapNode. This will be nil if .liveMap = NO. Note, MKMapView is *not* thread-safe. + */ +@property (nonatomic, readonly) MKMapView *mapView; + +/** + Set this to YES to turn the snapshot into an interactive MKMapView and vice versa. Defaults to NO. + */ +@property (nonatomic, assign, getter=isLiveMap) BOOL liveMap; + +/** + @abstract Whether ASMapNode should automatically request a new map snapshot to correspond to the new node size. Defaults to YES. + @discussion If mapSize is set then this will be set to NO, since the size will be the same in all orientations. + */ +@property (nonatomic, assign) BOOL needsMapReloadOnBoundsChange; + +/** + Set the delegate of the MKMapView. This can be set even before mapView is created and will be set on the map in the case that the liveMap mode is engaged. + */ +@property (nonatomic, weak) id mapDelegate; + +/** + * @discussion This method set the annotations of the static map view and also to the live map view. Passing an empty array clears the map of any annotations. + * @param annotations An array of objects that conform to the MKAnnotation protocol + */ +- (void)setAnnotations:(NSArray *)annotations; + +@end diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm new file mode 100644 index 0000000000..311000362e --- /dev/null +++ b/AsyncDisplayKit/ASMapNode.mm @@ -0,0 +1,210 @@ +/* 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 "ASMapNode.h" +#import +#import +#import + +@interface ASMapNode() +{ + ASDN::RecursiveMutex _propertyLock; + MKMapSnapshotter *_snapshotter; + MKMapSnapshotOptions *_options; + NSArray *_annotations; + ASDisplayNode *_mapNode; + CLLocationCoordinate2D _centerCoordinateOfMap; +} +@end + +@implementation ASMapNode + +@synthesize liveMap = _liveMap; +@synthesize needsMapReloadOnBoundsChange = _needsMapReloadOnBoundsChange; +@synthesize mapDelegate = _mapDelegate; + +- (instancetype)initWithCoordinate:(CLLocationCoordinate2D)coordinate +{ + if (!(self = [super init])) { + return nil; + } + self.backgroundColor = ASDisplayNodeDefaultPlaceholderColor(); + self.clipsToBounds = YES; + + _needsMapReloadOnBoundsChange = YES; + _liveMap = NO; + _centerCoordinateOfMap = kCLLocationCoordinate2DInvalid; + + _options = [[MKMapSnapshotOptions alloc] init]; + _options.region = MKCoordinateRegionMakeWithDistance(coordinate, 1000, 1000);; + + return self; +} + +- (void)setAnnotations:(NSArray *)annotations +{ + ASDN::MutexLocker l(_propertyLock); + _annotations = [annotations copy]; + if (annotations.count != _annotations.count) { + // Redraw + [self setNeedsDisplay]; + } +} + +- (void)setUpSnapshotter +{ + if (!_snapshotter) { + ASDisplayNodeAssert(!CGSizeEqualToSize(CGSizeZero, self.calculatedSize), @"self.calculatedSize can not be zero. Make sure that you are setting a preferredFrameSize or wrapping ASMapNode in a ASRatioLayoutSpec or similar."); + _options.size = self.calculatedSize; + _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; + } +} + +- (BOOL)isLiveMap +{ + ASDN::MutexLocker l(_propertyLock); + return _liveMap; +} + +- (void)setLiveMap:(BOOL)liveMap +{ + ASDN::MutexLocker l(_propertyLock); + if (liveMap == _liveMap) { + return; + } + _liveMap = liveMap; + liveMap ? [self addLiveMap] : [self removeLiveMap]; +} + + +- (BOOL)needsMapReloadOnBoundsChange +{ + ASDN::MutexLocker l(_propertyLock); + return _needsMapReloadOnBoundsChange; +} + +- (void)setNeedsMapReloadOnBoundsChange:(BOOL)needsMapReloadOnBoundsChange +{ + ASDN::MutexLocker l(_propertyLock); + _needsMapReloadOnBoundsChange = needsMapReloadOnBoundsChange; +} + +- (void)fetchData +{ + [super fetchData]; + if (_liveMap && !_mapNode) { + [self addLiveMap]; + } + else { + [self setUpSnapshotter]; + [self takeSnapshot]; + } +} + +- (void)clearFetchedData +{ + [super clearFetchedData]; + [self removeLiveMap]; +} + +- (void)takeSnapshot +{ + if (!_snapshotter.isLoading) { + [_snapshotter startWithCompletionHandler:^(MKMapSnapshot *snapshot, NSError *error) { + if (!error) { + UIImage *image = snapshot.image; + CGRect finalImageRect = CGRectMake(0, 0, image.size.width, image.size.height); + + UIGraphicsBeginImageContextWithOptions(image.size, YES, image.scale); + [image drawAtPoint:CGPointMake(0, 0)]; + + if (_annotations.count > 0 ) { + // Get a standard annotation view pin. Future implementations should use a custom annotation image property. + MKAnnotationView *pin = [[MKPinAnnotationView alloc] initWithAnnotation:nil reuseIdentifier:@""]; + UIImage *pinImage = pin.image; + for (idannotation in _annotations) + { + CGPoint point = [snapshot pointForCoordinate:annotation.coordinate]; + if (CGRectContainsPoint(finalImageRect, point)) + { + CGPoint pinCenterOffset = pin.centerOffset; + point.x -= pin.bounds.size.width / 2.0; + point.y -= pin.bounds.size.height / 2.0; + point.x += pinCenterOffset.x; + point.y += pinCenterOffset.y; + [pinImage drawAtPoint:point]; + } + } + } + + UIImage *finalImage = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + self.image = finalImage; + } + }]; + } +} + +- (void)resetSnapshotter +{ + if (!_snapshotter.isLoading) { + _options.size = self.calculatedSize; + _snapshotter = [[MKMapSnapshotter alloc] initWithOptions:_options]; + } +} + +#pragma mark - Action +- (void)addLiveMap +{ + if (self.isNodeLoaded && !_mapNode) { + _mapNode = [[ASDisplayNode alloc]initWithViewBlock:^UIView *{ + _mapView = [[MKMapView alloc]initWithFrame:CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height)]; + _mapView.delegate = _mapDelegate; + [_mapView setRegion:_options.region]; + [_mapView addAnnotations:_annotations]; + return _mapView; + }]; + [self addSubnode:_mapNode]; + + if (CLLocationCoordinate2DIsValid(_centerCoordinateOfMap)) { + [_mapView setCenterCoordinate:_centerCoordinateOfMap]; + } + } +} + +- (void)removeLiveMap +{ + if (_mapNode) { + _centerCoordinateOfMap = _mapView.centerCoordinate; + [_mapNode removeFromSupernode]; + _mapView = nil; + _mapNode = nil; + } + self.image = nil; +} + +#pragma mark - Layout +// Layout isn't usually needed in the box model, but since we are making use of MKMapView which is hidden in an ASDisplayNode this is preferred. +- (void)layout +{ + [super layout]; + if (_mapView) { + _mapView.frame = CGRectMake(0.0f, 0.0f, self.calculatedSize.width, self.calculatedSize.height); + } + else { + // If our bounds.size is different from our current snapshot size, then let's request a new image from MKMapSnapshotter. + if (!CGSizeEqualToSize(_options.size, self.bounds.size)) { + if (_needsMapReloadOnBoundsChange && self.image) { + [self resetSnapshotter]; + [self takeSnapshot]; + } + } + } +} + +@end diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index f72fbdce39..c51e2bb2ba 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -17,6 +17,7 @@ #import "ASAvailability.h" #import "ASBaseDefines.h" #import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNode+FrameworkPrivate.h" #import "ASLog.h" #import "ASPhotosFrameworkImageRequest.h" #import "ASEqualityHelpers.h" diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index e7905bebf2..c1437ef814 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -10,8 +10,9 @@ #import "ASBasicImageDownloader.h" #import "ASDisplayNode+Subclasses.h" -#import "ASThread.h" +#import "ASDisplayNode+FrameworkPrivate.h" #import "ASEqualityHelpers.h" +#import "ASThread.h" @interface ASNetworkImageNode () { @@ -30,10 +31,8 @@ BOOL _imageLoaded; } - @end - @implementation ASNetworkImageNode - (instancetype)initWithCache:(id)cache downloader:(id)downloader diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 530a26f638..2c73367e96 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -10,16 +10,14 @@ #import "ASTableViewInternal.h" #import "ASAssert.h" +#import "ASBatchFetching.h" #import "ASChangeSetDataController.h" #import "ASCollectionViewLayoutController.h" -#import "ASLayoutController.h" -#import "ASRangeController.h" -#import "ASBatchFetching.h" +#import "ASDisplayNode+FrameworkPrivate.h" #import "ASInternalHelpers.h" #import "ASLayout.h" - -// FIXME: Temporary nonsense import until method names are finalized and exposed -#import "ASDisplayNode+Subclasses.h" +#import "ASLayoutController.h" +#import "ASRangeController.h" static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; @@ -381,11 +379,14 @@ static BOOL _isInterceptedSelector(SEL sel) NSArray *indexPaths = [self indexPathsForVisibleRows]; NSMutableArray *visibleNodes = [[NSMutableArray alloc] init]; - [indexPaths enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { - ASCellNode *visibleNode = [self nodeForRowAtIndexPath:obj]; - [visibleNodes addObject:visibleNode]; - }]; - + for (NSIndexPath *indexPath in indexPaths) { + ASCellNode *node = [self nodeForRowAtIndexPath:indexPath]; + if (node) { + // It is possible for UITableView to return indexPaths before the node is completed. + [visibleNodes addObject:node]; + } + } + return visibleNodes; } @@ -829,6 +830,8 @@ static BOOL _isInterceptedSelector(SEL sel) - (ASCellNode *)dataController:(ASDataController *)dataController nodeAtIndexPath:(NSIndexPath *)indexPath { ASCellNode *node = [_asyncDataSource tableView:self nodeForRowAtIndexPath:indexPath]; + [node enterHierarchyState:ASHierarchyStateRangeManaged]; + ASDisplayNodeAssert([node isKindOfClass:ASCellNode.class], @"invalid node class, expected ASCellNode"); if (node.layoutDelegate == nil) { node.layoutDelegate = self; diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index f5d3b0381c..315638093f 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -223,25 +223,13 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation - (void)setFrame:(CGRect)frame { [super setFrame:frame]; - if (!CGSizeEqualToSize(frame.size, _constrainedSize)) { - // Our bounds have changed to a size that is not identical to our constraining size, - // so our previous layout information is invalid, and TextKit may draw at the - // incorrect origin. - _constrainedSize = CGSizeMake(-INFINITY, -INFINITY); - [self _invalidateRenderer]; - } + [self _invalidateRendererIfNeeded:frame.size]; } - (void)setBounds:(CGRect)bounds { [super setBounds:bounds]; - if (!CGSizeEqualToSize(bounds.size, _constrainedSize)) { - // Our bounds have changed to a size that is not identical to our constraining size, - // so our previous layout information is invalid, and TextKit may draw at the - // incorrect origin. - _constrainedSize = CGSizeMake(-INFINITY, -INFINITY); - [self _invalidateRenderer]; - } + [self _invalidateRendererIfNeeded:bounds.size]; } #pragma mark - Renderer Management @@ -283,6 +271,27 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation _renderer = nil; } +- (void)_invalidateRendererIfNeeded +{ + [self _invalidateRendererIfNeeded:self.bounds.size]; +} + +- (void)_invalidateRendererIfNeeded:(CGSize)newSize +{ + if ([self _needInvalidateRenderer:newSize]) { + // Our bounds of frame have changed to a size that is not identical to our constraining size, + // so our previous layout information is invalid, and TextKit may draw at the + // incorrect origin. + _constrainedSize = CGSizeMake(-INFINITY, -INFINITY); + [self _invalidateRenderer]; + } +} + +- (BOOL)_needInvalidateRenderer:(CGSize)newSize +{ + return !CGSizeEqualToSize(newSize, _constrainedSize); +} + #pragma mark - Modifying User Text - (void)setAttributedString:(NSAttributedString *)attributedString { @@ -377,6 +386,8 @@ static NSString *ASTextNodeTruncationTokenAttributeName = @"ASTextNodeTruncation - (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer { + [self _invalidateRendererIfNeeded]; + // Offset the text origin by any shadow padding UIEdgeInsets shadowPadding = [self shadowPadding]; CGPoint textOrigin = CGPointMake(self.bounds.origin.x - shadowPadding.left, self.bounds.origin.y - shadowPadding.top); diff --git a/AsyncDisplayKit/ASViewController.m b/AsyncDisplayKit/ASViewController.m index 3509ccbe85..ab2c6741e6 100644 --- a/AsyncDisplayKit/ASViewController.m +++ b/AsyncDisplayKit/ASViewController.m @@ -9,9 +9,7 @@ #import "ASViewController.h" #import "ASAssert.h" #import "ASDimension.h" - -// FIXME: Temporary nonsense import until method names are finalized and exposed -#import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNode+FrameworkPrivate.h" @implementation ASViewController { diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 1c592134cb..7040834238 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -13,6 +13,7 @@ #import #import #import +#import #import diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 1e3f7029d4..e8028161d6 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -28,22 +28,29 @@ NSMutableDictionary *_pendingIndexPaths; } +- (instancetype)initWithAsyncDataFetching:(BOOL)asyncDataFetchingEnabled +{ + self = [super initWithAsyncDataFetching:asyncDataFetchingEnabled]; + if (self != nil) { + _pendingNodes = [NSMutableDictionary dictionary]; + _pendingIndexPaths = [NSMutableDictionary dictionary]; + } + return self; +} + - (void)prepareForReloadData { - _pendingNodes = [NSMutableDictionary dictionary]; - _pendingIndexPaths = [NSMutableDictionary dictionary]; - - [[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) { + for (NSString *kind in [self supplementaryKinds]) { LOG(@"Populating elements of kind: %@", kind); NSMutableArray *indexPaths = [NSMutableArray array]; NSMutableArray *nodes = [NSMutableArray array]; [self _populateSupplementaryNodesOfKind:kind withMutableNodes:nodes mutableIndexPaths:indexPaths]; _pendingNodes[kind] = nodes; _pendingIndexPaths[kind] = indexPaths; - + // Measure loaded nodes before leaving the main thread [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; - }]; + } } - (void)willReloadData @@ -56,7 +63,7 @@ NSArray *editingNodes = [self editingNodesOfKind:kind]; NSMutableIndexSet *indexSet = [[NSMutableIndexSet alloc] initWithIndexesInRange:NSMakeRange(0, editingNodes.count)]; [self deleteSectionsOfKind:kind atIndexSet:indexSet completion:nil]; - + // Insert each section NSUInteger sectionCount = [self.collectionDataSource dataController:self numberOfSectionsForSupplementaryNodeOfKind:kind]; NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount]; @@ -75,7 +82,7 @@ - (void)prepareForInsertSections:(NSIndexSet *)sections { - [[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) { + for (NSString *kind in [self supplementaryKinds]) { LOG(@"Populating elements of kind: %@, for sections: %@", kind, sections); NSMutableArray *nodes = [NSMutableArray array]; NSMutableArray *indexPaths = [NSMutableArray array]; @@ -85,7 +92,7 @@ // Measure loaded nodes before leaving the main thread [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; - }]; + } } - (void)willInsertSections:(NSIndexSet *)sections @@ -97,25 +104,27 @@ } [self insertSections:sectionArray ofKind:kind atIndexSet:sections completion:nil]; - [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:nil]; - _pendingNodes[kind] = nil; - _pendingIndexPaths[kind] = nil; + [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:^(NSArray *nodes, NSArray *indexPaths) { + [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; + }]; + [_pendingNodes removeObjectForKey:kind]; + [_pendingIndexPaths removeObjectForKey:kind]; }]; } - (void)willDeleteSections:(NSIndexSet *)sections { - [[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) { + for (NSString *kind in [self supplementaryKinds]) { NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], sections); [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; [self deleteSectionsOfKind:kind atIndexSet:sections completion:nil]; - }]; + } } - (void)prepareForReloadSections:(NSIndexSet *)sections { - [[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) { + for (NSString *kind in [self supplementaryKinds]) { NSMutableArray *nodes = [NSMutableArray array]; NSMutableArray *indexPaths = [NSMutableArray array]; [self _populateSupplementaryNodesOfKind:kind withSections:sections mutableNodes:nodes mutableIndexPaths:indexPaths]; @@ -124,7 +133,7 @@ // Measure loaded nodes before leaving the main thread [self layoutLoadedNodes:nodes ofKind:kind atIndexPaths:indexPaths]; - }]; + } } - (void)willReloadSections:(NSIndexSet *)sections @@ -134,14 +143,14 @@ [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; // reinsert the elements [self batchLayoutNodes:nodes ofKind:kind atIndexPaths:_pendingIndexPaths[kind] completion:nil]; - _pendingNodes[kind] = nil; - _pendingIndexPaths[kind] = nil; + [_pendingNodes removeObjectForKey:kind]; + [_pendingIndexPaths removeObjectForKey:kind]; }]; } - (void)willMoveSection:(NSInteger)section toSection:(NSInteger)newSection { - [[self supplementaryKinds] enumerateObjectsUsingBlock:^(NSString *kind, NSUInteger idx, BOOL *stop) { + for (NSString *kind in [self supplementaryKinds]) { NSArray *indexPaths = ASIndexPathsForMultidimensionalArrayAtIndexSet([self editingNodesOfKind:kind], [NSIndexSet indexSetWithIndex:section]); NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths([self editingNodesOfKind:kind], indexPaths); [self deleteNodesOfKind:kind atIndexPaths:indexPaths completion:nil]; @@ -153,7 +162,7 @@ [updatedIndexPaths addObject:[sectionIndexPath indexPathByAddingIndex:[indexPath indexAtPosition:indexPath.length - 1]]]; }]; [self insertNodes:nodes ofKind:kind atIndexPaths:indexPaths completion:nil]; - }]; + } } - (void)_populateSupplementaryNodesOfKind:(NSString *)kind withMutableNodes:(NSMutableArray *)nodes mutableIndexPaths:(NSMutableArray *)indexPaths @@ -214,4 +223,4 @@ return (id)self.dataSource; } -@end +@end \ No newline at end of file diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index e9a728c42e..aa774b5b41 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -13,6 +13,7 @@ #import "ASAssert.h" #import "ASCellNode.h" #import "ASDisplayNode.h" +#import "ASMainSerialQueue.h" #import "ASMultidimensionalArrayUtils.h" #import "ASInternalHelpers.h" #import "ASLayout.h" @@ -31,7 +32,7 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; NSMutableDictionary *_completedNodes; // Main thread only. External data access can immediately query this if _externalCompletedNodes is unavailable. NSMutableDictionary *_editingNodes; // Modified on _editingTransactionQueue only. Updates propogated to _completedNodes. - + ASMainSerialQueue *_mainSerialQueue; NSMutableArray *_pendingEditCommandBlocks; // To be run on the main thread. Handles begin/endUpdates tracking. NSOperationQueue *_editingTransactionQueue; // Serial background queue. Dispatches concurrent layout and manages _editingNodes. @@ -63,6 +64,8 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; _completedNodes[ASDataControllerRowNodeKind] = [NSMutableArray array]; _editingNodes[ASDataControllerRowNodeKind] = [NSMutableArray array]; + _mainSerialQueue = [[ASMainSerialQueue alloc] init]; + _pendingEditCommandBlocks = [NSMutableArray array]; _editingTransactionQueue = [[NSOperationQueue alloc] init]; @@ -208,12 +211,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. NSMutableArray *completedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(editingNodes); - ASPerformBlockOnMainThread(^{ + [_mainSerialQueue performBlockOnMainThread:^{ _completedNodes[kind] = completedNodes; if (completionBlock) { completionBlock(nodes, indexPaths); } - }); + }]; } - (void)deleteNodesOfKind:(NSString *)kind atIndexPaths:(NSArray *)indexPaths completion:(void (^)(NSArray *nodes, NSArray *indexPaths))completionBlock @@ -227,13 +230,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; ASDeleteElementsInMultidimensionalArrayAtIndexPaths(editingNodes, indexPaths); _editingNodes[kind] = editingNodes; - ASPerformBlockOnMainThread(^{ + [_mainSerialQueue performBlockOnMainThread:^{ NSArray *nodes = ASFindElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); ASDeleteElementsInMultidimensionalArrayAtIndexPaths(_completedNodes[kind], indexPaths); if (completionBlock) { completionBlock(nodes, indexPaths); } - }); + }]; } - (void)insertSections:(NSMutableArray *)sections ofKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSArray *sections, NSIndexSet *indexSet))completionBlock @@ -250,12 +253,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // Deep copy is critical here, or future edits to the sub-arrays will pollute state between _editing and _complete on different threads. NSArray *sectionsForCompleted = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(sections); - ASPerformBlockOnMainThread(^{ + [_mainSerialQueue performBlockOnMainThread:^{ [_completedNodes[kind] insertObjects:sectionsForCompleted atIndexes:indexSet]; if (completionBlock) { completionBlock(sections, indexSet); } - }); + }]; } - (void)deleteSectionsOfKind:(NSString *)kind atIndexSet:(NSIndexSet *)indexSet completion:(void (^)(NSIndexSet *indexSet))completionBlock @@ -263,12 +266,12 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; if (indexSet.count == 0) return; [_editingNodes[kind] removeObjectsAtIndexes:indexSet]; - ASPerformBlockOnMainThread(^{ + [_mainSerialQueue performBlockOnMainThread:^{ [_completedNodes[kind] removeObjectsAtIndexes:indexSet]; if (completionBlock) { completionBlock(indexSet); } - }); + }]; } #pragma mark - Internal Data Querying + Editing @@ -512,14 +515,14 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; LOG(@"endUpdatesWithCompletion - beginning"); [_editingTransactionQueue addOperationWithBlock:^{ - ASPerformBlockOnMainThread(^{ + [_mainSerialQueue performBlockOnMainThread:^{ // Deep copy _completedNodes to _externalCompletedNodes. // Any external queries from now on will be done on _externalCompletedNodes, to guarantee data consistency with the delegate. _externalCompletedNodes = (NSMutableArray *)ASMultidimensionalArrayDeepMutableCopy(_completedNodes[ASDataControllerRowNodeKind]); LOG(@"endUpdatesWithCompletion - begin updates call to delegate"); [_delegate dataControllerBeginUpdates:self]; - }); + }]; }]; // Running these commands may result in blocking on an _editingTransactionQueue operation that started even before -beginUpdates. @@ -532,13 +535,13 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; [_pendingEditCommandBlocks removeAllObjects]; [_editingTransactionQueue addOperationWithBlock:^{ - ASPerformBlockOnMainThread(^{ + [_mainSerialQueue performBlockOnMainThread:^{ // Now that the transaction is done, _completedNodes can be accessed externally again. _externalCompletedNodes = nil; LOG(@"endUpdatesWithCompletion - calling delegate end"); [_delegate dataController:self endUpdatesAnimated:animated completion:completion]; - }); + }]; }]; } } @@ -819,11 +822,11 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; // i.e there might be some nodes that were measured using the old constrained size but haven't been added to _completedNodes // (see _layoutNodes:atIndexPaths:withAnimationOptions:). [_editingTransactionQueue addOperationWithBlock:^{ - ASPerformBlockOnMainThread(^{ + [_mainSerialQueue performBlockOnMainThread:^{ for (NSString *kind in [_completedNodes keyEnumerator]) { [self _relayoutNodesOfKind:kind]; } - }); + }]; }]; }]; } @@ -902,7 +905,20 @@ static void *kASSizingQueueContext = &kASSizingQueueContext; - (ASCellNode *)nodeAtIndexPath:(NSIndexPath *)indexPath { ASDisplayNodeAssertMainThread(); - return [self completedNodes][indexPath.section][indexPath.row]; + + NSArray *completedNodes = [self completedNodes]; + NSInteger section = indexPath.section; + NSInteger row = indexPath.row; + ASCellNode *node = nil; + + if (section >= 0 && row >= 0 && section < completedNodes.count) { + NSArray *completedNodesSection = completedNodes[section]; + if (row < completedNodesSection.count) { + node = completedNodesSection[row]; + } + } + + return node; } - (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode; diff --git a/AsyncDisplayKit/Details/ASMainSerialQueue.h b/AsyncDisplayKit/Details/ASMainSerialQueue.h new file mode 100644 index 0000000000..77e05d579e --- /dev/null +++ b/AsyncDisplayKit/Details/ASMainSerialQueue.h @@ -0,0 +1,15 @@ +// +// ASMainSerialQueue.h +// AsyncDisplayKit +// +// Created by Garrett Moon on 12/11/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import + +@interface ASMainSerialQueue : NSObject + +- (void)performBlockOnMainThread:(dispatch_block_t)block; + +@end diff --git a/AsyncDisplayKit/Details/ASMainSerialQueue.mm b/AsyncDisplayKit/Details/ASMainSerialQueue.mm new file mode 100644 index 0000000000..f9ce3481c5 --- /dev/null +++ b/AsyncDisplayKit/Details/ASMainSerialQueue.mm @@ -0,0 +1,67 @@ +// +// ASMainSerialQueue.m +// AsyncDisplayKit +// +// Created by Garrett Moon on 12/11/15. +// Copyright © 2015 Facebook. All rights reserved. +// + +#import "ASMainSerialQueue.h" + +#import "ASThread.h" + +@interface ASMainSerialQueue () +{ + ASDN::Mutex _serialQueueLock; + NSMutableArray *_blocks; +} + +@end + +@implementation ASMainSerialQueue + +- (instancetype)init +{ + if (!(self = [super init])) { + return nil; + } + + _blocks = [[NSMutableArray alloc] init]; + return self; +} + +- (void)performBlockOnMainThread:(dispatch_block_t)block +{ + ASDN::MutexLocker l(_serialQueueLock); + [_blocks addObject:block]; + ASDN::MutexUnlocker u(_serialQueueLock); + [self runBlocks]; +} + +- (void)runBlocks +{ + dispatch_block_t mainThread = ^{ + do { + ASDN::MutexLocker l(_serialQueueLock); + dispatch_block_t block; + if (_blocks.count > 0) { + block = [_blocks objectAtIndex:0]; + [_blocks removeObjectAtIndex:0]; + } else { + break; + } + ASDN::MutexUnlocker u(_serialQueueLock); + block(); + } while (true); + }; + + if ([NSThread isMainThread]) { + mainThread(); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + mainThread(); + }); + } +} + +@end diff --git a/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm b/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm index adbbf5ecfb..09a3623a26 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerPreload.mm @@ -8,7 +8,7 @@ #import "ASRangeHandlerPreload.h" #import "ASDisplayNode.h" -#import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" @implementation ASRangeHandlerPreload diff --git a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm index b2a751a8bd..206b7d7e62 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerRender.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerRender.mm @@ -10,7 +10,7 @@ #import "ASDisplayNode.h" #import "ASDisplayNode+Subclasses.h" -#import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" @interface ASRangeHandlerRender () @property (nonatomic,readonly) UIWindow *workingWindow; diff --git a/AsyncDisplayKit/Details/ASRangeHandlerVisible.mm b/AsyncDisplayKit/Details/ASRangeHandlerVisible.mm index d8daf51889..f17bc1cad3 100644 --- a/AsyncDisplayKit/Details/ASRangeHandlerVisible.mm +++ b/AsyncDisplayKit/Details/ASRangeHandlerVisible.mm @@ -8,7 +8,7 @@ #import "ASRangeHandlerVisible.h" #import "ASDisplayNode.h" -#import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" @implementation ASRangeHandlerVisible diff --git a/AsyncDisplayKit/Details/_ASDisplayLayer.mm b/AsyncDisplayKit/Details/_ASDisplayLayer.mm index 99e4bf68e8..12622d3bb6 100644 --- a/AsyncDisplayKit/Details/_ASDisplayLayer.mm +++ b/AsyncDisplayKit/Details/_ASDisplayLayer.mm @@ -14,6 +14,7 @@ #import "ASAssert.h" #import "ASDisplayNode.h" #import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" @implementation _ASDisplayLayer { diff --git a/AsyncDisplayKit/Details/_ASDisplayView.mm b/AsyncDisplayKit/Details/_ASDisplayView.mm index 8b5300269d..027a312733 100644 --- a/AsyncDisplayKit/Details/_ASDisplayView.mm +++ b/AsyncDisplayKit/Details/_ASDisplayView.mm @@ -15,6 +15,7 @@ #import "ASAssert.h" #import "ASDisplayNodeExtras.h" #import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNode+Subclasses.h" @interface _ASDisplayView () diff --git a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm index e74cd00e35..4242881a5b 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+AsyncDisplay.mm @@ -10,6 +10,7 @@ #import "_ASAsyncTransaction.h" #import "ASAssert.h" #import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+FrameworkPrivate.h" @implementation ASDisplayNode (AsyncDisplay) @@ -84,9 +85,9 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, return; } - BOOL rasterizingFromAscendent = [self __rasterizedContainerNode] != nil; + BOOL rasterizingFromAscendent = (_hierarchyState & ASHierarchyStateRasterized); - // if super node is rasterizing descendents, subnodes will not have had layout calls becase they don't have layers + // if super node is rasterizing descendents, subnodes will not have had layout calls because they don't have layers if (rasterizingFromAscendent) { [self __layout]; } @@ -178,7 +179,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, asyncdisplaykit_async_transaction_operation_block_t displayBlock = nil; - ASDisplayNodeAssert(rasterizing || ![self __rasterizedContainerNode], @"Rasterized descendants should never display unless being drawn into the rasterized container."); + ASDisplayNodeAssert(rasterizing || !(_hierarchyState & ASHierarchyStateRasterized), @"Rasterized descendants should never display unless being drawn into the rasterized container."); if (!rasterizing && self.shouldRasterizeDescendants) { CGRect bounds = self.bounds; @@ -296,7 +297,7 @@ static void __ASDisplayLayerDecrementConcurrentDisplayCount(BOOL displayIsAsync, ASDN::MutexLocker l(_propertyLock); - if ([self __rasterizedContainerNode]) { + if (_hierarchyState & ASHierarchyStateRasterized) { return; } diff --git a/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h new file mode 100644 index 0000000000..58cc12c35c --- /dev/null +++ b/AsyncDisplayKit/Private/ASDisplayNode+FrameworkPrivate.h @@ -0,0 +1,109 @@ +/* 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. + */ + +// +// The following methods are ONLY for use by _ASDisplayLayer, _ASDisplayView, and ASDisplayNode. +// These methods must never be called or overridden by other classes. +// + +#import "_ASDisplayLayer.h" +#import "_AS-objc-internal.h" +#import "ASDisplayNodeExtraIvars.h" +#import "ASDisplayNode.h" +#import "ASSentinel.h" +#import "ASThread.h" +#import "ASLayoutOptions.h" + +/** + Hierarchy state is propogated from nodes to all of their children when certain behaviors are required from the subtree. + Examples include rasterization and external driving of the .interfaceState property. + By passing this information explicitly, performance is optimized by avoiding iteration up the supernode chain. + Lastly, this avoidance of supernode traversal protects against the possibility of deadlocks when a supernode is + simultaneously attempting to materialize views / layers for its subtree (as many related methods require property locking) + + Note: as the hierarchy deepens, more state properties may be enabled. However, state properties may never be disabled / + cancelled below the point they are enabled. They continue to the leaves of the hierarchy. + */ + +typedef NS_OPTIONS(NSUInteger, ASHierarchyState) +{ + /** The node may or may not have a supernode, but no supernode has a special hierarchy-influencing option enabled. */ + ASHierarchyStateNormal = 0, + /** The node has a supernode with .shouldRasterizeDescendants = YES. + Note: the root node of the rasterized subtree (the one with the property set on it) will NOT have this state set. */ + ASHierarchyStateRasterized = 1 << 0, + /** The node or one of its supernodes is managed by a class like ASRangeController. Most commonly, these nodes are + ASCellNode objects or a subnode of one, and are used in ASTableView or ASCollectionView. + These nodes also recieve regular updates to the .interfaceState property with more detailed status information. */ + ASHierarchyStateRangeManaged = 1 << 1, +}; + +@interface ASDisplayNode () <_ASDisplayLayerDelegate> +{ +@protected + ASInterfaceState _interfaceState; + ASHierarchyState _hierarchyState; +} + +// These methods are recursive, and either union or remove the provided interfaceState to all sub-elements. +- (void)enterInterfaceState:(ASInterfaceState)interfaceState; +- (void)exitInterfaceState:(ASInterfaceState)interfaceState; + +// These methods are recursive, and either union or remove the provided hierarchyState to all sub-elements. +- (void)enterHierarchyState:(ASHierarchyState)hierarchyState; +- (void)exitHierarchyState:(ASHierarchyState)hierarchyState; + +/** + * @abstract Returns the Hierarchy State of the node. + * + * @return The current ASHierarchyState of the node, indicating whether it is rasterized or managed by a range controller. + * + * @see ASInterfaceState + */ +@property (nonatomic, readwrite) ASHierarchyState hierarchyState; + +// The two methods below will eventually be exposed, but their names are subject to change. +/** + * @abstract Ensure that all rendering is complete for this node and its descendents. + * + * @discussion Calling this method on the main thread after a node is added to the view heirarchy will ensure that + * placeholder states are never visible to the user. It is used by ASTableView, ASCollectionView, and ASViewController + * to implement their respective ".neverShowPlaceholders" option. + * + * If all nodes have layer.contents set and/or their layer does not have -needsDisplay set, the method will return immediately. + * + * This method is capable of handling a mixed set of nodes, with some not having started display, some in progress on an + * asynchronous display operation, and some already finished. + * + * In order to guarantee against deadlocks, this method should only be called on the main thread. + * It may block on the private queue, [_ASDisplayLayer displayQueue] + */ +- (void)recursivelyEnsureDisplay; + +/** + * @abstract Allows a node to bypass all ensureDisplay passes. Defaults to NO. + * + * @discussion Nodes that are expensive to draw and expected to have placeholder even with + * .neverShowPlaceholders enabled should set this to YES. + * + * ASImageNode uses the default of NO, as it is often used for UI images that are expected to synchronize with ensureDisplay. + * + * ASNetworkImageNode and ASMultiplexImageNode set this to YES, because they load data from a database or server, + * and are expected to support a placeholder state given that display is often blocked on slow data fetching. + */ +@property (nonatomic, assign) BOOL shouldBypassEnsureDisplay; + +@end + +@interface UIView (ASDisplayNodeInternal) +@property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node; +@end + +@interface CALayer (ASDisplayNodeInternal) +@property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node; +@end diff --git a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm index e9ec7d72e3..b23b994f8c 100644 --- a/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm +++ b/AsyncDisplayKit/Private/ASDisplayNode+UIViewBridge.mm @@ -8,9 +8,11 @@ #import "_ASCoreAnimationExtras.h" #import "_ASPendingState.h" +#import "ASInternalHelpers.h" #import "ASAssert.h" -#import "ASDisplayNode+Subclasses.h" #import "ASDisplayNodeInternal.h" +#import "ASDisplayNode+Subclasses.h" +#import "ASDisplayNode+FrameworkPrivate.h" #import "ASEqualityHelpers.h" /** @@ -219,9 +221,22 @@ - (void)setNeedsDisplay { - ASDisplayNode *rasterizedContainerNode = [self __rasterizedContainerNode]; - if (rasterizedContainerNode) { - [rasterizedContainerNode setNeedsDisplay]; + if (_hierarchyState & ASHierarchyStateRasterized) { + ASPerformBlockOnMainThread(^{ + // The below operation must be performed on the main thread to ensure against an extremely rare deadlock, where a parent node + // begins materializing the view / layer heirarchy (locking itself or a descendant) while this node walks up + // the tree and requires locking that node to access .shouldRasterizeDescendants. + // For this reason, this method should be avoided when possible. Use _hierarchyState & ASHierarchyStateRasterized. + ASDisplayNodeAssertMainThread(); + ASDisplayNode *rasterizedContainerNode = self.supernode; + while (rasterizedContainerNode) { + if (rasterizedContainerNode.shouldRasterizeDescendants) { + break; + } + rasterizedContainerNode = rasterizedContainerNode.supernode; + } + [rasterizedContainerNode setNeedsDisplay]; + }); } else { [_layer setNeedsDisplay]; } diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 0d3ab7efe3..f30361a3ab 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -22,13 +22,14 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)()); -typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { - ASDisplayNodeMethodOverrideNone = 0, - ASDisplayNodeMethodOverrideTouchesBegan = 1 << 0, - ASDisplayNodeMethodOverrideTouchesCancelled = 1 << 1, - ASDisplayNodeMethodOverrideTouchesEnded = 1 << 2, - ASDisplayNodeMethodOverrideTouchesMoved = 1 << 3, - ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4 +typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) +{ + ASDisplayNodeMethodOverrideNone = 0, + ASDisplayNodeMethodOverrideTouchesBegan = 1 << 0, + ASDisplayNodeMethodOverrideTouchesCancelled = 1 << 1, + ASDisplayNodeMethodOverrideTouchesEnded = 1 << 2, + ASDisplayNodeMethodOverrideTouchesMoved = 1 << 3, + ASDisplayNodeMethodOverrideLayoutSpecThatFits = 1 << 4 }; @class _ASPendingState; @@ -73,8 +74,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { _ASPendingState *_pendingViewState; - ASInterfaceState _interfaceState; - struct ASDisplayNodeFlags { // public properties unsigned synchronous:1; @@ -118,9 +117,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { // Bitmask to check which methods an object overrides. @property (nonatomic, assign, readonly) ASDisplayNodeMethodOverrides methodOverrides; -// These methods are recursive, and either union or remove the provided interfaceState to all sub-elements. -- (void)enterInterfaceState:(ASInterfaceState)interfaceState; -- (void)exitInterfaceState:(ASInterfaceState)interfaceState; // Swizzle to extend the builtin functionality with custom logic - (BOOL)__shouldLoadViewOrLayer; @@ -149,9 +145,6 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { // Display the node's view/layer immediately on the current thread, bypassing the background thread rendering. Will be deprecated. - (void)displayImmediately; -// Returns the ancestor node that rasterizes descendants, or nil if none. -- (ASDisplayNode *)__rasterizedContainerNode; - // Alternative initialiser for backing with a custom view class. Supports asynchronous display with _ASDisplayView subclasses. - (id)initWithViewClass:(Class)viewClass; @@ -160,12 +153,12 @@ typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { @property (nonatomic, assign) CGFloat contentsScaleForDisplay; -@end +/** + * This method has proven helpful in a few rare scenarios, similar to a category extension on UIView, + * but it's considered private API for now and its use should not be encouraged. + * @param checkViewHierarchy If YES, and no supernode can be found, method will walk up from `self.view` to find a supernode. + * If YES, this method must be called on the main thread and the node must not be layer-backed. + */ +- (ASDisplayNode *)_supernodeWithClass:(Class)supernodeClass checkViewHierarchy:(BOOL)checkViewHierarchy; -@interface UIView (ASDisplayNodeInternal) -@property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node; -@end - -@interface CALayer (ASDisplayNodeInternal) -@property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node; @end diff --git a/AsyncDisplayKitTests/ASDisplayNodeTests.m b/AsyncDisplayKitTests/ASDisplayNodeTests.m index e936c33c0a..f67fcad822 100644 --- a/AsyncDisplayKitTests/ASDisplayNodeTests.m +++ b/AsyncDisplayKitTests/ASDisplayNodeTests.m @@ -1722,7 +1722,7 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point ASTestWindow *window = [ASTestWindow new]; [window addSubview:cellNode.view]; XCTAssert(node.hasFetchedData); - XCTAssert(node.interfaceState == ASInterfaceStateFetchData); + XCTAssert(node.interfaceState == ASInterfaceStateInHierarchy); } - (void)testInitWithViewClass diff --git a/AsyncDisplayKitTests/ASSnapshotTestCase.mm b/AsyncDisplayKitTests/ASSnapshotTestCase.mm index a70b21a3d2..32c96590d6 100644 --- a/AsyncDisplayKitTests/ASSnapshotTestCase.mm +++ b/AsyncDisplayKitTests/ASSnapshotTestCase.mm @@ -7,6 +7,7 @@ */ #import "ASSnapshotTestCase.h" +#import "ASDisplayNode+FrameworkPrivate.h" #import "ASDisplayNodeInternal.h" @implementation ASSnapshotTestCase @@ -46,6 +47,7 @@ + (void)hackilySynchronouslyRecursivelyRenderNode:(ASDisplayNode *)node { +// TODO: Reconfigure this to be able to use [node recursivelyEnsureDisplay]; [self _recursivelySetDisplaysAsynchronously:NO forNode:node]; [self _recursivelyLayoutAndDisplayNode:node]; } diff --git a/Podfile.lock b/Podfile.lock deleted file mode 100644 index cf8e89d790..0000000000 --- a/Podfile.lock +++ /dev/null @@ -1,13 +0,0 @@ -PODS: - - FBSnapshotTestCase (1.8.1) - - OCMock (2.2.4) - -DEPENDENCIES: - - FBSnapshotTestCase (~> 1.8.1) - - OCMock (~> 2.2) - -SPEC CHECKSUMS: - FBSnapshotTestCase: 3dc3899168747a0319c5278f5b3445c13a6532dd - OCMock: a6a7dc0e3997fb9f35d99f72528698ebf60d64f2 - -COCOAPODS: 0.38.2 diff --git a/README.md b/README.md index 2a249299a1..f7743eced1 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ pod 'AsyncDisplayKit' (ASDK can also be used as a regular static library: Copy the project to your codebase manually, adding `AsyncDisplayKit.xcodeproj` to your workspace. Add -`libAsyncDisplayKit.a`, AssetsLibrary, and Photos to the "Link Binary With +`libAsyncDisplayKit.a`, MapKit, AssetsLibrary, and Photos to the "Link Binary With Libraries" build phase. Include `-lc++ -ObjC` in your project linker flags.) Import the framework header, or create an [Objective-C bridging diff --git a/examples/ASCollectionView/Sample.xcworkspace/contents.xcworkspacedata b/examples/ASCollectionView/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 7b5a2f3050..0000000000 --- a/examples/ASCollectionView/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/examples/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj b/examples/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj index 30be626c5c..e066c2fcd1 100644 --- a/examples/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj +++ b/examples/ASTableViewStressTest/Sample.xcodeproj/project.pbxproj @@ -118,7 +118,6 @@ 05E2127E19D4DB510098F589 /* Frameworks */, 05E2127F19D4DB510098F589 /* Resources */, F012A6F39E0149F18F564F50 /* Copy Pods Resources */, - E671F9E92DFB9088485E493B /* Embed Pods Frameworks */, ); buildRules = ( ); @@ -190,21 +189,6 @@ shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; - E671F9E92DFB9088485E493B /* Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Embed Pods Frameworks"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods/Pods-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; F012A6F39E0149F18F564F50 /* Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/examples/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata b/examples/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/ASTableViewStressTest/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/CustomCollectionView/Sample.xcworkspace/contents.xcworkspacedata b/examples/CustomCollectionView/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 7b5a2f3050..0000000000 --- a/examples/CustomCollectionView/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/examples/EditableText/Sample.xcworkspace/contents.xcworkspacedata b/examples/EditableText/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/EditableText/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/HorizontalWithinVerticalScrolling/Sample.xcworkspace/contents.xcworkspacedata b/examples/HorizontalWithinVerticalScrolling/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/HorizontalWithinVerticalScrolling/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/Kittens/Sample.xcworkspace/contents.xcworkspacedata b/examples/Kittens/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/Kittens/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/Multiplex/Sample.xcworkspace/contents.xcworkspacedata b/examples/Multiplex/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/Multiplex/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/Placeholders/Sample.xcworkspace/contents.xcworkspacedata b/examples/Placeholders/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/Placeholders/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/SocialAppLayout/Sample.xcworkspace/contents.xcworkspacedata b/examples/SocialAppLayout/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index 7b5a2f3050..0000000000 --- a/examples/SocialAppLayout/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - diff --git a/examples/SocialAppLayout/Sample.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/examples/SocialAppLayout/Sample.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings deleted file mode 100644 index 54782e32fd..0000000000 --- a/examples/SocialAppLayout/Sample.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings +++ /dev/null @@ -1,8 +0,0 @@ - - - - - IDEWorkspaceSharedSettings_AutocreateContextsIfNeeded - - - diff --git a/examples/Swift/Sample.xcworkspace/contents.xcworkspacedata b/examples/Swift/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/Swift/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata b/examples/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/SynchronousConcurrency/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/SynchronousKittens/Sample.xcworkspace/contents.xcworkspacedata b/examples/SynchronousKittens/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/SynchronousKittens/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/VerticalWithinHorizontalScrolling/Sample.xcworkspace/contents.xcworkspacedata b/examples/VerticalWithinHorizontalScrolling/Sample.xcworkspace/contents.xcworkspacedata deleted file mode 100644 index d98549fd35..0000000000 --- a/examples/VerticalWithinHorizontalScrolling/Sample.xcworkspace/contents.xcworkspacedata +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj index 3a7e63f9d1..aaaa497852 100644 --- a/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj +++ b/smoke-tests/Life Without CocoaPods/Life Without CocoaPods.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ 058969281ABCE1750059CE2A /* libAsyncDisplayKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 058969271ABCE1750059CE2A /* libAsyncDisplayKit.a */; }; 0589692A1ABCE17C0059CE2A /* AssetsLibrary.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 058969291ABCE17C0059CE2A /* AssetsLibrary.framework */; }; 0589692C1ABCE1820059CE2A /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0589692B1ABCE1820059CE2A /* Photos.framework */; }; + 92DD2FEC1BF4D8BB0074C9DD /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 92DD2FEB1BF4D8BB0074C9DD /* MapKit.framework */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -32,6 +33,7 @@ 058969271ABCE1750059CE2A /* libAsyncDisplayKit.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libAsyncDisplayKit.a; path = "../../build/Debug-iphoneos/libAsyncDisplayKit.a"; sourceTree = ""; }; 058969291ABCE17C0059CE2A /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = System/Library/Frameworks/AssetsLibrary.framework; sourceTree = SDKROOT; }; 0589692B1ABCE1820059CE2A /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = System/Library/Frameworks/Photos.framework; sourceTree = SDKROOT; }; + 92DD2FEB1BF4D8BB0074C9DD /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -39,6 +41,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 92DD2FEC1BF4D8BB0074C9DD /* MapKit.framework in Frameworks */, 0589692C1ABCE1820059CE2A /* Photos.framework in Frameworks */, 0589692A1ABCE17C0059CE2A /* AssetsLibrary.framework in Frameworks */, 058969281ABCE1750059CE2A /* libAsyncDisplayKit.a in Frameworks */, @@ -51,6 +54,7 @@ 058968E61ABCE06E0059CE2A = { isa = PBXGroup; children = ( + 92DD2FEB1BF4D8BB0074C9DD /* MapKit.framework */, 0589692B1ABCE1820059CE2A /* Photos.framework */, 058969291ABCE17C0059CE2A /* AssetsLibrary.framework */, 058969271ABCE1750059CE2A /* libAsyncDisplayKit.a */,