A comprehensive analysis of Swift 2 error handling techniques

A comprehensive analysis of Swift 2 error handling techniques

Error handling has been supported since Swift 1; it was inspired by Objective-C. Swift 2’s improvements make it even easier and more straightforward to handle unexpected states and conditions in your app.

Just like other common programming languages, there are different types of error handling techniques in Swift, depending on the type of error encountered and the overall structure of your application.

This tutorial will walk you through concrete examples of how to most effectively handle common error situations. You’ll also see how to upgrade error handling in projects written in earlier versions of Swift. The final article will focus on error handling techniques that may be available in future versions of Swift!

[Note] This tutorial assumes that you are familiar with using Swift 2 syntax, especially enumerations and optionals. If you need a refresher on these concepts, I recommend reading Greg Heo's article "What's New in Swift 2".

Next, let's start the journey of learning Swift 2 error handling technology. You will definitely experience different fun!

Introduction

This tutorial provides two beginner cases for everyone to learn and use. Their download addresses are: https://cdn4.raywenderlich.com/wp-content/uploads/2016/04/Avoiding-Errors-with-nil-Starter.playground-1.zip and https://cdn4.raywenderlich.com/wp-content/uploads/2016/04/Avoiding-Errors-with-Custom-Handling-Starter.playground-1.zip.

To follow along with the example code in this article, download these two sample projects.

First, use Xcode to open the first beginner case Errors with nil. Read through the code in the project and you will see several classes, structures, and enumeration definitions.

Please note the following parts of the code:

  1. protocol MagicalTutorialObject {
  2.  
  3. var avatar: String { get }
  4.  
  5. }

This protocol applies to all classes and structures used in this tutorial. It is used to provide a visual description of information about each object in the project - printed to the console.

  1. enum MagicWords: String {
  2.  
  3. case Abracadbra = "abracadabra"  
  4.  
  5. case Alakazam = "alakazam"  
  6.  
  7. case HocusPocus = "hocus pocus"  
  8.  
  9. case PrestoChango = "presto chango"  
  10.  
  11. }

This enum defines some of the spells that can be used to create a magic spell.

  1. struct Spell: MagicalTutorialObject {
  2.  
  3. var magicWords: MagicWords = .Abracadbra
  4.  
  5. var avatar = "*"  
  6.  
  7. }

This is a basic building block of magic. By default, it is initialized with the spell value "Abracadbra".

Now that you're familiar with some of the basic terminology in the examples we're going to cover, it's time to start working some magic.

Why you should care about error handling

Error handling is the art of failing gracefully.

— From Chapter 12 (“Error Handling”) of the book Swift Apprentice

Good error handling helps enhance the experience for both end users and software maintainers by making it easier to pinpoint problems, their causes, and their possible consequences. The more specific the error handling in your code, the easier it is to diagnose the problem. Error handling also allows the system to throw errors in an appropriate manner so as not to frustrate or disrupt the user.

But not all errors need to be handled. Language features themselves may allow you to completely avoid certain classes of errors if the programmer doesn't handle them. As a general rule, if you can avoid the possibility of errors, then follow that design approach. If you can't avoid potential error conditions, then explicitly handling errors is your best option.

Avoiding Swift errors with nil

Because Swift provides elegant optionals handling, you can completely avoid error conditions where a value is expected but not provided. As a smart programmer, you can take advantage of this feature by intentionally returning nil from an error condition. This approach is best used when you reach an error state but you don't have to take any action; that is, you choose not to take action instead of taking emergency action.

Two classic examples of using nil to avoid errors in Swift are failable initializers and guard statements.

Failable initializer

Failable initializers prevent the creation of an object unless sufficient information is provided. Prior to Swift 2 (and in other languages), this functionality was often implemented using the factory method pattern.

An example of Swift using this pattern can be seen in the createWithMagicWords method:

  1. static func createWithMagicWords(words: String) -> Spell? {
  2.  
  3. if let incantation = MagicWords(rawValue: words) {
  4.  
  5. var spell = Spell()
  6.  
  7. spell.magicWords = incantation
  8.  
  9. return spell
  10.  
  11. }
  12.  
  13. else {
  14.  
  15. return nil
  16.  
  17. }
  18.  
  19. }

The above initializer attempts to create a spell using the supplied spell; but if the words are not magic words, it returns nil instead.

You can see this method in action by looking at the magic creation at the top of the tutorial code:

You'll notice that first successfully creates a magic spell using the spell "abracadabra", while the spell "ascendio" does not produce this effect and returns the value of second as nil.

