Created
March 1, 2016 05:30
-
-
Save hborders/e69b3a20305d49d5b9f1 to your computer and use it in GitHub Desktop.
Is self guaranteed to stay alive until the end of a method call if it was referenced from within a __weak lvalue?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// WeakTestTests.m | |
// WeakTestTests | |
// | |
// Created by Borders, Heath on 2/29/16. | |
// Copyright © 2016 Heath Borders. All rights reserved. | |
// | |
#import <XCTest/XCTest.h> | |
@interface HJBTarget : NSObject | |
@property (nonatomic, nonnull) NSString *nonnullString; | |
+ (instancetype)new NS_UNAVAILABLE; | |
- (instancetype)init NS_UNAVAILABLE; | |
- (nonnull instancetype)initWithInsideMethodSemaphore:(nonnull dispatch_semaphore_t)insideMethodSemaphore | |
finishMethodSemaphore:(nonnull dispatch_semaphore_t)finishMethodSemaphore | |
expectation:(nonnull XCTestExpectation *)expectation; | |
- (void)methodUsingSelf; | |
@end | |
@interface WeakTestTests : XCTestCase | |
@end | |
@implementation WeakTestTests | |
- (void)testWeakSelfLivesThroughoutMethod { | |
XCTestExpectation *expectation = [self expectationWithDescription:@"finished"]; | |
dispatch_group_t setupDispatchGroup = dispatch_group_create(); | |
dispatch_queue_t setupBackgroundQueue = dispatch_queue_create("testWeakSelfLivesThroughoutMethod.setup", | |
DISPATCH_QUEUE_CONCURRENT); | |
dispatch_queue_t testBackgroundQueue = dispatch_queue_create("testWeakSelfLivesThroughoutMethod.test", | |
DISPATCH_QUEUE_CONCURRENT); | |
dispatch_semaphore_t insideMethodSemaphore = dispatch_semaphore_create(0); | |
dispatch_semaphore_t finishMethodSemaphore = dispatch_semaphore_create(0); | |
dispatch_group_async(setupDispatchGroup, | |
setupBackgroundQueue, | |
^{ | |
NS_VALID_UNTIL_END_OF_SCOPE HJBTarget *target = [[HJBTarget alloc] initWithInsideMethodSemaphore:insideMethodSemaphore | |
finishMethodSemaphore:finishMethodSemaphore | |
expectation:expectation]; | |
HJBTarget *weakTarget __weak = target; | |
dispatch_async(testBackgroundQueue, | |
^{ | |
XCTAssertNotNil(weakTarget.nonnullString); | |
[weakTarget methodUsingSelf]; | |
XCTAssertNil(weakTarget.nonnullString); | |
}); | |
NSLog(@"Waiting for insideMethodSemaphore on %s", | |
dispatch_queue_get_label(dispatch_get_current_queue())); | |
long insideMethodSemaphoreWaitSuccess = dispatch_semaphore_wait(insideMethodSemaphore, | |
dispatch_time(DISPATCH_TIME_NOW, | |
(int64_t)(5 * NSEC_PER_SEC))); | |
if (insideMethodSemaphoreWaitSuccess == 0) | |
{ | |
NSLog(@"%s is inside the method", | |
dispatch_queue_get_label(testBackgroundQueue)); | |
} | |
else | |
{ | |
XCTFail(@"Failed to acquire the insideMethodSemaphore. This likely happened because weakTarget was nil when we called -methodUsingSelf"); | |
} | |
}); | |
dispatch_group_wait(setupDispatchGroup, | |
dispatch_time(DISPATCH_TIME_NOW, | |
(int64_t)(5 * NSEC_PER_SEC))); | |
NSLog(@"setup block finished running. We're inside the method and the target should have no strong references left."); | |
NSLog(@"Signaling finishMethodSemaphore"); | |
dispatch_semaphore_signal(finishMethodSemaphore); | |
[self waitForExpectationsWithTimeout:10 * 1000 | |
handler:^(NSError * _Nullable error) { | |
XCTAssertNil(error, @"%@ %@", | |
[error localizedDescription], | |
[error userInfo]); | |
}]; | |
} | |
@end | |
@implementation HJBTarget { | |
dispatch_semaphore_t _Nonnull _insideMethodSemaphore; | |
dispatch_semaphore_t _Nonnull _finishMethodSemaphore; | |
XCTestExpectation * _Nonnull _expectation; | |
} | |
- (nonnull instancetype)initWithInsideMethodSemaphore:(nonnull dispatch_semaphore_t)insideMethodSemaphore | |
finishMethodSemaphore:(nonnull dispatch_semaphore_t)finishMethodSemaphore | |
expectation:(nonnull XCTestExpectation *)expectation | |
{ | |
self = [super init]; | |
NSLog(@"initted %@ on %s", | |
self, | |
dispatch_queue_get_label(dispatch_get_current_queue())); | |
_insideMethodSemaphore = insideMethodSemaphore; | |
_finishMethodSemaphore = finishMethodSemaphore; | |
_expectation = expectation; | |
return self; | |
} | |
- (void)dealloc { | |
NSLog(@"deallocing %@ on %s", | |
self, | |
dispatch_queue_get_label(dispatch_get_current_queue())); | |
} | |
#pragma mark - public API | |
- (void)methodUsingSelf { | |
// is self guaranteed to be non-nil as long as we call this method from | |
// a __weak lvalue that evaluated to non-nil? | |
const char *currentQueueLabel = dispatch_queue_get_label(dispatch_get_current_queue()); | |
NSLog(@"called %@ on %s", | |
NSStringFromSelector(_cmd), | |
currentQueueLabel); | |
// if self does get released, then we need to capture | |
// these instance fields while it's safe | |
dispatch_semaphore_t insideMethodSemaphore = _insideMethodSemaphore; | |
dispatch_semaphore_t finishMethodSemaphore = _finishMethodSemaphore; | |
XCTestExpectation *expectation = _expectation; | |
dispatch_semaphore_signal(insideMethodSemaphore); | |
NSLog(@"waiting for finish method semaphore on %p", | |
currentQueueLabel); | |
dispatch_semaphore_wait(finishMethodSemaphore, | |
dispatch_time(DISPATCH_TIME_NOW, | |
(int64_t)(5 * NSEC_PER_SEC))); | |
NSLog(@"At the end of methodUsingSelf. Is self nil? self: %@", | |
self); | |
[expectation fulfill]; | |
} | |
- (nonnull NSString *) nonnullString { | |
return @"nonnull"; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Yes! http://stackoverflow.com/a/35716299/9636