You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

250 lines
6.7 KiB

//
// RACCompoundDisposable.m
// ReactiveObjC
//
// Created by Josh Abernathy on 11/30/12.
// Copyright (c) 2012 GitHub, Inc. All rights reserved.
//
#import "RACCompoundDisposable.h"
#import "RACCompoundDisposableProvider.h"
#import <pthread/pthread.h>
// The number of child disposables for which space will be reserved directly in
// `RACCompoundDisposable`.
//
// This number has been empirically determined to provide a good tradeoff
// between performance, memory usage, and `RACCompoundDisposable` instance size
// in a moderately complex GUI application.
//
// Profile any change!
#define RACCompoundDisposableInlineCount 2
static CFMutableArrayRef RACCreateDisposablesArray(void) {
// Compare values using only pointer equality.
CFArrayCallBacks callbacks = kCFTypeArrayCallBacks;
callbacks.equal = NULL;
return CFArrayCreateMutable(NULL, 0, &callbacks);
}
@interface RACCompoundDisposable () {
// Used for synchronization.
pthread_mutex_t _mutex;
#if RACCompoundDisposableInlineCount
// A fast array to the first N of the receiver's disposables.
//
// Once this is full, `_disposables` will be created and used for additional
// disposables.
//
// This array should only be manipulated while _mutex is held.
RACDisposable *_inlineDisposables[RACCompoundDisposableInlineCount];
#endif
// Contains the receiver's disposables.
//
// This array should only be manipulated while _mutex is held. If
// `_disposed` is YES, this may be NULL.
CFMutableArrayRef _disposables;
// Whether the receiver has already been disposed.
//
// This ivar should only be accessed while _mutex is held.
BOOL _disposed;
}
@end
@implementation RACCompoundDisposable
#pragma mark Properties
- (BOOL)isDisposed {
pthread_mutex_lock(&_mutex);
BOOL disposed = _disposed;
pthread_mutex_unlock(&_mutex);
return disposed;
}
#pragma mark Lifecycle
+ (instancetype)compoundDisposable {
return [[self alloc] initWithDisposables:nil];
}
+ (instancetype)compoundDisposableWithDisposables:(NSArray *)disposables {
return [[self alloc] initWithDisposables:disposables];
}
- (instancetype)init {
self = [super init];
const int result __attribute__((unused)) = pthread_mutex_init(&_mutex, NULL);
NSCAssert(0 == result, @"Failed to initialize mutex with error %d.", result);
return self;
}
- (instancetype)initWithDisposables:(NSArray *)otherDisposables {
self = [self init];
#if RACCompoundDisposableInlineCount
[otherDisposables enumerateObjectsUsingBlock:^(RACDisposable *disposable, NSUInteger index, BOOL *stop) {
self->_inlineDisposables[index] = disposable;
// Stop after this iteration if we've reached the end of the inlined
// array.
if (index == RACCompoundDisposableInlineCount - 1) *stop = YES;
}];
#endif
if (otherDisposables.count > RACCompoundDisposableInlineCount) {
_disposables = RACCreateDisposablesArray();
CFRange range = CFRangeMake(RACCompoundDisposableInlineCount, (CFIndex)otherDisposables.count - RACCompoundDisposableInlineCount);
CFArrayAppendArray(_disposables, (__bridge CFArrayRef)otherDisposables, range);
}
return self;
}
- (instancetype)initWithBlock:(void (^)(void))block {
RACDisposable *disposable = [RACDisposable disposableWithBlock:block];
return [self initWithDisposables:@[ disposable ]];
}
- (void)dealloc {
#if RACCompoundDisposableInlineCount
for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
_inlineDisposables[i] = nil;
}
#endif
if (_disposables != NULL) {
CFRelease(_disposables);
_disposables = NULL;
}
const int result __attribute__((unused)) = pthread_mutex_destroy(&_mutex);
NSCAssert(0 == result, @"Failed to destroy mutex with error %d.", result);
}
#pragma mark Addition and Removal
- (void)addDisposable:(RACDisposable *)disposable {
NSCParameterAssert(disposable != self);
if (disposable == nil || disposable.disposed) return;
BOOL shouldDispose = NO;
pthread_mutex_lock(&_mutex);
{
if (_disposed) {
shouldDispose = YES;
} else {
#if RACCompoundDisposableInlineCount
for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
if (_inlineDisposables[i] == nil) {
_inlineDisposables[i] = disposable;
goto foundSlot;
}
}
#endif
if (_disposables == NULL) _disposables = RACCreateDisposablesArray();
CFArrayAppendValue(_disposables, (__bridge void *)disposable);
if (RACCOMPOUNDDISPOSABLE_ADDED_ENABLED()) {
RACCOMPOUNDDISPOSABLE_ADDED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount);
}
#if RACCompoundDisposableInlineCount
foundSlot:;
#endif
}
}
pthread_mutex_unlock(&_mutex);
// Performed outside of the lock in case the compound disposable is used
// recursively.
if (shouldDispose) [disposable dispose];
}
- (void)removeDisposable:(RACDisposable *)disposable {
if (disposable == nil) return;
pthread_mutex_lock(&_mutex);
{
if (!_disposed) {
#if RACCompoundDisposableInlineCount
for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
if (_inlineDisposables[i] == disposable) _inlineDisposables[i] = nil;
}
#endif
if (_disposables != NULL) {
CFIndex count = CFArrayGetCount(_disposables);
for (CFIndex i = count - 1; i >= 0; i--) {
const void *item = CFArrayGetValueAtIndex(_disposables, i);
if (item == (__bridge void *)disposable) {
CFArrayRemoveValueAtIndex(_disposables, i);
}
}
if (RACCOMPOUNDDISPOSABLE_REMOVED_ENABLED()) {
RACCOMPOUNDDISPOSABLE_REMOVED(self.description.UTF8String, disposable.description.UTF8String, CFArrayGetCount(_disposables) + RACCompoundDisposableInlineCount);
}
}
}
}
pthread_mutex_unlock(&_mutex);
}
#pragma mark RACDisposable
static void disposeEach(const void *value, void *context) {
RACDisposable *disposable = (__bridge id)value;
[disposable dispose];
}
- (void)dispose {
#if RACCompoundDisposableInlineCount
RACDisposable *inlineCopy[RACCompoundDisposableInlineCount];
#endif
CFArrayRef remainingDisposables = NULL;
pthread_mutex_lock(&_mutex);
{
_disposed = YES;
#if RACCompoundDisposableInlineCount
for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
inlineCopy[i] = _inlineDisposables[i];
_inlineDisposables[i] = nil;
}
#endif
remainingDisposables = _disposables;
_disposables = NULL;
}
pthread_mutex_unlock(&_mutex);
#if RACCompoundDisposableInlineCount
// Dispose outside of the lock in case the compound disposable is used
// recursively.
for (unsigned i = 0; i < RACCompoundDisposableInlineCount; i++) {
[inlineCopy[i] dispose];
}
#endif
if (remainingDisposables == NULL) return;
CFIndex count = CFArrayGetCount(remainingDisposables);
CFArrayApplyFunction(remainingDisposables, CFRangeMake(0, count), &disposeEach, NULL);
CFRelease(remainingDisposables);
}
@end