NSKeyedUnarchiver error handling - prevent crash in Swift - ios

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
)

Related

Swift NSExpression error handling [duplicate]

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)")
}

Backendless - How To Get Objects From 'Data'

How do I get all the objects from Backendless's database into a UITableView in my iOS app?
Looking at their Documentation, it doesn't clearly state how to get all objects. (I'm new to the platform)
Any help would be appreciated!
Here's how I do it in Swift (for my table of Blurb objects):
func retrieveBlurbs() {
let query = BackendlessDataQuery()
// Use backendless.persistenceService to obtain a ref to a data store for the class
let dataStore = self.backendless.persistenceService.of(Blurb.ofClass()) as IDataStore
dataStore.find(query, response: { (retrievedCollection) -> Void in
print("Successfully retrieved: \(retrievedCollection)")
self.blurbs = retrievedCollection.data as! [Blurb]
self.tableView.reloadData()
}) { (fault) -> Void in
print("Server reported an error: \(fault)")
}
}
I am also new to Backendless and really enjoying it! It's a lot like Parse, but better in a bunch of ways.
Start with this:
https://backendless.com/feature-16-data-retrieval-api-how-to-load-objects-from-an-mbaas-storage/
Then move on to this: https://backendless.com/feature-17-data-paging-or-how-to-efficiently-load-large-data-sets-in-a-mobile-app/
Both articles include concrete examples in Swift.
Try this:
- (void)viewDidLoad {
[super viewDidLoad];
[self getDataFromBackendless];
}
-(void)getDataFromBackendless {
#try {
BackendlessCollection *documents = [backendless.persistenceService of:[YOUR_TABLE_NAME class]];
currentPage =[documents getCurrentPage];
}
#catch (Fault *fault) {
NSLog(#"Server reported an error: %#", fault);
}
}
Then perform UITableView methods:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [currentPage count];
}

Binding a bool to multiple result in Swift

I am doing some experiments on ReactiveCocoa in both Objective-C language and Swift. However, I can not find a way to implement the same functionality with Swift
Here is the Objective-C version:
- (instancetype)init
{
self = [super init];
if (self) {
RAC(self, isLoginButtonEnabled) = [RACSignal combineLatest:#[RACObserve(self, username), RACObserve(self, password)] reduce:^(NSString *newUsername, NSString *newPassword) {
return #(newUsername.length && newPassword.length);
}];
}
return self;
}
I always got error when I do the same thing with Swift, BTW you need to import Swift-RAC-Macros in order to get RAC and RACOBserve in Swift:
override init() {
super.init()
RAC(self, "isLoginButtonEnabled", false) <~ RACSignal.combineLatest([RACObserve(self, "username"), RACObserve(self, "password")], reduce: {(newUsername: String?, newPassword: String?) -> AnyObject? in
return false;
})
}
For this I always get error:
(RAC, _) is not convertible to 'RACSignal'. Can anyone give me how to do that?

RLMException when calling callback closure

I am still trying to wrap my mind around on how to perform Realm queries with GDC.
I have this code in one of my classes:
class func placeNameForChatChannel(chatChannel: String, withCompletion handler: (String?)->()) {
dispatch_async(realmQueue) {
var channelEnvPredicate = NSPredicate(format: "channelName = %#", chatChannel)
var channelEnvs = PSTChannelEnvironment.objectsInRealm(realmdb, withPredicate: channelEnvPredicate)
if channelEnvs.count > 0 {
var channelEnvironment = channelEnvs[0] as! PSTChannelEnvironment
let placeName = channelEnvironment.placeName
handler(placeName)
} else {
handler(nil)
}
}
}
These two are declared as globals in my Application Delegate
var realmdb: RLMRealm {
return RLMRealm.defaultRealm()
}
var realmQueue = dispatch_queue_create("com.myapp.realmdb", DISPATCH_QUEUE_SERIAL)
I am getting the now infamous RLMException, reason: 'Realm accessed from incorrect thread when the handler callback is getting called.
What am I doing wrong?
You need to make sure that you recreate the RLMRealm on every dispatch to a GCD queue. If, instead of using that realmdb, you use RLMRealm.defaultRealm(), do things work?

No response when calling NSURLConnection in mixed Swift Objective-C environment

