Swift NSExpression error handling [duplicate] - ios

I am trying this code that is a calculator. How can I handle input from the user that is not valid?
//ANSWER: Bridging header to Objective-C// https://github.com/kongtomorrow/TryCatchFinally-Swift
Here is the same question but in objc but I want to do this in swift. Catching NSInvalidArgumentException from NSExpression
All I want to show is a message if it doesn't work, but now I am getting an exception when the user doesn't input the correct format.
import Foundation
var equation:NSString = "60****2" // This gives a NSInvalidArgumentException',
let expr = NSExpression(format: equation) // reason: 'Unable to parse the format string
if let result = expr.expressionValueWithObject(nil, context: nil) as? NSNumber {
let x = result.doubleValue
println(x)
} else {
println("failed")
}

More "Swifty" solution:
#implementation TryCatch
+ (BOOL)tryBlock:(void(^)())tryBlock
error:(NSError **)error
{
#try {
tryBlock ? tryBlock() : nil;
}
#catch (NSException *exception) {
if (error) {
*error = [NSError errorWithDomain:#"com.something"
code:42
userInfo:#{NSLocalizedDescriptionKey: exception.name}];
}
return NO;
}
return YES;
}
#end
This will generate Swift code:
class func tryBlock((() -> Void)!) throws
And you can use it with try:
do {
try TryCatch.tryBlock {
let expr = NSExpression(format: "60****2")
...
}
} catch {
// Handle error here
}

This is still an issue in Swift 2. As noted, the best solution is to use a bridging header and catch the NSException in Objective C.
https://medium.com/swift-programming/adding-try-catch-to-swift-71ab27bcb5b8 describes a good solution, but the exact code doesn't compile in Swift 2 because try and catch are now reserved keywords. You'll need to change the method signature to workaround this. Here's an example:
// https://medium.com/swift-programming/adding-try-catch-to-swift-71ab27bcb5b8
#interface TryCatch : NSObject
+ (void)tryBlock:(void (^)())try catchBlock:(void (^)(NSException *))catch finallyBlock:(void (^)())finally;
#end
#implementation TryCatch
+ (void)tryBlock:(void (^)())try catchBlock:(void (^)(NSException *))catch finallyBlock:(void (^)())finally {
#try {
try ? try() : nil;
}
#catch (NSException *e) {
catch ? catch(e) : nil;
}
#finally {
finally ? finally() : nil;
}
}
#end

A nice solution editing from https://github.com/kongtomorrow/TryCatchFinally-Swift:
First create TryCatch.h & TryCatch.m and bridge them to Swift:
TryCatch.h
#import <Foundation/Foundation.h>
void tryCatch(void(^tryBlock)(), void(^catchBlock)(NSException *e), void(^finallyBlock)());
TryCatch.m
#import <Foundation/Foundation.h>
void tryCatch(void(^tryBlock)(), void(^catchBlock)(NSException *e), void(^finallyBlock)()) {
#try {
tryBlock();
}
#catch (NSException *exception) {
catchBlock(exception);
}
#finally {
finallyBlock();
}
}
Then create the class TryCatch in Swift:
func `try`(`try`:()->()) -> TryCatch {
return TryCatch(`try`)
}
class TryCatch {
let tryFunc : ()->()
var catchFunc = { (e:NSException!)->() in return }
var finallyFunc : ()->() = {}
init(_ `try`:()->()) {
tryFunc = `try`
}
func `catch`(`catch`:(NSException)->()) -> TryCatch {
// objc bridging needs NSException!, not NSException as we'd like to expose to clients.
catchFunc = { (e:NSException!) in `catch`(e) }
return self
}
func finally(finally:()->()) {
finallyFunc = finally
}
deinit {
tryCatch(tryFunc, catchFunc, finallyFunc)
}
}
Finally, use it! :)
`try` {
let expn = NSExpression(format: "60****2")
//let resultFloat = expn.expressionValueWithObject(nil, context: nil).floatValue
// Other things...
}.`catch` { e in
// Handle error here...
print("Error: \(e)")
}

Related

CloudKit CKError extension not available in Objective-C?

