I have one managedObjectContext with concurency type of NSMainQueueConcurrencyType
+ (NSManagedObjectContext *)managedObjectContextMainThread
{
static NSManagedObjectContext *__managedObjectContext=nil;
#synchronized(self)
{
if (__managedObjectContext != nil)
{
}
else {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[__managedObjectContext setPersistentStoreCoordinator:coordinator];
}
}
}
return __managedObjectContext;
}
The main managedObjectContext is NEVER accessed outside of the main thread except when setting the other managedObjectContext.parent. So that mainManagedObjectContext is the parent for all threads.
Now when I run the program, sometimes it reach a deadlock. I pause the program and this is what I see:
As we see in the picture, there are 2 threads that seems to be in the deadlock. The first is the main thread.
It deadlock on #synchronize (self). Reasonable.
The other thread is deadlock on:
So it locks when trying to change the persistent store of the static variable that hold the __managedObjectContext.
Let me put the code again to reiterate:
+ (NSManagedObjectContext *)managedObjectContextMainThread
{
static NSManagedObjectContext *__managedObjectContext=nil;
#synchronized(self) //Main thread deadlock here
{
if (__managedObjectContext != nil)
{
}
else {
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil)
{
__managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[__managedObjectContext setPersistentStoreCoordinator:coordinator]; //Secondary thread dead lock here
}
}
}
return __managedObjectContext;
}
My question is why in the earth the [__managedObjectContext setPersistentStoreCoordinator:coordinator];
NOTHING else is accessing __managedObjectContext. The second thread (the non main one) is trying to set the __managedObjectContext as the parent context. The first thread is just waiting happily in #synchronized. It doesn't do anything.
So why the deadlock and how to solve that?
Oh the child managedObjectContext is created here:
#synchronized(self)
{
if ([managedObjectContexts objectForKey:[self threadKey]] == nil ) {
NSManagedObjectContext *threadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
threadContext.parentContext = [self managedObjectContextMainThread]; //Stuck here. This goes straight to above function managedObjectContextMainThread where it stucks.
threadContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
[managedObjectContexts setObject:threadContext forKey:[self threadKey]];
}
}
The problem is I tried to create the main managed object context on a thread different than the main thread.
For some reason it doesn't work.
I still love lazy loading. So all I need to do is to ensure that the main managedObjectContext is created on
the main thread
before any other managedObjectContexts are created.
I do not want to ensure that my program do not try to access the other managedObjectContexts first
So that looks like a job for dispatch_sync.
I then added this code:
dispatch_sync(dispatch_get_main_queue(),^{
[self managedObjectContextMainThread];//Access it once to make sure it's there
});
before I created all background child managedObjectContexts. That should be fast because once created the function will only return the static variable.
+(NSManagedObjectContext *)managedObjectContext {
NSThread *thread = [NSThread currentThread];
//BadgerNewAppDelegate *delegate = [BNUtilitiesQuick appDelegate];
//NSManagedObjectContext *moc = delegate.managedObjectContext;
if ([thread isMainThread]) {
//NSManagedObjectContext *moc = [self managedObjectContextMainThread];
return [self managedObjectContextMainThread];
}
else{
dispatch_sync(dispatch_get_main_queue(),^{
[self managedObjectContextMainThread];//Access it once to make sure it's there
});
}
// a key to cache the context for the given thread
NSMutableDictionary *managedObjectContexts =[self thread].managedObjectContexts;
#synchronized(self)
{
if ([managedObjectContexts objectForKey:[self threadKey]] == nil ) {
NSManagedObjectContext *threadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
threadContext.parentContext = [self managedObjectContextMainThread];
//threadContext.persistentStoreCoordinator= [self persistentStoreCoordinator]; //moc.persistentStoreCoordinator;// [moc persistentStoreCoordinator];
threadContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
[managedObjectContexts setObject:threadContext forKey:[self threadKey]];
}
}
return [managedObjectContexts objectForKey:[self threadKey]];
}
Related
In IOS 10, creating an NSManagedObjectContext and nsmanagedObject was in the followoing:
NSManagedObjectContext *context = ((AppDelegate*)[[UIApplication sharedApplication] delegate]).persistentContainer.viewContext;
NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:#"Leads" inManagedObjectContext:context];
but, in ios 9 and above, there isnt the presistentContainer, so how do i create an NSManagedObjectContext in IOS 9? I tried the following, but didnt work, it returned nil:
- (NSManagedObjectContext *)managedObjectContext {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
if (_managedObjectContext != nil) {
return _managedObjectContext;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (!coordinator) {
return nil;
}
_managedObjectContext = [[NSManagedObjectContext alloc] init];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
return _managedObjectContext;
}
In iOS 9, the instantiation of the NSManagedObjectContext changed to specify the concurrency type for this object.
This means that we now have to make a choice about what thread our managed object context is initialised on: the main queue, or perhaps a special background queue we’ve created. Our choices are:
NSPrivateQueueConcurrencyType
NSMainQueueConcurrencyType
So the below:
_managedObjectContext = [[NSManagedObjectContext alloc] init];
Should become:
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
ref: https://developer.apple.com/documentation/coredata/nsmanagedobjectcontext#//apple_ref/c/tdef/NSManagedObjectContextConcurrencyType
how in order load music, images, and other stuff to scene with progress bar and move bar smooth..
i guess logic of progress bar is to create new thread - load data and destroy thread
here is my code to load stuff but it's not work, progress bar appears but not updating value
-(void)s1
{
[[SimpleAudioEngine sharedEngine] preloadBackgroundMusic:#"game_music.caf"];
}
-(void)s2
{
[[SimpleAudioEngine sharedEngine] preloadEffect:#"tap.caf"];
}
-(void)startThread
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
EAGLContext *context = [[[EAGLContext alloc]
initWithAPI:kEAGLRenderingAPIOpenGLES1
sharegroup:[[[[CCDirector sharedDirector] openGLView] context] sharegroup]] autorelease];
[EAGLContext setCurrentContext:context];
[self performSelector:#selector(loadBar)];
//[self schedule:#selector(tick:)];
[self performSelector:#selector(s1)]; // uploading file
[self performSelector:#selector(progressUpdateValue)]; // add 10 value to progress
[self performSelector:#selector(s2)]; // uploading file
[self performSelector:#selector(progressUpdateValue)]; // add 10 value to progress
[self performSelector:#selector(replaceScene)
onThread:[[CCDirector sharedDirector] runningThread]
withObject:nil
waitUntilDone:false];
[pool release];
}
-(void)replaceScene
{
[[CCDirector sharedDirector]replaceScene:[GameScene node]];
}
-(id)init
{
self = [super init];
if (self != nil)
{
[NSThread detachNewThreadSelector:#selector(startThread) toTarget:self withObject:nil];
}
return self;
}
Thanks in advance.
interface.. there you go..)
#interface LoadScene : CCScene
{
GPLoadingBar *loadingBar;
float value;
}
Ok, so you should load resources in background, while updating loadBar in main thread. For the SimpleAudioEngine you really need NSThread, but CCTextureCashe has -addImageAsync method which allow you to load images asynchronously without problems. So your code should look something like this:
-(void)s1
{
[[SimpleAudioEngine sharedEngine] preloadBackgroundMusic:#"game_music.caf"];
}
-(void)s2
{
[[SimpleAudioEngine sharedEngine] preloadEffect:#"tap.caf"];
}
-(void)startThread
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[self s1];
loadingBar.value = 0.5;
[self s2];
[self performSelectorOnMainThread:#selector(replaceScene)
withObject:nil
waitUntilDone:false];
[pool release];
}
-(void)replaceScene
{
[[CCDirector sharedDirector]replaceScene:[GameScene node]];
}
-(id)init
{
self = [super init];
if (self != nil)
{
[self loadBar];
[NSThread detachNewThreadSelector:#selector(startThread) toTarget:self withObject:nil];
}
return self;
}
-(void) loadingFinished
{
[self replaceScene];
}
Cocos2d has its own updating loop, so you don't need to create your one for updating loadingBar. It also has a drawing loop, so if you want to dynamically update something on screen you should only set up values for update and not to stop main thread with loading resources
also, you could use [self s1] instead of [self performSelector:#selector(s1)] because they are identical. Hope that would help
I created a Singleton class named DataManager. I've set up some caching. However, when attempting to call the cache instance methods from within another instance method of DataManager, I'm getting EXC_BAD_ACCESS error on the line:
NSMutableArray *stops = [[DataManager sharedInstance] getCacheForKey:#"stops"];
DataManager.h
#interface DataManager : NSObject {
FMDatabase *db;
NSMutableDictionary *cache;
}
+ (DataManager *)sharedInstance;
// ... more methods
- (id)getCacheForKey:(NSString *)key;
- (void)setCacheForKey:(NSString *)key withValue:(id)value;
- (void)clearCache;
DataManager.m
- (NSArray *)getStops:(NSInteger)archived {
NSMutableArray *stops = [[DataManager sharedInstance] getCacheForKey:#"stops"];
if (stops != nil) {
NSLog(#"Stops: %#", stops);
return stops;
}
stops = [NSMutableArray array];
// set stops...
[[DataManager sharedInstance] setCacheForKey:#"stops" withValue:stops];
return stops;
}
It seems to occur when called from another view controller. That is the first view controller no error, second view controller, error.
This is my first attempt at a Singleton, so I'm sure I am making a simple mistake. But I am failing to see it myself.
Note: I've tried [self getCache...] with the same result.
UPDATE
Here is my Singleton implementation. Adapted from http://www.galloway.me.uk/tutorials/singleton-classes/
+ (DataManager *)sharedInstance {
#synchronized(self) {
if (!instance) {
instance = [[super allocWithZone:NULL] init];
}
}
return instance;
}
+ (id)allocWithZone:(NSZone *)zone {
return [[self sharedInstance] retain];
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)retain {
return self;
}
- (unsigned)retainCount {
return UINT_MAX;
}
- (oneway void)release {
// never release
}
- (id)autorelease {
return self;
}
- (id)init {
if (self = [super init]) {
if (db == nil){
BourbonAppDelegate *appDelegate = (BourbonAppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate createEditableCopyOfDatabaseIfNeeded];
db = [[FMDatabase alloc] initWithPath:[appDelegate getDBPath]];
}
if (![db open]) {
NSAssert(0, #"Failed to open database.");
[db release];
return nil;
}
[db setTraceExecution:YES];
[db setLogsErrors:TRUE];
cache = [NSMutableDictionary dictionary];
NSLog(#"cache: %#", cache);
}
return self;
}
Your cache object is autoreleased, so it's no longer in memory when you try to access it.
Use [NSMutableDictionary alloc] init] instead of [NSMutableDictionary dictionary] to get an instance that is retained.
Not a direct answer to your question, which was already answered.
However I'd just like to point out that your singleton implementation is sub-optimal. #synchronized is very expensive, and you can avoid using it every time you access the singleton:
if (!instance) {
#synchronized(self) {
if (!instance) {
instance = [[super allocWithZone:NULL] init];
}
}
}
An even better way to initialize a singleton would be:
+ (DataManager *)sharedInstance {
static DataManager *instance;
static dispatch_once_t donce;
dispatch_once(&donce, ^{
instance = [[self alloc] init];
});
return instance;
}
I have an app that uses NSOperationQueue intensively.
Sometimes I've noticed that some of the NSOperationQueues would "lock up" or enter a "isSuspended" state randomly even though my code never calls the setSuspended: method.
It's impossible to replicate and very hard to debug because whenever I hook the device up to Xcode to debug it, the app would reload and the bug would go away.
I added a lot of NSLogs at all possible points that might have a problem and just had to use the app for a few days until the bug reemerged.
Looking at the device system logs, I've discovered that [myOperationQueue operationCount] would increment but the opeations in queue will not execute.
I haven't tried manually setting "setSuspended:NO" yet but is that really necessary?
What could be causing this?
Here is a little bit of my code
The view controller that calls operations
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
self.operationQueue = [[[NSOperationQueue alloc] init] autorelease];
[self.operationQueue setMaxConcurrentOperationCount:2];
self.sendOperationQueue = [[[NSOperationQueue alloc] init] autorelease];
[self.sendOperationQueue setMaxConcurrentOperationCount:2];
self.receiveOperationQueue = [[[NSOperationQueue alloc] init] autorelease];
[self.receiveOperationQueue setMaxConcurrentOperationCount:1];
}
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[operationQueue cancelAllOperations];
[sendOperationQueue cancelAllOperations];
[receiveOperationQueue cancelAllOperations];
[operationQueue release];
[sendOperationQueue release];
[receiveOperationQueue release];
}
- (IBAction)sendMessage
{
if(![chatInput.text isEqualToString:#""])
{
NSString *message = self.chatInput.text;
SendMessageOperation *sendMessageOperation = [[SendMessageOperation alloc] initWithMatchData:matchData andMessage:message resendWithKey:nil];
[self.sendOperationQueue addOperation:sendMessageOperation];
[sendMessageOperation release];
}
}
NSOperation subclass SendMessageOperation
- (id)initWithMatchData:(MatchData*)data andMessage:(NSString*)messageString resendWithKey:(NSString*)resendKey
{
self = [super init];
if(self != nil)
{
if(data == nil || messageString == nil)
{
[self release];
return nil;
}
appDelegate = (YongoPalAppDelegate *) [[UIApplication sharedApplication] delegate];
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:[appDelegate persistentStoreCoordinator]];
[context setMergePolicy:NSMergeByPropertyObjectTrumpMergePolicy];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(mergeContextChanges:) name:NSManagedObjectContextDidSaveNotification object:context];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(mergeMainContextChanges:) name:NSManagedObjectContextDidSaveNotification object:appDelegate.managedObjectContext];
self.matchData = (MatchData*)[context objectWithID:[data objectID]];
matchNo = [[matchData valueForKey:#"matchNo"] intValue];
partnerNo = [[matchData valueForKey:#"partnerNo"] intValue];
self.message = messageString;
self.key = resendKey;
apiRequest = [[APIRequest alloc] init];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
self.matchData = nil;
self.message = nil;
self.key = nil;
[context release];
[apiRequest release];
[super dealloc];
}
- (void)start
{
if([self isCancelled] == YES)
{
[self willChangeValueForKey:#"isFinished"];
finished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
else
{
[self willChangeValueForKey:#"isExecuting"];
executing = YES;
[self main];
[self didChangeValueForKey:#"isExecuting"];
}
}
- (void)main
{
#try
{
NSAutoreleasePool *pool = [NSAutoreleasePool new];
bool taskIsFinished = NO;
while(taskIsFinished == NO && [self isCancelled] == NO)
{
NSDictionary *requestData = nil;
if(key == nil)
{
requestData = [self sendMessage];
}
else
{
requestData = [self resendMessage];
}
NSDictionary *apiResult = nil;
if(requestData != nil)
{
apiResult = [self sendMessageToServer:requestData];
}
if(apiResult != nil)
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"shouldConfirmSentMessage" object:nil userInfo:apiResult];
}
taskIsFinished = YES;
}
[self willChangeValueForKey:#"isFinished"];
[self willChangeValueForKey:#"isExecuting"];
finished = YES;
executing = NO;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];
[pool drain];
}
#catch (NSException *e)
{
NSLog(#"Exception %#", e);
}
}
- (BOOL)isConcurrent
{
return YES;
}
- (BOOL)isFinished
{
return finished;
}
- (BOOL)isExecuting
{
return executing;
}
- (void)mergeContextChanges:(NSNotification *)notification
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"mergeChatDataChanges" object:nil userInfo:[notification userInfo]];
}
- (void)mergeMainContextChanges:(NSNotification *)notification
{
NSSet *updated = [[notification userInfo] objectForKey:NSUpdatedObjectsKey];
for(NSManagedObject *thing in updated)
{
[[context objectWithID:[thing objectID]] willAccessValueForKey:nil];
}
[context mergeChangesFromContextDidSaveNotification:notification];
}
You're using:
setMaxConcurrentOperationCount:X
My hunch is that X operations in the queue have not finished, therefore causing any subsequent operations added to the queue to not run until this is the case.
From the NSOperationQueue's isSuspended method documentation:
If you want to know when the queue’s suspended state changes, configure a KVO observer to observe the suspended key path of the operation queue.
Another idea that you can implement in parallel with the observer is to create a subclass of NSOperationQueue that redefines the setSuspended: method. In the redefined version you can set a breakpoint if you are running on the debugger, or print a stacktrace to the log if you are running w/o debugger.
Hope this helps.
take a look at ASIHTTPRequestConfig.h, it has some flags that you can turn on to see what is really happening behind the scenes.
-(void)startThread {
m_bRunThread = YES;
if(m_bRunThread) {
NSThread* myThread = [[NSThread alloc]initWithTarget:self selector:#selector(display) object:theConditionLock];
[myThread start];
/*((WaitForSingleobject(event,1500) != WAIT_OBJECT_O) || !m_bRunThread) {
m_bRunThread = false;
Trace(_T("Unable to start display Thread\n"));
}*/
}
[self insert];
}
-(void)insert {
[theConditionLock lockWhenCondition:LOCK];
NSLog(#"I'm into insert function of thread!");
[theConditionLock unlockWithCondition:UNLOCK];
}
-(void)display {
NSLog(#"I'm into display function");
while (YES) {
[theConditionLock lockWhenCondition:LOCK];
NSAutoreleasePool* pool1 = [[NSAutoreleasePool alloc]init];
NSLog(#"Into the lock");
[theConditionLock unlockWithCondition:UNLOCK];
[pool1 drain];
}
}
Both the insert and the display methods are called from the startThread.display is called before the calling of the insert method.But i want the display to wait till the insert finishes its execution.And if its stopped a signal has to be sent to the start thread to display the error message.
How to do it.
But in the code above the display method is called first and it continues in the infinite loop.
If you want to call insert before display simply move it above the call to start the thread.
m_bRunThread = YES;
[self insert];
if(m_bRunThread)
{
NSThread* myThread = [[NSThread alloc]initWithTarget:self selector:#selector(display) object:theConditionLock];
[myThread start];
}
you could try: performSelector:onThread:withObject:waitUntilDone: on self
From my understanding you want to sync the insert and display threads, such that display is called after insert (startThread), and report back to insert (startThread) (not sure if I got it right).
If I hot it right, something like this should do the trick (not tested, might need some small changes):
[self insert];
NSThread* myThread = [[NSThread alloc] init];
[self performSelector:#selector(display) onThread:myThread withObject:nil waitUntilDone:YES];
//myThread stopped, check its return status (maybe set some variables) and display error message
this would be another way:
m_bRunThread = YES;
[self insert];
if(m_bRunThread)
{
NSThread* myThread = [[NSThread alloc]initWithTarget:self selector:#selector(display) object:theConditionLock];
[myThread start];
}
while(m_bRunThread){//check if display is still running
[NSThread sleepForTimeInterval:0.5];
}
//display thread stopped