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.
Related
My app crashes when I try to perform sent or cancel button actions in MFMailComposeViewController.
here is my code.
I have imported message framework and added delegates in .h file.
NSString *emailTitle = #"Test Email";
NSString *messageBody = #"Welcome Guest";
NSArray *recipentsArray = [[NSArray alloc]initWithObjects:#"xxx#gmail.com", nil];
[[UINavigationBar appearance] setBackgroundImage:nil forBarMetrics:UIBarMetricsDefault];
[[UINavigationBar appearance] setBackgroundColor:nil];
MFMailComposeViewController *mc = [[MFMailComposeViewController alloc] init];
mc.mailComposeDelegate = self;
[mc setSubject:emailTitle];
[mc setMessageBody:messageBody isHTML:NO];
[mc setToRecipients:recipentsArray];
[self presentViewController:mc animated:YES completion:NULL];(void) mailComposeController:(MFMailComposeViewController *)controller
didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error
{
switch (result)
{
case MFMailComposeResultCancelled:
NSLog(#"Mail cancelled");
break;
case MFMailComposeResultSaved:
NSLog(#"Mail saved");
break;
case MFMailComposeResultSent:
NSLog(#"Mail sent");
break;
case MFMailComposeResultFailed:
NSLog(#"Mail sent failure: %#", [error localizedDescription]);
break;
default:
break;
}
// Close the Mail Interface
[self dismissViewControllerAnimated:YES completion:NULL];
}
This is how my mf mail composer looks like :----
Just make the MFMailComposeViewController *mc in global.
I mean declare it outside this method. It's crashing because at the end of method Your mc gets deallocated.
Interface
#interface Demo()
{
MFMailComposeViewController *mc;
}
#end
Implementation
-(void)ShareViaEmail {
objMailComposeController = [[MFMailComposeViewController alloc] init];
objMailComposeController.mailComposeDelegate = self;
if ([MFMailComposeViewController canSendMail]) {
[objMailComposeController setSubject:#"Hello"];
[objMailComposeController setMessageBody:#"This is Body of Message" isHTML:NO];
NSData *ImageData = [NSData dataWithContentsOfFile:self.aStrSourceName];
NSString *mimeType;
if ([[self CheckExtensionOfURL:self.aStrSourceName]isEqualToString:#"Image"]) {
mimeType = #"image/png";
}
else if ([[self CheckExtensionOfURL:self.aStrSourceName]isEqualToString:#"PDF"]) {
mimeType = #"application/pdf";
} else if ([[self CheckExtensionOfURL:self.aStrSourceName]isEqualToString:#"Audio"]) {
mimeType = #"audio/mpeg";
} else if ([[self CheckExtensionOfURL:self.aStrSourceName]isEqualToString:#"Video"]) {
mimeType = #"video/mp4";
}
[objMailComposeController addAttachmentData:ImageData mimeType:mimeType fileName:#"attachement"];
}
[self.navigationController presentViewController:objMailComposeController animated:YES completion:Nil];
}
- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error {
switch (result) {
case MFMailComposeResultCancelled:
[self ShowAlertView:kMsgMailCancelled];
break;
case MFMailComposeResultFailed:
[self ShowAlertView:kMsgMailFailed];
break;
case MFMailComposeResultSaved:
[self ShowAlertView:kMsgMailSaved];
break;
case MFMailComposeResultSent:
[self ShowAlertView:kMsgSent];
break;
default:
break;
}
// CHECK OUT THIS, THIS MIGHT BE IN YOUR CASE
[self.navigationController dismissViewControllerAnimated:controller completion:nil];
}
EDITED: Thanks to your help I am closing in on the solution to my problem. I have fixed the missing "#end error", and am left with just one "Expected identifier or "("" error.
This is the code, the error is noted:
#define SYSTEM_VERSION_LESS_THAN(v) ([[[UIDevice currentDevice] systemVersion] \
compare:v options:NSNumericSearch] == NSOrderedAscending)
#import "WViewController.h"
#import <SkillzSDK-iOS/Skillz.h>
#interface WViewController ()
#end
#implementation WViewController
- (void)viewDidLoad
{
[super viewDidLoad];
//create backdrop image
NSMutableString *imgFile = [[NSMutableString alloc] init];
if (![globalBackgroundImage isEqualToString:#""]) [imgFile setString:globalBackgroundImage]; //falls back to first image setting for all devices;
if ((IS_WIDESCREEN_PHONE)&&(![widescreenBackgroundImage isEqualToString:#""])) [imgFile setString:widescreenBackgroundImage]; //you can specify a different image for
if ((IS_IPAD)&&(![iPadBackgroundImage isEqualToString:#""])) [imgFile setString:iPadBackgroundImage]; //widescreen phones & for iPad
if (![imgFile isEqualToString:#""]) {
UIImage *img = [[UIImage imageNamed:imgFile] retain];
CGSize imgSz = [img size];
CGSize screenSz = [[UIScreen mainScreen] bounds].size;
float imgWH = imgSz.width/imgSz.height;
float screenWH = screenSz.width/screenSz.height;
CGRect backdropFrame;
if (imgWH>=screenWH) backdropFrame = CGRectMake((screenSz.width/2)-((screenSz.height*imgWH)/2), 0, screenSz.height*imgWH, screenSz.height); //image wider than screen
else backdropFrame = CGRectMake(0, ((screenSz.height/2)-((screenSz.width/imgWH)/2)), screenSz.width, screenSz.width/imgWH);
UIImageView *backdropImageView = [[UIImageView alloc] initWithFrame:backdropFrame];
[backdropImageView setImage:img];
[backdropImageView setAlpha:backgroundImageOpacity];
[self.view addSubview:backdropImageView];
}
[self.view setBackgroundColor:globalBackgroundColor];
//init GameCenter
[[GameCenterManager sharedManager] setupManager];
[[GameCenterManager sharedManager] setDelegate:self];
//initialize the view for when the player is in the game
gameView = [[WgameView alloc] initWithFrame:[[UIScreen mainScreen] bounds] fromViewController:self];
[gameView setHidden:YES];
[self.view addSubview:gameView];
//initialize the view for then the player is on the home screen
homeView = [[WhomeView alloc] initWithFrame:[[UIScreen mainScreen] bounds] fromViewController:self];
[homeView setHidden:YES];
[self.view addSubview:homeView];
}
- (void) viewDidAppear:(BOOL)animated {
//go to home screen right away
[self goHome];
//show a RevMob fullscreen ad if we're supposed to
if (revMobActive) {
if (showRevMobFullscreenOnLaunch) {
[[RevMobAds session] showFullscreen];
}
}
//show a Chartboost ad if we're supposed to
if (chartboostActive) {
if (showChartboostOnLaunch) {
[[Chartboost sharedChartboost] showInterstitial:CBLocationHomeScreen];
}
}
}
#pragma mark game flow
-(void) multiplayerButtonPressed:(id)sender
{
NSLog(#"Multiplayer button pressed, launching Skillz!");
// Launching Skillz in landscape mode
[[Skillz skillzInstance] launchSkillzForOrientation:SkillzLandscape
launchHasCompleted:^{
// This code is called after the Skillz UI launches.
NSLog(#"Skillz just launched.");
} tournamentWillBegin:^(NSDictionary *gameRules) {
// This code is called when a player starts a game in the Skillz portal.
NSLog(#"Tournament with rules: %#", gameRules);
NSLog(#"Now starting a game…");
// INCLUDE CODE HERE TO START YOUR GAME
// …..
// …..
// …..
// END OF CODE TO START GAME
} skillzWillExit:^{
// This code is called when exiting the Skillz portal
//back to the normal game.
NSLog(#"Skillz exited.");
}];
}
- (void) startGame:(UIButton*)sender {
//hide RevMob banner ad if we're supposed to
if (revMobActive) {
if (showRevMobBannerOnHomeScreen) {
[[RevMobAds session] hideBanner];
}
}
//starts game in the mode corresponding to which button was tapped
[[WGameModeEngine sharedInstance] setCurrentGameMode:sender.titleLabel.text];
[gameView startGame];
[homeView setHidden:YES];
[gameView setHidden:NO];
//init timer if timed game
if ([[WGameModeEngine sharedInstance] isTimedGame]) {
timedGameTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(decrementTime) userInfo:nil repeats:YES] retain];
}
//notify game engine to play sound if configured
[[WGameModeEngine sharedInstance] soundEventDidHappen:#"BeginGame"];
}
- (void) goHomeButtonPressed {
[self stopGame];
[self goHome];
}
- (void) stopGame {
//stop timer if this was a timed game
if (timedGameTimer) {
[timedGameTimer invalidate];
[timedGameTimer release];
timedGameTimer=nil;
}
}
- (void) goHome {
[gameView setHidden:YES];
[homeView setHidden:NO];
//show a RevMob banner ad if we're supposed to
if (revMobActive) {
if (showRevMobBannerOnHomeScreen) {
[[RevMobAds session] showBanner];
}
}
}
- (void) decrementTime {
[[WGameModeEngine sharedInstance] timeDecreased]; //report to our game model that time has decreased
if ([[WGameModeEngine sharedInstance] timeLeft]<=0) { //if 0 seconds left,
[self timedGameEnded]; //game has ended
}
if (([[WGameModeEngine sharedInstance] timeLeft]<6)&&([[WGameModeEngine sharedInstance] timeLeft]>0)) {
//notify game engine to play sound if configured
[[WGameModeEngine sharedInstance] soundEventDidHappen:#"FiveSecondCountdown"];
}
[gameView updateLabels]; //update gameView's score and time labels
}
- (void) timedGameEnded {
//game over!
[self stopGame];
//notify game engine to play sound if configured
[[WGameModeEngine sharedInstance] soundEventDidHappen:#"GameOver"];
//show an alert with score and list of words found (if you want, you can add a whole separate screen for this instead of simple alert!)
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"Game Over" message:[NSString stringWithFormat:#"You scored %d points!\n\nWords found:\n%#",[[WGameModeEngine sharedInstance] getScore],[[[WGameModeEngine sharedInstance] getWordsFound] componentsJoinedByString:#" "]] delegate: nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert setDelegate:self];
[alert show];
[alert release];
//report score to GameCenter
int sc = [[WGameModeEngine sharedInstance] getScore];
if (sc>0) [self reportScore:sc forCategory:[[[[WGameModeEngine sharedInstance] getCurrentGameMode] componentsSeparatedByString:#" "] componentsJoinedByString:#"_"]];
[#"com.bundle.appname" stringByAppendingString:[[[[[WGameModeEngine sharedInstance] getCurrentGameMode] lowercaseString] componentsSeparatedByString:#" "] componentsJoinedByString:#""]];
}
- (void) alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
//share latest score on Facebook if we're supposed to
if (FacebookShareEnabled) {[self facebookShare];}
//go to home screen
[self goHome];
//show a RevMob fullscreen ad if we're supposed to
if (revMobActive) {
if (showRevMobFullscreenWhenGameOver) {
[[RevMobAds session] showFullscreen];
}
}
//show a Chartboost ad if we're supposed to
if (chartboostActive) {
if (showChartboostWhenGameOver) {
[[Chartboost sharedChartboost] showInterstitial:CBLocationHomeScreen];
}
}
}
#pragma mark GameCenter
- (void)gameCenterManager:(GameCenterManager *)manager authenticateUser:(UIViewController *)gameCenterLoginController {
if (revMobActive) {
if (showRevMobBannerOnHomeScreen) {
[[RevMobAds session] hideBanner];
}
}
[self presentViewController:gameCenterLoginController animated:YES completion:^(void)
{if (revMobActive) {
if (showRevMobBannerOnHomeScreen) {
[[RevMobAds session] showBanner];
}}}];
}
if (isGameOver) {<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ERROR IS HERE>>>>>>>>>>>>>>>
if ([[Skillz skillzInstance] tournamentIsInProgress]) {
// The game ended and it was in a Skillz tournament,
// so report the score and go back to Skillz.
[[Skillz skillzInstance] completeTurnWithGameData:gameData
playerScore:playerScore
playerCurrentTotalScore:playerCurrentTotalScore
opponentCurrentTotalScore:opponentCurrentTotalScore
roundOutcome:turnOutcome
matchOutcome:matchOutcome
withCompletion:^{
// Code in this block is called when exiting to Skillz
// and reporting the score.
NSLog(#"Reporting score to Skillz…");
}];
} else {
// Otherwise single player game, so take the normal action
}
}
- (void) reportScore: (int64_t) score forCategory: (NSString*) category
ORIGINAL QUESTION:
I am really new to coding and hoping that someone can help me with what I am sure is a very simple problem. I have looked at a lot of other answers regarding this error, but they don't seem to apply to my situation.
The following code is part of a game app I am working on using Xcode, trying to integrate it with a third party system called SKILLZ. I did not write any of the code and am trying to understand it as I proceed with the integration.
I have noted within the code where I am getting the error:
#import "WAppDelegate.h"
#import "WViewController.h"
#import <SkillzSDK-iOS/Skillz.h>
#implementation WAppDelegate
- (void)dealloc
{
[_window release];
[_viewController release];
[super dealloc];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//initialize language engine
[WLanguageEngine sharedInstance];
//initialize game mode engine
[WGameModeEngine sharedInstance];
//initialize RevMob
if (revMobActive) {
[RevMobAds startSessionWithAppID:RevMobAppID];
}
if (revMobActive&&revMobTestingMode) {
[RevMobAds session].testingMode = RevMobAdsTestingModeWithAds;
// or
//[RevMobAds session].testingMode = RevMobAdsTestingModeWithoutAds;
}
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
self.viewController = [[[WViewController alloc] initWithNibName:#"WViewController" bundle:nil] autorelease];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;
}
{<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<**<<EXPECTED IDENTIFIER OR "(">> ERROR OCCURS HERE**
// INITIALIZE SKILLZ HERE
// 940 is the game ID that was given to us by the Skillz Developer Portal.
// SkillzSandbox specifies that we will use the sandbox server since we
// are still developing the game.
// SkillzProduction specifies that we will use the production server since
// the game is ready for release to the AppStore
[[Skillz skillzInstance] skillzInitForGameId:#"940"
environment:SkillzSandbox];
I am getting a second occurrence of this error in a different part of the app code, but am hoping that if I can sort this one out that it will help me to understand the other.
Hoping that this is not off-topic or too specific, and that someone might be able to help.
Cheers
Jen
{<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<> ERROR OCCURS HERE
// INITIALIZE SKILLZ HERE
// 940 is the game ID that was given to us by the Skillz Developer Portal.
// SkillzSandbox specifies that we will use the sandbox server since we
// are still developing the game.
// SkillzProduction specifies that we will use the production server since
// the game is ready for release to the AppStore
[[Skillz skillzInstance] skillzInitForGameId:#"940"
environment:SkillzSandbox];
return YES;
}
This block is outside of - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
Perhaps you forgot a declaration or added an extraneous } somewhere
In Mavericks, the methods to open and close NSSheets has changed. And to make it tougher, the Release Notes do not match the current documentation (see below).
I'm trying to do this:
MainSheetController (NSWindowController):
-(IBAction)callSheet:(id)sender {
[sheetController openSheet];
}
SheetController (NSObject):
(void)openSheet {
[[NSBundle mainBundle] loadNibNamed:sheetName owner:self topLevelObjects:nil];
NSLog(#"1");
[self.mainWindowController.window beginSheet:self.sheet completionHandler:nil];
NSLog(#"2");
}
I get to 2, with no errors or warnings, but no sheet..
Current Documentation:
#if NS_BLOCKS_AVAILABLE
- (void)beginSheet:(NSWindow *)sheetWindow completionHandler:(void (^)(NSModalResponse returnCode))handler NS_AVAILABLE_MAC(10_9);
- (void)beginCriticalSheet:(NSWindow *)sheetWindow completionHandler:(void (^)(NSModalResponse returnCode))handler NS_AVAILABLE_MAC(10_9);
#endif
- (IBAction)userButtonPressed:(id)sender {
UserLoginWindowController * wc = [UserLoginWindowController new];
// we keep a reference, so the WC doesn't deallocate
self.modalWindowController = wc;
[[self window] beginSheet:[wc window] completionHandler:^(NSModalResponse returnCode) {
self.modalWindowController = nil;
}];
}
in the UserLoginWindowController
- (IBAction)cancelButtonPressed:(id)sender {
[[[self window] sheetParent] endSheet:[self window] returnCode:NSModalResponseCancel];
}
- (IBAction)showSheet:(id)sender
{
if (_windowController == nil)
{
_windowController = [MyWindowController new];
}
[[self window] beginSheet:[_windowController window] completionHandler:^(NSModalResponse returnCode)
{
}];
}
// And inside your MyWindowController class:
- (id)init
{
self = [super initWithWindowNibName:#"MyWindowNibName"]; // TODO: Change the name of your NIB
return self;
}
In your nib file, make sure the "Visible At Launch" flag is unticked for the window.
I figured out how to do this. Hope it's ok to post..
MainWindow.h
#interface MainWindowController : NSWindowController {
NSString *sheetName;
IBOutlet NSWindow *sheet;
id result1;
id result2;
...
id resultn;
}
#property (strong) NSString *sheetName;
#property (strong, nonatomic) IBOutlet NSWindow *sheet;
-(IBAction)callSheet0:(id)sender;
-(IBAction)callSheet1:(id)sender;
-(IBAction)callSheetn:(id)sender;
- (void)openSheet;
- (IBAction)save:(id)sender;
- (IBAction)cancel:(id)sender;
MainWindow.m
- (void)windowDidLoad
{
NSLog(#"%s", __FUNCTION__);
[super windowDidLoad];
sheetName = [[NSString alloc] init];
}
-(IBAction)callSheet0:(id)sender {
NSLog(#"%s", __FUNCTION__);
sheetName = #"Sheet0";
[self openSheet];
}
....
-(IBAction)callSheetn:(id)sender {
NSLog(#"%s", __FUNCTION__);
sheetName = #"Sheetn";
[self openSheet];
- (void)openSheet {
NSLog(#"%s", __FUNCTION__);
NSLog(#"sheetName: %#",sheetName );
[[NSBundle mainBundle] loadNibNamed:sheetName owner:self topLevelObjects:nil];
[self.window beginSheet:self.sheet completionHandler:nil];
}
- (void)save:(NSButton*)sender {
switch ([sender tag]) {
case 0:
[self doSave1];
result = #"1";
break;
case 1:
[self doSave2];
result = #"2";
break;
case n:
[self doSaven];
result = #"x";
break;
}
[self endSheet:self.sheet returnCode:result];
}
- (IBAction)cancel:(id)sender {
NSLog(#"%s", __FUNCTION__);
result = #"0";
[self endSheet:self.sheet returnCode:result];
// returnCode is optional
}
//endSheet:(NSWindow *)sheetWindow {
- (void)endSheet:(NSWindow *)sheetWindow returnCode:returnCode {
//NSLog(#"%s", __FUNCTION__);
[sheetWindow orderOut:self];
}
- (void)save:(NSButton*)sender {
switch ([sender tag]) {
case 0:
[self doSave1];
result = #"1";
break;
case n:
[self doSave3];
result = #"3";
break;
}
[self endSheet:self.sheet returnCode:result];
}
With this method, new in 10.9,I don't need a special sheet controller, and control remains quote local.
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 ?
I am using FBFriendPickerViewController to load friends after user signs in. However, an empty table view is being loaded. The friends of the user from fb are not showing up.
Heres the code.
- (IBAction)inviteButtonTouchHandler:(id)sender {
if (!_friendPickerController) {
_friendPickerController = [[FBFriendPickerViewController alloc] initWithNibName:nil bundle:nil];
_friendPickerController.delegate = self;
_friendPickerController.title = #"Select friends";
_friendPickerController.allowsMultipleSelection = NO;
}
[_friendPickerController clearSelection];
[_friendPickerController loadData];
[self presentViewController:_friendPickerController animated:YES completion:nil];
}
This code is called after login which is done like this in appDelegate following the Facebook Tutorial -
- (void)openSession
{
NSArray *permissions = #[#"friends_about_me"];
[FBSession openActiveSessionWithReadPermissions:permissions
allowLoginUI:YES
completionHandler:
^(FBSession *session,
FBSessionState state, NSError *error) {
[self sessionStateChanged:session state:state error:error];
}];
}
You need to add the following code in viewDidLoad method of your viewController.
if (!FBSession.activeSession.isOpen) {
// if the session is closed, then we open it here, and establish a handler for state changes
[FBSession.activeSession openWithCompletionHandler:^(FBSession *session,
FBSessionState state,
NSError *error) {
switch (state) {
case FBSessionStateClosedLoginFailed:
{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Error"
message:error.localizedDescription
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alertView show];
}
break;
default:
break;
}
}];
}