I thought that I had quite understood weak references and blocks, however when trying the below code snippets, there are a few things that I don't understand.
Method test1: all fine the object is not retained
Method test2: I don't understand why the object seems to get retained until the end of method test3! Even explicitly setting object = nil at the end of method test2 does not change anything.
Method test3: the object is not retained. Why is method test2 not behaving like this?
As a side question, I was actually wondering if weak variables are thread safe? ie if I will never get any BAD_ACCESS exception when trying to access a weak variable from different threads.
#interface Object : NSObject
#property (nonatomic) NSInteger index;
#end
#implementation Object
- (id)initWithIndex:(NSInteger) index {
if (self = [super init]) {
_index = index;
}
return self;
}
- (void)dealloc {
NSLog(#"Deallocating object %d", _index);
}
#end
Test methods
- (void) test1 {
NSLog(#"test1");
Object* object = [[Object alloc] initWithIndex:1];
NSLog(#"Object: %#", object);
__weak Object* weakObject = object;
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
//NSLog(#"Weak object: %#", weakObject);
[NSThread sleepForTimeInterval:2];
NSLog(#"Exiting dispatch");
});
[NSThread sleepForTimeInterval:1];
NSLog(#"Exiting method");
}
- (void) test2 {
NSLog(#"test2");
Object* object = [[Object alloc] initWithIndex:2];
NSLog(#"Object: %#", object);
__weak Object* weakObject = object;
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
NSLog(#"Weak object: %#", weakObject);
[NSThread sleepForTimeInterval:2];
NSLog(#"Exiting dispatch");
});
[NSThread sleepForTimeInterval:1];
NSLog(#"Exiting method");
}
- (void) test3 {
NSLog(#"test3");
Object* object = [[Object alloc] initWithIndex:3];
NSLog(#"Object: %#", object);
NSValue *weakObject = [NSValue valueWithNonretainedObject:object];
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
NSLog(#"Weak object: %#", [weakObject nonretainedObjectValue]);
[NSThread sleepForTimeInterval:2];
NSLog(#"Exiting dispatch");
});
[NSThread sleepForTimeInterval:1];
NSLog(#"Exiting method");
}
- (void) test {
[self test1];
[NSThread sleepForTimeInterval:3];
[self test2];
[NSThread sleepForTimeInterval:3];
[self test3];
}
The output of the above is:
2013-05-11 19:09:56.753 test[1628:c07] test1
2013-05-11 19:09:56.754 test[1628:c07] Object: <Object: 0x7565940>
2013-05-11 19:09:57.755 test[1628:c07] Exiting method
2013-05-11 19:09:57.756 test[1628:c07] Deallocating object 1
2013-05-11 19:09:58.759 test[1628:1503] Exiting dispatch
2013-05-11 19:10:00.758 test[1628:c07] test2
2013-05-11 19:10:00.758 test[1628:c07] Object: <Object: 0x71c8260>
2013-05-11 19:10:00.759 test[1628:1503] Weak object: <Object: 0x71c8260>
2013-05-11 19:10:01.760 test[1628:c07] Exiting method
2013-05-11 19:10:02.760 test[1628:1503] Exiting dispatch
2013-05-11 19:10:04.761 test[1628:c07] test3
2013-05-11 19:10:04.762 test[1628:c07] Object: <Object: 0x71825f0>
2013-05-11 19:10:04.763 test[1628:1503] Weak object: <Object: 0x71825f0>
2013-05-11 19:10:05.764 test[1628:c07] Exiting method
2013-05-11 19:10:05.764 test[1628:c07] Deallocating object 3
2013-05-11 19:10:05.767 test[1628:c07] Deallocating object 2
2013-05-11 19:10:06.764 test[1628:1503] Exiting dispatch
I have two observations on your three tests before I touch on a few of your questions:
Your testing is complicated by the fact that you're running all three tests in a row, not yielding back to the run loop, and thus your autorelease pool is not getting flushed (so it making things look like they're persisting longer than they normally would). You should do you testing, one test at a time, to really understand what's going on. It's not good if you're drawing conclusions about the lifespan of some object, whereas you really may just be experiencing some artifact of the fact that you're not letting the autorelease pool from being flushed.
You are doing all of these tests as dispatch_async, which starts the dispatched block very quickly, sometimes more quickly than it takes the underlying object to fall out of scope and you're often accessing the weakObject as one of the first steps in the dispatched block. I'd suggest using dispatch_after (so you're really giving the calling method a chance to let the variables fall out of scope), so you'll better see what's going on.
Your tests are a good data point, but I think it's useful to also test the same stuff using dispatch_after and do one test at a time with fewer of those sleepForTimeInterval. It feels like some of the idiosyncrasies of your tests are counterfeiting some key behavior.
Anyway you ask:
Method test2: I don't understand why the object seems to get retained until the end of method test3! Even explicitly setting object = nil at the end of method test2 does not change anything.
It's undoubtedly fallen into the autorelease pool, which won't be drained until the test method is done.
To my prior points, try doing test2 again, but have the operation wait two seconds before accessing the weakObject (or get rid of all of these sleepForTimeInterval statements and use dispatch_after instead of dispatch_sync):
- (void) test2 {
NSLog(#"test2");
Object* object = [[Object alloc] initWithIndex:2];
NSLog(#"Object: %#", object);
__weak Object* weakObject = object;
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
[NSThread sleepForTimeInterval:2]; // new sleep
NSLog(#"Weak object: %#", weakObject);
[NSThread sleepForTimeInterval:2];
NSLog(#"Exiting dispatch");
});
// [NSThread sleepForTimeInterval:1]; // not really necessary
NSLog(#"Exiting method");
}
You'll see that this behaves more like you expected.
Method test3: the object is not retained. Why is method test2 not behaving like this?
Needless to say, your test3 is seriously bad news, easily crashing itself. For example, try commenting out the sleep line:
- (void) test3 {
NSLog(#"test3");
Object* object = [[Object alloc] initWithIndex:3];
NSLog(#"Object: %#", object);
NSValue *weakObject = [NSValue valueWithNonretainedObject:object];
dispatch_async(dispatch_queue_create(NULL, NULL), ^{
NSLog(#"Weak object: %#", [weakObject nonretainedObjectValue]);
[NSThread sleepForTimeInterval:2];
NSLog(#"Exiting dispatch");
});
// [NSThread sleepForTimeInterval:1];
NSLog(#"Exiting method");
}
It strikes me that it's behaving less like weak and more like unsafe_unretained.
As a side question, I was actually wondering if weak variables are thread safe? ie if I will never get any BAD_ACCESS exception when trying to access a weak variable from different threads.
You can get exceptions in a lot of ways. If you pass weakObject to some method that requires that it not be nil (e.g. NSMutableArray method addObject), you'll get an exception. You can also get exceptions if you dereference ivars for a nil object pointer, e.g. obj->objectIvar. For example, imagine a Object instance method, doSomethingLater, which uses a weak reference to ensure that it doesn't retain the Object, but then has a local strong reference so it can dereference the ivar:
- (void)doSomethingLater
{
__weak Object *weakSelf = self;
double delayInSeconds = 10.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
Object *strongSelf = weakSelf;
NSLog(#"%d", strongSelf->_index); // **BAD** - this can crash of `self` has been released by this point
});
}
Thus, you usually replace the above with:
- (void)doSomethingLater
{
__weak Object *weakSelf = self;
double delayInSeconds = 10.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
Object *strongSelf = weakSelf;
if (strongSelf) {
NSLog(#"%d", strongSelf->_index);
}
});
}
To be perfectly honest, though, the details of why that first code sample can crash and the second can't is less important than the obvious fact that judicious use of your object references in asynchronous programming is important, and failure to handle the situations carefully can result in exceptions. Frequently, checking that the weakObject is not nil can prevent many of these sorts of issues (with some caveats that I'm not going to go into). This is less important when calling object methods (because sending any message to nil results in nil), but it is important when your weakObject is a parameter or being dereferenced for an ivar.
But to be clear, none of that really has any bearing on thread-safety, though. You achieve thread safety through proper handling of synchronization, such as locking mechanisms or through judicious use of queues (either serial queue; or the reader/writer pattern of a concurrent queue with dispatch_barrier_async for writes and dispatch_sync for reads).
Just because you have code where you're handling object references carefully so you don't get exceptions, doesn't mean you've achieved thread-safety. There's a whole other layer of concerns that thread-safety entails.
Related
Say you have a method that returns information in two separate blocks, like so:
#interface SomeObject : NSObject
- (instancetype)initWithA:(NSString *)aInfo bInfo:(NSString *)bInfo;
#end
- (void)someMethod:(void (^)(NSString *aInfo))firstBlock
secondBlock:(void (^)(NSString *bInfo))secondBlock {
firstBlock(#"a"); secondBlock(#"b");
}
- (void)ourMethod:(void (^)(SomeObject *object))completionBlock {
SomeObject *someObject = [[SomeObject alloc] initWithA:aInfo bInfo:bInfo];
[self someMethod:^(NSString *aInfo) {
//
} secondBlock:^(NSString *bInfo) {
//
}];
completionBlock(someObject);
}
How would you initialize someObject and pass it back when both of the blocks have completed?
Assume that both blocks are executed asynchronously.
I tried fiddling with GCD's dispatch groups to solve this, however, it didn't seem optimal.
Since you need to create your someObject with the values obtained from the two blocks used in the call to someMethod, you need to create someObject after both blocks have been called.
- (void)ourMethod:(void (^)(BOOL initializationComplete))completionBlock {
__block NSString *a = nil;
__block NSString *b = nil;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_enter(group);
[self someMethod:^(NSString *aInfo) {
a = aInfo;
dispatch_group_leave(group);
} secondBlock:^(NSString *bInfo) {
b = bInfo;
dispatch_group_leave(group);
}];
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
SomeObject *someObject = [[SomeObject alloc] initWithA:a bInfo:b];
completionBlock(someObject);
});
}
This doesn't block the caller of ourMethod and it ensures the completion block is only called once both blocks are done.
This solution assumes the two blocks are run asynchronously.
You can use a semaphore, but-- in general-- making an asynchronous operation synchronous is a red flag indicating bad design.
Are the two blocks asynchronous in and of themselves? If so, you could have __block BOOL firstDone = NO; and __block BOOL secondDone = NO; and check appropriately to see if it is time to call the completionBlock. Still ugly and you'll want a synchronization primitive in there to ensure you don't hit a race, but that'd work.
If firstBlock() and secondBlock() are synchronous and on the same queue, then just call completionBlock() after the second is done.
Or, alternatively, if they are asynchronous and simultaneously scheduled, toss 'em on an asynchronous queue and then toss a barrier block on the queue that calls the completionBlock.
I have been trying to understand the reason behind this crash for the sake of understanding more about how blocks behave. I have a really simple class to trigger this crash.
#implementation BlockCrashTest
- (void)doSomething
{
dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_block_t block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_group_t group = dispatch_group_create();
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
dispatch_group_enter(group);
[strongSelf performSomethingAsync:^{
dispatch_group_leave(group);
}];
if(dispatch_group_wait(group, time) != 0) {
NSLog(#"group already finished");
}
};
dispatch_async(queue, block);
}
- (void)performSomethingAsync:(void(^)(void))completion
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(5);
completion();
});
}
- (void)dealloc
{
NSLog(#"released object");
}
#end
Now, if I allocate the class and simply call method doSomething to it,
BlockCrashTest *someObject = [[BlockCrashTest alloc] init];
[someObject doSomething];
It crashes with the exception, EXC_BAD_INSTRUCTION and following stack traces,
#0 0x000000011201119a in _dispatch_semaphore_dispose ()
#1 0x0000000112013076 in _dispatch_dispose ()
#2 0x0000000112026172 in -[OS_dispatch_object _xref_dispose] ()
#3 0x000000010ef4c2fd in __29-[BlockCrashTest doSomething]_block_invoke at /Users/Sandeep/Desktop/Test Block Crash/Test Block Crash/ViewController.m:35
#4 0x0000000112005ef9 in _dispatch_call_block_and_release ()
If I modify the method doSomething, such that it do not use weak but uses self then the crash do not occur and the methods seem to execute as expected,
- (void)doSomething
{
dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = ^{
dispatch_group_t group = dispatch_group_create();
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
dispatch_group_enter(group);
[self performSomethingAsync:^{
dispatch_group_leave(group);
}];
if(dispatch_group_wait(group, time) != 0) {
NSLog(#"group already finished");
}
};
dispatch_async(queue, block);
}
Why does it crash, my understanding is that using weak inside the block would make sure that the method would not be called, if the object is released and I thought that weak is safer than using self inside the block.
The above code works fine with weakSelf, if I retain the object BlockCrashTest and call the method to it.
I would be really happy if someone could explain the reason behind the crash and what exactly happens with these 3 different variants of code above that one crash and other seem to work fine.
Note: This is in one way or other related to the crash listed in
thread,
Objective-C crash on __destroy_helper_block_.
I have been able to reproduce the exact same stack traces with my code
above.
A couple of observations:
You cannot have a dispatch group with unbalanced "enter" and "leave" when the dispatch_group_t object is deallocated. And as ilya pointed out, because of your pattern, strongSelf is nil, so you're entering the group, but not leaving it.
A very common pattern in the weakSelf and strongSelf dance is to just check to see if strongSelf was nil or not, resolving the imbalance. Thus, if strongSelf is nil, it bypasses the dispatch group stuff altogether, but if it is not nil, both "enter" and "leave" will be called:
- (void)doSomething {
dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL);
typeof(self) weakSelf = self;
dispatch_async(queue, ^{
typeof(self) strongSelf = weakSelf;
if (strongSelf) {
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[strongSelf performSomethingAsync:^{
dispatch_group_leave(group);
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
}
});
}
Clearly, you must make sure that performSomethingAsync method, itself, always calls the block which leaves the group.
The other way of solving this (if you don't have assurances that all of the "enter" and "leave" will be balanced), is to use semaphores:
- (void)doSomething {
dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL);
typeof(self) weakSelf = self;
dispatch_async(queue, ^{
typeof(self) strongSelf = weakSelf;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[strongSelf performSomethingAsync:^{
dispatch_semaphore_signal(semaphore);
}];
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
if (dispatch_semaphore_wait(semaphore, time) != 0) {
NSLog(#"semaphore not received in time");
}
});
}
Frankly, even when using semaphores, like I have above, I still think one would want to check to confirm that strongSelf was not nil. Concurrent programming is confusing enough without adding the possibility of the message to a nil object resulting in an no-op.
- (void)doSomething {
dispatch_queue_t queue = dispatch_queue_create("com.queue.test", DISPATCH_QUEUE_SERIAL);
typeof(self) weakSelf = self;
dispatch_async(queue, ^{
typeof(self) strongSelf = weakSelf;
if (strongSelf) {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[strongSelf performSomethingAsync:^{
dispatch_semaphore_signal(semaphore);
}];
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
if (dispatch_semaphore_wait(semaphore, time) != 0) {
NSLog(#"semaphore not received in time");
}
}
});
}
You'll get the same crash even if you remove call of self performSomethingAsync:. This crash caused by libdispatch semaphore API. You can see assembly trace of crashed function _dispatch_semaphore_dispose in Xcode:
If we try to figure out what happens in this code we'll see that you explicitly mark current block entered the group by calling dispatch_group_enter. Then performSomethingAsync doesn't call because strongSelf == nil. Which means that dispatch_group_enter is not balanced with dispatch_group_leave this cause the group couldn't dispose properly and crashed (see asm listing).
If you use self this code also crashed because dispatch_group_leave(group); called from the different thread with dispatch_group_enter Which is also cause the same crash but in another perspective: calls not balanced in the same thread. performSomethingAsync called completion block in different queue not in yours "com.queue.test".
This example is just wrong using of dispatch_groups APIs. To see how properly use it see apple doc.
MacOS 10.8 and iOS 6.0 introduced ARC to dispatch objects. From the documentation GCD Objects and Automatic Reference Counting:
When you build your app using the Objective-C compiler, all dispatch objects are Objective-C objects. As such, when automatic reference counting (ARC) is enabled, dispatch objects are retained and released automatically just like any other Objective-C object. When ARC is not enabled, use the dispatch_retain and dispatch_release functions (or Objective-C semantics) to retain and release your dispatch objects. You cannot use the Core Foundation retain/release functions.
If you need to use retain/release semantics in an ARC-enabled app with a later deployment target (for maintaining compatibility with existing code), you can disable Objective-C-based dispatch objects by adding -DOS_OBJECT_USE_OBJC=0 to your compiler flags.
In your case ARC is happily managing the life cycle of your dispatch_group_t. And, unfortunately, your code is causing the group to be released while the lock is still waiting. When the group times out it is released - so when dispatch_group_leave is called it crashes, as the group has already been released.
I would suggest at the very least checking wether the group is NULL before attempting to leave it.
Additionally, your wait result logic is reversed. A zero result indicates the group was emptied before the timeout, a non zero result indicates the timeout was hit.
I'm new to GCD, and what seems to be a simple use of it doesn't work for me. I have the following code:
+ (void)synchronizationTimerFired:(NSTimer *)theTimer
{
if ((synchronizationUpNeededFlag) || (synchronizationDownNeededFlag))
{
if ((!synchronizationUpInProgressDepthQuantity) && (!synchronizationDownInProgressDepthQuantity))
{
dispatch_queue_t synchronizationQueue = dispatch_queue_create("synchronizationQueue",NULL);
dispatch_async(synchronizationQueue, ^(void) {
NSLog(#"Top");
...code...
...code...
...code...
NSLog(#"Bottom");
});
}
}
// Check if there is no timer, or if it is not currently valid,
// and yet if synchronization is turned on,
// then establish a repeating timer to attend to synchronization related matters.
if ((!synchronizationTimer) || (!synchronizationTimer.isValid))
{
if (synchronizationOnFlag)
{
synchronizationTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(synchronizationTimerFired:) userInfo:nil repeats:YES];
}
}
}
The log reads "Top" and nothing else. The code in the middle doesn't have an endless loop- it just never executes all the way through. I can put breakpoints in the code in the middle and there's a point where the program execution will break, and after which it won't. And there's a point right in the middle where sometimes the execution will stop at the breakpoint and other times it doesn't.
It seems to me as though the synchronizationQueue dispatch queue is being deallocated, but I can't call dispatch_retain because the compiler complains that dispatch_retain cannot be used in ARC. What am I missing?
In response to people asking about the code in-between, the program execution stops in this method call (represented by one of those lines of ...code...) at the line that says if (fetchArray.count), commented below.
+ (NSDate *)latestParseReceivedDownUpdatedAtDateForCoreDataEntityNameString:(NSString *)coreDataEntityNameString
{
NSDate *functionReturnValue = nil;
// Create fetchRequest
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:coreDataEntityNameString];
// Set sort descriptor
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"parseReceivedDownUpdatedAtDate" ascending:NO]]];
// We are only interested in one result
[fetchRequest setFetchLimit:1];
// Execute fetchRequest
NSError *fetchError = nil;
NSArray *fetchArray = [JBSAPPDELEGATE.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError];
if (fetchArray == nil)
{
NSLog(#"Unresolved error %#, %#", fetchError, [fetchError userInfo]);
abort();
}
// If there are any records at all in our persistent store, we'll have exactly one.
// But that doesn't mean it won't be nil, as if that record has never come down from
// parse it will be a nil date on the managed object.
if (fetchArray.count) // PROGRAM EXECUTION STOPS EITHER HERE, OR JUST BEFORE HERE
{
NSManagedObject *managedObject = [fetchArray objectAtIndex:0];
functionReturnValue = [managedObject valueForKey:#"parseReceivedDownUpdatedAtDate"];
}
return functionReturnValue;
}
I will add that if I simply comment out the call to dispatch_async that everything executes fine. It just executes on the main thread, which I'd rather have it not do.
Is your managedObjectContext an NSManagedObjectContext? If so, did you create a managed object context on the specific thread that you are using for your dispatch queue? If you read the docs on NSManagedObjectContext, it says:
...a context assumes the default owner is the thread or queue that
allocated it—this is determined by the thread that calls its init
method. You should not, therefore, initialize a context on one thread
then pass it to a different thread. Instead, you should pass a
reference to a persistent store coordinator and have the receiving
thread/queue create a new context derived from that.
I bet you created yourself a deadlock somewhere.
Use the debugger. Check what every thread is doing. You'll probably find some thread that you think should be continuing and isn't.
Could somebody, please, explain me why I get error EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0) in dispatch_semaphore_wait in the following code:
-(void) initialize {
dispatch_queue_t queue = dispatch_queue_create("My queue", NULL);
dispatch_semaphore_t sem = dispatch_semaphore_create(1);
self.queue = queue;
self.sem = sem;
self.myarray = [[NSMutableArray alloc]init];
[self.myarray addObject: [[MyObject alloc] init]];
}
-(MyObject *) method1 {
//do something
dispatch_semaphore_wait(self.sem, DISPATCH_TIME_FOREVER);
MyObject *obj = [self.myarray objectAtIndex:0];
dispatch_barrier_sync(self.queue, ^{
[self.myarray removeObjectAtIndex:0];
});
return obj;
}
-(void) method2:(MyObject *)object {
//do something
dispatch_barrier_async(self.queue, ^{
[self.myarray addObject:object];
dispatch_semaphore_signal(self.sem);
});
}
I found similar question Why does this code cause "EXC_BAD_INSTRUCTION"?, but in my case I am using ARC and I do not write explicitly nowhere dispatch_release(sem);
The sem you created in your initialize method is locally scoped to that method. It needs to be accessible to the other methods. If you have an iVar named sem that you're attempting to assign, you're shadowing it by declaring a local variable in initialize. (Same thing with queue, by the way.)
Also, you appear to have a typo here, in that you call dispatch_semaphore_wait(sen, DISPATCH_TIME_FOREVER); (i.e. se n vs se m)
You are allowing simultaneous access to the array self.myarray without sufficient protection. You modify the array with -addObject: and -removeObjectAtIndex: on the serial queue self.queue but you read from it using -objectAtIndex: without any protection. That means you may be reading from it at the same time you're writing to it, which is not safe. You need to also put the -objectAtIndex: call on the serial queue.
Also, you are using barrier functions with a serial queue, which doesn't make any sense.
-(MyObject *) method1 {
//do something
dispatch_semaphore_wait(self.sem, DISPATCH_TIME_FOREVER);
__block MyObject *obj;
dispatch_sync(self.queue, ^{
obj = [self.myarray objectAtIndex:0];
[self.myarray removeObjectAtIndex:0];
});
return obj;
}
-(void) method2:(MyObject *)object {
//do something
dispatch_async(self.queue, ^{
[self.myarray addObject:object];
dispatch_semaphore_signal(self.sem);
});
}
This kind of crash will happen when you are running a (vector)extension which is not supported on your CPU.
For example, in xcode 5 under "project-settings / build-settings / Code Generation, set the
"Enable Additional Vector extensions" to "AVX2". Build your executable.
Now run it on an:
Intel Core i5: it's going to crash (wherever the compiler decided to use avx2) with 'exc_i386_invop subcode=0x0'.
Intel Core i7: it will work.
Long story short, I'm tired of the absurd concurrency rules associated with NSManagedObjectContext (or rather, its complete lack of support for concurrency and tendency to explode or do other incorrect things if you attempt to share an NSManagedObjectContext across threads), and am trying to implement a thread-safe variant.
Basically what I've done is created a subclass that tracks the thread that it was created on, and then maps all method invocations back to that thread. The mechanism for doing this is slightly convoluted, but the crux of it is that I've got some helper methods like:
- (NSInvocation*) invocationWithSelector:(SEL)selector {
//creates an NSInvocation for the given selector
NSMethodSignature* sig = [self methodSignatureForSelector:selector];
NSInvocation* call = [NSInvocation invocationWithMethodSignature:sig];
[call retainArguments];
call.target = self;
call.selector = selector;
return call;
}
- (void) runInvocationOnContextThread:(NSInvocation*)invocation {
//performs an NSInvocation on the thread associated with this context
NSThread* currentThread = [NSThread currentThread];
if (currentThread != myThread) {
//call over to the correct thread
[self performSelector:#selector(runInvocationOnContextThread:) onThread:myThread withObject:invocation waitUntilDone:YES];
}
else {
//we're okay to invoke the target now
[invocation invoke];
}
}
- (id) runInvocationReturningObject:(NSInvocation*) call {
//returns object types only
[self runInvocationOnContextThread:call];
//now grab the return value
__unsafe_unretained id result = nil;
[call getReturnValue:&result];
return result;
}
...and then the subclass implements the NSManagedContext interface following a pattern like:
- (NSArray*) executeFetchRequest:(NSFetchRequest *)request error:(NSError *__autoreleasing *)error {
//if we're on the context thread, we can directly call the superclass
if ([NSThread currentThread] == myThread) {
return [super executeFetchRequest:request error:error];
}
//if we get here, we need to remap the invocation back to the context thread
#synchronized(self) {
//execute the call on the correct thread for this context
NSInvocation* call = [self invocationWithSelector:#selector(executeFetchRequest:error:) andArg:request];
[call setArgument:&error atIndex:3];
return [self runInvocationReturningObject:call];
}
}
...and then I'm testing it with some code that goes like:
- (void) testContext:(NSManagedObjectContext*) context {
while (true) {
if (arc4random() % 2 == 0) {
//insert
MyEntity* obj = [NSEntityDescription insertNewObjectForEntityForName:#"MyEntity" inManagedObjectContext:context];
obj.someNumber = [NSNumber numberWithDouble:1.0];
obj.anotherNumber = [NSNumber numberWithDouble:1.0];
obj.aString = [NSString stringWithFormat:#"%d", arc4random()];
[context refreshObject:obj mergeChanges:YES];
[context save:nil];
}
else {
//delete
NSArray* others = [context fetchObjectsForEntityName:#"MyEntity"];
if ([others lastObject]) {
MyEntity* target = [others lastObject];
[context deleteObject:target];
[context save:nil];
}
}
[NSThread sleepForTimeInterval:0.1];
}
}
So essentially, I spin up some threads targeting the above entry point, and they randomly create and delete entities. This almost works the way it should.
The problem is that every so often one of the threads will get an EXC_BAD_ACCESS when calling obj.<field> = <value>;. It's not clear to me what the problem is, because if I print obj in the debugger everything looks good. Any suggestions on what the problem might be (other than the fact that Apple recommends against subclassing NSManagedObjectContext) and how to fix it?
P.S. I'm aware of GCD and NSOperationQueue and other techniques typically used to "solve" this problem. None of those offer what I want. What I'm looking for is an NSManagedObjectContext that can be freely, safely, and directly used by any number of threads to view and change application state without requiring any external synchronization.
As noa rightly pointed out, the problem was that although I had made the NSManagedObjectContext thread-safe, I had not instrumented the NSManagedObject instances themselves to be thread-safe. Interactions between the thread-safe context and the non-thread-safe entities were responsible for my periodic crashes.
In case anyone is interested, I created a thread-safe NSManagedObject subclass by injecting my own setter methods in lieu of (some of) the ones that Core Data would normally generate. This is accomplished using code like:
//implement these so that we know what thread our associated context is on
- (void) awakeFromInsert {
myThread = [NSThread currentThread];
}
- (void) awakeFromFetch {
myThread = [NSThread currentThread];
}
//helper for re-invoking the dynamic setter method, because the NSInvocation requires a #selector and dynamicSetter() isn't one
- (void) recallDynamicSetter:(SEL)sel withObject:(id)obj {
dynamicSetter(self, sel, obj);
}
//mapping invocations back to the context thread
- (void) runInvocationOnCorrectThread:(NSInvocation*)call {
if (! [self myThread] || [NSThread currentThread] == [self myThread]) {
//okay to invoke
[call invoke];
}
else {
//remap to the correct thread
[self performSelector:#selector(runInvocationOnCorrectThread:) onThread:myThread withObject:call waitUntilDone:YES];
}
}
//magic! perform the same operations that the Core Data generated setter would, but only after ensuring we are on the correct thread
void dynamicSetter(id self, SEL _cmd, id obj) {
if (! [self myThread] || [NSThread currentThread] == [self myThread]) {
//okay to execute
//XXX: clunky way to get the property name, but meh...
NSString* targetSel = NSStringFromSelector(_cmd);
NSString* propertyNameUpper = [targetSel substringFromIndex:3]; //remove the 'set'
NSString* firstLetter = [[propertyNameUpper substringToIndex:1] lowercaseString];
NSString* propertyName = [NSString stringWithFormat:#"%#%#", firstLetter, [propertyNameUpper substringFromIndex:1]];
propertyName = [propertyName substringToIndex:[propertyName length] - 1];
//NSLog(#"Setting property: name=%#", propertyName);
[self willChangeValueForKey:propertyName];
[self setPrimitiveValue:obj forKey:propertyName];
[self didChangeValueForKey:propertyName];
}
else {
//call back on the correct thread
NSMethodSignature* sig = [self methodSignatureForSelector:#selector(recallDynamicSetter:withObject:)];
NSInvocation* call = [NSInvocation invocationWithMethodSignature:sig];
[call retainArguments];
call.target = self;
call.selector = #selector(recallDynamicSetter:withObject:);
[call setArgument:&_cmd atIndex:2];
[call setArgument:&obj atIndex:3];
[self runInvocationOnCorrectThread:call];
}
}
//bootstrapping the magic; watch for setters and override each one we see
+ (BOOL) resolveInstanceMethod:(SEL)sel {
NSString* targetSel = NSStringFromSelector(sel);
if ([targetSel startsWith:#"set"] && ! [targetSel contains:#"Primitive"]) {
NSLog(#"Overriding selector: %#", targetSel);
class_addMethod([self class], sel, (IMP)dynamicSetter, "v#:#");
return YES;
}
return [super resolveInstanceMethod:sel];
}
This, in conjunction with my thread-safe context implementation, solved the problem and got me what I wanted; a thread-safe context that I can pass around to whomever I want without having to worry about the consequences.
Of course this is not a bulletproof solution, as I have identified at least the following limitations:
/* Also note that using this tool carries several small caveats:
*
* 1. All entities in the data model MUST inherit from 'ThreadSafeManagedObject'. Inheriting directly from
* NSManagedObject is not acceptable and WILL crash the app. Either every entity is thread-safe, or none
* of them are.
*
* 2. You MUST use 'ThreadSafeContext' instead of 'NSManagedObjectContext'. If you don't do this then there
* is no point in using 'ThreadSafeManagedObject' (and vice-versa). You need to use the two classes together,
* or not at all. Note that to "use" ThreadSafeContext, all you have to do is replace every [[NSManagedObjectContext alloc] init]
* with an [[ThreadSafeContext alloc] init].
*
* 3. You SHOULD NOT give any 'ThreadSafeManagedObject' a custom setter implementation. If you implement a custom
* setter, then ThreadSafeManagedObject will not be able to synchronize it, and the data model will no longer
* be thread-safe. Note that it is technically possible to work around this, by replicating the synchronization
* logic on a one-off basis for each custom setter added.
*
* 4. You SHOULD NOT add any additional #dynamic properties to your object, or any additional custom methods named
* like 'set...'. If you do the 'ThreadSafeManagedObject' superclass may attempt to override and synchronize
* your implementation.
*
* 5. If you implement 'awakeFromInsert' or 'awakeFromFetch' in your data model class(es), thne you MUST call
* the superclass implementation of these methods before you do anything else.
*
* 6. You SHOULD NOT directly invoke 'setPrimitiveValue:forKey:' or any variant thereof.
*
*/
However, for most typical small to medium-sized projects I think the benefits of a thread-safe data layer significantly outweigh these limitations.
Why not just instantiate your context using one of the provided concurrency types, and leverage performBlock / performBlockAndWait?
That implements the necessary thread confinement with having to mangle with the implementation of Core Data's accessor methods. Which, as you will soon find out will be either very painful to get right or end quite badly for your users.
A great tutorial by Bart Jacobs entitled: Core Data from Scratch: Concurrency for those that need an elegant solution for iOS 5.0 or later and/or Lion or later. Two approaches are described in detail, the more elegant solution involves parent/child managed object contexts.