Open chat

In iOS development, it is common for a class to have multiple attributes, and there are many keywords used to modify attributes, and we often encounter the following interview questions:

  • atomicnonatomicCan be used to modify a property, why iOS development is commonnonatomicModify properties?atomicIs it thread-safe?
@property (copy) NSString *name;
@property (atomic, copy) NSString *name;
@property (nonatomic, copy) NSString *name;
Copy the code
  • What’s the difference between these three lines of code? A: If the compiler automatically generates getters and setters, lines 1 and 2 make no difference (the default keyword isatomic), the third line is different from the first two. If we implement getters and setters manually, what difference would those three lines make

From this, it follows that the atomic keyword is the default keyword when defining attributes. It is well known that the performance of atomic keyword modifier properties is lower than that of nonatomic keyword modifier properties. So nonatomic is usually used to define properties in iOS development. The goal is to improve performance and save poor resources. But why is the performance of attributes modified by the atomic keyword poor?

First, let’s understand the concept of atomicity:

  • In chemistry, an atom is the smallest unit of an element that retains its chemical properties, and the smallest unit means that it cannot be divided.
  • When applied to computing, the above concept can be understood as a series of operations that are indivisible.
int a = 10;
int b = 20;
int c = a + b;
Copy the code
  • For example, the above three lines of code require that they be an integral part of multiple threads. We need to lock the above three lines of code so that only one thread can perform this operation. Then lock it. The locking operation guarantees atomicity.
Int a = 10; int b = 20; int c = a + b; Unlock operationCopy the code

Back in iOS development, when you define a property, the compiler automatically generates member variables with underscores and getter and setter methods for you. If you use atomic to modify the property, then when the compiler generates setter and getter methods for you, The internal implementation of getter and setter methods locks the value. The purpose of locking is to ensure the safety/integrity of the value stored in getter and setter methods. The access to the value inside getter and setter methods is thread-safe, but the operation on the property is not thread-safe. Reference objC source code, we can find the answer. The objc-accessors.mm class has two functions:

id objc_getProperty(id self, SEL _cmd, ptrdiff_t offset, BOOL atomic) {
    if (offset == 0) {
        return object_getClass(self);
    }

    // Retain release world
    id *slot = (id*) ((char*)self + offset);
    if(! atomic)return *slot;
        
    // Atomic retain release world
    spinlock_t& slotlock = PropertyLocks[slot];
    slotlock.lock();
    id value = objc_retain(*slot);
    slotlock.unlock();
    
    // for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
    return objc_autoreleaseReturnValue(value);
}

Copy the code
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if(! atomic) { oldValue = *slot; *slot = newValue; }else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}
Copy the code
  • In the above two functions, you can see that the parameter in the function contains a parameter of type bool, atomic
  • When the incomingatomicIf it is true, spinlock will be used to lock and unlock the device. If it is true, spinlock will be used to lock and unlock the device.

Scene: Atomic modify attribute values, if use A and B two threads, A thread to assignment of property, value B threads for operation, when A thread assignment for half of the time, because of the internal locking setter method, A thread will hold the lock, when B threads operating values, found A thread holds A lock, so will be waiting, When thread A finishes the assignment, the setter method releases the lock inside to ensure that A complete value is set. Then thread B performs the value operation, and the getter method holds the lock inside. After obtaining the complete value, the getter method unlocks the lock and returns the complete value.

If you use nonatomic to modify the value of A property, there are two threads, A and B. Thread A assigns the value of the property, and when thread A is halfway through the assignment, thread B does the evaluation. Because the setter method is not locked internally, the assignment is not complete, and thread B cannot get A complete value from the getter method. Taking an incomplete value and doing something unexpected can happen.

Atomic does not guarantee that a thread is safe, only that the memory is intact.

Scenario: Use atomic to modify attributes if there are threads A, B, and C. Among them A and B threads assignment on A property for operation at the same time, the value of C thread for operation, then can ensure C thread can get into A complete value, but the content of this value may be A thread assigned A value, may also be A thread B assigned values, also may be the original value, has made the full value, but the value is not necessarily programmers want, So atomic is not thread-safe, it just ensures that setter and getter methods for properties are thread-safe inside. If you want to be truly thread-safe, you need to lock and unlock before and after the assignment, and use the same lock.

Why do you sayatomicAre keywords performance-consuming?

If an atomic operation has a lock on the bottom, it is also referred to as a spin lock, and the spin lock is busy and so on. It can be interpreted as a while loop that waits forever, and the performance is definitely lower than if nonatomic did not lock.

In normal development, nonatomic can improve performance when thread safety is not involved, such as when some UI controls must be operated on the main thread. However, to really involve thread safety, not only rely on the compiler, programmers need to control themselves.