Catching RealmSwift write errors i.e. delete - swift

How exactly do I catch RealmSwift add/delete errors?
i.e.
do {
try realm.write {
realm.delete(MyRealmObject())
}
completion(true)
} catch {
completion(false)
}
In this example I've deliberately tried deleting a RealmObject that I've just made up (to make it fail), but it doesn't catch, instead I get this error:
Terminating app due to uncaught exception 'RLMException', reason: 'Can only delete an object from the Realm it belongs to.'
I haven't seen any examples of people handling specific delete/add errors - is there a reason for this?

Not sure if this is what you're looking for but the following code catches and identifies specific add errors. However, errors accessing different instances of realm are handled differently. So starting with this...
class MyObject: Object {
#objc dynamic msg = ""
}
do {
let realm = try Realm()
let a0 = MyObject()
a0.msg = "Hello, World"
try! realm.write {
realm.add(a0)
}
} catch let error as NSError {
print(error.localizedDescription)
}
to then expand.... do the following to ensure the object being deleted matches the realm you are deleting it from.
do {
let realm = try Realm()
if a0.realm == realm {
try! realm.write {
realm.delete(a0)
}
}
} catch let error as NSError {
print(error.localizedDescription)
}

Realm developers extend NSError class with their custom description Can only delete an object from the Realm it belongs to and do FatalError in the predefined list of errors they expect ( may be for a good use of the their library as a help to the developer to correctly use it ) , so they intentionally don't throw an exception that the try can catch

Related

Why try/catch block doesn't cover all the runtime exceptions?

