blob: 975afbfe39ebe98d514e4c13729cdd8f4182431c [file] [log] [blame] [edit]
//
// RACSerialDisposable.m
// ReactiveCocoa
//
// Created by Justin Spahr-Summers on 2013-07-22.
// Copyright (c) 2013 GitHub, Inc. All rights reserved.
//
#import "RACSerialDisposable.h"
#import <libkern/OSAtomic.h>
@interface RACSerialDisposable () {
// A reference to the receiver's `disposable`. This variable must only be
// modified atomically.
//
// If this is `self`, no `disposable` has been set, but the receiver has not
// been disposed of yet. `self` is never stored retained.
//
// If this is `nil`, the receiver has been disposed.
//
// Otherwise, this is a retained reference to the inner disposable and the
// receiver has not been disposed of yet.
void * volatile _disposablePtr;
}
@end
@implementation RACSerialDisposable
#pragma mark Properties
- (BOOL)isDisposed {
return _disposablePtr == nil;
}
- (RACDisposable *)disposable {
RACDisposable *disposable = (__bridge id)_disposablePtr;
return (disposable == self ? nil : disposable);
}
- (void)setDisposable:(RACDisposable *)disposable {
[self swapInDisposable:disposable];
}
#pragma mark Lifecycle
+ (instancetype)serialDisposableWithDisposable:(RACDisposable *)disposable {
RACSerialDisposable *serialDisposable = [[self alloc] init];
serialDisposable.disposable = disposable;
return serialDisposable;
}
- (id)init {
self = [super init];
if (self == nil) return nil;
_disposablePtr = (__bridge void *)self;
OSMemoryBarrier();
return self;
}
- (id)initWithBlock:(void (^)(void))block {
self = [self init];
if (self == nil) return nil;
self.disposable = [RACDisposable disposableWithBlock:block];
return self;
}
- (void)dealloc {
self.disposable = nil;
}
#pragma mark Inner Disposable
- (RACDisposable *)swapInDisposable:(RACDisposable *)newDisposable {
void * const selfPtr = (__bridge void *)self;
// Only retain the new disposable if it's not `self`.
// Take ownership before attempting the swap so that a subsequent swap
// receives an owned reference.
void *newDisposablePtr = selfPtr;
if (newDisposable != nil) {
newDisposablePtr = (void *)CFBridgingRetain(newDisposable);
}
void *existingDisposablePtr;
// Keep trying while we're not disposed.
while ((existingDisposablePtr = _disposablePtr) != NULL) {
if (!OSAtomicCompareAndSwapPtrBarrier(existingDisposablePtr, newDisposablePtr, &_disposablePtr)) {
continue;
}
// Return nil if _disposablePtr was set to self. Otherwise, release
// the old value and return it as an object.
if (existingDisposablePtr == selfPtr) {
return nil;
} else {
return CFBridgingRelease(existingDisposablePtr);
}
}
// At this point, we've found out that we were already disposed.
[newDisposable dispose];
// Failed to swap, clean up the ownership we took prior to the swap.
if (newDisposable != nil) {
CFRelease(newDisposablePtr);
}
return nil;
}
#pragma mark Disposal
- (void)dispose {
void *existingDisposablePtr;
while ((existingDisposablePtr = _disposablePtr) != NULL) {
if (OSAtomicCompareAndSwapPtrBarrier(existingDisposablePtr, NULL, &_disposablePtr)) {
if (existingDisposablePtr != (__bridge void *)self) {
RACDisposable *existingDisposable = CFBridgingRelease(existingDisposablePtr);
[existingDisposable dispose];
}
break;
}
}
}
@end