atomic的作用只是给getter和setter加了个锁,atomic只能保证代码进入getter或者setter函数内部时是安全的,一旦出了getter和setter,线程就不再是安全的了 这时候处理线程安全 就得靠自己加锁了
http://liuduo.me/2018/02/08/objective-c-atomic/
Ever wondered how Apple is handling atomic setting/getting of properties? By now you have likely heard about spinlocks, semaphores, locks, @synchronized - so what’s Apple using? Thankfully, the Objective-C runtime is public, so we can take a look behind the curtain.
A nonatomic property setter might look like this:
- (void)setUserName:(NSString *)userName {
if (userName != _userName) {
[userName retain];
[_userName release];
_userName = userName;
}
}
This is the variant with manual retain/release; however, the ARC-generated code looks similar. When we look at this code it’s obvious why this means trouble when setUserName:
is called concurrently. We could end up releasing _userName
twice, which can corrupt memory and lead to hard-to-find bugs.
What’s happening internally for any property that’s not manually implemented is that the compiler generates a call to objc_setProperty_non_gc(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy)
. In our example, the call parameters would look like this:
objc_setProperty_non_gc(self, _cmd,
(ptrdiff_t)(&_userName) - (ptrdiff_t)(self), userName, NO, NO);`
The ptrdiff_t might look weird to you, but in the end it’s simple pointer arithmetic, since an Objective-C class is just another C struct.
objc_setProperty
calls down to following method:
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);
}
Aside from the rather funny name, this method is actually fairly straightforward and uses one of the 128 available spinlocks in PropertyLocks
. This is a pragmatic and fast approach – the worst case scenario is that a setter might have to wait for an unrelated setter to finish because of a hash collision.
While those methods aren’t declared in any public header, it is possible to call them manually. I’m not saying this is a good idea, but it’s interesting to know and could be quite useful if you want atomic properties and to implement the setter at the same time.
// Manually declare runtime methods.
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)
Refer to this gist for the full snippet including code to handle structs. But keep in mind that we don’t recommend using this.
https://www.objc.io/issues/2-concurrency/thread-safe-class-design/#the-deallocation-problem
atomic (Default)
As the word explains itself, a single thread will be able to access the property at a given time. To explain more, only one thread will be able to access getter/setter of a property. Other threads have to wait until first thread releases get/set method. Let’s say we have a property firstName which is atomic.
- Thread A => obj.firstName=@"A"
- Thread B => obj.firstName=@"B"
- Thread C => NSLog("First Name: %@",obj.firstName);
It will ensure that the value of property remains consistent throughout the lifecycle. By default all properties are atomic.
Example atomic property:
@property (strong,atomic) NSString *firstName;
OR
@property (strong) NSString *firstName;
Sample getter setter: This is how the methods of a property will look for an atomic property after
@synthesize firstName = _firstName;
-(NSString *)firstName{
@synchronized (self) {
return _firstName;
}
}
-(void)setFirstName:(NSString *)firstName{
@synchronized (self) {
if(_firstName != firstName){
[_firstName release];
_firstName = [firstName retain];
}
}
}
Note: the Objective-C 2.0 specification, mentions that locks are used internally, but it doesn’t specify exactly how. What you see above, is roughly what an atomic getter/setter would look like, but it might not be accurate.
Pros: Ensures that user gets a valid value and not some garbage
Cons: Slow, as it has code to ensure read write safety
https://nabeelarif.github.io/post/atomic-vs-nonatomic/