What I try to do is
...
let path: URL? = URL(string: "")
do {
let data = try Data(contentsOf: path!)
}
catch {
print("ERROR: \(error)")
}
...
I know that this code won't work, but what I expect is to catch the error in the catch block instead I get a runtime exception and crash.
try this
..
if let path = URL(string: "someStringForUrl") {
do {
let data = try Data(contentsOf: path)
}
catch {
print("ERROR: \(error)")
}
}
...
You never reach the try, so you never reach the catch. Before any of that can happen, the path! forced unwrap fails and crashes the app.
(Crashing the app is a runtime exception, which is a completely different thing from the Swift try/catch mechanism. It doesn't not "cover all the runtime exceptions"; it doesn't cover any runtime exceptions. You're confusing apples with elephants; they are totally unrelated. Runtime exceptions are not thrown-caught Error objects, and vice versa.)

SwiftUI + Realm: removing a row from List results in exception 'RLMException', reason: 'Object has been deleted or invalidated.'

I'm trying to port a little RSS feed reader app from UIKit over to SwiftUI, this app uses Realm for persistence.
In order to make Realm bindable in SwiftUI, I added the following code to my project:
import Foundation
import SwiftUI
import RealmSwift
final class FeedData: ObservableObject {
#Published var feeds: [Feed] {
didSet {
cleanRealm()
}
}
private var feedsToken: NotificationToken?
private func activateFeedsToken() {
let realm = try! Realm()
let feeds = realm.objects(Feed.self)
feedsToken = feeds.observe { _ in
self.feeds = Array(feeds)
}
}
func cleanRealm() {
let realm = try! Realm()
let tempFeeds = realm.objects(Feed.self)
let diff = feeds.difference(from: tempFeeds)
for change in diff {
switch change {
case .remove(_, let element, _):
do {
try realm.write {
print("Removing \(element.name)")
if element.isInvalidated {
print("Error: element invalidated.")
} else {
realm.delete(element)
print("Removed \(element.name)")
}
}
} catch {
fatalError(error.localizedDescription)
}
default:
break
}
}
}
init() {
let realm = try! Realm()
feeds = Array(realm.objects(Feed.self))
activateFeedsToken()
}
deinit {
feedsToken?.invalidate()
}
}
So, we have a feeds array with a DidSet observer that invokes the cleanRealm() function when triggered, which then uses collection diffing to remove Feed objects which are no longer in the array but still stored in Realm - I'm aware this is super clunky, but I figured it would at least keep the array in sync with the Realm database.
In my SwiftUI view, I then use FeedData as EnvironmentObject and use a List showing all Feed objects using ForEach.
When a user deletes a feed from the List, it is then removed from the array like so:
.onDelete(perform: deleteItems)
Which then calls this function:
func deleteItems(at offsets: IndexSet) {
print("Removing feeds at requested offsets.")
feedData.feeds.remove(atOffsets: offsets)
print("Removed feeds at requested offsets.")
}
Problem: when I run my app and then delete an entry from the list, the following exception is thrown:
* Terminating app due to uncaught exception 'RLMException', reason: 'Object has been deleted or invalidated.'
* First throw call stack:
(0x1a4a97ab0 0x1a47b1028 0x10098f6a8 0x100996324 0x1009962e8 0x10000dfa4 0x1b215d830 0x1b215d3f0 0x1b215d170 0x1b215f470 0x1db363c4c 0x1db36a0c8 0x1db36a240 0x1db36a6c0 0x1b22d647c 0x1db3ca2c4 0x1db3c9f04 0x1b21a924c 0x1b21a943c 0x1b21a9b50 0x1b22d647c 0x1daefcd50 0x1db3f0ac4 0x1db3eb7b8 0x1db3ead20 0x1db14dca4 0x1db14c5c0 0x1db4d5d0c 0x1db1bdc1c 0x1db1b836c 0x1db1bef70 0x1cf72c9c0 0x1cf713e9c 0x1cf714164 0x1cf719130 0x1db07f9f0 0x1db084e10 0x1db3ac770 0x1db07f96c 0x1cf719284 0x1db081ca0 0x1db081a48 0x1db0816c8 0x1db081834 0x1db3ac770 0x1db0817fc 0x1db09f848 0x1daf00a10 0x1daf00970 0x1daf00a8c 0x1a4a12668 0x1a4a0d308 0x1a4a0d8b8 0x1a4a0d084 0x1aec56534 0x1a8b7b8b8 0x10004e520 0x1a488ce18)
libc++abi.dylib: terminating with uncaught exception of type NSException
My research so far suggests that when a Realm object is deleted, it is mutated before being removed entirely.
So, I think what may be going on here is that when the object is mutated prior to being removed from Realm, SwiftUI detects this change, redraws the view and then tries accessing the now invalidated Realm object resulting in the exception being raised.
Realm objects do have a isInvalidated property that I should probably check before adding it to the List, but AFAIK (and please do feel free to correct me on this) there is no way in a ForEach block to check for such a condition and "continue" to the next array item if desired.
Any help with this is greatly appreciated, I've been messing with this issue all day and just cannot find a good solution, it's probably obvious but I'm also still learning about all the wonderful stuff SwiftUI can do while working on this sample project.
Thanks!
Update:
Following Jay's advice, I managed to modify my FeedData class so that it exposes a Realm Results property for my app to work with, instead of copying to a separate array.
That also means I no longer need to do any collection diffing when deleting feeds, but since SwiftUI's .onDelete(perform:) modifier expects a function that accepts an IndexSet, my delete function is still a bit of a hack.
The updated FeedData class now looks as follows:
import Foundation
import SwiftUI
import RealmSwift
final class FeedData: ObservableObject {
#Published var feeds: Results<Feed>
private var feedsToken: NotificationToken?
private func activateFeedsToken() {
let realm = try! Realm()
let feeds = realm.objects(Feed.self)
feedsToken = feeds.observe { _ in
self.feeds = feeds
}
}
func deleteItems(at offsets: IndexSet) {
print("deleteItems called.")
let realm = try! Realm()
do {
try realm.write {
offsets.forEach { index in
print("Attempting to access index \(index).")
if index < feeds.count {
print("Index is valid.")
let item = feeds[index]
print("Removing \(item.name)")
realm.delete(item)
print("Removed item.")
}
}
}
} catch {
fatalError(error.localizedDescription)
}
}
init() {
let realm = try! Realm()
feeds = realm.objects(Feed.self)
activateFeedsToken()
}
deinit {
feedsToken?.invalidate()
}
}
While that technically works, my issue now is that my view's ForEach() method will be called as soon as an object is deleted from the Realm (tested by using breakpoints), this happens even before the Realm notification is dispatched.
This then results in an attempt to access the index of the deleted object, in which case the app will crash with an index out of bounds error from Realm.
That should probably be a separate question though, as my original issue is resolved.
#Jay, can you please mark your first comment as an answer to my question so that I can approve it?
Thanks for the great help!
I ran into this problem, too. Not sure what's going on, some complicated multiple call behavior caused by SwiftUI, I think. When I expanded my notificationToken observation block and switch on the changes to return if a deletion is called, it works fine. In your case:
feedsToken = feeds.observe { [weak self] (changes: RealmCollectionChange) in
switch changes {
case .initial:
print("Initial call")
case .update(_, let deletions, _, _):
print("Updates made!")
if !deletions.isEmpty { return }
self.feeds = feeds
case .error(let error):
print("Error observing changes")
}
}

