Skip to content

Instantly share code, notes, and snippets.

@hborders
Created March 1, 2016 05:30
Show Gist options
  • Save hborders/e69b3a20305d49d5b9f1 to your computer and use it in GitHub Desktop.
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?
//
// 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
@hborders
Copy link
Author

hborders commented Mar 1, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment