2

Pre-automatic reference counting, you could do the appropriate pointer casts in Objective-c to allow you to use bool OSAtomicCompareAndSwapPtr(void* oldValue, void* newValue, void* volatile *theValue); to attempt to atomically swap pointers when dealing with multithreaded accesses.

Under ARC these pointer casts are not valid. Is there an equivalent atomic pointer swap available under ARC for iOS? I was hoping to avoid the more expensive locking if this alternative is still available.

FooBard
  • 21
  • 2

3 Answers3

1

Disclaimer: code in this answer is not tested!

First of all I'd like to mention that most pointer uses don't really need compare-and-swap. C pointer reads and writes are atomic by themselves. See this SO answer for more detail. Same goes for ARM. So if you implement atomic getters and setters you only need a memory barrier to guarantee that other threads see fully initialized objects:

NSObject * global;
NSObject * get() { return global; }
void set(NSObject * value) { OSMemoryBarrier(); global = value; }

Now back to the question, because who knows, maybe there are real uses for compare-and-swapping objects. The casts are still possible, you just declare them differently now:

NSString * a = @"A";
NSObject * b = @"B";
OSAtomicCompareAndSwapPtrBarrier(
    (__bridge void *)a,
    (__bridge void *)b,
    (__bridge void * *)&a);

However this code has a problem: the string @"A" loses a reference, and @"B" gets referenced twice, without ARC knowing. Therefore @"A" will leak, and the program will likely crash when leaving the scope because @"B" will be released twice while only having the retain counter of 1.

I think the only option is to use Core Foundation objects. You can use the fact that NSObject is toll-free bridged with CFType. I couldn't find any definitive documentation on this but it follows from common sense and practical evidence. So e.g. it is possible to implement a singleton:

CFTypeRef instance;
Thingamabob * getInstance() {
  if (!instance) {
    CFTypeRef t = (__bridge_retained CFTypeRef)[Thingamabob new];
    if (!OSAtomicCompareAndSwapPtrBarrier(NULL, t, &instance)) {
      CFRelease(t);
    }
  }
  return (__bridge Thingamabob *)instance;
}
Community
  • 1
  • 1
SnakE
  • 2,355
  • 23
  • 31
0

You may be able to use this easily, if one condition is met, and maybe if you're willing to play games.

Create a new file, mark it as not using ARC in the Build Phase, and put this swap into a small C function. At the top of the function get the object's retainCounts, and if they are equal (and you have reason to believe they are not sitting in an autorelease pool) you can just swap them, as ARC will insure the proper releases to each.

If they are not equal, well, you can play games by changing the retain count.

David H
  • 40,852
  • 12
  • 92
  • 138
  • Thanks. Unfortunately making this dependent on checking the retain counts adds another non-thread safe step to the swap. Given the performance advantages of swap I had hoped it hadn't been forgotten in the move to ARC. Ah well, I'll do some more hefty locking. – FooBard Sep 18 '12 at 13:54
0

I found these comparable classes - before and update which help illustrate update

BEFORE

#import "SBlockDisposable.h"

#import <libkern/OSAtomic.h>
#import <objc/runtime.h>

@interface SBlockDisposable ()
{
    void *_block;
}

@end

@implementation SBlockDisposable

- (instancetype)initWithBlock:(void (^)())block
{
    self = [super init];
    if (self != nil)
    {
        _block = (__bridge_retained void *)[block copy];
    }
    return self;
}

- (void)dealloc
{
    void *block = _block;
    if (block != NULL)
    {
        if (OSAtomicCompareAndSwapPtr(block, 0, &_block))
        {
            if (block != nil)
            {
                __strong id strongBlock = (__bridge_transfer id)block;
                strongBlock = nil;
            }
        }
    }
}

- (void)dispose
{
    void *block = _block;
    if (block != NULL)
    {
        if (OSAtomicCompareAndSwapPtr(block, 0, &_block))
        {
            if (block != nil)
            {
                __strong id strongBlock = (__bridge_transfer id)block;
                ((dispatch_block_t)strongBlock)();
                strongBlock = nil;
            }
        }
    }
}

@end

AFTER

//
//  KAGRACDisposable.m
//  ReactiveCocoa
//
//  Created by Josh Abernathy on 3/16/12.
//  Copyright (c) 2012 GitHub, Inc. All rights reserved.
//

#import "KAGRACDisposable.h"
#import "KAGRACScopedDisposable.h"

#import <stdatomic.h>

@interface KAGRACDisposable () {
    // A copied block of type void (^)(void) containing the logic for disposal,
    // a pointer to `self` if no logic should be performed upon disposal, or
    // NULL if the receiver is already disposed.
    //
    // This should only be used atomically.
    void * volatile _disposeBlock;
}

@end

@implementation KAGRACDisposable

#pragma mark Properties

- (BOOL)isDisposed {
    return _disposeBlock == NULL;
}

#pragma mark Lifecycle

- (id)init {
    self = [super init];
    if (self == nil) return nil;

    _disposeBlock = (__bridge void *)self;
    atomic_thread_fence(memory_order_seq_cst);

    return self;
}

- (id)initWithBlock:(void (^)(void))block {
    NSCParameterAssert(block != nil);

    self = [super init];
    if (self == nil) return nil;

    _disposeBlock = (void *)CFBridgingRetain([block copy]); 
    atomic_thread_fence(memory_order_seq_cst);

    return self;
}

+ (instancetype)disposableWithBlock:(void (^)(void))block {
    return [[self alloc] initWithBlock:block];
}

- (void)dealloc {
    if (_disposeBlock == NULL || _disposeBlock == (__bridge void *)self) return;

    CFRelease(_disposeBlock);
    _disposeBlock = NULL;
}

#pragma mark Disposal

- (void)dispose {
    void (^disposeBlock)(void) = NULL;

    while (YES) {
        void *blockPtr = _disposeBlock;
        if (atomic_compare_exchange_strong((volatile _Atomic(void*)*)&_disposeBlock, &blockPtr, NULL)) {
            if (blockPtr != (__bridge void *)self) {
                disposeBlock = CFBridgingRelease(blockPtr);
            }

            break;
        }
    }

    if (disposeBlock != nil) disposeBlock();
}

#pragma mark Scoped Disposables

- (KAGRACScopedDisposable *)asScopedDisposable {
    return [KAGRACScopedDisposable scopedDisposableWithDisposable:self];
}

@end
johndpope
  • 5,035
  • 2
  • 41
  • 43