Fix retain cycles in ASDisplayNode and ASTableView

This commit is contained in:
Ian Cloutier 2014-10-09 19:00:38 -04:00
parent f83f113493
commit 41c3289a11
6 changed files with 122 additions and 5 deletions

View File

@ -120,6 +120,7 @@
058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A45195D058D00B7D73C /* ASDisplayNodeExtraIvars.h */; settings = {ATTRIBUTES = (Public, ); }; };
05A6D05A19D0EB64002DD95E /* ASDealloc2MainObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */; settings = {ATTRIBUTES = (Public, ); }; };
05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C9C128419E616EF00E942A0 /* ASTableViewTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
6BDC61F61979037800E50D21 /* AsyncDisplayKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
DB7121BCD50849C498C886FB /* libPods-AsyncDisplayKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */; };
/* End PBXBuildFile section */
@ -238,6 +239,7 @@
058D0A45195D058D00B7D73C /* ASDisplayNodeExtraIvars.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASDisplayNodeExtraIvars.h; sourceTree = "<group>"; };
05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASDealloc2MainObject.h; path = ../Details/ASDealloc2MainObject.h; sourceTree = "<group>"; };
05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASDealloc2MainObject.m; path = ../Details/ASDealloc2MainObject.m; sourceTree = "<group>"; };
3C9C128419E616EF00E942A0 /* ASTableViewTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableViewTests.m; sourceTree = "<group>"; };
6BDC61F51978FEA400E50D21 /* AsyncDisplayKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AsyncDisplayKit.h; sourceTree = "<group>"; };
EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
FAD7085290B84183BD13BA1A /* Pods-AsyncDisplayKitTests.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-AsyncDisplayKitTests.xcconfig"; path = "Pods/Pods-AsyncDisplayKitTests.xcconfig"; sourceTree = "<group>"; };
@ -344,6 +346,7 @@
058D0A30195D057000B7D73C /* ASDisplayNodeTestsHelper.h */,
058D0A31195D057000B7D73C /* ASDisplayNodeTestsHelper.m */,
058D0A32195D057000B7D73C /* ASMutableAttributedStringBuilderTests.m */,
3C9C128419E616EF00E942A0 /* ASTableViewTests.m */,
058D0A33195D057000B7D73C /* ASTextNodeCoreTextAdditionsTests.m */,
058D0A34195D057000B7D73C /* ASTextNodeRendererTests.m */,
058D0A35195D057000B7D73C /* ASTextNodeShadowerTests.m */,
@ -688,6 +691,7 @@
058D0A39195D057000B7D73C /* ASDisplayNodeAppearanceTests.m in Sources */,
058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */,
058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */,
3C9C128519E616EF00E942A0 /* ASTableViewTests.m in Sources */,
058D0A38195D057000B7D73C /* ASDisplayLayerTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -236,7 +236,7 @@
/**
* @abstract The receiver's supernode.
*/
@property (nonatomic, readonly, assign) ASDisplayNode *supernode;
@property (nonatomic, readonly, weak) ASDisplayNode *supernode;
/** @name Drawing and Updating the View */

View File

@ -47,8 +47,8 @@ static BOOL _isInterceptedSelector(SEL sel)
@end
@implementation _ASTableViewProxy {
id<NSObject> _target;
ASTableView *_interceptor;
id<NSObject> __weak _target;
ASTableView * __weak _interceptor;
}
- (instancetype)initWithTarget:(id<NSObject>)target interceptor:(ASTableView *)interceptor
@ -140,7 +140,8 @@ static BOOL _isInterceptedSelector(SEL sel)
- (void)setDelegate:(id<UITableViewDelegate>)delegate
{
ASDisplayNodeAssert(NO, @"ASTableView uses asyncDelegate, not UITableView's delegate property.");
// Our UIScrollView superclass sets its delegate to nil on dealloc. Only assert if we get a non-nil value here.
ASDisplayNodeAssert(delegate == nil, @"ASTableView uses asyncDelegate, not UITableView's delegate property.");
}
- (void)setAsyncDataSource:(id<ASTableViewDataSource>)asyncDataSource

View File

@ -32,7 +32,7 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector);
@protected
ASDN::RecursiveMutex _propertyLock; // Protects access to the _view, _pendingViewState, _subnodes, _supernode, _renderingSubnodes, and other properties which are accessed from multiple threads.
ASDisplayNode *_supernode;
ASDisplayNode * __weak _supernode;
ASSentinel *_displaySentinel;
ASSentinel *_replaceAsyncSentinel;

View File

@ -783,6 +783,32 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
[v release];
}
- (void)testAddingSubnodeDoesNotCreateRetainCycle
{
ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init];
__block BOOL didDealloc = NO;
node.willDeallocBlock = ^(ASDisplayNode *n){
didDealloc = YES;
};
ASDisplayNode *subnode = [[ASDisplayNode alloc] init];
// verify initial
XCTAssertTrue(1 == node.retainCount, @"unexpected retain count:%tu", node.retainCount);
XCTAssertTrue(1 == subnode.retainCount, @"unexpected retain count:%tu", subnode.retainCount);
[node addSubnode:subnode];
XCTAssertTrue(2 == subnode.retainCount, @"node should retain subnode when added. retain count:%tu", node.retainCount);
XCTAssertTrue(1 == node.retainCount, @"subnode should not retain node when added. retain count:%tu", node.retainCount);
[subnode release];
XCTAssertTrue(1 == subnode.retainCount, @"subnode should be retained by node. retain count:%tu", subnode.retainCount);
[node release];
XCTAssertTrue(didDealloc, @"unexpected node lifetime:%@", node);
}
- (void)testMainThreadDealloc
{
__block BOOL didDealloc = NO;

View File

@ -0,0 +1,86 @@
/* 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 <XCTest/XCTest.h>
#import "ASTableView.h"
@interface ASTestTableView : ASTableView
@property (atomic, copy) void (^willDeallocBlock)(ASTableView *tableView);
@end
@implementation ASTestTableView
- (void)dealloc
{
if (_willDeallocBlock) {
_willDeallocBlock(self);
}
[super dealloc];
}
@end
@interface ASTableViewTestDelegate : NSObject <ASTableViewDataSource, ASTableViewDelegate>
@property (atomic, copy) void (^willDeallocBlock)(ASTableViewTestDelegate *delegate);
@end
@implementation ASTableViewTestDelegate
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 0;
}
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath
{
return nil;
}
- (void)dealloc
{
if (_willDeallocBlock) {
_willDeallocBlock(self);
}
[super dealloc];
}
@end
@interface ASTableViewTests : XCTestCase
@end
@implementation ASTableViewTests
- (void)testTableViewDoesNotRetainItselfAndDelegate
{
ASTestTableView *tableView = [[ASTestTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
__block BOOL tableViewDidDealloc = NO;
tableView.willDeallocBlock = ^(ASTableView *v){
tableViewDidDealloc = YES;
};
ASTableViewTestDelegate *delegate = [[ASTableViewTestDelegate alloc] init];
__block BOOL delegateDidDealloc = NO;
delegate.willDeallocBlock = ^(ASTableViewTestDelegate *d){
delegateDidDealloc = YES;
};
tableView.asyncDataSource = delegate;
tableView.asyncDelegate = delegate;
[delegate release];
XCTAssertTrue(delegateDidDealloc, @"unexpected delegate lifetime:%@", delegate);
XCTAssertNoThrow([tableView release], @"unexpected exception when deallocating table view:%@", tableView);
XCTAssertTrue(tableViewDidDealloc, @"unexpected table view lifetime:%@", tableView);
}
@end