Reading Data from Realm Database (Swift)

I am new to Realm DataBase and I need a way to read data from realmCloud, but from two different app projects. The way I have tried to implement this is by using query-synced realm. At the moment I'm using a singe realm user to write the data in one app, and the same realm user to read data from another app. The problem is that making a query from the second app(the one used for reading) doesn't return any realm objects ( I have also noticed that user identifier is different from the first one, and also the user permissions are nil.
I have tried setting permissions directly from RealmStudio since documentation is not precise on how to set them from code
func openRealm() {
do {
realm = try Realm(configuration: SyncUser.current!.configuration())
let queryResults = realm.objects(*className*.self)
let syncSubscription = queryResults.subscribe()
let notificationToken = queryResults.observe() { [weak self] (changes) in
switch (changes) {
case .initial: print(queryResults)
case .error(let error): print(error)
default: print("default")
}
}
for token in queryResults {
print(token.tokenString)
}
syncSubscription.unsubscribe()
notificationToken.invalidate()
} catch {
print(error)
}
}
This function prints the data in one app project, but used in another app project with the same user logged in, and the same classFile referenced in the project, it does not. (note that SyncUser.current.identifier is different also
There are a couple of issues.
Some of these calls are asynchronous and the code in your question is going out of scope before the data is sync'd (retreived). The bottom line is code is faster than the internet and you need to design the flow of the app around async calls; don't try to work with the data until it's available.
For example
let notificationToken = queryResults.observe() { [weak self] (changes) in
//here is where results are fully populated
}
// this code may run before results are populated //
for token in queryResults {
print(token.tokenString)
}
Also, let notificationToken is a local var and goes out of scope before the results are populated as well.
These issues are super easy to fix. First is to keep the notification token alive while waiting for results to be populated and the second is to work with the results inside the closure, as that's when they are valid.
var notificationToken: NotificationToken? = nil //a class var
func openRealm() {
do {
let config = SyncUser.current?.configuration()
let realm = try Realm(configuration: config!)
let queryResults = realm.objects(Project.self)
let syncSubscription = queryResults.subscribe(named: "my-projects")
self.notificationToken = queryResults.observe() { changes in
switch changes {
case .initial:
print("notification: initial results are populated")
queryResults.forEach { print($0) }
case .update(_, let deletions, let insertions, let modifications):
print("notification: results, inserted, deleteed or modified")
insertions.forEach { print($0) } //or mods or dels
case .error(let error):
fatalError("\(error)")
}
}
} catch {
print(error)
}
}
deinit {
self.notificationToken?.invalidate()
}
The other advantage of keeping that token (and its corresponding code) alive is when there are further changes, your app will be notified. So if another project is added for example, the code in the 'changes' section will run and display that change.

Realm write transaction failing, despite being in transaction

I have this code, which should append messages with a new message:
func addMessage(_ message: Message) {
do {
try Realm().write {
self.messages.append(message)
}
} catch let error {
print("could not add message due to error:\n\(error)")
}
}
However, I get an exception Cannot modify managed RLMArray outside of a write transaction It doesn't make any sense to me, because I'm already in a write transaction...
You need to create a Realm object before applying the write module.
According to the GitHub documentation, you can try code like this:
func addMessage(_ message: Message) {
do {
let realm = try! Realm()
try! realm.write {
self.messages.append(message)
}
} catch let error {
print("Could not add message due to error:\n\(error)")
}
}
Hope it helps!
You can use let realm = try! Realm() with a custom default configuration by setting a default configuration see here:
var config = Realm.Configuration()
// Set this as the configuration used for the default Realm
Realm.Configuration.defaultConfiguration = config
The trouble was I was using a plain Realm object with no special configuration. Since I am using Realm Mobile Platform, I needed to create a Realm object with the same config each time I want to write to that DB:
let configuration = Realm.Configuration(
syncConfiguration: SyncConfiguration(user: user, realmURL: URL(string: "realm://127.0.0.1:9080/~/speciail")!)
)
self.realm = try! Realm(configuration: configuration)
//now do the write transaction!
It took a bit of refactoring, but I have it now. My thanks to those of you who took the time to help me.

Error-Handling in Swift-Language

I haven't read too much into Swift but one thing I noticed is that there are no exceptions.
So how do they do error handling in Swift? Has anyone found anything related to error-handling?
Swift 2 & 3
Things have changed a bit in Swift 2, as there is a new error-handling mechanism, that is somewhat more similar to exceptions but different in detail.
1. Indicating error possibility
If function/method wants to indicate that it may throw an error, it should contain throws keyword like this
func summonDefaultDragon() throws -> Dragon
Note: there is no specification for type of error the function actually can throw. This declaration simply states that the function can throw an instance of any type implementing ErrorType or is not throwing at all.
2. Invoking function that may throw errors
In order to invoke function you need to use try keyword, like this
try summonDefaultDragon()
this line should normally be present do-catch block like this
do {
let dragon = try summonDefaultDragon()
} catch DragonError.dragonIsMissing {
// Some specific-case error-handling
} catch DragonError.notEnoughMana(let manaRequired) {
// Other specific-case error-handlng
} catch {
// Catch all error-handling
}
Note: catch clause use all the powerful features of Swift pattern matching so you are very flexible here.
You may decided to propagate the error, if your are calling a throwing function from a function that is itself marked with throws keyword:
func fulfill(quest: Quest) throws {
let dragon = try summonDefaultDragon()
quest.ride(dragon)
}
Alternatively, you can call throwing function using try?:
let dragonOrNil = try? summonDefaultDragon()
This way you either get the return value or nil, if any error occurred. Using this way you do not get the error object.
Which means that you can also combine try? with useful statements like:
if let dragon = try? summonDefaultDragon()
or
guard let dragon = try? summonDefaultDragon() else { ... }
Finally, you can decide that you know that error will not actually occur (e.g. because you have already checked are prerequisites) and use try! keyword:
let dragon = try! summonDefaultDragon()
If the function actually throws an error, then you'll get a runtime error in your application and the application will terminate.
3. Throwing an error
In order to throw an error you use throw keyword like this
throw DragonError.dragonIsMissing
You can throw anything that conforms to ErrorType protocol. For starters NSError conforms to this protocol but you probably would like to go with enum-based ErrorType which enables you to group multiple related errors, potentially with additional pieces of data, like this
enum DragonError: ErrorType {
case dragonIsMissing
case notEnoughMana(requiredMana: Int)
...
}
Main differences between new Swift 2 & 3 error mechanism and Java/C#/C++ style exceptions are follows:
Syntax is a bit different: do-catch + try + defer vs traditional try-catch-finally syntax.
Exception handling usually incurs much higher execution time in exception path than in success path. This is not the case with Swift 2.0 errors, where success path and error path cost roughly the same.
All error throwing code must be declared, while exceptions might have been thrown from anywhere. All errors are "checked exceptions" in Java nomenclature. However, in contrast to Java, you do not specify potentially thrown errors.
Swift exceptions are not compatible with ObjC exceptions. Your do-catch block will not catch any NSException, and vice versa, for that you must use ObjC.
Swift exceptions are compatible with Cocoa NSError method conventions of returning either false (for Bool returning functions) or nil (for AnyObject returning functions) and passing NSErrorPointer with error details.
As an extra syntatic-sugar to ease error handling, there are two more concepts
deferred actions (using defer keyword) which let you achieve the same effect as finally blocks in Java/C#/etc
guard statement (using guard keyword) which let you write little less if/else code than in normal error checking/signaling code.
Swift 1
Runtime errors:
As Leandros suggests for handling runtime errors (like network connectivity problems, parsing data, opening file, etc) you should use NSError like you did in ObjC, because the Foundation, AppKit, UIKit, etc report their errors in this way. So it's more framework thing than language thing.
Another frequent pattern that is being used are separator success/failure blocks like in AFNetworking:
var sessionManager = AFHTTPSessionManager(baseURL: NSURL(string: "yavin4.yavin.planets"))
sessionManager.HEAD("/api/destoryDeathStar", parameters: xwingSquad,
success: { (NSURLSessionDataTask) -> Void in
println("Success")
},
failure:{ (NSURLSessionDataTask, NSError) -> Void in
println("Failure")
})
Still the failure block frequently received NSError instance, describing the error.
Programmer errors:
For programmer errors (like out of bounds access of array element, invalid arguments passed to a function call, etc) you used exceptions in ObjC. Swift language does not seem to have any language support for exceptions (like throw, catch, etc keyword). However, as documentation suggests it is running on the same runtime as ObjC, and therefore you are still able to throw NSExceptions like this:
NSException(name: "SomeName", reason: "SomeReason", userInfo: nil).raise()
You just cannot catch them in pure Swift, although you may opt for catching exceptions in ObjC code.
The questions is whether you should throw exceptions for programmer errors, or rather use assertions as Apple suggests in the language guide.
Update June 9th 2015 - Very important
Swift 2.0 comes with try, throw, and catch keywords and the most exciting is:
Swift automatically translates Objective-C methods that produce errors into methods that throw an error according to Swift's native error handling functionality.
Note: Methods that consume errors, such as delegate methods or methods
that take a completion handler with an NSError object argument, do not
become methods that throw when imported by Swift.
Excerpt From: Apple Inc. “Using Swift with Cocoa and Objective-C (Swift 2 Prerelease).” iBooks.
Example: (from the book)
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *URL = [NSURL fileURLWithPath:#"/path/to/file"];
NSError *error = nil;
BOOL success = [fileManager removeItemAtURL:URL error:&error];
if (!success && error){
NSLog(#"Error: %#", error.domain);
}
The equivalent in swift will be:
let fileManager = NSFileManager.defaultManager()
let URL = NSURL.fileURLWithPath("path/to/file")
do {
try fileManager.removeItemAtURL(URL)
} catch let error as NSError {
print ("Error: \(error.domain)")
}
Throwing an Error:
*errorPtr = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotOpenFile userInfo: nil]
Will be automatically propagated to the caller:
throw NSError(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, userInfo: nil)
From Apple books, The Swift Programming Language it's seems errors should be handle using enum.
Here is an example from the book.
enum ServerResponse {
case Result(String, String)
case Error(String)
}
let success = ServerResponse.Result("6:00 am", "8:09 pm")
let failure = ServerResponse.Error("Out of cheese.")
switch success {
case let .Result(sunrise, sunset):
let serverResponse = "Sunrise is at \(sunrise) and sunset is at \(sunset)."
case let .Error(error):
let serverResponse = "Failure... \(error)"
}
From: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/br/jEUH0.l
Update
From Apple news books, "Using Swift with Cocoa and Objective-C". Runtime exceptions not occur using swift languages, so that's why you don't have try-catch. Instead you use Optional Chaining.
Here is a stretch from the book:
For example, in the code listing below, the first and second lines are
not executed because the length property and the characterAtIndex:
method do not exist on an NSDate object. The myLength constant is
inferred to be an optional Int, and is set to nil. You can also use an
if–let statement to conditionally unwrap the result of a method that
the object may not respond to, as shown on line three
let myLength = myObject.length?
let myChar = myObject.characterAtIndex?(5)
if let fifthCharacter = myObject.characterAtIndex(5) {
println("Found \(fifthCharacter) at index 5")
}
Excerpt From: Apple Inc. “Using Swift with Cocoa and Objective-C.” iBooks. https://itun.es/br/1u3-0.l
And the books also encourage you to use cocoa error pattern from Objective-C (NSError Object)
Error reporting in Swift follows the same pattern it does in
Objective-C, with the added benefit of offering optional return
values. In the simplest case, you return a Bool value from the
function to indicate whether or not it succeeded. When you need to
report the reason for the error, you can add to the function an
NSError out parameter of type NSErrorPointer. This type is roughly
equivalent to Objective-C’s NSError **, with additional memory safety
and optional typing. You can use the prefix & operator to pass in a
reference to an optional NSError type as an NSErrorPointer object, as
shown in the code listing below.
var writeError : NSError?
let written = myString.writeToFile(path, atomically: false,
encoding: NSUTF8StringEncoding,
error: &writeError)
if !written {
if let error = writeError {
println("write failure: \(error.localizedDescription)")
}
}
Excerpt From: Apple Inc. “Using Swift with Cocoa and Objective-C.” iBooks. https://itun.es/br/1u3-0.l
There are no Exceptions in Swift, similar to Objective-C's approach.
In development, you can use assert to catch any errors which might appear, and need to be fixed before going to production.
The classic NSError approach isn't altered, you send an NSErrorPointer, which gets populated.
Brief example:
var error: NSError?
var contents = NSFileManager.defaultManager().contentsOfDirectoryAtPath("/Users/leandros", error: &error)
if let error = error {
println("An error occurred \(error)")
} else {
println("Contents: \(contents)")
}
The recommended 'Swift Way' is:
func write(path: String)(#error: NSErrorPointer) -> Bool { // Useful to curry error parameter for retrying (see below)!
return "Hello!".writeToFile(path, atomically: false, encoding: NSUTF8StringEncoding, error: error)
}
var writeError: NSError?
let written = write("~/Error1")(error: &writeError)
if !written {
println("write failure 1: \(writeError!.localizedDescription)")
// assert(false) // Terminate program
}
However I prefer try/catch as I find it easier to follow because it moves the error handling to a separate block at the end, this arrangement is sometimes called "Golden Path". Lucky you can do this with closures:
TryBool {
write("~/Error2")(error: $0) // The code to try
}.catch {
println("write failure 2: \($0!.localizedDescription)") // Report failure
// assert(false) // Terminate program
}
Also it is easy to add a retry facility:
TryBool {
write("~/Error3")(error: $0) // The code to try
}.retry {
println("write failure 3 on try \($1 + 1): \($0!.localizedDescription)")
return write("~/Error3r") // The code to retry
}.catch {
println("write failure 3 catch: \($0!.localizedDescription)") // Report failure
// assert(false) // Terminate program
}
The listing for TryBool is:
class TryBool {
typealias Tryee = NSErrorPointer -> Bool
typealias Catchee = NSError? -> ()
typealias Retryee = (NSError?, UInt) -> Tryee
private var tryee: Tryee
private var retries: UInt = 0
private var retryee: Retryee?
init(tryee: Tryee) {
self.tryee = tryee
}
func retry(retries: UInt, retryee: Retryee) -> Self {
self.retries = retries
self.retryee = retryee
return self
}
func retry(retryee: Retryee) -> Self {
return self.retry(1, retryee)
}
func retry(retries: UInt) -> Self {
// For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
self.retries = retries
retryee = nil
return self
}
func retry() -> Self {
return retry(1)
}
func catch(catchee: Catchee) {
var error: NSError?
for numRetries in 0...retries { // First try is retry 0
error = nil
let result = tryee(&error)
if result {
return
} else if numRetries != retries {
if let r = retryee {
tryee = r(error, numRetries)
}
}
}
catchee(error)
}
}
You can write a similar class for testing an Optional returned value instead of Bool value:
class TryOptional<T> {
typealias Tryee = NSErrorPointer -> T?
typealias Catchee = NSError? -> T
typealias Retryee = (NSError?, UInt) -> Tryee
private var tryee: Tryee
private var retries: UInt = 0
private var retryee: Retryee?
init(tryee: Tryee) {
self.tryee = tryee
}
func retry(retries: UInt, retryee: Retryee) -> Self {
self.retries = retries
self.retryee = retryee
return self
}
func retry(retryee: Retryee) -> Self {
return retry(1, retryee)
}
func retry(retries: UInt) -> Self {
// For some reason you can't write the body as "return retry(1, nil)", the compiler doesn't like the nil
self.retries = retries
retryee = nil
return self
}
func retry() -> Self {
return retry(1)
}
func catch(catchee: Catchee) -> T {
var error: NSError?
for numRetries in 0...retries {
error = nil
let result = tryee(&error)
if let r = result {
return r
} else if numRetries != retries {
if let r = retryee {
tryee = r(error, numRetries)
}
}
}
return catchee(error)
}
}
The TryOptional version enforces a non-Optional return type that makes subsequent programming easier, e.g. 'Swift Way:
struct FailableInitializer {
init?(_ id: Int, error: NSErrorPointer) {
// Always fails in example
if error != nil {
error.memory = NSError(domain: "", code: id, userInfo: [:])
}
return nil
}
private init() {
// Empty in example
}
static let fallback = FailableInitializer()
}
func failableInitializer(id: Int)(#error: NSErrorPointer) -> FailableInitializer? { // Curry for retry
return FailableInitializer(id, error: error)
}
var failError: NSError?
var failure1Temp = failableInitializer(1)(error: &failError)
if failure1Temp == nil {
println("failableInitializer failure code: \(failError!.code)")
failure1Temp = FailableInitializer.fallback
}
let failure1 = failure1Temp! // Unwrap
Using TryOptional:
let failure2 = TryOptional {
failableInitializer(2)(error: $0)
}.catch {
println("failableInitializer failure code: \($0!.code)")
return FailableInitializer.fallback
}
let failure3 = TryOptional {
failableInitializer(3)(error: $0)
}.retry {
println("failableInitializer failure, on try \($1 + 1), code: \($0!.code)")
return failableInitializer(31)
}.catch {
println("failableInitializer failure code: \($0!.code)")
return FailableInitializer.fallback
}
Note auto-unwrapping.
Edit: Although this answer works, it is little more than Objective-C transliterated into Swift. It has been made obsolete by changes in Swift 2.0. Guilherme Torres Castro's answer above is a very good introduction to the preferred way of handling errors in Swift. VOS
It took a bit of figuring it out but I think I've sussed it. It seems ugly though. Nothing more than a thin skin over the Objective-C version.
Calling a function with an NSError parameter...
var fooError : NSError ? = nil
let someObject = foo(aParam, error:&fooError)
// Check something was returned and look for an error if it wasn't.
if !someObject {
if let error = fooError {
// Handle error
NSLog("This happened: \(error.localizedDescription)")
}
} else {
// Handle success
}`
Writing the function that takes an error parameter...
func foo(param:ParamObject, error: NSErrorPointer) -> SomeObject {
// Do stuff...
if somethingBadHasHappened {
if error {
error.memory = NSError(domain: domain, code: code, userInfo: [:])
}
return nil
}
// Do more stuff...
}
Basic wrapper around objective C that gives you the try catch feature.
https://github.com/williamFalcon/SwiftTryCatch
Use like:
SwiftTryCatch.try({ () -> Void in
//try something
}, catch: { (error) -> Void in
//handle error
}, finally: { () -> Void in
//close resources
})
As Guilherme Torres Castro said, in Swift 2.0, try, catch, do can be used in the programming.
For example, In CoreData fetch data method, instead of put &error as a parameter into the managedContext.executeFetchRequest(fetchRequest, error: &error), now we only need to use use managedContext.executeFetchRequest(fetchRequest) and then handle the error with try, catch (Apple Document Link)
do {
let fetchedResults = try managedContext.executeFetchRequest(fetchRequest) as? [NSManagedObject]
if let results = fetchedResults{
people = results
}
} catch {
print("Could not fetch")
}
If you have already download the xcode7 Beta. Try to search throwing errors in Documentations and API Reference and choose the first showing result, it gives a basic idea what can be done for this new syntax. However, fully documentation is not post for many APIs yet.
More fancy Error Handling techniques can be found in
What's New in Swift (2015 Session 106 28m30s)
This is an update answer for swift 2.0. I am looking forward feature rich Error handling model like in java. Finally, they announced the good news. here
Error handling model: The new error handling model in Swift 2.0 will
instantly feel natural, with familiar try, throw, and catch keywords.
Best of all, it was designed to work perfectly with the Apple SDKs and
NSError. In fact, NSError conforms to a Swift’s ErrorType. You’ll
definitely want to watch the WWDC session on What’s New in Swift to
hear more about it.
e.g :
func loadData() throws { }
func test() {
do {
try loadData()
} catch {
print(error)
}}
Starting with Swift 2, as others have already mentioned, error handling is best accomplished through the use of do/try/catch and ErrorType enums. This works quite well for synchronous methods, but a little cleverness is required for asynchronous error handling.
This article has a great approach to this problem:
https://jeremywsherman.com/blog/2015/06/17/using-swift-throws-with-completion-callbacks/
To summarize:
// create a typealias used in completion blocks, for cleaner code
typealias LoadDataResult = () throws -> NSData
// notice the reference to the typealias in the completionHandler
func loadData(someID: String, completionHandler: LoadDataResult -> Void)
{
completionHandler()
}
then, the call to the above method would be as follows:
self.loadData("someString",
completionHandler:
{ result: LoadDataResult in
do
{
let data = try result()
// success - go ahead and work with the data
}
catch
{
// failure - look at the error code and handle accordingly
}
})
This seems a bit cleaner than having a separate errorHandler callback passed to the asynchronous function, which was how this would be handled prior to Swift 2.
Error handling is a new feature of Swift 2.0. It uses the try, throw and catch keywords.
See the Apple Swift 2.0 announcement on the official Apple Swift blog
Nice and simple lib to handle exception:
TryCatchFinally-Swift
Like a few others it wraps around the objective C exception features.
Use it like this:
try {
println(" try")
}.catch { e in
println(" catch")
}.finally {
println(" finally")
}
enum CheckValidAge : Error{
case overrage
case underage
}
func checkValidAgeForGovernmentJob(age:Int)throws -> Bool{
if age < 18{
throw CheckValidAge.underage
}else if age > 25{
throw CheckValidAge.overrage
}else{
return true
}
}
do {
try checkValidAgeForGovernmentJob(age: 26)
print("You are valid for government job ")
}catch CheckValidAge.underage{
print("You are underage for government job ")
}catch CheckValidAge.overrage{
print("You are overrage for government job ")
}
Change age in try checkValidAgeForGovernmentJob(age: 26)
Out Put
You are overrage for government job
What I have seen is that because of the nature of the device you don't want to be throwing a bunch of cryptic error handling messages at the user. That is why most functions return optional values then you just code to ignore the optional. If a function comes back nil meaning it failed you can pop a message or whatever.