@@ -575,7 +575,7 @@ - (void)__unloadNode
575575
576576- (BOOL )_locked_shouldLoadViewOrLayer
577577{
578- return !(_hierarchyState & ASHierarchyStateRasterized);
578+ return !_flags. isDeallocating && ! (_hierarchyState & ASHierarchyStateRasterized);
579579}
580580
581581- (UIView *)_locked_viewToLoad
@@ -633,61 +633,39 @@ - (CALayer *)_locked_layerToLoad
633633 return layer;
634634}
635635
636- - (void )_loadViewOrLayerIsLayerBacked : (BOOL )isLayerBacked
636+ - (void )_locked_loadViewOrLayerIsLayerBacked : (BOOL )isLayerBacked
637637{
638- {
639- ASDN::MutexLocker l (__instanceLock__);
640-
641- if (_flags.isDeallocating ) {
642- return ;
643- }
644-
645- if (![self _locked_shouldLoadViewOrLayer ]) {
646- return ;
647- }
648-
649- if (isLayerBacked) {
650- TIME_SCOPED (_debugTimeToCreateView);
651- _layer = [self _locked_layerToLoad ];
652- static int ASLayerDelegateAssociationKey;
653-
654- /* *
655- * CALayer's .delegate property is documented to be weak, but the implementation is actually assign.
656- * Because our layer may survive longer than the node (e.g. if someone else retains it, or if the node
657- * begins deallocation on a background thread and it waiting for the -dealloc call to reach main), the only
658- * way to avoid a dangling pointer is to use a weak proxy.
659- */
660- ASWeakProxy *instance = [ASWeakProxy weakProxyWithTarget: self ];
661- _layer.delegate = (id <CALayerDelegate >)instance;
662- objc_setAssociatedObject (_layer, &ASLayerDelegateAssociationKey, instance, OBJC_ASSOCIATION_RETAIN_NONATOMIC );
663- } else {
664- TIME_SCOPED (_debugTimeToCreateView);
665- _view = [self _locked_viewToLoad ];
666- _view.asyncdisplaykit_node = self;
667- _layer = _view.layer ;
668- }
669- _layer.asyncdisplaykit_node = self;
670-
671- self._locked_asyncLayer .asyncDelegate = self;
672- }
673-
674- {
675- TIME_SCOPED (_debugTimeToApplyPendingState);
676- [self _applyPendingStateToViewOrLayer ];
677- }
678- {
679- TIME_SCOPED (_debugTimeToAddSubnodeViews);
680- [self _addSubnodeViewsAndLayers ];
681- }
682- {
683- TIME_SCOPED (_debugTimeForDidLoad);
684- [self _didLoad ];
638+ if (isLayerBacked) {
639+ TIME_SCOPED (_debugTimeToCreateView);
640+ _layer = [self _locked_layerToLoad ];
641+ static int ASLayerDelegateAssociationKey;
642+
643+ /* *
644+ * CALayer's .delegate property is documented to be weak, but the implementation is actually assign.
645+ * Because our layer may survive longer than the node (e.g. if someone else retains it, or if the node
646+ * begins deallocation on a background thread and it waiting for the -dealloc call to reach main), the only
647+ * way to avoid a dangling pointer is to use a weak proxy.
648+ */
649+ ASWeakProxy *instance = [ASWeakProxy weakProxyWithTarget: self ];
650+ _layer.delegate = (id <CALayerDelegate >)instance;
651+ objc_setAssociatedObject (_layer, &ASLayerDelegateAssociationKey, instance, OBJC_ASSOCIATION_RETAIN_NONATOMIC );
652+ } else {
653+ TIME_SCOPED (_debugTimeToCreateView);
654+ _view = [self _locked_viewToLoad ];
655+ _view.asyncdisplaykit_node = self;
656+ _layer = _view.layer ;
685657 }
658+ _layer.asyncdisplaykit_node = self;
659+
660+ self._locked_asyncLayer .asyncDelegate = self;
686661}
687662
688663- (void )_didLoad
689664{
665+ ASDisplayNodeAssertMainThread ();
666+ ASDisplayNodeAssertLockUnownedByCurrentThread (__instanceLock__);
690667 ASDisplayNodeLogEvent (self, @" didLoad" );
668+ TIME_SCOPED (_debugTimeForDidLoad);
691669
692670 [self didLoad ];
693671
@@ -729,29 +707,95 @@ - (BOOL)_locked_isNodeLoaded
729707
730708- (UIView *)view
731709{
710+ ASDN::MutexLocker l (__instanceLock__);
711+
732712 ASDisplayNodeAssert (!_flags.layerBacked , @" Call to -view undefined on layer-backed nodes" );
733- if (_flags.layerBacked ) {
713+ BOOL isLayerBacked = _flags.layerBacked ;
714+ if (isLayerBacked) {
715+ return nil ;
716+ }
717+
718+ if (_view != nil ) {
719+ return _view;
720+ }
721+
722+ if (![self _locked_shouldLoadViewOrLayer ]) {
734723 return nil ;
735724 }
725+
726+ // Loading a view needs to happen on the main thread
727+ ASDisplayNodeAssertMainThread ();
728+ [self _locked_loadViewOrLayerIsLayerBacked: isLayerBacked];
729+
730+ // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout
731+ // but automatic subnode management would require us to modify the node tree
732+ // in the background on a loaded node, which isn't currently supported.
733+ if (_pendingViewState.hasSetNeedsLayout ) {
734+ // Need to unlock before calling setNeedsLayout to avoid deadlocks.
735+ // MutexUnlocker will re-lock at the end of scope.
736+ ASDN::MutexUnlocker u (__instanceLock__);
737+ [self __setNeedsLayout ];
738+ }
739+
740+ [self _locked_applyPendingStateToViewOrLayer ];
741+
742+ {
743+ // The following methods should not be called with a lock
744+ ASDN::MutexUnlocker u (__instanceLock__);
736745
737- if (_view == nil ) {
738- ASDisplayNodeAssertMainThread ();
739- [self _loadViewOrLayerIsLayerBacked: NO ];
746+ // No need for the lock as accessing the subviews or layers are always happening on main
747+ [self _addSubnodeViewsAndLayers ];
748+
749+ // A subclass hook should never be called with a lock
750+ [self _didLoad ];
740751 }
741752
742753 return _view;
743754}
744755
745756- (CALayer *)layer
746757{
747- if (_layer == nil ) {
748- ASDisplayNodeAssertMainThread ();
749-
750- if (!_flags.layerBacked ) {
751- return self.view .layer ;
752- }
758+ ASDN::MutexLocker l (__instanceLock__);
759+ if (_layer != nil ) {
760+ return _layer;
761+ }
762+
763+ BOOL isLayerBacked = _flags.layerBacked ;
764+ if (!isLayerBacked) {
765+ // No need for the lock and call the view explicitly in case it needs to be loaded first
766+ ASDN::MutexUnlocker u (__instanceLock__);
767+ return self.view .layer ;
768+ }
769+
770+ if (![self _locked_shouldLoadViewOrLayer ]) {
771+ return nil ;
772+ }
773+
774+ // Loading a layer needs to happen on the main thread
775+ ASDisplayNodeAssertMainThread ();
776+ [self _locked_loadViewOrLayerIsLayerBacked: isLayerBacked];
777+
778+ // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout
779+ // but automatic subnode management would require us to modify the node tree
780+ // in the background on a loaded node, which isn't currently supported.
781+ if (_pendingViewState.hasSetNeedsLayout ) {
782+ // Need to unlock before calling setNeedsLayout to avoid deadlocks.
783+ // MutexUnlocker will re-lock at the end of scope.
784+ ASDN::MutexUnlocker u (__instanceLock__);
785+ [self __setNeedsLayout ];
786+ }
787+
788+ [self _locked_applyPendingStateToViewOrLayer ];
789+
790+ {
791+ // The following methods should not be called with a lock
792+ ASDN::MutexUnlocker u (__instanceLock__);
793+
794+ // No need for the lock as accessing the subviews or layers are always happening on main
795+ [self _addSubnodeViewsAndLayers ];
753796
754- [self _loadViewOrLayerIsLayerBacked: YES ];
797+ // A subclass hook should never be called with a lock
798+ [self _didLoad ];
755799 }
756800
757801 return _layer;
@@ -2793,6 +2837,8 @@ - (void)_addSubnodeViewsAndLayers
27932837{
27942838 ASDisplayNodeAssertMainThread ();
27952839
2840+ TIME_SCOPED (_debugTimeToAddSubnodeViews);
2841+
27962842 for (ASDisplayNode *node in self.subnodes ) {
27972843 [self _addSubnodeSubviewOrSublayer: node];
27982844 }
@@ -3761,44 +3807,49 @@ - (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
37613807
37623808#pragma mark - Pending View State
37633809
3764- - (void )_applyPendingStateToViewOrLayer
3810+ - (void )_locked_applyPendingStateToViewOrLayer
37653811{
37663812 ASDisplayNodeAssertMainThread ();
37673813 ASDisplayNodeAssert (self.nodeLoaded , @" must have a view or layer" );
37683814
3815+ TIME_SCOPED (_debugTimeToApplyPendingState);
3816+
37693817 // If no view/layer properties were set before the view/layer were created, _pendingViewState will be nil and the default values
37703818 // for the view/layer are still valid.
3771-
3772- [self applyPendingViewState ];
3773-
3774- __instanceLock__.lock ();
3819+ [self _locked_applyPendingViewState ];
37753820
37763821 if (_flags.displaySuspended ) {
37773822 self._locked_asyncLayer .displaySuspended = YES ;
37783823 }
37793824 if (!_flags.displaysAsynchronously ) {
37803825 self._locked_asyncLayer .displaysAsynchronously = NO ;
37813826 }
3782-
3783- __instanceLock__.unlock ();
37843827}
37853828
37863829- (void )applyPendingViewState
37873830{
37883831 ASDisplayNodeAssertMainThread ();
3832+ ASDisplayNodeAssertLockUnownedByCurrentThread (__instanceLock__);
3833+
37893834 ASDN::MutexLocker l (__instanceLock__);
3790-
37913835 // FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout
37923836 // but automatic subnode management would require us to modify the node tree
37933837 // in the background on a loaded node, which isn't currently supported.
37943838 if (_pendingViewState.hasSetNeedsLayout ) {
3795- // Need to unlock before calling setNeedsLayout to avoid deadlocks.
3796- // MutexUnlocker will re-lock at the end of scope.
3839+ // Need to unlock before calling setNeedsLayout to avoid deadlocks.
3840+ // MutexUnlocker will re-lock at the end of scope.
37973841 ASDN::MutexUnlocker u (__instanceLock__);
37983842 [self __setNeedsLayout ];
37993843 }
3844+
3845+ [self _locked_applyPendingViewState ];
3846+ }
38003847
3801- if (self.layerBacked ) {
3848+ - (void )_locked_applyPendingViewState
3849+ {
3850+ ASDisplayNodeAssertMainThread ();
3851+
3852+ if (_flags.layerBacked ) {
38023853 [_pendingViewState applyToLayer: self .layer];
38033854 } else {
38043855 BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags (_flags);
0 commit comments