Factory methods are an old-school style of programming. Actually, there is a better way to achieve the same thing in Swift. To do this, you just need to use a failable initializer instead of a factory method to update the spell expansion.

So, you can delete the createWithMagicWords(_:) method and replace it with the following:

  1. init?(words: String) {
  2.  
  3. if let incantation = MagicWords(rawValue: words) {
  4.  
  5. self.magicWords = incantation
  6.  
  7. }
  8.  
  9. else {
  10.  
  11. return nil
  12.  
  13. }
  14.  
  15. }

Here, you've simplified the code by not explicitly creating and returning the spell object.

The line that assigns values ​​to first and second will now throw a compile-time error:

  1. let first = Spell.createWithMagicWords( "abracadabra" )
  2.  
  3. let second = Spell.createWithMagicWords( "ascendio" )

You need to change these statements to use the new initializer. To do this, just replace the above line of code with the following:

  1. let first = Spell(words: "abracadabra" )
  2.  
  3. let second = Spell(words: "ascendio" )

After this, all errors should be fixed and the example project should compile without errors. Your code is much cleaner after this modification - but you can do better than this!

Guard Statement

A guard is a quick way to assert that something is true. Then, if the check fails, you can execute a block of code that you designed beforehand.

Guards were introduced in Swift 2 and are commonly used to handle errors in a way that bubbles up through the call stack, and eventually the error will be handled. Guard statements allow for premature exit from a function or method; this makes it clearer to the programmer what conditions need to exist for the remaining processing logic to run.

To further simplify the magic failable constructor, let's modify the above code using the guard method:

  1. init?(words: String) {
  2.  
  3. guard let incantation = MagicWords(rawValue: words) else {
  4.  
  5. return nil
  6.  
  7. }
  8.  
  9. self.magicWords = incantation
  10.  
  11. }

With this change, there is no need to have a separate else clause on a separate line; also, the failure case is more obvious because it is now at the top of the initializer.

Note that the values ​​of the first and second magic constants have not changed, but the code has become more concise.

Avoiding Errors with Custom Processors

We have avoided some errors by simplifying the magic of failable initializers and using nil in a clever way. Next, let's handle some more complex errors.

To learn the following error handling techniques, open the project Avoiding-Errors-with-Custom-Handling-Starter.playground-1.

Please note the following features in the code:

  1. struct Spell: MagicalTutorialObject {
  2.  
  3. var magicWords: MagicWords = .Abracadbra
  4.  
  5. var avatar = "*"  
  6.  
  7. init?(words: String) {
  8.  
  9. guard let incantation = MagicWords(rawValue: words) else {
  10.  
  11. return nil
  12.  
  13. }
  14.  
  15. self.magicWords = incantation
  16.  
  17. }
  18.  
  19. init?(magicWords: MagicWords) {
  20.  
  21. self.magicWords = magicWords
  22.  
  23. }
  24.  
  25. }

Here is the definition of the Spell constructor, which we have modified briefly to match the work you completed in the first part of this tutorial. Also, note the use of the MagicalTutorialObject protocol and the introduction of another failable initializer for convenience.

  1. protocol Familiar: MagicalTutorialObject {
  2.  
  3. var noise: String { get }
  4.  
  5. var name : String? { get set }
  6.  
  7. init()
  8.  
  9. init( name : String?)
  10.  
  11. }

The Familiar protocol here will be applicable to various pets (such as bats and toads, which are raised and driven by witches), which has been used in the second sample project of this article.

