ably-cocoa/Source/Private/ARTGCD.m

81 lines
3.0 KiB
Objective-C

#import "ARTGCD.h"
@implementation ARTScheduledBlockHandle {
dispatch_semaphore_t _semaphore;
dispatch_block_t _block;
dispatch_block_t _scheduledBlock;
}
- (instancetype)initWithDelay:(NSTimeInterval)delay queue:(dispatch_queue_t)queue block:(dispatch_block_t)block {
self = [super init];
if (self == nil)
return nil;
// Use a sempaphore to coorindate state. We use a reference here to decouple it when creating a block we'll schedule.
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
__weak ARTScheduledBlockHandle *weakSelf = self;
_scheduledBlock = dispatch_block_create(0, ^{
dispatch_block_t copiedBlock = nil;
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
{
// Get a strong reference to self within our semaphore to avoid potential race conditions
ARTScheduledBlockHandle *strongSelf = weakSelf;
if (strongSelf != nil) {
copiedBlock = strongSelf->_block; // copied below
}
}
dispatch_semaphore_signal(semaphore);
// If our block is non-nil, our scheduled block was still valid by the time this was invoked
if (copiedBlock != nil) {
copiedBlock();
}
});
_block = [block copy];
_semaphore = semaphore;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC * delay)), queue, _scheduledBlock);
return self;
}
- (void)cancel {
// Cancel within our semaphore for predictable behavior if our block is invoked while we're cancelling
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
{
dispatch_block_cancel(_scheduledBlock);
_block = nil;
}
dispatch_semaphore_signal(_semaphore);
}
- (void)dealloc {
// Explicitly cancel when we deallocate. This happens implicitly since our scheduled block keeps a weak
// reference to self, but we want to make sure that the weak reference can be safely accessed, even if
// we're in the midst of deallocating.
[self cancel];
}
@end
ARTScheduledBlockHandle *artDispatchScheduled(NSTimeInterval seconds, dispatch_queue_t queue, dispatch_block_t block) {
// We don't pass the block directly; instead, we put it in a property, and
// read it back from the property once the timer fires. This gives us the
// chance to set the property to nil when cancelling the timer, thus
// releasing our retain on the block early. dispatch_block_cancel doesn't do
// this, it retains the block even if you cancel the dispatch until the
// dispatch time passes. (How this is a good idea escapes me.)
//
// From Apple's documentation [1]:
//
// > Release of any resources associated with the block object is delayed
// > until execution of the block object is next attempted (or any execution
// > already in progress completes).
//
// https://developer.apple.com/documentation/dispatch/1431058-dispatch_block_cancel
return [[ARTScheduledBlockHandle alloc] initWithDelay:seconds queue:queue block:block];
}