Nested Types

To nest a type within another type, write its definition within the outer braces of the type it supports. Types can be nested to as many levels as are required.

Nested Types in Action

struct BlackjackCard {
   
   // nested Suit enumeration
   enum Suit: Character {
       case spades = “♠”, hearts = “♡”, diamonds = “♢”, clubs = “♣”
   }
   
   // nested Rank enumeration
   enum Rank: Int {
       case two = 2, three, four, five, six, seven, eight, nine, ten
       case jack, queen, king, ace
       struct Values {
           let first: Int, second: Int?
       }
       var values: Values {
           switch self {
           case .ace:
               return Values(first: 1, second: 11)
           case .jack, .queen, .king:
               return Values(first: 10, second: nil)
           default:
               return Values(first: self.rawValue, second: nil)
           }
       }
   }
   
   // BlackjackCard properties and methods
   let rank: Rank, suit: Suit
   var description: String {
       var output = “suit is (suit.rawValue),”
       output += “ value is (rank.values.first)”
       if let second = rank.values.second {
           output += “ or (second)”
       }
       return output
   }
}

let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades)
print(“theAceOfSpades: (theAceOfSpades.description)”)
// Prints “theAceOfSpades: suit is ♠, value is 1 or 11”

let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
// heartsSymbol is “♡”

Nested Types

To nest a type within another type, write its definition within the outer braces of the type it supports. Types can be nested to as many levels as are required.

Nested Types in Action

struct BlackjackCard {
   
   // nested Suit enumeration
   enum Suit: Character {
       case spades = “♠”, hearts = “♡”, diamonds = “♢”, clubs = “♣”
   }
   
   // nested Rank enumeration
   enum Rank: Int {
       case two = 2, three, four, five, six, seven, eight, nine, ten
       case jack, queen, king, ace
       struct Values {
           let first: Int, second: Int?
       }
       var values: Values {
           switch self {
           case .ace:
               return Values(first: 1, second: 11)
           case .jack, .queen, .king:
               return Values(first: 10, second: nil)
           default:
               return Values(first: self.rawValue, second: nil)
           }
       }
   }
   
   // BlackjackCard properties and methods
   let rank: Rank, suit: Suit
   var description: String {
       var output = “suit is (suit.rawValue),”
       output += “ value is (rank.values.first)”
       if let second = rank.values.second {
           output += “ or (second)”
       }
       return output
   }
}

let theAceOfSpades = BlackjackCard(rank: .ace, suit: .spades)
print(“theAceOfSpades: (theAceOfSpades.description)”)
// Prints “theAceOfSpades: suit is ♠, value is 1 or 11”

let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
// heartsSymbol is “♡”

Type Casting

Type casting is a way to check the type of an instance, or to treat that instance as a different superclass or subclass from somewhere else in its own class hierarchy.

Type casting in Swift is implemented with the is and as operators. These two operators provide a simple and expressive way to check the type of a value or cast a value to a different type.

Defining a Class Hierarchy for Type Casting

class MediaItem {
   var name: String
   init(name: String) {
       self.name = name
   }
}

class Movie: MediaItem {
   var director: String
   init(name: String, director: String) {
       self.director = director
       super.init(name: name)
   }
}
class Song: MediaItem {
   var artist: String
   init(name: String, artist: String) {
       self.artist = artist
       super.init(name: name)
   }
}

The final snippet creates a constant array called library, which contains two Movie instances and three Song instances. The type of the library array is inferred by initializing it with the contents of an array literal. Swift’s type checker is able to deduce that Movie and Song have a common superclass of MediaItem, and so it infers a type of [MediaItem] for the library array:

let library = [
   Movie(name: “Casablanca”, director: “Michael Curtiz”),
   Song(name: “Blue Suede Shoes”, artist: “Elvis Presley”),
   Movie(name: “Citizen Kane”, director: “Orson Welles”),
   Song(name: “The One And Only”, artist: “Chesney Hawkes”),
   Song(name: “Never Gonna Give You Up”, artist: “Rick Astley”)
]
// the type of “library” is inferred to be [MediaItem]

The items stored in library are still Movie and Song instances behind the scenes. However, if you iterate over the contents of this array, the items you receive back are typed as MediaItem, and not as Movie or Song. In order to work with them as their native type, you need to check their type, or downcast them to a different type, as described below.

