ManagedObjectContexts with threads (dispatch queues) gets into a deadlock on iOS7 - xcode

I know there are many threads about NSManagedObjectContexts and threads but my problem seems to be only specific to iOS7. (Or at least not visible in OS6)
I have an app that makes use of dispatch_queue_ and runs multiple threads to fetch data from the server and update the UI. The app was working fine on iOS6 but on iOS7 it seems to get into deadlocks(mutex wait). See below the stack trace -
The "wait" happens in different methods usually when executing a fetch request and saving a (different) context. The commit Method is as follows :
-(void)commit:(BOOL) shouldUndoIfError forMoc:(NSManagedObjectContext*)moc {
#try {
// shouldUndoIfError = NO;
// get the moc for this thread
NSManagedObjectContext *moc = [self safeManagedObjectContext];
NSThread *thread = [NSThread currentThread];
NSLog(#"got login");
if ([thread isMainThread] == NO) {
// only observe notifications other than the main thread
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:moc];
NSLog(#"not main thread");
}
NSError *error;
if (![moc save:&error]) {
// fail
NSLog(#"ERROR: SAVE OPERATION FAILED %#", error);
if(shouldUndoIfError) {
[moc undo];
}
}
if ([thread isMainThread] == NO) {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSManagedObjectContextDidSaveNotification
object:moc];
}
}
#catch (NSException *exception) {
NSLog(#"Store commit - %#",exception);
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:#"name",#"store commit",#"exception", exception.description, nil];
[Flurry logEvent:#"MyException" withParameters:dictionary timed:YES];
}
#finally {
NSLog(#"Store saved");
}
}
How I'm creating new contexts for each thread :
-(NSManagedObjectContext *)safeManagedObjectContext {
#try {
if(self.managedObjectContexts == nil){
NSMutableDictionary *_dict = [[NSMutableDictionary alloc]init];
self.managedObjectContexts = _dict;
[_dict release];
_dict = nil;
}
NSManagedObjectContext *moc = self.managedObjectContext;
NSThread *thread = [NSThread currentThread];
if ([thread isMainThread]) {
return moc;
}
// a key to cache the context for the given thread
NSString *threadKey = [NSString stringWithFormat:#"%p", thread];
if ( [self.managedObjectContexts valueForKey:threadKey] == nil) {
// create a context for this thread
NSManagedObjectContext *threadContext = [[[NSManagedObjectContext alloc] init] retain];
[threadContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
[threadContext setPersistentStoreCoordinator:[moc persistentStoreCoordinator]];
[threadContext setUndoManager:nil];
// cache the context for this thread
[self.managedObjectContexts setObject:threadContext forKey:threadKey];
NSLog(#"added a context to dictionary, length is %d",[self.managedObjectContexts count]);
}
return [self.managedObjectContexts objectForKey:threadKey];
}
#catch (NSException *exception) {
//
}
#finally {
//
}
}
What I have so far :
One Persistent Store coordinator.
Each New thread has its own Managed Object Context.
Strange part is that the same code worked fine on OS6 but not on OS7. I am still using the xcode4.6.3 to compile the code. Most of the code works on this principle, I run a thread, fetch data, commit it and then post a notification. Could the freeze/deadlock be because the notification gets posted and my UI elements fetch the data before the save(&merge) are reflected? Anything else that I'm missing ?

Related

XPC Between two cocoa applications in workspace, the NSXPCConnection is immediately being invalidated

I have two Cocoa Applications, one is going to be the sender and another the receiver in this XPC relationship.
In the applicationDidFinishLaunching in the sender, I first open the second receiver application
NSError* error = nil;
NSURL* url = [[NSBundle mainBundle] bundleURL];
url = [url URLByAppendingPathComponent:#"Contents" isDirectory:YES];
url = [url URLByAppendingPathComponent:#"MacOS" isDirectory:YES];
url = [url URLByAppendingPathComponent:#"TestXPCHelper.app" isDirectory:YES];
[[NSWorkspace sharedWorkspace] launchApplicationAtURL:url
options:NSWorkspaceLaunchWithoutActivation
configuration:[NSDictionary dictionary]
error:&error];
if ( error )
{
NSLog(#"launchApplicationAtURL:%# error = %#", url, error);
[[NSAlert alertWithError:error] runModal];
}
Then I create my NSXPCConnection
assert([NSThread isMainThread]);
if (self.testConnection == nil) {
self.testConnection = [[NSXPCConnection alloc] initWithMachServiceName:NEVER_TRANSLATE(#"com.TechSmith.TestXPCHelper") options:NSXPCConnectionPrivileged];
self.testConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:#protocol(TestXPCProtocol)];
self.testConnection.interruptionHandler = ^{
NSLog(#"Connection Terminated");
};
self.testConnection.invalidationHandler = ^{
self.testConnection.invalidationHandler = nil;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
self.testConnection = nil;
}];
};
[self.testConnection resume];
}
Then I try to send a message over the connection (the connection is already invalidated by here)
id<TestXPCProtocol> testRemoteObject= [self.testConnection remoteObjectProxy];
[testRemoteObject testXPCMethod2];
[[self.testConnection remoteObjectProxyWithErrorHandler:^(NSError * proxyError){
NSLog(#"%#", proxyError);
}] testXPCMethod:^(NSString* reply) {
NSLog(#"%#", reply);
}];
And here is the app delegate for my receiver application:
#interface AppDelegate () <NSXPCListenerDelegate, TestXPCProtocol>
#property (weak) IBOutlet NSWindow *window;
#property NSXPCListener *xpcListener;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSLog(#"TESTING123");
self.xpcListener = [[NSXPCListener alloc] initWithMachServiceName:#"com.TechSmith.TestXPCHelper"];
self.xpcListener.delegate = self;
[self.xpcListener resume];
}
- (void)applicationDidBecomeActive:(NSNotification *)notification {
NSLog(#"ACTIVE234");
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
- (void)run
{
NSLog(#"RUNNING");
// Tell the XPC listener to start processing requests.
[self.xpcListener resume];
// Run the run loop forever.
[[NSRunLoop currentRunLoop] run];
}
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
{
NSLog(#"LISTENING");
assert(listener == self.xpcListener);
#pragma unused(listener)
assert(newConnection != nil);
newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:#protocol(TestXPCProtocol)];
newConnection.exportedObject = self;
[newConnection resume];
return YES;
}
- (void)testXPCMethod:(void(^)(NSString * version))reply
{
NSLog(#"HEY");
reply(#"REPLY HERE");
}
- (void)testXPCMethod2
{
NSLog(#"TWO!");
}
Here is the proxyError when I try to send a message over the connection:
Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service
named com.TechSmith.TestXPCHelper was invalidated." UserInfo={NSDebugDescription=The
connection to service named com.TechSmith.TestXPCHelper was invalidated.}
So I think I am doing something wrong with my instantiation of the NSXPCConnection. I can't find a good example of two applications speaking to eachother-- it's always one application and a service. Is that what my problem is? I need a service inbetween the applications talking?
Is there any way to get more information on why this connection is being invalidated? That would also help a lot
So pretty straight forward problem here,
Turns out initWithMachServiceName is explicitly looking for a mach service. I was using an identifier of another application process.
If I actually use an identifier of a valid mach service, there is no issue
Note that there are two other ways to create an NSXPCConnection,
with an NSXPCEndpoint or with a XPCService identifier

NSManagedObjectContext save notification on background thread

I have NSManagedObjectContext in background thread. When context in main thread save context on background thread don't know about it.
I try to use NSManagedObjectContextDidSaveNotification on main context but then i can't run marge methode in background thread. bellow is my code.
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(managedObjectContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:nil];
if (![[currentThread threadDictionary] objectForKey:#"managedObjectContext"]) {
NSManagedObjectContext *managedObjectContext = [[NSManagedObjectContext alloc] init];
// Configure Managed Object Context
[managedObjectContext setPersistentStoreCoordinator:_mOCMainSetting.persistentStoreCoordinator];
[[currentThread threadDictionary] setObject:managedObjectContext forKey:#"managedObjectContext"];
if (!_contextArray) {
_contextArray = [NSArray array];
}
NSMutableArray *mutableContextArray = [_contextArray mutableCopy];
[mutableContextArray addObject:managedObjectContext];
_contextArray = mutableContextArray;
}
NSManagedObjectContext *context = [[currentThread threadDictionary] objectForKey:#"managedObjectContext"];
options = (Options*)[context objectWithID:_optionsID];
return options;
}
- (void)managedObjectContextDidSave:(NSNotification *)notification {
// dispatch_async(dispatch_get_main_queue(), ^{
for (NSManagedObjectContext *context in _contextArray) {
[context mergeChangesFromContextDidSaveNotification:notification];
}
// });
}
can you ask me how to run merge method in correct thread.

NSFetchRequest fetches zero matches of entity from database created using UIManagedDocument

I am using Justin Driscoll's article on Core Data with UIManagedDocument in singleton pattern to set it up for UITabViewController. I am running the app on Simulator. Its working fine for the first time. The database is created successfully and I can see data in the tableview controller for each tab. But when I restart my application, the tableviews are empty because NSFetchRequest fetches 0 matches for the entity. The same fetch request fetches correct result during the first run.
I think its something to do with asynchronous nature of loading data and data not autosaving before I stop the app in simulator. So data is not available in second run of app.
The way I am doing my data loading as seen in the code. The fetchDataIntoDocument method does the initial loading of data.
// Document Handler Singleton Class
-(void) performWithDocument:(OnDocumentReady)onDocumentReady {
void (^OnDocumentDidLoad)(BOOL) = ^(BOOL Success) {
onDocumentReady(self.document);
};
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.document.fileURL path]]) {
**[self fetchDataIntoDocument:self.document];**
[self.document saveToURL:self.document.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:OnDocumentDidLoad];
} else if (self.document.documentState == UIDocumentStateClosed) {
[self.document openWithCompletionHandler:OnDocumentDidLoad];
} else if (self.document.documentState == UIDocumentStateNormal) {
OnDocumentDidLoad(YES);
}
}
-(void)fetchDataIntoDocument:(UIManagedDocument *)document {
MyEntityDataController *dc= [[MyEntityDataController alloc] init];
NSDictionary *entityInfo =[dc getEntityInfo];
[document.managedObjectContext performBlock:^{
[Entity createEntityWithInfo:entityInfo inManagedObjectContext:document.managedObjectContext];
}];
}
My TableViewController class
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (!self.databaseDocument) {
[[LTDatabaseDocumentHandler sharedDatabaseDocumentHandler] performWithDocument:^ (UIManagedDocument *document) {
self.databaseDocument = document;
[self populateTableViewArrayFromDocument:self.databaseDocument];
}];
}
}
Within populateTableViewArrayFromDocument I am executing my fetch request
-(void)populateTableViewArrayFromDocument:(UIManagedDocument *)document
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Entity2"];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[request setSortDescriptors:sortDescriptors];
NSError *error = nil;
NSArray *matches = [self.databaseDocument.managedObjectContext executeFetchRequest:request error:&error];
NSLog(#" matches count for Entity2 %d", matches.count);
for (Entity2 *entity2 in matches) {
//do stuff with data and add it to tableview array
}
}
I think I have found why you have this problem. I have just run into this issue and it took some research to figure it out. Basically, you are right. The problem is indeed in the asynchronous nature of UIManagedDocument. You need to wait until the document loads into memory and then do your fetching.
This is the code I use to make sure the document is ready:
if ([[NSFileManager defaultManager] fileExistsAtPath:[_URLDocument path]]) {
[_managedDocument openWithCompletionHandler:^(BOOL success){
[self ready]
if (!success) {
// Handle the error.
}
}];
}
Hope this helps, cheers!

Mac In-app purchase not working

I am implementing in-app purchase in a Mac app using the following code.
#implementation AppStoreObserver
-(id) init {
self = [super init];
if (self) {
_products = [[NSMutableArray alloc] init];
[[SKPaymentQueue defaultQueue] addTransactionObserver:self];
}
return self;
}
-(void)dealloc {
[_products release];
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
[super dealloc];
}
#pragma mark - Query products
-(void)loadStoreForProducts:(NSArray *)productIds {
SKProductsRequest *productRequest = [[[SKProductsRequest alloc] initWithProductIdentifiers:[NSSet setWithArray:productIds]] autorelease];
productRequest.delegate = self;
[productRequest start];
}
#pragma mark - Product Delegate
//Got product info
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response
{
[_products removeAllObjects];
for (SKProduct *_product in response.products) {
[_products addObject:_product];
}
[[NSNotificationCenter defaultCenter] postNotificationName:kNotificationStoreManagerProductsReceivedInvalid
object:nil
userInfo:[NSDictionary dictionaryWithObjectsAndKeys:response.invalidProductIdentifiers, #"invalidProductIdentifiers", _products, #"products", nil]];
}
#pragma mark - Purchase
-(void) triggerPurchase:(SKProduct *) product {
[[SKPaymentQueue defaultQueue] addPayment:[SKPayment paymentWithProduct:product]];
}
- (BOOL)canMakePurchases
{
return [SKPaymentQueue canMakePayments];
}
- (void)purchaseProduct:(NSString *)productId
{
if([self canMakePurchases]){
for (SKProduct *_product in _products) {
if ([_product.productIdentifier isEqualToString:productId]) {
[self triggerPurchase:_product];
break;
}
}
}
}
#pragma mark - Payment Queue observers
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions
{
for (SKPaymentTransaction* transaction in transactions)
{
//NSLog(#"%d", transaction.transactionState);
switch (transaction.transactionState)
{
case SKPaymentTransactionStatePurchasing:
NSLog(#"Purchasing");
break;
case SKPaymentTransactionStatePurchased:
NSLog(#"Purchased");
[self completeTransaction:transaction];
break;
case SKPaymentTransactionStateFailed:
NSLog(#"Error: %#", transaction.error);
[self failedTransaction:transaction];
break;
case SKPaymentTransactionStateRestored:
NSLog(#"Restored");
[self restoreTransaction:transaction];
break;
default:
break;
}
}
}
#pragma mark - Custom handlers
- (void)restoreTransaction:(SKPaymentTransaction *)transaction
{
NSLog(#"restore transaction %# %#",transaction.transactionIdentifier , transaction.originalTransaction);
[self finishTransaction:transaction wasSuccessful:YES];
}
-(void) restoreCompletedTransactions {
[[SKPaymentQueue defaultQueue] restoreCompletedTransactions];
}
- (void) completeTransaction: (SKPaymentTransaction *)transaction
{
[self finishTransaction:transaction wasSuccessful:YES];
}
- (void) failedTransaction: (SKPaymentTransaction *)transaction
{
if (transaction.error.code != SKErrorPaymentCancelled)
{
// error!
[self finishTransaction:transaction wasSuccessful:NO];
}
else
{
// this is fine, the user just cancelled, so don’t notify
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
return;
}
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
[alert addButtonWithTitle:#"OK"];
[alert setMessageText:#"Alert"];
[alert setInformativeText:#"In-App Purchase Failed"];
[alert setAlertStyle:NSWarningAlertStyle];
[alert runModal];
}
- (void)finishTransaction:(SKPaymentTransaction *)transaction wasSuccessful:(BOOL)wasSuccessful
{
[[SKPaymentQueue defaultQueue] finishTransaction:transaction];
//NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:transaction, #"transaction" , nil];
if (wasSuccessful) {
// send out a notification that we’ve finished the transaction
} else {
NSLog(#"user cancelled");
}
}
#end
I am able to retrieve SKProduct successfully but as soon as I purchase a product using a test account the process halts without failing. The SKPaymentTransactionStatePurchasing state is reached but nothing happens afterwards. Even the purchase confirmation dialog is not presented.
Once I stop and re-run the app the last transaction is completed successfully but as soon as I try to make another transaction the same thing happens. I have spent several days trying to identify the cause without success. I hope someone here is able to identify the problem. Thanks for your time.

Intercept web requests from a WebView Flash plugin

I've got a desktop browser app which uses a WebView to host a Flash plugin. The Flash plugin makes regular requests to an external website for new data, which it then draws as fancy graphics.
I'd like to intercept these web requests and get at the data (so I can display it via Growl, instead of keeping a desktop window around). But best I can tell, requests made by Flash don't get picked up by the normal WebView delegates.
Is there another place I can set a hook? I tried installing a custom NSURLCache via [NSURLCache setSharedURLCache] but that never got called. I also tried method swizzling a few of the other classes (like NSCachedURLResponse) but couldn't find a way in. Any ideas? Many thanks!
Surprised no one answered this, it is actually pretty easy. Create a subclass of NSURLProtocol, and then call registerClass to start intercepting.
[NSURLProtocol registerClass:[MyCustomURLProtocol class]];
Here are the important bits of the subclass:
#define REQUEST_HEADER_TAG #"x-mycustomurl-intercept"
+ (BOOL)canInitWithRequest:(NSURLRequest*)theRequest
{
// Check for the custom header on the request to break the
// infinite loop created by the [startLoading] below.
if ([theRequest valueForHTTPHeaderField:REQUEST_HEADER_TAG]) {
return NO;
}
if ([theRequest.URL.scheme caseInsensitiveCompare:#"http"] == NSOrderedSame) {
return YES;
}
return NO;
}
+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)theRequest
{
return theRequest;
}
- (id)initWithRequest:(NSURLRequest*)theRequest
cachedResponse:(NSCachedURLResponse*)cachedResponse
client:(id<NSURLProtocolClient>)client
{
// Add a custom header on the request to break the
// infinite loop created by the [startLoading] below.
NSMutableURLRequest* newRequest = [theRequest mutableCopy];
[newRequest setValue:#"" forHTTPHeaderField:REQUEST_HEADER_TAG];
// Now continue the process with this "tagged" request
self = [super initWithRequest:theRequest
cachedResponse:cachedResponse
client:client];
if (self) {
// capture the data received
[self setRequest:newRequest];
receivedData = [[NSMutableData data] retain];
}
[newRequest release];
return self;
}
- (void)dealloc
{
[connection release];
[request release];
[receivedData release];
[super dealloc];
}
- (void)startLoading
{
// Load the data off the web as usual, but set myself up as the delegate
// so I can intercept the response data as it comes in.
[self setConnection:[NSURLConnection connectionWithRequest:request delegate:self]];
}
- (void)stopLoading
{
[connection cancel];
}
#pragma mark NSURLConnection delegate implementation
- (void)connection:(NSURLConnection*)conn
didReceiveResponse:(NSURLResponse*)response
{
[[self client] URLProtocol:self
didReceiveResponse:response
cacheStoragePolicy:[request cachePolicy]];
[receivedData setLength:0];
}
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
[[self client] URLProtocol:self didLoadData:data];
[receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection*)conn
{
[[self client] URLProtocolDidFinishLoading:self];
[self setConnection:nil];
if (requestTag != 0) {
if (requestDelegate &&
[requestDelegate respondsToSelector:
#selector(finishedLoadingData:forURL:taggedWith:)]) {
[requestDelegate finishedLoadingData:receivedData
forURL:[request URL]
taggedWith:requestTag];
}
}
}
- (void)connection:(NSURLConnection*)conn didFailWithError:(NSError*)error
{
[[self client] URLProtocol:self didFailWithError:error];
[self setConnection:nil];
}

Resources