I've created the Class StartConnection to handle my NSURL requests. It gets called from my AppDelegate twice and that works well. It's called by one other class as well and that also works well. This is the implementation of StartConnection:
#import "StartConnection.h"
#import "BlaBlaBlog-swift.h"
#implementation StartConnection
{
BOOL startedForBlog;
}
- (void)getRssFileWithUrl: (NSString*)rssUrlString forBlog:(BOOL)forBlog
{
startedForBlog = forBlog;
NSURL *url = [NSURL URLWithString:rssUrlString];
NSURLRequest *rssRequest = [NSURLRequest requestWithURL:url];
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:rssRequest delegate:self];
[connection start];
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
dataSize = [response expectedContentLength];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
if (receivedData==nil )
{
receivedData = [[NSMutableData alloc]init];
}
// Append the new data to the instance variable you declared
[receivedData appendData:data];
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
self.receivedDataComplete = receivedData;
if (startedForBlog){
[self.delegate performSelector: #selector(receiveDataCompleted)];
}
else
[self.delegate performSelector: #selector(receivePodCastDataCompleted)];
}
To get some hands on experience with SWIFT I've added an experimental SWIFT class to my code that also uses StartConnection.h. In the debugger I can see an instance of StartConnection being created and the getFileWithUrl methode seems to be kicked of normally. But that's all that happens. None of the delegate methods is called.
This is SWIFT class:
import UIKit
class CheckActuality: NSObject, GetReceivedDataProtocol, WordPressParserDelegate {
var retrievePostData = StartConnection()
var parseCompleted: Bool=false
var result: Bool = true
lazy var wpParser = WordPressParser()
lazy var defaults = NSUserDefaults.standardUserDefaults()
func isActual () -> Bool {
var url = "http://blablablog.nl/new_api.php?function=get_recent_posts&count=1"
self.retrievePostData.delegate=self
self.retrievePostData.getRssFileWithUrl(url, forBlog:true)
while !self.parseCompleted
{
// wait till wpparser has completed
}
if self.wpParser.arrayWithPostDictionaries.count == 1
// Some info has been retrieved
{
var posts: NSArray = wpParser.arrayWithPostDictionaries
var post: NSDictionary = posts.objectAtIndex(0) as NSDictionary
var latestPostUrl: String = post.objectForKey("postUrl") as String
var currentLatestPostUrl = defaults.stringForKey("ttt")
if latestPostUrl != currentLatestPostUrl {
result = false
}
else {
result = true
}
}
return result
}
func receiveDataCompleted () {
if self.retrievePostData.receivedDataComplete != nil
{
self.wpParser.delegate=self
self.wpParser.parseData(retrievePostData.receivedDataComplete)
}
else
{
// warning no internet
}
}
func wpParseCompleted () {
self.parseCompleted=true
}
}
And to be complete, the call in my AppDelegate look like this:
//
// retrieving PostData. Create a connection, set delegate to self en start with created url
//
retrievePostData = [[StartConnection alloc]init];
retrievePostData.delegate = self;
NSString *url = [[wordPressUrl stringByAppendingString:apiString] stringByAppendingString:pageCountString];
[retrievePostData getRssFileWithUrl:url forBlog:(BOOL)true];
//
// retrieving PodcastData. Create a connection, set delegate to self en start with created url
//
retrievePodCastData = [[StartConnection alloc]init];
retrievePodCastData.delegate = self;
[retrievePodCastData getRssFileWithUrl:podcastUrl forBlog:(BOOL)false];
I'm breaking my head for almost a day. Hope some of you far more experienced guys can help this starter out.
The while !parseCompleted loop is blocking the main thread until the download and parsing is done. But the processing of the received data happens on the main thread, too, so if that thread is blocked, your app will be deadlocked.
I would eliminate that while loop and put all of the post processing inside your receivedDataComplete method.
By the way, implicit in this change is the fact that isActual must be an asynchronous method. Thus, rather than returning a Bool value, it should be a Void return type but you should employ the completionHandler pattern (and I'd change it to also return an error object, too):
class CheckActuality: NSObject, GetReceivedDataProtocol, WordPressParserDelegate {
let errorDomain = "com.domain.app.CheckActuality"
lazy var retrievePostData = StartConnection()
lazy var wpParser = WordPressParser()
lazy var defaults = NSUserDefaults.standardUserDefaults()
var completionHandler: ((latestPost: Bool!, error: NSError?)->())!
func isActual(completionHandler: (latestPost: Bool!, error: NSError?)->()) {
// save the completionHandler, which will be called by `receiveDataCompleted` or `wpParseCompleted`
self.completionHandler = completionHandler
// now perform query
let url = "http://blablablog.nl/new_api.php?function=get_recent_posts&count=1" // as an aside, use `let` here
retrievePostData.delegate = self // also note that use of `self` is redundant here
retrievePostData.getRssFileWithUrl(url, forBlog:true)
}
func receiveDataCompleted () {
if retrievePostData.receivedDataComplete != nil {
wpParser.delegate = self
wpParser.parseData(retrievePostData.receivedDataComplete)
} else {
// frankly, I'd rather see you change this `receiveDataCompleted` return the `NSError` from the connection, but in the absence of that, let's send our own error
let error = NSError(domain: errorDomain, code: 1, userInfo: nil)
completionHandler(latestPost: nil, error: error)
}
}
func wpParseCompleted () {
if wpParser.arrayWithPostDictionaries.count == 1 { // Some info has been retrieved
let posts: NSArray = wpParser.arrayWithPostDictionaries
let post: NSDictionary = posts.objectAtIndex(0) as NSDictionary
let latestPost: String = post.objectForKey("postUrl") as String
let currentlatestPost = defaults.stringForKey("ttt")
completionHandler(latestPost: (latestPost != currentlatestPost), error: nil)
}
// again, I'd rather see you return a meaningful error returned by the WordPressParser, but I'll make up an error object for now
let error = NSError(domain: errorDomain, code: 2, userInfo: nil)
completionHandler(latestPost: nil, error: error)
}
}
Now, I don't know if latestPost is the appropriate name for the value you were trying to return, so change that to whatever makes sense for your routine. Also, the name isActual doesn't really make sense, but I'll let you change that to whatever you want.
Anyway, when you use it, you'd use the trailing closure syntax to specify the completionHandler block that will be performed asynchronously:
let checkActuality = CheckActuality()
func someFunc() {
checkActuality.isActual() { latestPost, error in
if error != nil {
// do whatever error handling you want
println(error)
} else if latestPost {
// yes, latest post
} else {
// nope
}
}
// note, do not try to check `latestPost` here because the
// above closure runs asynchronously
}
Needless to say, this is a completionHandler pattern, but looking at your code, you seem to favor delegate patterns. If you wanted to implement this using the delegate pattern, you can. But the idea is the same: This isActual method (whatever you end up renaming it to) runs asynchronously, so you have to inform the caller when it is complete.

Resources