Next, let’s look at the definition of a witch:

  1. struct Witch: MagicalBeing {
  2.  
  3. var avatar = "*"  
  4.  
  5. var name : String?
  6.  
  7. var familiar: Familiar?
  8.  
  9. var spells: [Spell] = []
  10.  
  11. var hat: Hat?
  12.  
  13. init( name : String?, familiar: Familiar?) {
  14.  
  15. self.name = name  
  16.  
  17. self.familiar = familiar
  18.  
  19. if let s = Spell(magicWords: .PrestoChango) {
  20.  
  21. self.spells = [s]
  22.  
  23. }
  24.  
  25. }
  26.  
  27. init( name : String?, familiar: Familiar?, hat: Hat?) {
  28.  
  29. self.init( name : name , familiar: familiar)
  30.  
  31. self.hat = hat
  32.  
  33. }
  34.  
  35. func turnFamiliarIntoToad() -> Toad {
  36.  
  37. if let hat = hat {
  38.  
  39. if hat.isMagical { // When have you ever seen a Witch perform a spell without her magical hat on ? :]
  40.  
  41. if let familiar = familiar { // Check if witch has a familiar
  42.  
  43. if let toad = familiar as ? Toad { // Check if familiar is already a toad - no magic required
  44.  
  45. return toad
  46.  
  47. } else {
  48.  
  49. if hasSpellOfType(.PrestoChango) {
  50.  
  51. if let name = familiar. name {
  52.  
  53. return Toad( name : name )
  54.  
  55. }
  56.  
  57. }
  58.  
  59. }
  60.  
  61. }
  62.  
  63. }
  64.  
  65. }
  66.  
  67. return Toad( name : "New Toad" ) // This is an entirely new Toad.
  68.  
  69. }
  70.  
  71. func hasSpellOfType(type: MagicWords) -> Bool { // Check if witch currently has appropriate spell in their spellbook
  72.  
  73. return spells. contains { $0.magicWords == type }
  74.  
  75. }
  76.  
  77. }

Now, let's briefly summarize:

Initialize witch: use name and familiar parameters, or add a hat parameter.

A witch knows a finite number of spells; these are stored in spells, which is an array of magic objects.

Every witch seems to have a hobby of turning her pet into a toad by using the PrestoChango spell in the turnFamiliarIntoToad() method.

Note the number of indentation characters in the turnFamiliarIntoToad() method above. Also, note that if anything goes wrong in this method, a brand new Toad will be returned. This may seem confusing (and a bit wrong!). In the next section, you'll understand this code better by using custom error handling techniques.

Use refactoring techniques

In the above turnFamiliarIntoToad() method, multiple levels of nested statements are used to control the program flow, and reading such nested code is quite difficult.

As you saw earlier, the use of guard statements and multiple optional bindings helps to clean up the above code pyramid. However, utilizing the do-catch mechanism can completely eliminate this problem by decoupling control flow from error state handling.

The do-catch mechanism usually appears before and after the following keywords:

throws

do

catch

try

defer

ErrorType

To see these keywords in action, you can throw several custom errors. First, you define the statements you want to handle, and you can use an enumeration to list all the possible errors.

Then, add the following code to your sample project (the one associated with the playground content) above the witch definition:

  1. enum ChangoSpellError: ErrorType {
  2.  
  3. case HatMissingOrNotMagical
  4.  
  5. case NoFamiliar
  6.  
  7. case FamiliarAlreadyAToad
  8.  
  9. case SpellFailed(reason: String)
  10.  
  11. case SpellNotKnownToWitch
  12.  
  13. }

Please note two points about ChangoSpellError:

It conforms to the ErrorType protocol, which is a requirement when defining errors in the Swift language.

In the case of SpellFailed, a custom reason can be specified for the spell failure via an associated value.

