Reputation: 1207
My properties:
@property (nonatomic, strong) NSMutableData *data1;
@property (nonatomic, strong) NSData *data2;
When I modify data1
, as NSMutableData
in multi-threading, it does crash. When I modify data2
, as NSData
in multi-threading, it does not crash.
for (int i = 0; i < 1000; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
self.data1 = [[NSMutableData alloc] init]; // crashes
self.data2 = [[NSData alloc] init]; // does not crash
});
}
Why? What's the difference?
Upvotes: 0
Views: 170
Reputation: 21259
@property (nonatomic, strong) NSMutableData *data;
We had something called MRC before the ARC. MRC stands for Manual Reference Counting and ARC stands for Automatic Reference Counting. In these times, one can implement setter in this way:
- (void)setData:(NSMutableData *)data {
id oldValue = _data;
if (oldValue == data) {
return;
}
_data = [data retain];
[oldValue release];
}
In ARC, it's way simpler:
- (void)setData:(NSMutableData *)data {
_data = data;
}
Why? Because the compiler automatically inserts retain
& release
calls
for you. But it's important to know that these calls are still there.
objc_storeStrong
Put this property in any of your class and select Product - Perform Action - Assemble "YourClass.m" from the Xcode menu. You'll get generated assembly of the file (mine is in the AppDelegate
):
"-[AppDelegate setData:]": ## -- Begin function -[AppDelegate setData:]
## @"\01-[AppDelegate setData:]"
Lfunc_begin5:
.loc 1 13 0 ## MRC/AppDelegate.m:13:0
.cfi_startproc
## %bb.0:
##DEBUG_VALUE: -[AppDelegate setData:]:self <- $rdi
##DEBUG_VALUE: -[AppDelegate setData:]:self <- $rdi
##DEBUG_VALUE: -[AppDelegate setData:]:_cmd <- $rsi
##DEBUG_VALUE: -[AppDelegate setData:]:data <- $rdx
##DEBUG_VALUE: -[AppDelegate setData:]:data <- $rdx
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset %rbp, -16
movq %rsp, %rbp
.cfi_def_cfa_register %rbp
Ltmp12:
.loc 1 0 0 prologue_end ## MRC/AppDelegate.m:0:0
addq $16, %rdi
Ltmp13:
##DEBUG_VALUE: -[AppDelegate setData:]:self <- [DW_OP_LLVM_entry_value 1] $rdi
movq %rdx, %rsi
Ltmp14:
##DEBUG_VALUE: -[AppDelegate setData:]:_cmd <- [DW_OP_LLVM_entry_value 1] $rsi
##DEBUG_VALUE: -[AppDelegate setData:]:data <- $rsi
popq %rbp
Ltmp15:
jmp _objc_storeStrong ## TAILCALL
Ltmp16:
Lfunc_end5:
.cfi_endproc
Even if you can't read assembly, don't worry, you'll see that there's not much going on and that there's the objc_storeStrong
function call. The
source code looks like:
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
What it does?
prev
variableAs you can see there's a lot of stuff going on between the id prev = *location
and objc_release(prev)
lines.
Back to your crash. The crash you see is something like this:
(lldb) bt
* thread #6, queue = 'com.apple.root.default-qos', stop reason = EXC_BAD_ACCESS (code=1, address=0x20d71fa95490)
frame #0: 0x00007fff6d15130f libobjc.A.dylib`objc_release + 31
frame #1: 0x00000001069c821e MRC`-[AppDelegate setData1:](self=0x0000600001472660, _cmd="setData1:", data1=0 bytes) at AppDelegate.m:0
objc_release
is crashing. Why? Scroll up and check the objc_storeStrong
function implementation again. Remember how many things it does between the id prev = *location
and objc_release(prev)
lines?
And now imagine that in your multi-threaded environment, you call objc_storeStrong
thousand times, before the first call finishes, another is started, stores the same pointer in the prev
variable and then it is going to release the same object for 2nd, 3rd, 4th, ... time.
Okay, I understand, but why NSData
property doesn't crash? Well,
NSData
is immutable and when you just allocate & initialize it, you'll actually get shared _NSZeroData
- NSData
subclass used for zero length NSData
.
You can check it in a simple way:
NSData *d1 = [[NSData alloc] init];
NSData *d2 = [[NSData alloc] init];
NSData *d3 = [[NSData alloc] init];
NSData *d4 = [[NSData alloc] init];
NSLog(@"%p %p %p %p", d1, d2, d3, d4);
Output on my computer is:
0x60000001cd10 0x60000001cd10 0x60000001cd10 0x60000001cd10
%p
prints pointer (memory address) and all d1
, d2
, d3
& d4
contains pointer to the same object.
Back to the objc_storeStrong
implementation:
void
objc_storeStrong(id *location, id obj)
{
id prev = *location;
if (obj == prev) {
return;
}
objc_retain(obj);
*location = obj;
objc_release(prev);
}
Pointers do equal in this case hence the early return. In other words, this function does nothing in this specific case.
Start with the Threading Programming Guide. Learn more about atomic
vs nonoatomic
, (not-)thread-safe data types, @synchronized
and other primitives you can use in multi-threaded environment.
Upvotes: 2