I read somewhere here that CKError is not available in Objective-C, and I concur. For instance, this extension is available in Swift.
#available(OSX 10.10, iOS 8.0, watchOS 3.0, *)
extension CKError {
/// Retrieve partial error results associated by item ID.
public var partialErrorsByItemID: [AnyHashable : Error]? { get }
/// The original CKRecord object that you used as the basis for
/// making your changes.
public var ancestorRecord: CKRecord? { get }
/// The CKRecord object that was found on the server. Use this
/// record as the basis for merging your changes.
public var serverRecord: CKRecord? { get }
/// The CKRecord object that you tried to save. This record is based
/// on the record in the CKRecordChangedErrorAncestorRecordKey key
/// but contains the additional changes you made.
public var clientRecord: CKRecord? { get }
/// The number of seconds after which you may retry a request. This
/// key may be included in an error of type
/// `CKErrorServiceUnavailable` or `CKErrorRequestRateLimited`.
public var retryAfterSeconds: Double? { get }
}
The problem is that I need these objects in my Objective-C project.
I've somehow (I believe) managed to get the partialErrorsByItemID in Objective-C by making a category for NSError and a little comprehension of the documentation of CKError.h, like so:
CKErrorCode ckErrorCode = (CKErrorCode) _code;
if (ckErrorCode == CKErrorPartialFailure) {
// When a CKErrorPartialFailure happens this key will be set in the error's userInfo dictionary.
// The value of this key will be a dictionary, and the values will be errors for individual items with the keys being the item IDs that failed.
NSDictionary *dicError = _userInfo;
if ([dicError objectForKey:CKPartialErrorsByItemIDKey] != nil) {
NSDictionary *dic = (NSDictionary *)[dicError objectForKey:CKPartialErrorsByItemIDKey];
for (NSString* key in dic) {
NSError *newError = dic[key];
if (code == newError.code) {
match = YES;
}
}
} else {
return NO;
}
}
But again, my problem is how to get the objects serverRecord and the clientRecord. Any idea?
Here's an Objective-C category that replicates most of the CKError structure of Swift. I didn't add errorCode, localizedDescription or errorUserInfo since NSError already provides those as code, localizedDescription, and userInfo.
CloudKitExtensions.h
#import <CloudKit/CloudKit.h>
NS_ASSUME_NONNULL_BEGIN
extern const double UnknownRetrySeconds;
#interface NSError (CKError)
- (NSDictionary<id, NSError *> * _Nullable)partialErrorsByItemID;
- (CKRecord * _Nullable)ancestorRecord;
- (CKRecord * _Nullable)clientRecord;
- (CKRecord * _Nullable)serverRecord;
- (double)retryAfterSeconds; // returns UnknownRetrySeconds if not available
#end
NS_ASSUME_NONNULL_END
CloudKitExtensions.m
#import "CloudKitExtensions.h"
const double UnknownRetrySeconds = -1;
#implementation NSError (CKError)
- (NSDictionary<id, NSError *> * _Nullable)partialErrorsByItemID {
if ([self.domain isEqualToString:CKErrorDomain] && self.code == CKErrorPartialFailure) {
return self.userInfo[CKPartialErrorsByItemIDKey];
} else {
return nil;
}
}
- (CKRecord * _Nullable)ancestorRecord {
if ([self.domain isEqualToString:CKErrorDomain] && self.code == CKErrorServerRecordChanged) {
return self.userInfo[CKRecordChangedErrorAncestorRecordKey];
} else {
return nil;
}
}
- (CKRecord * _Nullable)clientRecord {
if ([self.domain isEqualToString:CKErrorDomain] && self.code == CKErrorServerRecordChanged) {
return self.userInfo[CKRecordChangedErrorClientRecordKey];
} else {
return nil;
}
}
- (CKRecord * _Nullable)serverRecord {
if ([self.domain isEqualToString:CKErrorDomain] && self.code == CKErrorServerRecordChanged) {
return self.userInfo[CKRecordChangedErrorServerRecordKey];
} else {
return nil;
}
}
- (double)retryAfterSeconds {
if ([self.domain isEqualToString:CKErrorDomain]) {
NSNumber *delayVal = self.userInfo[CKErrorRetryAfterKey];
return delayVal ? [delayVal doubleValue] : UnknownRetrySeconds;
} else {
return UnknownRetrySeconds;
}
}
#end

Converting a block from Objective-C to Swift