Next, add the throws keyword to the method signature to indicate that errors may occur when calling this method:

  1. func turnFamiliarIntoToad() throws -> Toad {

Then, update the MagicalBeing protocol:

  1. protocol MagicalBeing: MagicalTutorialObject {
  2.  
  3. var name : String? { get set }
  4.  
  5. var spells: [Spell] { get set }
  6.  
  7. func turnFamiliarIntoToad() throws -> Toad
  8.  
  9. }

Now that you have listed all the error conditions, you can refactor the turnFamiliarIntoToad() method.

Handle hat related errors

First, modify the following statement to ensure that the witch is wearing her all-important hat, by changing the statement:

  1. if let hat = hat {

Modified to:

  1. guard let hat = hat else {
  2.  
  3. throw ChangoSpellError.HatMissingOrNotMagical
  4.  
  5. }

【Note】Don't forget to delete the } symbol at the bottom of the method; otherwise the project will have a compilation error!

The next line contains a Boolean check, also related to hats:

  1. if hat.isMagical {

You could choose to add a separate guard statement to perform this check; however, it is clearer to have a group of checks in one line of code. Therefore, you could change the first guard statement to look like this:

  1. guard let hat = hat where hat.isMagical else {
  2.  
  3. throw ChangoSpellError.HatMissingOrNotMagical
  4.  
  5. }

Now, with this modification, the if hat.isMagical { check part is also eliminated.

In the next section, you'll continue unpacking that dreaded pyramid of conditionals.

Handling Familiar-related errors

[Translator's note] In this article, I translated "familiar" as "pet", referring to the various small animals raised and driven by the witches mentioned above.

Next, let's modify the statement that checks whether the witch contains familiar, that is, change the statement:

  1. if let familiar = familiar {

Modified to throw an error from another guard statement:

  1. guard let familiar = familiar else {
  2.  
  3. throw ChangoSpellError.NoFamiliar
  4.  
  5. }

For now, let's ignore any errors that occur, as your upcoming code changes will make them go away.

Handling Toad-related errors

In the next line, you need to return the existing toad if the witch wants to cast the turnFamiliarIntoToad() spell on the unsuspecting amphibian, but using an explicit error will better inform her of the mistake she's made. To do this, replace the following:

  1. if let toad = familiar as ? Toad {
  2.  
  3. return toad
  4.  
  5. }

Modify the statement to the following:

  1. if familiar is Toad {
  2.  
  3. throw ChangoSpellError.FamiliarAlreadyAToad
  4.  
  5. }

Note that as? is changed to is here, which makes it easier to check for conformance to the protocol without necessarily using the result. The keyword is can also be used for more general types of comparisons. If you are interested in learning more about is and as, I recommend reading the Type Casting section of The Swift Programming Language on Apple's official website (https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html).

Using the above technique, we can move the contents of the else clause outside the else clause, thereby deleting the else clause.

Handling magic related errors

Finally, the call to the hasSpellOfType(type:) method ensures that the witch has the appropriate spell in her spellbook. To do this, we replace the following code:

  1. if hasSpellOfType(.PrestoChango) {
  2.  
  3. if let toad = f as ? Toad {
  4.  
  5. return toad
  6.  
  7. }
  8.  
  9. }

Change the code to the following:

  1. guard hasSpellOfType(.PrestoChango) else {
  2.  
  3. throw ChangoSpellError.SpellNotKnownToWitch
  4.  
  5. }
  6.  
  7. guard let name = familiar. name   else {
  8.  
  9. let reason = "Familiar doesn't have a name."  
  10.  
  11. throw ChangoSpellError.SpellFailed(reason: reason)
  12.  
  13. }
  14.  
  15. return Toad( name : name )

Now, you can delete the last line of code without any problem, that is, delete the following line:

  1. return Toad( name : "New Toad" )

Now you have the following concise method. I've included a few additional points to further explain what the code does:

  1. func turnFamiliarIntoToad() throws -> Toad {
  2.  
  3. // When have you ever seen a Witch perform a spell without her magical hat on ? :]
  4.  
  5. guard let hat = hat where hat.isMagical else {
  6.  
  7. throw ChangoSpellError.HatMissingOrNotMagical
  8.  
  9. }
  10.  
  11. // Check if witch has a familiar
  12.  
  13. guard let familiar = familiar else {
  14.  
  15. throw ChangoSpellError.NoFamiliar
  16.  
  17. }
  18.  
  19. // Check if familiar is already a toad - if so, why are you casting the spell?
  20.  
  21. if familiar is Toad {
  22.  
  23. throw ChangoSpellError.FamiliarAlreadyAToad
  24.  
  25. }
  26.  
  27. guard hasSpellOfType(.PrestoChango) else {
  28.  
  29. throw ChangoSpellError.SpellNotKnownToWitch
  30.  
  31. }
  32.  
  33. // Check if the familiar has a name  
  34.  
  35. guard let name = familiar. name   else {
  36.  
  37. let reason = "Familiar doesn't have a name."  
  38.  
  39. throw ChangoSpellError.SpellFailed(reason: reason)
  40.  
  41. }
  42.  
  43. // It all checks out ! Return a toad with the same name   as the witch's familiar
  44.  
  45. return Toad( name : name )
  46.  
  47. }

Previously, returning an optional from the turnFamiliarIntoToad() method simply said "something went wrong while applying this magic." However, using custom errors like this allows you to more clearly indicate the error state and react to it accordingly.

Other places suitable for custom errors

Now that you have established that methods can throw custom Swift errors, you need to further handle these errors. The standard mechanism for doing this is called a do-catch statement, which is similar to the try-catch mechanism used in other languages ​​such as Java.

Now, add the following code to the top of your project file:

  1. func exampleOne() {
  2.  
  3. print( "" ) // Add an empty line in the debug area
  4.  
  5. // 1
  6.  
  7. let salem = Cat( name : "Salem Saberhagen" )
  8.  
  9. salem.speak()
  10.  
  11. // 2
  12.  
  13. let witchOne = Witch( name : "Sabrina" , familiar: salem)
  14.  
  15. do {
  16.  
  17. // 3
  18.  
  19. try witchOne.turnFamiliarIntoToad()
  20.  
  21. }
  22.  
  23. // 4
  24.  
  25. catch let error as ChangoSpellError {
  26.  
  27. handleSpellError(error)
  28.  
  29. }
  30.  
  31. // 5
  32.  
  33. catch {
  34.  
  35. print( "Something went wrong, are you feeling OK?" )
  36.  
  37. }
  38.  
  39. }

The following are the tasks implemented by this function:

1. Create the witch's pet, a cat named Salem.

2. Create a witch named Sabrina.

3. Try to turn the cat into a toad.

4. Catch a ChangoSpellError error and handle the error appropriately.

5. ***, catch all other errors and print a friendly message.

After adding the above, you will see a compiler error. Now, let's fix this.

The handleSpellError() method has not yet been defined, so add the following code above the previous exampleOne() function definition:

  1. func handleSpellError(error: ChangoSpellError) {
  2.  
  3. let prefix = "Spell Failed."  
  4.  
  5. switch error {
  6.  
  7. case .HatMissingOrNotMagical:
  8.  
  9. print( "\(prefix) Did you forget your hat, or does it need its batteries charged?" )
  10.  
  11. case .FamiliarAlreadyAToad:
  12.  
  13. print( "\(prefix) Why are you trying to change a Toad into a Toad?" )
  14.  
  15. default :
  16.  
  17. print(prefix)
  18.  
  19. }
  20.  
  21. }

***, add the following to the bottom of the file and run the project code:

  1. exampleOne()

You should see the debug console output show something like:

Catching Errors

Given below is a brief summary of each Swift 2 error handling technique used in the above code snippet.

Catch Clause

You can use pattern matching in Swift to handle specific errors or to handle several error types together.

The above code shows you several ways to capture errors: one is to capture specific ChangoSpell errors, and the other is to handle the remaining errors together.

The try clause

You can use the try clause in conjunction with the do-catch clause to clearly indicate which lines or sections of code may throw an error.

You can use the try command in several different ways, the one used above is one of the following:

try - Standard usage of do-catch statements is clear and direct, which is what is used in the code above.

try? - essentially handles errors by ignoring them; if an error is thrown, the result of the statement will be nil.

try! - This clause emphasizes a desired outcome: in theory, a statement can throw an error; but in practice, such an error condition will never occur. The try! clause can be used in programming code such as loading a file, where you are sure that some required media exists. Use this clause with caution.

Now, let's look at the use of the try? clause in detail. You can cut and paste the following code at the bottom of your file above:

  1. func exampleTwo() {
  2.  
  3. print( "" ) // Add an empty line in the debug area
  4.  
  5. let toad = Toad( name : "Mr. Toad" )
  6.  
  7. toad.speak()
  8.  
  9. let hat = Hat()
  10.  
  11. let witchTwo = Witch( name : "Elphaba" , familiar: toad, hat: hat)
  12.  
  13. let newToad = try? witchTwo.turnFamiliarIntoToad()
  14.  
  15. if newToad != nil { // Same logic as : if let _ = newToad
  16.  
  17. print( "Successfully changed familiar into toad." )
  18.  
  19. }
  20.  
  21. else {
  22.  
  23. print( "Spell failed." )
  24.  
  25. }
  26.  
  27. }

Notice the difference in exampleOne in the code above. Here, you don't have to worry about outputting a specific error, but you still want to capture the fact that an error occurred. No Toad pet is created here; therefore, the value of newToad is nil.

Propagating Errors

throws

If a function or method throws an error, the throws keyword is required in Swift. Thrown errors are automatically propagated up the call stack, but it is generally considered a bad practice to let errors propagate too far from where they originated. Significant propagation throughout a code base that increases the likelihood of errors escaping proper error handling opportunities; for this reason, the throws keyword ensures that propagation is documented in the code and easily understood by the programmer.

rethrows

All the examples you've seen so far have used throws, but what about rethrows?

rethrows tells the compiler that this function should throw an error only if its arguments throw an error. Here is a straightforward example (no need to add it to the previous file):

  1. func doSomethingMagical(magicalOperation: () throws -> MagicalResult) rethrows -> MagicalResult {
  2.  
  3. return try magicalOperation()
  4.  
  5. }

Here, the doSomethingMagical(_:) method throws an error only if the magicalOperation parameter provided to the function throws an error. If successful, it returns a MagicalResult value.

Manipulating error handling behavior

defer

While automatic propagation works well in most cases, there are situations where you may want to further control the behavior of your application as the error propagates up the call stack.

The defer statement provides a mechanism to allow a program to perform "cleanup" work whenever the current scope is exited, such as when a method or function returns. It is used to manage resources that need to be cleaned up - regardless of whether the action was successful or not. As such, it is particularly useful in error handling contexts.

[Translator's note] It is recommended that you understand this in conjunction with the finally clause in languages ​​such as C++ and Java.

To understand the use of defer, add the following method to the Witch structure:

  1. func speak() {
  2.  
  3. defer {
  4.  
  5. print( "*cackles*" )
  6.  
  7. }
  8.  
  9. print( "Hello my pretties." )
  10.  
  11. }

Then, add the following code to the bottom of the previous file:

  1. func exampleThree() {
  2.  
  3. print( "" ) // Add an empty line in the debug area
  4.  
  5. let witchThree = Witch( name : "Hermione" , familiar: nil, hat: nil)
  6.  
  7. witchThree.speak()
  8.  
  9. }
  10.  
  11. exampleThree()

In the debug console, you should see the witch giggle after she finishes saying all of her words.

Interestingly, defer statements are executed in the reverse order of the order in which they are programmed.

Now, let's add another defer to the speak() statement so that the witch can chuckle. Then, the witch chuckles after she finishes speaking:

  1. func speak() {
  2.  
  3. defer {
  4.  
  5. print( "*cackles*" )
  6.  
  7. }
  8.  
  9. defer {
  10.  
  11. print( "*screeches*" )
  12.  
  13. }
  14.  
  15. print( "Hello my pretties." )
  16.  
  17. }

Did you notice the order of output in the debug console? That’s exactly what defer statements are capable of!

More interesting things about errors

The Swift statements provided in this article bring them in line with many other popular languages, separating Swift from Objective-C's NSError-based approach to error handling. While most Objective-C errors are interpreted literally, the static analyzer in the compiler does a great job of helping you determine which errors you need and when you need to catch them.

Although do-catch related support statements also have a large overhead in other languages; however, in Swift language, they are basically treated like any other statement. This will ensure their effectiveness and high efficiency.

However, just because you can create custom errors and throw them, don't use them arbitrarily. In this regard, it is recommended that you first establish some guidelines for the projects you develop: when to throw and catch errors. In this regard, I make the following suggestions:

Make sure error types are clearly named throughout your codebase.

Use Optionals when a single error state is desired.

Use custom error handling techniques when more than one error condition exists.

Do not allow errors to propagate too far from their source.

The future of Swift error handling

Several advanced error handling ideas are frequently discussed in various Swift forums. One of the most talked about concepts is the problem of untyped propagation.

"...we believe we can extend our current model to support untyped propagation of common errors. How this can be done well - especially without completely sacrificing code size and performance - will be the subject of a lot of in-depth research. We anticipate that there will be no problem implementing this approach in Swift 2.0." (from "Swift 2.x Error Handling")

Whether or not you enjoy mainstream error handling in Swift 3, or are happy with what exists today, it’s good to know that clean error handling techniques are being actively discussed and improved everywhere as the language continues to evolve.

summary

You can download the completed playground example project for further discussion at https://cdn2.raywenderlich.com/wp-content/uploads/2016/04/Magical-Error-Handling-in-Swift.zip.

If you’re eager to see what’s new in Swift 3, I recommend checking out Swift Language Proposals.

Hopefully, by now you’ve really gotten into error handling techniques in Swift.

<<:  iOS vs Android: Will Apple repeat the mistakes of its PC business?

>>:  A preliminary exploration of the application prospects of VR technology industry: technological innovation defines a wonderful future

Recommend

How to use social marketing to promote APP in the early stage of launch?

Faced with a fiercely competitive market, social ...

How to write an excellent event planning and implementation plan?

Google is in talks with Chinese internet company N...

Douyin agent operation: six advantages of Douyin live streaming

Live streaming on Douyin has become one of the im...

Toutiao Nanny-level Marketing Promotion Strategy

Recently, when I was providing marketing consulti...

Loongson: A pure Chinese chip with a new lease of life

Speaking of chips, they have always been the focu...

How to keep her energy job? She looks for oil in shale cracks

Your browser does not support the video tag Oil i...

618 Marketing Promotion Plan Creation Guide, 1 Step to Get It Done

Every marketing plan with soul must not be a pile...