| // |
| // 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 |