ios 原子属性nonatomic/atomic

Posted 想名真难

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ios 原子属性nonatomic/atomic相关的知识,希望对你有一定的参考价值。

原子属性 (Atomic Properties)

你曾经好奇过 Apple 是怎么处理 atomic 的设置/读取属性的么?至今为止,你可能听说过自旋锁 (spinlocks),信标 (semaphores),锁 (locks),@synchronized 等,Apple 用的是什么呢?因为 Objctive-C 的 runtime 是开源的,所以我们可以一探究竟。

在MRC下, 一个非原子的 setter 看起来是这个样子的:

- (void)setUserName:(NSString *)userName 
	if (userName != _userName) 
		[userName retain/copy]; // 根据属性的内存管理语义
		[_userName release];
		_userName = userName;
	

这是一个MRC下的retain/release 的版本,ARC 生成的代码和这个看起来也是类似的。当我们看这段代码时,显而易见要是 setUserName: 被并发调用的话会造成麻烦。我们可能会释放 _userName 两次,这回使内存错误,并且导致难以发现的 bug。

对于任何没有手动实现的属性,编译器都会生成一个 objc_setProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 的调用。在我们的例子中,这个调用的参数是这样的:

一共6个参数, 下面有具体实现

objc_setProperty_non_gc(self, _cmd,
(ptrdiff_t)(&_userName) - (ptrdiff_t)(self),
userName, NO, NO);

ptrdiff_t 可能会吓到你,但是实际上这就是一个简单的指针算术,因为其实 Objective-C 的类仅仅只是 C 结构体而已。

objc_setProperty 调用的是如下方法:

static inline void reallySetProperty(id self, SEL _cmd, id newValue,
                                     ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) 
    id oldValue;
    id *slot = (id*) ((char*)self + offset);
    if (copy) 
        newValue = [newValue copyWithZone:NULL];
     else if (mutableCopy) 
        newValue = [newValue mutableCopyWithZone:NULL];
     else 
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    
    if (!atomic) 
        oldValue = *slot;
        *slot = newValue;
     else 
        spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)];
        _spin_lock(slotlock);
        oldValue = *slot;
        *slot = newValue;
        _spin_unlock(slotlock);
    
    objc_release(oldValue);

除开方法名字很有趣以外,其实方法实际做的事情非常直接,它使用了在 PropertyLocks 中的 128 个自旋锁中的 1 个来给操作上锁。这是一种务实和快速的方式,最糟糕的情况下,如果遇到了哈希碰撞,那么 setter 需要等待另一个和它无关的 setter 完成之后再进行工作。

虽然这些方法没有定义在任何公开的头文件中,但我们还是可用手动调用他们。我不是说这是一个好的做法,但是知道这个还是蛮有趣的,而且如果你想要同时实现原子属性和自定义的 setter 的话,这个技巧就非常有用了。

// 手动声明运行时的方法
extern void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset,
id newValue, BOOL atomic, BOOL shouldCopy);

extern id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic);


#define PSTAtomicRetainedSet(dest, src) objc_setProperty(self, _cmd, (ptrdiff_t)(&dest) - (ptrdiff_t)(self), src, YES, NO)

#define PSTAtomicAutoreleasedGet(src) objc_getProperty(self, _cmd, (ptrdiff_t)(&src) - (ptrdiff_t)(self), YES)

为何不用 @synchronized ?

你也许会想问为什么苹果不用 @synchronized(self) 这样一个已经存在的运行时特性来锁定属性?? 主要原因还是效率问题.

你可以看看苹果的源代码,就会发现其实发生了很多的事情。Apple 使用了最多三个加/解锁序列,还有一部分原因是他们也添加了异常开解(exception unwinding)机制。相比于更快的自旋锁方式,@synchronized实现要慢得多。由于设置某个属性一般来说会相当快,因此自旋锁更适合用来完成这项工作。@synchonized(self) 更适合使用在你需要确保在发生错误时代码不会产生死锁,而是抛出异常的时候。

多线程下出错案例分析

if (self.contents) 
    CFAttributedStringRef stringRef = CFAttributedStringCreate(NULL, 
      (__bridge CFStringRef)self.contents, NULL);
    // 渲染字符串

多线程下存在contents属性在通过检查之后却又被设成了nil而导致EXC_BAD_ACCESS崩溃。捕获这个变量就可以简单修复这个问题。

NSString *contents = self.contents;
if (contents) 
    CFAttributedStringRef stringRef = CFAttributedStringCreate(NULL, 
      (__bridge CFStringRef)contents, NULL);
    // 渲染字符串


数组,字典尽量用不可变的版本,没有多线程并发的问题,
如果需要添加/删除操作,可以使用局部变量作为可变版本,可变版本的修改完成后进行copy.
注意:对不可变的属性进行赋值的操作也要保证线程安全

// 方案1:使用atomic,下面的方法就没有必要加锁了,
// 方案2:此处用nonatomic,在set/get的地方加锁,
// 一开始尝试了只在set加锁,get不加锁,发现不可以,必须set/get都加锁才能线程安全
@property (nonatomic, strong) NSArray *dataArray;

- (void)addDelegate:(id<NSObject>)delegate 
    @synchronized(self) 
        NSMutableArray *tempArray = [NSMutableArray arrayWithArray:self.dataArray];
        [tempArray addObject:delegate];
        self.dataArray = [tempArray copy];
    


- (void)removeDelegate:(id<NSObject>)delegate 
    @synchronized(self) 
        NSMutableArray *tempArray = [NSMutableArray arrayWithArray:self.dataArray];
        [tempArray removeObject:delegate];
        self.dataArray = [tempArray copy];
    


- (void)removeAllDelegates 
    @synchronized(self) 
        self.dataArray = nil;
    


- (void)callDelegate 
    NSArray *array = nil;
    @synchronized(self) 
        array= [NSArray arrayWithArray:self.dataArray];
    
    [array enumerateObjectsUsingBlock:^(id<NSObject> delegate, NSUInteger idx, BOOL *stop) 
        // 调用delegate
    ];

以上是关于ios 原子属性nonatomic/atomic的主要内容,如果未能解决你的问题,请参考以下文章

关于@property()的那些属性及ARC简介

iOS核心笔记——多线程-原子/非原子属性

iOS 内存管理之属性关键字

iOS 属性关键字

ios 原子属性atomic加锁性能与锁对比, 不推荐的原因

ios 原子属性atomic加锁性能与锁对比, 不推荐的原因