I have written the following method that returns a block that I've written in Objective-C. No matter how many times I mess with the syntax I can't get a swift version of this method that the compiler likes.
- (TWCInviteAcceptanceBlock)acceptHandler
{
return ^(TWCConversation * _Nullable conversation, NSError * _Nullable error) {
if (conversation) {
NSLog("Yay")
}
else {
NSLog(#"Boo")
}
};
}
Any ideas?
Off the top of my head:
func acceptHandler() -> TWCInviteAcceptanceBlock {
return { (conversation: TWCConversation?, error: NSError?) in
if let conversation = conversation {
print("Yay")
} else {
print("Boo")
}
}
}

Using PromiseKit to force sequential download

I am using PromiseKit and would like to force sequential download of JSONs. The count of JSONs might change.
I have read this about chaining.
If I had a fixed number of say 3 downloads, this would be fine.
But what if I had a changing count of download that I would like to download sequentially?
This is my code for 2 URLs. I wonder how I could do this with dateUrlArray[i] iteration over the array?
- (void)downloadJSONWithPromiseKitDateArray:(NSMutableArray *)dateUrlArray {
[self.operationManager GET:dateUrlArray[0]
parameters:nil]
.then(^(id responseObject, AFHTTPRequestOperation *operation) {
NSDictionary *resultDictionary = (NSDictionary *) responseObject;
Menu *menu = [JsonMapper mapMenuFromDictionary:resultDictionary];
if (menu) {
[[DataAccess instance] addMenuToRealm:menu];
}
return [self.operationManager GET:dateUrlArray[1]
parameters:nil];
}).then(^(id responseObject, AFHTTPRequestOperation *operation) {
NSDictionary *resultDictionary = (NSDictionary *) responseObject;
Menu *menu = [JsonMapper mapMenuFromDictionary:resultDictionary];
if (menu) {
[[DataAccess instance] addMenuToRealm:menu];
}
})
.catch(^(NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self handleCatchwithError:error];
});
}).finally(^{
dispatch_async(dispatch_get_main_queue(), ^{
DDLogInfo(#".....finally");
});
});
}
The concept you're looking for is thenable chaining. You want to chain multiple promises in a for loop.
My Objective-C is really rusty - but it should look something like:
// create an array for the results
__block NSMutableArray *results = [NSMutableArray arrayWithCapacity:[urls count]];
// create an initial promise
PMKPromise *p = [PMKPromise promiseWithValue: nil]; // create empty promise
for (id url in urls) {
// chain
p = p.then(^{
// chain the request and storate
return [self.operationManager GET:url
parameters:nil].then(^(id responseObject, AFHTTPRequestOperation *operation) {
[results addObject:responseObject]; // reference to result
return nil;
});
});
}
p.then(^{
// all results available here
});
For those of us looking for a Swift 2.3 solution:
import PromiseKit
extension Promise {
static func resolveSequentially(promiseFns: [()->Promise<T>]) -> Promise<T>? {
return promiseFns.reduce(nil) { (fn1: Promise<T>?, fn2: (()->Promise<T>)?) -> Promise<T>? in
return fn1?.then({ (_) -> Promise<T> in
return fn2!()
}) ?? fn2!()
}
}
}
Note that this function returns nil if the promises array is empty.
Example of use
Below is an example of how to upload an array of attachments in sequence:
func uploadAttachments(attachments: [Attachment]) -> Promise<Void> {
let promiseFns = attachments.map({ (attachment: Attachment) -> (()->Promise<Void>) in
return {
return self.uploadAttachment(attachment)
}
})
return Promise.resolveSequentially(promiseFns)?.then({}) ?? Promise()
}
func uploadAttachment(attachment: Attachment) -> Promise<Void> {
// Do the actual uploading
return Promise()
}
Thanks for Vegard's answer and I rewrite for Swift 3:
extension Promise {
static func resolveSequentially(promiseFns: [()->Promise<T>]) -> Promise<T>? {
return promiseFns.reduce(nil) { (fn1: Promise<T>?, fn2: (()->Promise<T>)?) -> Promise<T>? in
return fn1?.then{ (_) -> Promise<T> in
return fn2!()
} ?? fn2!()
}
}
}
/* Example */
func uploadAttachments(_ attachments: [Attachment]) -> Promise<Void> {
let promiseFns = attachments.map({ (attachment: Attachment) -> (()->Promise<Void>) in
return {
return self. uploadAttachment(attachment)
}
})
return Promise.resolveSequentially(promiseFns: promiseFns)?.then{Void -> Void in} ?? Promise { Void -> Void in }
}

NSKeyedUnarchiver error handling - prevent crash in Swift

