mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-17 19:09:56 +00:00
ASDisplayNode and several subclasses had previously cleared memory-heavy objects like the backing store and text layout manager when the node's view or layer is removed from a visible heirarchy. This works great in any system that uses a "working range", where exiting the range removes the node from the hierarchy and reclaiming memory at that time is important. However, for standard UIViewController patterns (unused in Paper), this behavior causes highly undesirable thrashing (leading to visible flashes & wasteful re-rendering of content). After this change, node subclasses should implement -reclaimMemory if they need to perform any other cleanup besides backing store destruction when they leave a working range or other scenario where memory reduction is valuable. To trigger this behavior, calling code should use -recursivelyReclaimMemory. r=nadi
217 lines
6.7 KiB
Plaintext
217 lines
6.7 KiB
Plaintext
/* 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 "_ASDisplayView.h"
|
|
|
|
#import <objc/runtime.h>
|
|
|
|
#import "_ASCoreAnimationExtras.h"
|
|
#import "_ASAsyncTransactionContainer.h"
|
|
#import "ASAssert.h"
|
|
#import "ASDisplayNodeExtras.h"
|
|
#import "ASDisplayNodeInternal.h"
|
|
#import "ASDisplayNode+Subclasses.h"
|
|
|
|
@interface _ASDisplayView ()
|
|
@property (nonatomic, assign, readwrite) ASDisplayNode *asyncdisplaykit_node;
|
|
|
|
// Keep the node alive while its view is active. If you create a view, add its layer to a layer hierarchy, then release
|
|
// the view, the layer retains the view to prevent a crash. This replicates this behaviour for the node abstraction.
|
|
@property (nonatomic, retain, readwrite) ASDisplayNode *keepalive_node;
|
|
@end
|
|
|
|
@implementation _ASDisplayView
|
|
{
|
|
__unsafe_unretained ASDisplayNode *_node; // Though UIView has a .node property added via category, since we can add an ivar to a subclass, use that for performance.
|
|
BOOL _inHitTest;
|
|
BOOL _inPointInside;
|
|
}
|
|
|
|
@synthesize asyncdisplaykit_node = _node;
|
|
|
|
+ (Class)layerClass
|
|
{
|
|
return [_ASDisplayLayer class];
|
|
}
|
|
|
|
#pragma mark - NSObject Overrides
|
|
- (id)init
|
|
{
|
|
return [self initWithFrame:CGRectZero];
|
|
}
|
|
|
|
- (NSString *)description
|
|
{
|
|
// The standard UIView description is useless for debugging because all ASDisplayNode subclasses have _ASDisplayView-type views.
|
|
// This allows us to at least see the name of the node subclass and get its pointer directly from [[UIWindow keyWindow] recursiveDescription].
|
|
return [NSString stringWithFormat:@"<%@, view = %@>", _node, [super description]];
|
|
}
|
|
|
|
#pragma mark - UIView Overrides
|
|
|
|
- (id)initWithFrame:(CGRect)frame
|
|
{
|
|
if (!(self = [super initWithFrame:frame]))
|
|
return nil;
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)willMoveToSuperview:(UIView *)newSuperview
|
|
{
|
|
// Keep the node alive while the view is in a view hierarchy. This helps ensure that async-drawing views can always
|
|
// display their contents as long as they are visible somewhere, and aids in lifecycle management because the
|
|
// lifecycle of the node can be treated as the same as the lifecycle of the view (let the view hierarchy own the
|
|
// view).
|
|
UIView *currentSuperview = self.superview;
|
|
if (!currentSuperview && newSuperview) {
|
|
self.keepalive_node = _node;
|
|
}
|
|
else if (currentSuperview && !newSuperview) {
|
|
self.keepalive_node = nil;
|
|
}
|
|
}
|
|
|
|
- (void)willMoveToWindow:(UIWindow *)newWindow
|
|
{
|
|
BOOL visible = newWindow != nil;
|
|
if (visible && !_node.inWindow) {
|
|
[_node __enterHierarchy];
|
|
} else if (!visible && _node.inWindow) {
|
|
[_node __exitHierarchy];
|
|
}
|
|
}
|
|
|
|
- (void)didMoveToSuperview
|
|
{
|
|
// FIXME maybe move this logic into ASDisplayNode addSubnode/removeFromSupernode
|
|
UIView *superview = self.superview;
|
|
|
|
// If superview's node is different from supernode's view, fix it by setting supernode to the new superview's node. Got that?
|
|
if (!superview)
|
|
[_node __setSupernode:nil];
|
|
else if (superview != _node.supernode.view)
|
|
[_node __setSupernode:superview.asyncdisplaykit_node];
|
|
}
|
|
|
|
- (void)setNeedsDisplay
|
|
{
|
|
// Standard implementation does not actually get to the layer, at least for views that don't implement drawRect:.
|
|
if (ASDisplayNodeThreadIsMain()) {
|
|
[self.layer setNeedsDisplay];
|
|
} else {
|
|
dispatch_async(dispatch_get_main_queue(), ^ {
|
|
[self.layer setNeedsDisplay];
|
|
});
|
|
}
|
|
}
|
|
|
|
- (void)setNeedsLayout
|
|
{
|
|
if (ASDisplayNodeThreadIsMain()) {
|
|
[super setNeedsLayout];
|
|
} else {
|
|
dispatch_async(dispatch_get_main_queue(), ^ {
|
|
[super setNeedsLayout];
|
|
});
|
|
}
|
|
}
|
|
|
|
- (void)layoutSubviews
|
|
{
|
|
if (ASDisplayNodeThreadIsMain()) {
|
|
[_node __layout];
|
|
} else {
|
|
// FIXME: CRASH This should not be happening because of the way we gate -setNeedsLayout, but it has been seen.
|
|
ASDisplayNodeFailAssert(@"not reached assertion");
|
|
dispatch_async(dispatch_get_main_queue(), ^ {
|
|
[_node __layout];
|
|
});
|
|
}
|
|
}
|
|
|
|
- (UIViewContentMode)contentMode
|
|
{
|
|
return ASDisplayNodeUIContentModeFromCAContentsGravity(self.layer.contentsGravity);
|
|
}
|
|
|
|
- (void)setContentMode:(UIViewContentMode)contentMode
|
|
{
|
|
ASDisplayNodeAssert(contentMode != UIViewContentModeRedraw, @"Don't do this. Use needsDisplayOnBoundsChange instead.");
|
|
|
|
// Do our own mapping so as not to call super and muck up needsDisplayOnBoundsChange. If we're in a production build, fall back to resize if we see redraw
|
|
self.layer.contentsGravity = (contentMode != UIViewContentModeRedraw) ? ASDisplayNodeCAContentsGravityFromUIContentMode(contentMode) : kCAGravityResize;
|
|
}
|
|
|
|
#pragma mark - Event Handling + UIResponder Overrides
|
|
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
|
|
{
|
|
[_node touchesBegan:touches withEvent:event];
|
|
}
|
|
|
|
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
|
|
{
|
|
[_node touchesMoved:touches withEvent:event];
|
|
}
|
|
|
|
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
|
|
{
|
|
[_node touchesEnded:touches withEvent:event];
|
|
}
|
|
|
|
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
|
|
{
|
|
[_node touchesCancelled:touches withEvent:event];
|
|
}
|
|
|
|
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
|
|
{
|
|
// REVIEW: We should optimize these types of messages by setting a boolean in the associated ASDisplayNode subclass if
|
|
// they actually override the method. Same goes for -pointInside:withEvent: below. Many UIKit classes use that
|
|
// pattern for meaningful reductions of message send overhead in hot code (especially event handling).
|
|
|
|
// Set boolean so this method can be re-entrant. If the node subclass wants to default to / make use of UIView
|
|
// hitTest:, it will call it on the view, which is _ASDisplayView. After calling into the node, any additional calls
|
|
// should use the UIView implementation of hitTest:
|
|
if (!_inHitTest) {
|
|
_inHitTest = YES;
|
|
UIView *hitView = [_node hitTest:point withEvent:event];
|
|
_inHitTest = NO;
|
|
return hitView;
|
|
} else {
|
|
return [super hitTest:point withEvent:event];
|
|
}
|
|
}
|
|
|
|
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
|
|
{
|
|
// See comments in -hitTest:withEvent: for the strategy here.
|
|
if (!_inPointInside) {
|
|
_inPointInside = YES;
|
|
BOOL result = [_node pointInside:point withEvent:event];
|
|
_inPointInside = NO;
|
|
return result;
|
|
} else {
|
|
return [super pointInside:point withEvent:event];
|
|
}
|
|
}
|
|
|
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_6_0
|
|
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
|
|
{
|
|
return [_node gestureRecognizerShouldBegin:gestureRecognizer];
|
|
}
|
|
#endif
|
|
|
|
- (void)asyncdisplaykit_asyncTransactionContainerStateDidChange
|
|
{
|
|
[_node asyncdisplaykit_asyncTransactionContainerStateDidChange];
|
|
}
|
|
|
|
@end
|