Checking Type

Use the type check operator (is) to check whether an instance is of a certain subclass type. The type check operator returns true if the instance is of that subclass type and false if it is not.

var movieCount = 0
var songCount = 0
for item in library {
   if item is Movie {
       movieCount += 1
   } else if item is Song {
       songCount += 1
   }
}
print(“Media library contains (movieCount) movies and (songCount) songs”)
// Prints “Media library contains 2 movies and 3 songs”

Downcasting

A constant or variable of a certain class type may actually refer to an instance of a subclass behind the scenes. Where you believe this is the case, you can try to downcast to the subclass type with a type cast operator (as? or as!).

Because downcasting can fail, the type cast operator comes in two different forms. The conditional form, as?, returns an optional value of the type you are trying to downcast to. The forced form, as!, attempts the downcast and force-unwraps the result as a single compound action.

Use the conditional form of the type cast operator (as?) when you are not sure if the downcast will succeed. This form of the operator will always return an optional value, and the value will be nil if the downcast was not possible. This enables you to check for a successful downcast.

Use the forced form of the type cast operator (as!) only when you are sure that the downcast will always succeed. This form of the operator will trigger a runtime error if you try to downcast to an incorrect class type.

for item in library {
   if let movie = item as? Movie {
       print(“Movie: (movie.name), dir. (movie.director)”)
   } else if let song = item as? Song {
       print(“Song: (song.name), by (song.artist)”)
   }
}
// Movie: Casablanca, dir. Michael Curtiz
// Song: Blue Suede Shoes, by Elvis Presley
// Movie: Citizen Kane, dir. Orson Welles
// Song: The One And Only, by Chesney Hawkes
// Song: Never Gonna Give You Up, by Rick Astley

Type Casting for Any and AnyObject

Swift provides two special types for working with nonspecific types:

  • Any can represent an instance of any type at all, including function types.
  • AnyObject can represent an instance of any class type.

var things = [Any]()
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append(“hello”)
things.append((3.0, 5.0))
things.append(Movie(name: “Ghostbusters”, director: “Ivan Reitman”))
things.append({ (name: String) -> String in “Hello, (name)” })

(참고사항 if case https://stackoverflow.com/a/37888514)

(참고사항

Optional Pattern

https://stackoverflow.com/a/37738664)

for thing in things {
   switch thing {
   case 0 as Int:
       print(“zero as an Int”)
   case 0 as Double:
       print(“zero as a Double”)
   case let someInt as Int:
       print(“an integer value of (someInt)”)
   case let someDouble as Double where someDouble > 0:
       print(“a positive double value of (someDouble)”)
   case is Double:
       print(“some other double value that I don’t want to print”)
   case let someString as String:
       print(“a string value of “(someString)””)
   case let (x, y) as (Double, Double):
       print(“an (x, y) point at (x), (y)”)
   case let movie as Movie:
       print(“a movie called (movie.name), dir. (movie.director)”)
   case let stringConverter as (String) -> String:
       print(stringConverter(“Michael”))
   default:
       print(“something else”)
   }
}
// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of “hello”
// an (x, y) point at 3.0, 5.0
// a movie called Ghostbusters, dir. Ivan Reitman
// Hello, Michael

NOTE

The Any type represents values of any type, including optional types. Swift gives you a warning if you use an optional value where a value of type Any is expected. If you really do need to use an optional value as an Any value, you can use the as operator to explicitly cast the optional to Any, as shown below.

let optionalNumber: Int? = 3
things.append(optionalNumber)        // Warning
things.append(optionalNumber as Any) // No warning

Type Casting

Type casting is a way to check the type of an instance, or to treat that instance as a different superclass or subclass from somewhere else in its own class hierarchy.

Type casting in Swift is implemented with the is and as operators. These two operators provide a simple and expressive way to check the type of a value or cast a value to a different type.

Defining a Class Hierarchy for Type Casting

class MediaItem {
   var name: String
   init(name: String) {
       self.name = name
   }
}

class Movie: MediaItem {
   var director: String
   init(name: String, director: String) {
       self.director = director
       super.init(name: name)
   }
}
class Song: MediaItem {
   var artist: String
   init(name: String, artist: String) {
       self.artist = artist
       super.init(name: name)
   }
}

The final snippet creates a constant array called library, which contains two Movie instances and three Song instances. The type of the library array is inferred by initializing it with the contents of an array literal. Swift’s type checker is able to deduce that Movie and Song have a common superclass of MediaItem, and so it infers a type of [MediaItem] for the library array:

let library = [
   Movie(name: “Casablanca”, director: “Michael Curtiz”),
   Song(name: “Blue Suede Shoes”, artist: “Elvis Presley”),
   Movie(name: “Citizen Kane”, director: “Orson Welles”),
   Song(name: “The One And Only”, artist: “Chesney Hawkes”),
   Song(name: “Never Gonna Give You Up”, artist: “Rick Astley”)
]
// the type of “library” is inferred to be [MediaItem]

The items stored in library are still Movie and Song instances behind the scenes. However, if you iterate over the contents of this array, the items you receive back are typed as MediaItem, and not as Movie or Song. In order to work with them as their native type, you need to check their type, or downcast them to a different type, as described below.

Checking Type

Use the type check operator (is) to check whether an instance is of a certain subclass type. The type check operator returns true if the instance is of that subclass type and false if it is not.

var movieCount = 0
var songCount = 0
for item in library {
   if item is Movie {
       movieCount += 1
   } else if item is Song {
       songCount += 1
   }
}
print(“Media library contains (movieCount) movies and (songCount) songs”)
// Prints “Media library contains 2 movies and 3 songs”

Downcasting

A constant or variable of a certain class type may actually refer to an instance of a subclass behind the scenes. Where you believe this is the case, you can try to downcast to the subclass type with a type cast operator (as? or as!).

Because downcasting can fail, the type cast operator comes in two different forms. The conditional form, as?, returns an optional value of the type you are trying to downcast to. The forced form, as!, attempts the downcast and force-unwraps the result as a single compound action.

Use the conditional form of the type cast operator (as?) when you are not sure if the downcast will succeed. This form of the operator will always return an optional value, and the value will be nil if the downcast was not possible. This enables you to check for a successful downcast.

Use the forced form of the type cast operator (as!) only when you are sure that the downcast will always succeed. This form of the operator will trigger a runtime error if you try to downcast to an incorrect class type.

for item in library {
   if let movie = item as? Movie {
       print(“Movie: (movie.name), dir. (movie.director)”)
   } else if let song = item as? Song {
       print(“Song: (song.name), by (song.artist)”)
   }
}
// Movie: Casablanca, dir. Michael Curtiz
// Song: Blue Suede Shoes, by Elvis Presley
// Movie: Citizen Kane, dir. Orson Welles
// Song: The One And Only, by Chesney Hawkes
// Song: Never Gonna Give You Up, by Rick Astley

Type Casting for Any and AnyObject

Swift provides two special types for working with nonspecific types:

  • Any can represent an instance of any type at all, including function types.
  • AnyObject can represent an instance of any class type.

var things = [Any]()
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append(“hello”)
things.append((3.0, 5.0))
things.append(Movie(name: “Ghostbusters”, director: “Ivan Reitman”))
things.append({ (name: String) -> String in “Hello, (name)” })

(참고사항 if case https://stackoverflow.com/a/37888514)

(참고사항

Optional Pattern

https://stackoverflow.com/a/37738664)

for thing in things {
   switch thing {
   case 0 as Int:
       print(“zero as an Int”)
   case 0 as Double:
       print(“zero as a Double”)
   case let someInt as Int:
       print(“an integer value of (someInt)”)
   case let someDouble as Double where someDouble > 0:
       print(“a positive double value of (someDouble)”)
   case is Double:
       print(“some other double value that I don’t want to print”)
   case let someString as String:
       print(“a string value of “(someString)””)
   case let (x, y) as (Double, Double):
       print(“an (x, y) point at (x), (y)”)
   case let movie as Movie:
       print(“a movie called (movie.name), dir. (movie.director)”)
   case let stringConverter as (String) -> String:
       print(stringConverter(“Michael”))
   default:
       print(“something else”)
   }
}
// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of “hello”
// an (x, y) point at 3.0, 5.0
// a movie called Ghostbusters, dir. Ivan Reitman
// Hello, Michael

NOTE

The Any type represents values of any type, including optional types. Swift gives you a warning if you use an optional value where a value of type Any is expected. If you really do need to use an optional value as an Any value, you can use the as operator to explicitly cast the optional to Any, as shown below.

let optionalNumber: Int? = 3
things.append(optionalNumber)        // Warning
things.append(optionalNumber as Any) // No warning

Where “where” may be used?

Where “where” may be used?

Error Handling


Representing and Throwing Errors

In Swift, errors are represented by values of types that conform to the Error protocol. This empty protocol indicates that a type can be used for error handling.

Swift enumerations are particularly well suited to modeling a group of related error conditions, with associated values allowing for additional information about the nature of an error to be communicated.

enum VendingMachineError: Error {
   case invalidSelection
   case insufficientFunds(coinsNeeded: Int)
   case outOfStock
}

 You use a throw statement to throw an error. 

// 여기는 throw이고 throw가 발생할수 있는함수에 표시할때는 throws

throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

Handling Errors

When an error is thrown, some surrounding piece of code must be responsible for handling the error.

There are four ways to handle errors in Swift. 1. You can propagate the error from a function to the code that calls that function,2. handle the error using a docatch statement, 3. handle the error as an optional value, or 4. assert that the error will not occur. Each approach is described in a section below.

When a function throws an error, it changes the flow of your program, so it’s important that you can quickly identify places in your code that can throw errors. To identify these places in your code, write the try keyword—or the try? or try! variation—before a piece of code that calls a function, method, or initializer that can throw an error. These keywords are described in the sections below.

Propagating Errors Using Throwing Functions

To indicate that a function, method, or initializer can throw an error, you write the throws keyword in the function’s declaration after its parameters. A function marked with throws is called a throwing function. If the function specifies a return type, you write the throws keyword before the return arrow (->).

// 여기는 throws이고 결과로 throw를 발생시킬때는 throw 

func canThrowErrors() throws -> String
func cannotThrowErrors() -> String

A throwing function propagates errors that are thrown inside of it to the scope from which it’s called.

NOTE

Only throwing functions can propagate errors. Any errors thrown inside a nonthrowing function must be handled inside the function.

(함수에 throws가 표시되있지 않은 경우 상부로 propagate할수 없고 그 안에서 처리해야 한다는 의미)

struct Item {
   var price: Int
   var count: Int
}
class VendingMachine {
   var inventory = [
       "Candy Bar": Item(price: 12, count: 7),
       "Chips": Item(price: 10, count: 4),
       "Pretzels": Item(price: 7, count: 11)
   ]
   var coinsDeposited = 0
   
   func vend(itemNamed name: String) throws {

       // 참고 guard 구문 https://ericcerney.com/swift-guard-statement/

      // guard는 조건이 만족되지 않을 경우 else{} 안의 내용이 실행된다.
       guard let item = inventory[name] else {
           throw VendingMachineError.invalidSelection
       }
       
       guard item.count > 0 else {
           throw VendingMachineError.outOfStock
       }
       
       guard item.price <= coinsDeposited else {
           throw VendingMachineError.insufficientFunds(coinsNeeded: item.price – coinsDeposited)
       }
       
       coinsDeposited -= item.price
       
       var newItem = item
       newItem.count -= 1
       inventory[name] = newItem
       
       print(“Dispensing (name)”)
   }
}

Because the vend(itemNamed:) method propagates any errors it throws, any code that calls this method must either handle the errors—using a docatch statement, try?, or try!—or continue to propagate them. For example, the buyFavoriteSnack(person:vendingMachine:) in the example below is also a throwing function, and any errors that the vend(itemNamed:) method throws will propagate up to the point where the buyFavoriteSnack(person:vendingMachine:) function is called.

let favoriteSnacks = [
   "Alice": “Chips”,
   "Bob": “Licorice”,
   "Eve": “Pretzels”,
]
func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {

   // 

The nil-coalescing operator a ?? b is a shortcut for a != nil ? a! : b

   let snackName = favoriteSnacks[person] ?? “Candy Bar”
   try vendingMachine.vend(itemNamed: snackName)
}

In this example, the buyFavoriteSnack(person: vendingMachine:) function looks up a given person’s favorite snack and tries to buy it for them by calling the vend(itemNamed:) method. Because the vend(itemNamed:)method can throw an error, it’s called with the try keyword in front of it.

Throwing initializers can propagate errors in the same way as throwing functions. For example, the initializer for the PurchasedSnack structure in the listing below calls a throwing function as part of the initialization process, and it handles any errors that it encounters by propagating them to its caller.

struct PurchasedSnack {
   let name: String
   init(name: String, vendingMachine: VendingMachine) throws {
       try vendingMachine.vend(itemNamed: name)
       self.name = name
   }
}

Handling Errors Using Do-Catch

You use a docatch statement to handle errors by running a block of code. If an error is thrown by the code in the do clause, it is matched against the catch clauses to determine which one of them can handle the error.

Here is the general form of a docatch statement:

do {
   try expression
   statements
} catch pattern 1 {
   statements
} catch pattern 2 where condition {
   statements
}

You write a pattern after catch to indicate what errors that clause can handle. If a catch clause doesn’t have a pattern, the clause matches any error and binds the error to a local constant named error. For more information about pattern matching, see Patterns.

The catch clauses don’t have to handle every possible error that the code in its do clause can throw. If none of the catch clauses handle the error, the error propagates to the surrounding scope. However, the error must be handled by some surrounding scope—either by an enclosing docatch clause that handles the error or by being inside a throwing function. For example, the following code handles all three cases of the VendingMachineError enumeration, but all other errors have to be handled by its surrounding scope:

var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
   try buyFavoriteSnack(person: “Alice”, vendingMachine: vendingMachine)
} catch VendingMachineError.invalidSelection {
   print(“Invalid Selection.”)
} catch VendingMachineError.outOfStock {
   print(“Out of Stock.”)
} catch VendingMachineError.insufficientFunds(let coinsNeeded) {
   print(“Insufficient funds. Please insert an additional (coinsNeeded) coins.”)
}
// Prints “Insufficient funds. Please insert an additional 2 coins.”

Converting Errors to Optional Values

You use try? to handle an error by converting it to an optional value. If an error is thrown while evaluating the try? expression, the value of the expression is nil. (try 바로 뒤에 따라오는 함수 수행중에 error가 발생하면 optional 변수건 상수건 nil을 저장하고 수행중 error없이 수행이 마무리되면 함수에서 return 된 값을 저장한다. 이후에 nil인지 아닌지 확인하고 그에 맞는 작업을 수행 하면 된다.)

func someThrowingFunction() throws -> Int {
   // …
}
let x = try? someThrowingFunction()

// 결국 위의 예시와 같은 내용
let y: Int?
do {
   y = try someThrowingFunction()
} catch {
   y = nil
}

If someThrowingFunction() throws an error, the value of x and y is nil. Otherwise, the value of x and y is the value that the function returned. 

func fetchData() -> Data? {
   if let data = try? fetchDataFromDisk() { return data }
   if let data = try? fetchDataFromServer() { return data }
   return nil
}

Disabling Error Propagation

Sometimes you know a throwing function or method won’t, in fact, throw an error at runtime. On those occasions, you can write try! before the expression to disable error propagation and wrap the call in a runtime assertion that no error will be thrown. If an error actually is thrown, you’ll get a runtime error.

(runtime에서 error가 발생하지 않을 것을 확신하는 경우 try!를 통해 error propagation를 막을 수 있다. )

let photo = try! loadImage(atPath: “./Resources/John Appleseed.jpg”)

Specifying Cleanup Actions

You use a defer statement to execute a set of statements just before code execution leaves the current block of code. This statement lets you do any necessary cleanup that should be performed regardless of how execution leaves the current block of code—whether it leaves because an error was thrown or because of a statement such as return or break. For example, you can use a defer statement to ensure that file descriptors are closed and manually allocated memory is freed.(defer구문은 현재 블럭의 코드가 다수행 되면 수행된다. error가 발생하던 아니던 수행하게 된다.)

A defer statement defers execution until the current scope is exited. This statement consists of the defer keyword and the statements to be executed later. The deferred statements may not contain any code that would transfer control out of the statements, such as a break or a return statement, or by throwing an error. Deferred actions are executed in the reverse of the order that they’re written in your source code. That is, the code in the first defer statement executes last, the code in the second defer statement executes second to last, and so on. The last defer statement in source code order executes first.

func processFile(filename: String) throws {
   if exists(filename) {
       let file = open(filename)
       defer {
           close(file)
       }
       while let line = try file.readline() {
           // Work with the file.
       }
       // close(file) is called here, at the end of the scope.
   }
}

NOTE

You can use a defer statement even when no error handling code is involved.