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的主要内容,如果未能解决你的问题,请参考以下文章