Sometimes you see a piece of iOS - Objective-C code use a Try/Catch structure.
For example this example from: http://docs.xrtml.org/2-1-0/pubsub/ios/ortcclient.html
- (void)viewDidLoad
{
[super viewDidLoad];
// Instantiate OrtcClient
ortcClient = [OrtcClient OrtcClientWithConfig:self];
// Post permissions
#try {
NSMutableDictionary* myPermissions = [[NSMutableDictionary alloc] init];
[myPermissions setObject:#"w" forKey:#"channel1"];
[myPermissions setObject:#"w" forKey:#"channel2"];
[myPermissions setObject:#"r" forKey:#"channelread"];
BOOL result = [ortcClient saveAuthentication:#"http://ortc-developers.realtime.co/server/2.1/" isCLuster:YES authenticationToken:#"myAuthenticationToken" authenticationTokenIsPrivate:NO applicationKey:#"myApplicationKey" timeToLive:1800 privateKey:#"myPrivateKey" permissions:myPermissions];
if (result) {
// Permissions correctly posted
}
else {
// Unable to post permissions
}
}
#catch (NSException* exception) {
// Exception posting permissions
}
// Set connection properties
[ortcClient setConnectionMetadata:#"clientConnMeta"];
[ortcClient setClusterUrl:#"http://ortc-developers.realtime.co/server/2.1/"];
// Connect
[ortcClient connect:#"myApplicationKey" authenticationToken:#"myAuthenticationToken"];
}
Why use such a structure, couldn't you just check for an NSError (indirect) return from the saveAuthentication:isCLuster:authenticationToken:... method like 'regular' Cocoa-Touch code does? For example when reading JSON:
NSError *error = nil;
id result = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:&error];
if (error == nil){
NSLog(#"%#", result);
}else{
NSLog(#"%#", [error localizedDescription]);
}
Use try catch where you expect a condition that cannot be recovered from or which may lead to an undefined behaviour such as crash, use NSError where recovereable errors are expected like wrong values from a json object or xml.
You can go throughApple documentation about exception programming.
In general, try-catch is more robust, does not require you to define an exact position of where to test (could be a block) and provides info about the exception.
Related
I want to disable the journal mode in my core data app.
This is the code:
- (NSPersistentContainer *)persistentContainer {
// The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
#synchronized (self) {
if (_persistentContainer == nil) {
_persistentContainer = [[NSPersistentContainer alloc] initWithName:#"My_App"];
NSURL *url = [[self applicationFilesDirectory] URLByAppendingPathComponent:#"My_App.sqlite"];
NSPersistentStoreDescription *description=[[NSPersistentStoreDescription alloc]initWithURL:url];
NSMutableDictionary *pragmaOptions=[NSMutableDictionary dictionary];
[pragmaOptions setObject:#"DELETE" forKey:#"journal_mode"];
[description setOption:pragmaOptions forKey:#"NSQlitePragmaOptions"];
_persistentContainer.persistentStoreDescriptions=#[description];
[_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
if (error != nil) {
// Replace this implementation with code to handle the error appropriately.
// abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
NSLog(#"Unresolved error %#, %#", error, error.userInfo);
abort();
}
}];
}
}
return _persistentContainer;}
I also verified with this:
NSArray *descriptionArray=[_persistentContainer persistentStoreDescriptions];
NSPersistentStoreDescription * description=[descriptionArray objectAtIndex:0];
NSLog(#"%#",description.options);
and the result of NSLog is:
{
NSInferMappingModelAutomaticallyOption = 1;
NSMigratePersistentStoresAutomaticallyOption = 1;
NSQlitePragmaOptions = {
"journal_mode" = DELETE;
};
but when I restart my app I always find in the application directory all the three file My_App.sqlite, My_App.sqlite-shm and My_App.sqlite-wal.
Why doesn't it work?
I'm trying to configure an Open Graph post. I've followed the code examples on the FB developer site, and, using a test_user, a post is supposedly successfully generated. Here is my code:
- (void)createOGObjectForImage:(NSURL *)imageURL
{
NSMutableDictionary<FBGraphObject> *object =
[FBGraphObject openGraphObjectForPostWithType:#"ogminigame:mini_game"
title:#"Mini Game"
image:#"https://fbstatic-a.akamaihd.net/images/devsite/attachment_blank.png"
url:imageURL
description:#""];
[FBRequestConnection startForPostWithGraphPath:#"me"
graphObject:object
completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if(!error) {
NSLog(#"Result: %#", [result description]);
[self createOGActionWithResult:result];
} else {
NSLog(#"Error posting the Open Graph object to the Object API: %#", error);
[self sharePhotoWithShareDialog:self openGraphAction:nil];
}
}];
}
However, nothing is appearing on the test user wall. When I step through the code, I can see that when I create the OG object, the result has the following contents:
"FACEBOOK_NON_JSON_RESULT" = true;
More specifically, it looks like this:
So when I create my Action, when I try to retrieve the objectID using:
NSString *objectId = [result objectForKey:#"id"];
it obviously returns nil. Am I missing a stage with the result object? I've tried searching for similar problems but there doesn't seem to be much in the way of an explanation.
Well, it seems the code supplied on the Facebook developer site where you supply your custom stories is incorrect. Namely, it should look like:
- (void)createOGObjectForImage:(NSURL *)imageURL
{
NSMutableDictionary<FBOpenGraphObject> *object = [FBGraphObject openGraphObjectForPostWithType:#"ogminigame:mini_game"
title:#"Mini Game"
image:#"https://fbstatic-a.akamaihd.net/images/devsite/attachment_blank.png"
url:imageURL
description:#""];
[FBRequestConnection startForPostOpenGraphObject:object completionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
if(!error) {
NSLog(#"Result: %#", [result description]);
[self createOGActionWithResult:result];
} else {
NSLog(#"Error posting the Open Graph object to the Object API: %#", error);
[self sharePhotoWithShareDialog:self openGraphAction:nil];
}
}];
}
So you cast the dictionary to an FBOpenGraphObject, rather than an FBGraphObject and call startForPostOpenGraphObject instead of startForPostWithGraphPath.
It seems to me Facebook need to be a bit more consistent with their documentation.
I still have nothing showing on the test_account page, but at least the above doesn't seem to be the cause of the issue...
I am planning to develop a turn-based game and is trying to understand how to communicate with Game Center and send and receive mach data. I have read about it and tested this for days now and just cannot get it to work as planned.
The only thing i try to do with the code below is to be able to save and then read the mach data. I am using two sandbox Game Center accounts for the turns.
The turns are sending the same data by pressing "endTurn" button. Every time i run the actual user is authenticated and the app is set up correctly (i believe).
This is a test app without any other purpose than test what i stated. Below is the code i use for the match data processing.
I would really appreciate any ideas and tips on what i may do wrong. Before i started serious testing i did post a similar question but that did not solve this problem, https://stackoverflow.com/questions/14447392/start-gamecenter-turn-based-match-and-initiate-match-data-for-the-very-first-tim.
I also try to catch the participants but with no success, which may mean that it is the problem when processing the completionhandler.
-(IBAction)endTurn:(id)sender {
[_gameDictionary setObject:#"The Object" forKey:#"The Key"];
NSLog(#"_gameDictionary: %#", _gameDictionary);
NSData *data = [NSPropertyListSerialization dataFromPropertyList:_gameDictionary format:NSPropertyListXMLFormat_v1_0 errorDescription:nil];
GKTurnBasedParticipant *nextPlayer;
if (_match.currentParticipant == [_match.participants objectAtIndex:0]) {
nextPlayer = [[_match participants] lastObject];
} else {
nextPlayer = [[_match participants]objectAtIndex:0];
}
NSLog(#"_match.currentParticipant: %#", _match.currentParticipant);
[self.match endTurnWithNextParticipant:nextPlayer matchData:data completionHandler:^(NSError *error) {
if (error) {
NSLog(#"An error occured updating turn: %#", [error localizedDescription]);
}
[self.navigationController popViewControllerAnimated:YES];
}];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
_gameDictionary = [[NSMutableDictionary alloc]init];
[self.match loadMatchDataWithCompletionHandler:^(NSData *matchData, NSError *error) {
NSDictionary *myDict = [NSPropertyListSerialization propertyListFromData:_match.matchData mutabilityOption:NSPropertyListImmutable format:nil errorDescription:nil];
[_gameDictionary addEntriesFromDictionary: myDict];
if (error) {
NSLog(#"loadMatchData - %#", [error localizedDescription]);
}
}];
NSLog(#"_gameDictionary: %#", _gameDictionary);
}
Output:
"gk-cdx" = "17.173.254.218:4398";
"gk-commnat-cohort" = "17.173.254.220:16386";
"gk-commnat-main0" = "17.173.254.219:16384";
"gk-commnat-main1" = "17.173.254.219:16385";
}
2013-02-11 22:44:11.707 GC_test1[8791:14f03] _gameDictionary: {
}
2013-02-11 22:44:13.894 GC_test1[8791:14f03] _gameDictionary: {
The Object = The Key;
}
2013-02-11 22:44:13.894 GC_test1[8791:14f03] _match.currentParticipant: (null)
The fact that _match.currentParticipant evaluates to nil is troubling. I suspect that _match was never initialized, or is nil, or that it was not obtained from a Game Center facility such as loadMatchesWithCompletionHandler:, the GKTurnBasedMatchmakerViewController, or using findMatchForRequest:withCompletionHandler:.
For a new match, if created through any of these facilities, currentParticipant would be guaranteed to represent the local player. You are not allowed to instantiate a GKTurnBasedMatch yourself.
To resolve this issue at least for testing, you could assign a new _match from within the completion handler of findMatchForRequest:withCompletionHandler:. Only then should you be allowed to press your test button.
I'm using what seems to be a simple invocation of the NSFileVersion class method removeOtherVersionsOfItemAtURL: inside a coordinated writing block for some iCloud conflict resolution.
When my devices go into 'spaz mode', which is a technical term for repeatedly opening and closing the application on a few devices, an EXC_BAD_ACCESS exception is thrown internally. Code snippet:
- (void)compareVersionChanges:(NSFileVersion *)version {
if (![DataLoader iCloudPreferenceEnabled]) {
NSLog(#"Ignoring iCloud changes (version comparison) based on user preference");
return;
}
NSLog(#"compareVersionChanges");
dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(aQueue, ^(void) {
NSError *readError = nil;
NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] initWithFilePresenter:(id)self];
[coordinator coordinateReadingItemAtURL:[version URL] options:0 error:&readError byAccessor:^(NSURL *newURL) {
DataContext *loadedContext = nil;
NSData *data = [NSData dataWithContentsOfURL:newURL];
NSError *e = nil;
loadedContext = [self convertXmlDataToContext:data error:&e];
if (e) {
NSLog(#"Done loading, error: %#", e);
[[DataLoader applicationDelegate] displayError:e];
loadedContext = nil;
}
if (!loadedContext) {
return;
}
id appDelegate = [DataLoader applicationDelegate];
DataContext *inMemoryContext = nil;
if (appDelegate != nil && [appDelegate respondsToSelector:#selector(context)]) {
inMemoryContext = [appDelegate performSelector:#selector(context)];
}
if (inMemoryContext) {
NSLog(#"Performing iCloud context synchronizating...");
DataContextSynchronizer *synchronizer = [[DataContextSynchronizer alloc] init];
ChangeSet *changes = [synchronizer compareLocalContext:inMemoryContext andRemoteContext:loadedContext];
if ([[changes changes] count] > 0) {
[SelectionManager disable];
#synchronized(appDelegate) {
NSLog(#"Applying synchronization changes...");
[synchronizer applyChangeSet:changes toDataContext:inMemoryContext];
NSLog(#"Synchronization changes applied");
}
[SelectionManager enable];
if ([appDelegate respondsToSelector:#selector(setSkipRefreshSave:)]) {
[appDelegate performSelector:#selector(setSkipRefreshSave:) withObject:[NSNumber numberWithBool:YES]];
}
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^(void) {
[SelectionManager notifyListeners];
});
if ([appDelegate respondsToSelector:#selector(setSkipRefreshSave:)]) {
[appDelegate performSelector:#selector(setSkipRefreshSave:) withObject:[NSNumber numberWithBool:NO]];
}
[self save:[[DataLoader applicationDelegate] context]];
} else {
NSLog(#"No sync changes applicable.");
}
NSError *coordinateWriteRemoveError = nil;
[coordinator coordinateWritingItemAtURL:newURL options:NSFileCoordinatorWritingForDeleting error:&coordinateWriteRemoveError byAccessor:^(NSURL *theURL) {
theURL = [theURL copy];
NSError *removeOtherVersionsError = nil;
[NSFileVersion removeOtherVersionsOfItemAtURL:theURL error:&removeOtherVersionsError];
if (removeOtherVersionsError) {
NSLog(#"Error removing other versions: %#", removeOtherVersionsError);
}
}];
if (coordinateWriteRemoveError) {
NSLog(#"Error occurred coordinating write for deletion of other file versions: %#", coordinateWriteRemoveError);
}
}
}];
if (readError) {
NSLog(#"Done loading (outside block) error: %#", readError);
}
});
}
I thought a little syntax highlighting might make this easier to examine:
Link to image of code snippet and failure stack in Xcode
The error actually occurs on line 1404, and as you can see from the below screenshot, it's deep in Apple code territory.
Link to image of debugger
Before submitting a radar, I thought I'd check here to see if there's something I'm doing wrong? The extra [... copy] on line 1402 was just a quick check to make sure I'm not losing the reference to the block-provided argument, and will be removed.
Edit: An important note! I'm using ARC.
Edit 2: I've noticed that when calling:
[NSFileVersion otherVersionsOfItemAtURL:theURL]
The return value is nil, which indicates (via the documentation):
...or nil if there is no such file. The array does not contain the version object returned by the currentVersionOfItemAtURL: method.
So by checking the return value of this method before I make the call to removeOtherVersionsOfItemAtURL:, it has alleviated the issue. But I still find it strange that an EXC_BAD_ACCESS is thrown, rather than that method handling it properly.
I've noticed that when calling:
[NSFileVersion otherVersionsOfItemAtURL:theURL]
immediately prior to the call to removeOtherVersionsOfItemAtURL:, the return value is nil, which indicates (via the documentation):
Returns: An array of file version objects or nil if there is no such
file. The array does not contain the version object returned by the
currentVersionOfItemAtURL: method.
So by checking the return value of this method before I make the call to removeOtherVersionsOfItemAtURL:, it has alleviated the issue. But I still find it strange that an EXC_BAD_ACCESS is thrown by removeOtherVersionsOfItemAtURL:, rather than that method simply returning NO, or simply populating the provided NSError object.
I'll be filing a Radar and will update here when I hear back.
Having a heck of a time with this one.
I've got a super-simple Cocoa app containing one WebView, a WebScripting API defined in the page, and a single NSObject defined on that API. When I turn on the debugger tools (in the embedded WebView), I can see the API on the JavaScript window object, and I can see my "api" property defined on that -- but when I call the API's "get" method, the arguments aren't being serialized -- when the Obj-C method gets called, the arguments are missing. See below, which hopefully illustrates:
I've combed through the docs, I've (apparently) set the appropriate methods to expose everything that needs to be exposed, and I can see the method being called. There has to be something stupid I'm missing, but as a relative newbie to this environment, I'm not seeing it.
Thanks in advance for your help!
Have you set WebKitDeveloperExtras to YES in your default user defaults when you send -[NSUserDefaults registerDefaults:]?
Depending on what version of Xcode you're using you could be getting a known error. If you're using LLDB on anything but the most recent version, it might not be giving you the right variables in the debugger. The solution has been to use GDB instead of LLDB until Apple fixes the problem. But I think they fixed the problem in the latest version. I'd change the debugger to use GDB and see if you're getting the right variables in Xcode. (Product-> Edit Scheme...-> Run -> Debugger). I came across this problem in iOS, though, so I don't know its applicability to OSX. Worth a try anyway.
I originally came across the problem here: https://stackoverflow.com/a/9485349/1147934
I process javascript in the main thread of my app from a local file stored in the apps directory. I check for beginning and ending tokens for the js functions I am executing and whether the function contains a variable.
Hopefully this can give you some good ideas for your issue. You could also do alerts in the js to see if the values post correctly as you run the app (I am sure you thought of that already, but it's worth mentioning.) Happy coding! I hope this helps!
in the .h file define:
NSMutableString *processedCommand;
NSArray *commandArguments;
In the .m file:
// tokens
#define kOpenToken #"<%%"
#define kCloseToken #"%%>"
// this will throw
-(void)executeJScriptCommand:(NSString *)aCommand {
[self performSelectorOnMainThread:#selector(executeThisCommand:) withObject:aCommand waitUntilDone:YES];
}
// this will throw
-(NSString *)executeCommand:(NSString *)command {
NSString *aCommand = [[[command stringByReplacingOccurrencesOfString:kOpenToken withString:#""]
stringByReplacingOccurrencesOfString:kCloseToken withString:#""]
stringByTrimmingLeadingAndTrailingWhitespaces];
if ([aCommand hasPrefix:#"="])
{
// variable. get value
[self getVariableFromCommand:aCommand];
}
else {
[self executeThisCommand:aCommand];
}
NSString *returnValue = [NSString stringWithString:processedCommand];
self.processedCommand = nil;
self.commandArguments = nil;
return returnValue;
}
-(void)executeThisCommand:(NSString *)aCommand {
BOOL hasError = NO;
// clear result
self.processedCommand = nil;
self.commandArguments = nil;
BOOL isFromJS = NO;
NSString *function = nil;
NSMutableArray *commandParts = nil;
#try {
// first, break the command into its parts and extract the function that needs to be called, and the (optional) arguments
commandParts = [[NSMutableArray alloc] initWithArray:[aCommand componentsSeparatedByString:#":"]];
if ([[[commandParts objectAtIndex:0] lowercaseString] isEqualToString:#"js-call"]) {
isFromJS = YES;
[commandParts removeObjectAtIndex:0];
}
// get our function, arguments
function = [[commandParts objectAtIndex:0] retain];
[commandParts removeObjectAtIndex:0];
if ([commandParts count] > 0){
if (isFromJS == YES) {
NSString *arguments = [[commandParts objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
if ([arguments length] > 0) {
self.commandArguments = [arguments JSONValue];
}
}
else {
self.commandArguments = [NSArray arrayWithArray:commandParts];
}
}
// build invoke
SEL sel = NSSelectorFromString(function);
if ([self respondsToSelector:sel]) {
[self performSelectorOnMainThread:sel withObject:nil waitUntilDone:YES];
// using invocation causes a SIGABORT because the try/catch block was not catching the exception.
// using perform selector fixed the problem (i.e., the try/catch block now correctly catches the exception, as expected)
}
else {
[appDelegate buildNewExceptionWithName:#"" andMessage:[NSString stringWithFormat:#"Object does not respond to selector %#", function]];
}
}
#catch (NSException * e) {
hasError = YES;
[self updateErrorMessage:[NSString stringWithFormat:#"Error processing command %#: %#", aCommand, [e reason]]];
}
#finally {
[function release];
[commandParts release];
}
if (hasError == YES) {
[appDelegate buildNewExceptionWithName:#"executeThisCommand" andMessage:self.errorMessage];
}
}
// this can return nil
-(NSString *)getQueryStringValue:(NSString *)name {
NSString *returnValue = nil;
if (queryString != nil) {
returnValue = [queryString objectForKey:[name lowercaseString]];
}
return returnValue;
}