Since Swift currently doesn't have try-catch, how am I supposed to prevent crashes with bad data in this line of code?
var myObject = NSKeyedUnarchiver.unarchiveObjectWithData(data) as MyClass
UPDATE
I created a very simple case in a playground for demonstration. Assume we don't know what's in data, how can I catch the SIGABRT on the second line? Is there no way to check to make sure it is possible to unarchive an NSData object before calling unarchiveObjectWithData?
var data = "foo bar".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!
if let unarc = NSKeyedUnarchiver.unarchiveObjectWithData(data) { // Execution was interrupted: signal SIGABRT
}
I think your best bet for now, until Apple updates the implementation of NSKeyedUnarchiver to not use exceptions or adds exception support to Swift, you are going to have to use an Objective-C wrapper to try-catch.
You can see an example of a wrapper here:
https://medium.com/swift-programming/adding-try-catch-to-swift-71ab27bcb5b8
Essentially, you can introduce a single Objective-C function or class that will allow you to use a try-catch block from Swift. I like implementing the above example as an initializer to make it cleaner in Swift:
// In Objective-C
// ----------------------
#interface try: NSObject
- (id)initWithTry:(void(^)())try catch:(void(^)(NSException *exception))catch finally:(void(^)())finally;
#end
#implementation try
- (id)initWithTry:(void(^)())try catch:(void(^)(NSException *exception))catch finally:(void(^)())finally
{
self = [super init];
if (self) {
#try {
try ? try() : nil;
}
#catch (NSException *exception) {
catch ? catch(exception) : nil;
}
#finally {
finally ? finally() : nil;
}
}
return self;
}
#end
// From Swift (make sure you import the objc header in your bridging header
// ----------------------
var data = "foo bar".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)!
try(
try: { () -> Void in
if let unarc: AnyObject = NSKeyedUnarchiver.unarchiveObjectWithData(data) { // Execution was interrupted: signal SIGABRT
println(unarc)
}
},
catch: { exception in
println("Failed to parse data: \(exception)")
},
finally: nil
)

StrongLoop Loopback example in Swift

I'm trying to implement the example LoopBack iOS app in Swift
Create a LoopBack iOS app: part one
and I'm having some trouble translating from the ObjectiveC
- (void) getBooks
{
//Error Block
void (^loadErrorBlock)(NSError *) = ^(NSError *error){
NSLog(#"Error on load %#", error.description);
};
void (^loadSuccessBlock)(NSArray *) = ^(NSArray *models){
NSLog(#"Success count %d", models.count);
self.tableData = models;
[self.myTable reloadData];
};
//This line gets the Loopback model "book" through the adapter defined in AppDelegate
LBModelRepository *allbooks = [[booksAppDelegate adapter] repositoryWithModelName:prototypeName];
//Logic - Get all books. If connection fails, load the error block, if it passes, call the success block and pass allbooks to it.
[allbooks allWithSuccess:loadSuccessBlock failure:loadErrorBlock];
};
Here's my version
func getBooks() {
var errorBlock = {
(error: NSError!) -> Void in
NSLog("Error on load %#", error.description)
}
var successBlock = {
(models: NSArray!) -> Void in
NSLog("Success count %d", models.count)
self.tableData = models
self.booksTable.reloadData()
}
// get the "book" model
var allBooks: LBModelRepository = adapter.repositoryWithModelName(prototypeName)
// get all books
allBooks.allWithSuccess(successBlock, errorBlock)
}
but I get a compiler error on the call to allWithSuccess:
Cannot convert the expressions type 'Void' to type 'LBModelAllSuccessBlock!'
What am I missing?
UPDATE:
If I declare the success block as follows, it works:
var successBlock = {
(models: AnyObject[]!) -> () in
self.tableData = models
self.booksTable.reloadData()
}
Thanks for the answer!!!!
If anyone is looking for the last version of Swift and LoopBack iOS SDK, it worked for me like this:
func getBooks() {
// Error Block
let errorBlock = {
(error: NSError!) -> Void in
NSLog("Error on load %#", error.description)
}
// Success Block
let successBlock = {
(models: [AnyObject]!) -> () in
self.tableData = models
self.myTable.reloadData()
}
// This line gets the Loopback model "book" through the adapter defined in AppDelegate
let allBooks:LBPersistedModelRepository = AppDelegate.adapter.repositoryWithModelName(prototypeName, persisted: true) as! LBPersistedModelRepository
// Logic - Get all books. If connection fails, load the error block, if it passes, call the success block and pass allbooks to it.
allBooks.allWithSuccess(successBlock, failure: errorBlock)
}

Resources