After reading Apple's documentation, I try to put in evidence atomicity or non-atomicity of a property in Objective-C. To do this I create a class Person which has first and last name.
Person.h
@interface Person : NSObject
@property (nonatomic, strong) NSString *firstName;
@property (nonatomic, strong) NSString *lastName;
- (instancetype)initWithFirstName:(NSString *)fn lastName:(NSString *)ln;
@end
Person.m
@implementation Person
- (instancetype)initWithFirstName:(NSString *)fn lastName:(NSString *)ln {
if (self = [super init]) {
self.firstName = fn;
self.lastName = ln;
}
return self;
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}
@end
In another class, here my AppDelegate, I have a nonatomic property which is an instance of Person.
@property (strong, nonatomic) Person *p;
In the implementation file, I create three concurrent queues. In the first queue I read the property, in two other queues I write different values of person.
From what I understand, I could have Bob Frost or Jack Sponge output in my log, since I declared my property as nonatomic. But that didn't happened. I don't understand why. Am I missing something or misunderstanding something?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
Person *bob = [[Person alloc] initWithFirstName:@"Bob" lastName:@"Sponge"];
Person *jack = [[Person alloc] initWithFirstName:@"Jack" lastName:@"Frost"];
self.p = bob;
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue3 = dispatch_queue_create("queue3", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue1, ^{
while (YES) {
NSLog(@"%@", self.p);
}
});
dispatch_async(queue2, ^{
while (YES) {
self.p = bob;
}
});
dispatch_async(queue3, ^{
while (YES) {
self.p = jack;
}
});
return YES;
}
In Objective-C the implementation of an atomic property allows properties to be safely read and written from different threads. For nonatomic properties, the underlying pointer of a read value could be released when a new value is being written at the same time.
Atomic means only one thread accesses the variable (static type). Atomic is thread-safe, but it is slow. Nonatomic means multiple threads access the variable (dynamic type). Nonatomic is thread-unsafe, but it is fast.
Defining a property as atomic should guarantee that it can be safely read and written from different threads. Non-atomic properties have no guarantee regarding the returned value, but they come with enhanced speed of accessing these properties. Note: Property atomicity is not synonymous with an object's thread safety.
Atomic:- is the default behavior. it will ensure the present process is completed by the CPU, before another process accesses the variable.it is not fast, as it ensures the process is completed entirelyNon-Atomic: - is NOT the default behavior.
Having non-atomic properties makes the possibility of partial writes possible, but by no means certain.
In your Person class the only way you are setting first and last names is in the init method, and then you set the first name and then the last name immediately after. Setting the first name and last name will occur VERY close to each other, with little chance for another thread to mess things up between operations.
Furthermore, you create your Person objects in the main thread, before you running concurrent operations. By the time your current code runs, the objects already exist and you no longer change their name values, so there's no chance of a race condition or a partial write with name values. You are simply changing self.p between 2 objects that don't change once they are created.
That said, what IS unpredictable about your code is what person object will be in self.p at any instant. You should see the values displayed alternate between Bob Sponge and Jack Frost unpredictably.
A better test would be something like this:
(Assume each TestObject's x1 and x2 values should always be kept the same.)
@interface TestObject : NSObject
@property (nonatomic, assign) int x1;
@property (nonatomic, assign) int x2;
@end
@interface AppDelegate
@property (nonatomic, strong) TestObject *thing1;
@property (nonatomic, strong) TestObject *thing2;
@property (nonatomic, strong) NSTimer *aTimer;
@property (nonatomic, strong) NSTimer *secondTimer;
@end
And then code like this:
#include <stdlib.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
self.thing1 = [[TestObject alloc] init];
self.thing2 = [[TestObject alloc] init];
dispatch_async(queue1, ^
{
for (int x = 0; x < 100; x++)
{
usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds
int thing1Val = arc4random_uniform(10000);
int thing2Val = arc4random_uniform(10000);
_thing1.x1 = thing1Val;
usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds
_thing2.x1 = thing2Val;
_thing1.x2 = thing1Val; //thing1's x1 and x2 should now match
usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds
_thing2.x2 = thing2Val; //And now thing2's x1 and x2 should also both match
}
});
//Do the same thing on queue2
dispatch_async(queue2, ^
{
for (int x = 0; x < 100; x++)
{
usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds
int thing1Val = arc4random_uniform(10000);
int thing2Val = arc4random_uniform(10000);
_thing1.x1 = thing1Val;
usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds
_thing2.x1 = thing2Val;
_thing1.x2 = thing1Val; //thing1's x1 and x2 should now match
usleep(arc4random_uniform(50000)); //sleep for 0 to 50k microseconds
_thing2.x2 = thing2Val; //And now thing2's x1 and x2 should also both match
}
});
//Log the values in thing1 and thing2 every .1 second
self.aTimer = [NSTimer scheduledTimerWithTimeInterval:.1
target:self
selector:@selector(logThings:)
userInfo:nil
repeats:YES];
//After 5 seconds, kill the timer.
self.secondTimer = [NSTimer scheduledTimerWithTimeInterval:5.0
target:self
selector:@selector(stopRepeatingTimer:)
userInfo:nil
repeats:NO];
return YES;
}
- (void)stopRepeatingTimer:(NSTimer *)timer
{
[self.aTimer invalidate];
}
- (void)logThings:(NSTimer *)timer
{
NSString *equalString;
if (_thing1.x1 == _thing1.x2)
{
equalString = @"equal";
}
else
{
equalString = @"not equal";
}
NSLog(@"%@ : thing1.x1 = %d, thing1.x2 = %d",
equalString,
_thing1.x1,
_thing1.x2);
if (_thing2.x1 == _thing2.x2)
{
equalString = @"equal";
}
else
{
equalString = @"not equal";
}
NSLog(@"%@ : thing2.x1 = %d, thing2.x2 = %d",
equalString,
_thing2.x1,
_thing2.x2);
}
In the code above, each queue creates a series of random values, and sets both the x1 and x2 properties of a couple of objects to those random values in a repeating loop. It delays for a small random interval between setting the x1 and x2 property of each object. That delay simulates a background task taking some amount of time to finish work that should be atomic. It also introduces a window where another thread could change the second value before the current thread is able to set the second value.
If you run the code above you will almost certainly find that the x1 and x2 values of thing1 and thing2 are sometimes different.
The code above would not be helped by atomic properties. You would need to assert a lock of some sort between setting the x1 and x2 property of each object (perhaps using the @synchronized
directive).
(Note that I banged the code above together in the forum editor. I haven't tried to compile it, much less debug it. There are doubtless a few typos.)
(Note 2, to the person who edited my code: Code formatting is a matter of style and personal taste. I use a variation on "Allman indentation." I appreciate the typos corrections, but I despise K&R style indentation. Don't impose your style on my code.
A property being atomic
means that all actions performed by a read, and all actions performed by a write, are done atomically. (This is completely independent of consistency between two separate properties, as in your example, which cannot be achieved simply by adding (atomic)
.)
This matters particularly in two cases:
For object pointers, the implicit [_property release]; [newValue retain]; _property = newValue
operations that ARC performs when you store a new value, and the implicit value = _property; [value retain];
which happens when you load the value.
Large datatypes whose actual values can't be atomically loaded/stored, regardless of retain/release semantics.
Here is an example which illustrates both potential problems:
typedef struct {
NSUInteger x;
NSUInteger xSquared; // cached value of x*x
} Data;
@interface Producer : NSObject
@property (nonatomic) Data latestData;
@property (nonatomic) NSObject *latestObject;
@end
@implementation Producer
- (void)startProducing
{
// Produce new Data structs.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
for (NSUInteger x = 0; x < NSUIntegerMax; x++) {
Data newData;
newData.x = x;
newData.xSquared = x * x;
// Since the Data struct is too large for a single store,
// the setter actually updates the two fields separately.
self.latestData = newData;
}
});
// Produce new objects.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
while (true) {
// Release the previous value; retain the new value.
self.latestObject = [NSObject new];
}
});
[NSTimer scheduledTimerWithTimeInterval:0.01 target:self selector:@selector(logStatus) userInfo:nil repeats:YES];
}
- (void)logStatus
{
// Implicitly retain the current object for our own uses.
NSObject *o = self.latestObject;
NSLog(@"Latest object: %@", o);
// Validate the consistency of the data.
Data latest = self.latestData;
NSAssert(latest.x * latest.x == latest.xSquared, @"WRONG: %lu^2 != %lu", latest.x, latest.xSquared);
NSLog(@"Latest data: %lu^2 = %lu", latest.x, latest.xSquared);
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
[[Producer new] startProducing];
[[NSRunLoop mainRunLoop] run];
}
return 0;
}
With nonatomic
, for the object property, you'll occasionally get EXC_BAD_ACCESS crashes, and log messages like this:
AtomicTest[2172:57275] Latest object: <NSObject: 0x100c04a00>
objc[2172]: NSObject object 0x100c04a00 overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug
And for the Data struct, the assertion will occasionally fail:
AtomicTest[2240:59304] *** Assertion failure in -[Producer logStatus], main.m:58
AtomicTest[2240:59304] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'WRONG: 55937112^2 != 3128960610774769'
(Notice that the value of xSquared
, 3128960610774769, is actually 559371132 rather than 559371122.)
Making the properties (atomic)
rather than (nonatomic)
avoids both of these problems, at the cost of slightly slower execution.
Side note: the same problem occurs even in Swift, because there is no notion of atomic properties:
class Object { }
var obj = Object()
dispatch_async(dispatch_get_global_queue(0, 0)) {
while true {
obj = Object()
}
}
while true {
// This sometimes crashes, and sometimes deadlocks
let o = obj
print("Current object: \(o)")
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With