mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-11 17:00:02 +00:00
[ASDisplayNode] Add locking to view and layer in ASDisplayNode (#3179)
* Add locking to view and layer in ASDisplayNode * Another approach * Some more * Address comments
This commit is contained in:
parent
bf41847665
commit
ef2ed54d0b
@ -575,7 +575,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
|
||||
- (BOOL)_locked_shouldLoadViewOrLayer
|
||||
{
|
||||
return !(_hierarchyState & ASHierarchyStateRasterized);
|
||||
return !_flags.isDeallocating && !(_hierarchyState & ASHierarchyStateRasterized);
|
||||
}
|
||||
|
||||
- (UIView *)_locked_viewToLoad
|
||||
@ -633,61 +633,39 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
return layer;
|
||||
}
|
||||
|
||||
- (void)_loadViewOrLayerIsLayerBacked:(BOOL)isLayerBacked
|
||||
- (void)_locked_loadViewOrLayerIsLayerBacked:(BOOL)isLayerBacked
|
||||
{
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
if (_flags.isDeallocating) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (![self _locked_shouldLoadViewOrLayer]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isLayerBacked) {
|
||||
TIME_SCOPED(_debugTimeToCreateView);
|
||||
_layer = [self _locked_layerToLoad];
|
||||
static int ASLayerDelegateAssociationKey;
|
||||
|
||||
/**
|
||||
* CALayer's .delegate property is documented to be weak, but the implementation is actually assign.
|
||||
* Because our layer may survive longer than the node (e.g. if someone else retains it, or if the node
|
||||
* begins deallocation on a background thread and it waiting for the -dealloc call to reach main), the only
|
||||
* way to avoid a dangling pointer is to use a weak proxy.
|
||||
*/
|
||||
ASWeakProxy *instance = [ASWeakProxy weakProxyWithTarget:self];
|
||||
_layer.delegate = (id<CALayerDelegate>)instance;
|
||||
objc_setAssociatedObject(_layer, &ASLayerDelegateAssociationKey, instance, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
} else {
|
||||
TIME_SCOPED(_debugTimeToCreateView);
|
||||
_view = [self _locked_viewToLoad];
|
||||
_view.asyncdisplaykit_node = self;
|
||||
_layer = _view.layer;
|
||||
}
|
||||
_layer.asyncdisplaykit_node = self;
|
||||
|
||||
self._locked_asyncLayer.asyncDelegate = self;
|
||||
}
|
||||
|
||||
{
|
||||
TIME_SCOPED(_debugTimeToApplyPendingState);
|
||||
[self _applyPendingStateToViewOrLayer];
|
||||
}
|
||||
{
|
||||
TIME_SCOPED(_debugTimeToAddSubnodeViews);
|
||||
[self _addSubnodeViewsAndLayers];
|
||||
}
|
||||
{
|
||||
TIME_SCOPED(_debugTimeForDidLoad);
|
||||
[self _didLoad];
|
||||
if (isLayerBacked) {
|
||||
TIME_SCOPED(_debugTimeToCreateView);
|
||||
_layer = [self _locked_layerToLoad];
|
||||
static int ASLayerDelegateAssociationKey;
|
||||
|
||||
/**
|
||||
* CALayer's .delegate property is documented to be weak, but the implementation is actually assign.
|
||||
* Because our layer may survive longer than the node (e.g. if someone else retains it, or if the node
|
||||
* begins deallocation on a background thread and it waiting for the -dealloc call to reach main), the only
|
||||
* way to avoid a dangling pointer is to use a weak proxy.
|
||||
*/
|
||||
ASWeakProxy *instance = [ASWeakProxy weakProxyWithTarget:self];
|
||||
_layer.delegate = (id<CALayerDelegate>)instance;
|
||||
objc_setAssociatedObject(_layer, &ASLayerDelegateAssociationKey, instance, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
||||
} else {
|
||||
TIME_SCOPED(_debugTimeToCreateView);
|
||||
_view = [self _locked_viewToLoad];
|
||||
_view.asyncdisplaykit_node = self;
|
||||
_layer = _view.layer;
|
||||
}
|
||||
_layer.asyncdisplaykit_node = self;
|
||||
|
||||
self._locked_asyncLayer.asyncDelegate = self;
|
||||
}
|
||||
|
||||
- (void)_didLoad
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
|
||||
ASDisplayNodeLogEvent(self, @"didLoad");
|
||||
TIME_SCOPED(_debugTimeForDidLoad);
|
||||
|
||||
[self didLoad];
|
||||
|
||||
@ -729,14 +707,47 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
ASDisplayNodeAssert(!_flags.layerBacked, @"Call to -view undefined on layer-backed nodes");
|
||||
if (_flags.layerBacked) {
|
||||
BOOL isLayerBacked = _flags.layerBacked;
|
||||
if (isLayerBacked) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (_view == nil) {
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[self _loadViewOrLayerIsLayerBacked:NO];
|
||||
if (_view != nil) {
|
||||
return _view;
|
||||
}
|
||||
|
||||
if (![self _locked_shouldLoadViewOrLayer]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Loading a view needs to happen on the main thread
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[self _locked_loadViewOrLayerIsLayerBacked:isLayerBacked];
|
||||
|
||||
// FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout
|
||||
// but automatic subnode management would require us to modify the node tree
|
||||
// in the background on a loaded node, which isn't currently supported.
|
||||
if (_pendingViewState.hasSetNeedsLayout) {
|
||||
// Need to unlock before calling setNeedsLayout to avoid deadlocks.
|
||||
// MutexUnlocker will re-lock at the end of scope.
|
||||
ASDN::MutexUnlocker u(__instanceLock__);
|
||||
[self __setNeedsLayout];
|
||||
}
|
||||
|
||||
[self _locked_applyPendingStateToViewOrLayer];
|
||||
|
||||
{
|
||||
// The following methods should not be called with a lock
|
||||
ASDN::MutexUnlocker u(__instanceLock__);
|
||||
|
||||
// No need for the lock as accessing the subviews or layers are always happening on main
|
||||
[self _addSubnodeViewsAndLayers];
|
||||
|
||||
// A subclass hook should never be called with a lock
|
||||
[self _didLoad];
|
||||
}
|
||||
|
||||
return _view;
|
||||
@ -744,14 +755,47 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
|
||||
- (CALayer *)layer
|
||||
{
|
||||
if (_layer == nil) {
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
if (_layer != nil) {
|
||||
return _layer;
|
||||
}
|
||||
|
||||
BOOL isLayerBacked = _flags.layerBacked;
|
||||
if (!isLayerBacked) {
|
||||
// No need for the lock and call the view explicitly in case it needs to be loaded first
|
||||
ASDN::MutexUnlocker u(__instanceLock__);
|
||||
return self.view.layer;
|
||||
}
|
||||
|
||||
if (![self _locked_shouldLoadViewOrLayer]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Loading a layer needs to happen on the main thread
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[self _locked_loadViewOrLayerIsLayerBacked:isLayerBacked];
|
||||
|
||||
// FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout
|
||||
// but automatic subnode management would require us to modify the node tree
|
||||
// in the background on a loaded node, which isn't currently supported.
|
||||
if (_pendingViewState.hasSetNeedsLayout) {
|
||||
// Need to unlock before calling setNeedsLayout to avoid deadlocks.
|
||||
// MutexUnlocker will re-lock at the end of scope.
|
||||
ASDN::MutexUnlocker u(__instanceLock__);
|
||||
[self __setNeedsLayout];
|
||||
}
|
||||
|
||||
[self _locked_applyPendingStateToViewOrLayer];
|
||||
|
||||
{
|
||||
// The following methods should not be called with a lock
|
||||
ASDN::MutexUnlocker u(__instanceLock__);
|
||||
|
||||
// No need for the lock as accessing the subviews or layers are always happening on main
|
||||
[self _addSubnodeViewsAndLayers];
|
||||
|
||||
if (!_flags.layerBacked) {
|
||||
return self.view.layer;
|
||||
}
|
||||
|
||||
[self _loadViewOrLayerIsLayerBacked:YES];
|
||||
// A subclass hook should never be called with a lock
|
||||
[self _didLoad];
|
||||
}
|
||||
|
||||
return _layer;
|
||||
@ -2793,6 +2837,8 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
TIME_SCOPED(_debugTimeToAddSubnodeViews);
|
||||
|
||||
for (ASDisplayNode *node in self.subnodes) {
|
||||
[self _addSubnodeSubviewOrSublayer:node];
|
||||
}
|
||||
@ -3761,17 +3807,16 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
||||
|
||||
#pragma mark - Pending View State
|
||||
|
||||
- (void)_applyPendingStateToViewOrLayer
|
||||
- (void)_locked_applyPendingStateToViewOrLayer
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssert(self.nodeLoaded, @"must have a view or layer");
|
||||
|
||||
TIME_SCOPED(_debugTimeToApplyPendingState);
|
||||
|
||||
// If no view/layer properties were set before the view/layer were created, _pendingViewState will be nil and the default values
|
||||
// for the view/layer are still valid.
|
||||
|
||||
[self applyPendingViewState];
|
||||
|
||||
__instanceLock__.lock();
|
||||
[self _locked_applyPendingViewState];
|
||||
|
||||
if (_flags.displaySuspended) {
|
||||
self._locked_asyncLayer.displaySuspended = YES;
|
||||
@ -3779,26 +3824,32 @@ ASDISPLAYNODE_INLINE BOOL nodeIsInRasterizedTree(ASDisplayNode *node) {
|
||||
if (!_flags.displaysAsynchronously) {
|
||||
self._locked_asyncLayer.displaysAsynchronously = NO;
|
||||
}
|
||||
|
||||
__instanceLock__.unlock();
|
||||
}
|
||||
|
||||
- (void)applyPendingViewState
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
|
||||
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
|
||||
// FIXME: Ideally we'd call this as soon as the node receives -setNeedsLayout
|
||||
// but automatic subnode management would require us to modify the node tree
|
||||
// in the background on a loaded node, which isn't currently supported.
|
||||
if (_pendingViewState.hasSetNeedsLayout) {
|
||||
//Need to unlock before calling setNeedsLayout to avoid deadlocks.
|
||||
//MutexUnlocker will re-lock at the end of scope.
|
||||
// Need to unlock before calling setNeedsLayout to avoid deadlocks.
|
||||
// MutexUnlocker will re-lock at the end of scope.
|
||||
ASDN::MutexUnlocker u(__instanceLock__);
|
||||
[self __setNeedsLayout];
|
||||
}
|
||||
|
||||
[self _locked_applyPendingViewState];
|
||||
}
|
||||
|
||||
if (self.layerBacked) {
|
||||
- (void)_locked_applyPendingViewState
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (_flags.layerBacked) {
|
||||
[_pendingViewState applyToLayer:self.layer];
|
||||
} else {
|
||||
BOOL specialPropertiesHandling = ASDisplayNodeNeedsSpecialPropertiesHandlingForFlags(_flags);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user