1. Representing and Throwing Errors πŸ‘©β€πŸ’»

Swift 의 μ—λŸ¬ μ²˜λ¦¬λŠ” Cocoa 와 Objective-C μ—μ„œ 'NSError class'λ₯Ό μ‚¬μš©ν•˜λŠ” μ—λŸ¬ 처리 νŒ¨ν„΄κ³Ό μƒν˜Έ 운용 λœλ‹€. Handling Cocoa Errors in Swift

Swift μ—μ„œ μ—λŸ¬λŠ” Error protocol 을 λ”°λ₯΄λŠ” Types 의 κ°’μœΌλ‘œ ν‘œν˜„λœλ‹€. 그러기 μœ„ν•΄μ„œ Error protocol 을 μ±„νƒν•˜λ„λ‘ ν•΄μ•Όν•œλ‹€. Swift 의 Enumerations λŠ” μ—°κ΄€λœ Error conditions λ₯Ό κ·Έλ£Ήν™”ν•˜λŠ”λ° μ ν•©ν•˜λ‹€.

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

μ—λŸ¬λ₯Ό λ˜μ§€κΈ° μœ„ν•΄ throw statement λ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€. λ‹€μŒ μ˜ˆμ œλŠ” μžνŒκΈ°κ°€ 동전이 5개 더 ν•„μš”ν•˜λ‹€λŠ” μ—λŸ¬λ₯Ό λ°œμƒμ‹œν‚€λŠ” κ²½μš°λ‹€.

throw VendingMachineError.insufficientFunds(coinsNeeded: 5)

2. Handling Errors πŸ‘©β€πŸ’»

μ—λŸ¬κ°€ λ°œμƒν•˜λ©΄ μ—λŸ¬λŠ” μ£Όλ³€ μ½”λ“œμ— μ˜ν•΄ 문제λ₯Ό μˆ˜μ •ν•˜κ±°λ‚˜, λŒ€μ•ˆ μ ‘κ·Ό 방식을 μ‹œλ„ν•˜κ±°λ‚˜, μ‚¬μš©μžμ—κ²Œ μ•Œλ¦Ό λ“±μ˜ 방법을 톡해 λ°˜λ“œμ‹œ μ²˜λ¦¬λ˜μ–΄μ•Όν•œλ‹€.

ν•¨μˆ˜μ—μ„œ μ—λŸ¬κ°€ λ°œμƒν•˜λ©΄ ν”„λ‘œκ·Έλž¨μ˜ 흐름이 λ³€κ²½λ˜λ―€λ‘œ, μ½”λ“œμ—μ„œ μ—λŸ¬κ°€ λ°œμƒν•œ μœ„μΉ˜λ₯Ό λΉ λ₯΄κ²Œ μ°ΎλŠ” 것이 맀우 μ€‘μš”ν•˜λ‹€. 이λ₯Ό μœ„ν•΄ Functions, Methods, Initializers λ₯Ό ν˜ΈμΆœν•˜λŠ” μ½”λ“œ μ•žμ— try(or try? or try!) keyword λ₯Ό μž‘μ„±ν•΄ try expression 으둜 μ½”λ“œλ₯Ό μž‘μ„±ν•œλ‹€.

Swift 의 μ—λŸ¬ μ²˜λ¦¬λŠ” λ‹€λ₯Έ μ–Έμ–΄μ˜ try-catch & throw와 μœ μ‚¬ν•˜λ‹€. ν•˜μ§€λ§Œ Objective-C λ₯Ό ν¬ν•¨ν•œ λ§Žμ€ 언어와 달리 Swift 의 μ—λŸ¬ μ²˜λ¦¬λŠ” 계산 λΉ„μš©μ΄ 많이 λ“œλŠ” Call Stack ν•΄μ œ(unwinding)을 ν¬ν•¨ν•˜μ§€ μ•ŠλŠ”λ‹€.
Swift 의 throw statement 의 μ„±λŠ₯ νŠΉμ„±μ€ return statement 와 μœ μ‚¬ν•˜λ‹€.

1. Propagating Errors Using Throwing Functions

μ—λŸ¬λ₯Ό throwν•  수 μžˆλŠ” Functions, Methods, Initializersλ₯Ό μœ„ν•΄ ν•¨μˆ˜μ˜ parameters 뒀에 throws keyword λ₯Ό μž‘μ„±ν•˜λ©°, 이런 ν•¨μˆ˜λ₯Ό Throwing Functions라 ν•œλ‹€. Throwing Functions 둜 μž‘μ„±ν•˜λ©΄ λ°œμƒν•œ μ—λŸ¬λ₯Ό 직접 μ²˜λ¦¬ν•˜μ§€ μ•Šκ³  throw ν•΄μ„œ ν˜ΈμΆœν•œ μ½”λ“œμ˜ μ£Όλ³€ μ½”λ“œκ°€ μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•˜λ„λ‘ ν•  수 μžˆλ‹€.

Syntax

func canThrowErrors() throws -> String

Throwing Functions 만 μ—λŸ¬λ₯Ό throwing ν•  수 μžˆλ‹€. Nonthrowing Functions 은 λ°œμƒν•œ λͺ¨λ“  μ—λŸ¬λ₯Ό ν•¨μˆ˜ μ•ˆμ—μ„œ μ²˜λ¦¬ν•΄μ•Όν•œλ‹€.

Throwing Functions, Throwing Initializers λ₯Ό ν˜ΈμΆœν•  λ•ŒλŠ” μ—λŸ¬κ°€ μ²˜λ¦¬λ˜μ§€ μ•Šκ³  thorw 될 수 μžˆμŒμ„ λΆ„λͺ…νžˆ ν•΄μ•Όν•œλ‹€. 즉, tryλ₯Ό μ΄μš©ν•΄ ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜λ©°, ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•œ μ½”λ“œμ˜ μ£Όλ³€ μ½”λ“œμ— μ˜ν•΄ μ—λŸ¬κ°€ μ²˜λ¦¬λ˜κ±°λ‚˜, κ³„μ†ν•΄μ„œ propagation ν•΄μ•Όν•œλ‹€.

  • ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•œ μ½”λ“œμ˜ μ£Όλ³€ μ½”λ“œκ°€ μ—λŸ¬λ₯Ό 처리 : do-catch statement λ₯Ό μ΄μš©ν•΄ μ£Όλ³€ μ½”λ“œκ°€ μ—λŸ¬λ₯Ό 직접 μ²˜λ¦¬ν•œλ‹€.
  • ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•œ μ½”λ“œμ˜ context κ°€ λ‹€μ‹œ μ—λŸ¬λ₯Ό throw : throws keyword λ₯Ό μ΄μš©ν•΄ μ—λŸ¬λ₯Ό 더 μœ„λ‘œ throw μ²˜λ¦¬ν•œλ‹€.
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 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)")
    }
}

자판기의 vend(itemNamed:) λ©”μ„œλ“œλŠ” μƒν’ˆμ΄ μ‘΄μž¬ν•˜λŠ”μ§€ ν™•μΈν•˜κ³ , 재고λ₯Ό ν™•μΈν•œ ν›„, μΆ©λΆ„ν•œ λˆμ„ λ„£μ—ˆλŠ”μ§€ 확인해 μƒν’ˆμ„ μ œκ³΅ν•œλ‹€. λ§Œμ•½, μ–΄λ””μ„œλ“  μ—λŸ¬κ°€ λ°œμƒν•  경우 λ©”μ„œλ“œλŠ” μ¦‰μ‹œ μ’…λ£Œλ˜κ³  μ—λŸ¬λ₯Ό throwν•œλ‹€.

let favoriteSnacks = [
    "Alice": "Chips",
    "Queen": "Licorice",
    "Eve": "Pretzels"
]

func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
    let snackName = favoriteSnacks[person] ?? "Candy Bar"
    try vendingMachine.vend(itemNamed: snackName)
}

vend(itemNamed:) λ©”μ„œλ“œκ°€ Throwing Functions이기 λ•Œλ¬Έμ— try keyword λ₯Ό μ‚¬μš©ν•΄ ν˜ΈμΆœν•΄μ•Όν•œλ‹€. 그리고 이것을 ν˜ΈμΆœν•œ μ½”λ“œμ˜ μ£Όλ³€ μ½”λ“œμ— μ˜ν•΄ μ—λŸ¬κ°€ μ²˜λ¦¬λ˜κ±°λ‚˜ 계속 propagation ν•΄μ•Όν•œλ‹€.

μ½”λ“œλŠ” λ‹¨μˆœνžˆ try vendingMachine.vend(itemNamed: snackName)λ₯Ό μ΄μš©ν•΄ 호좜만 ν•˜κ³  μžˆμœΌλ―€λ‘œ μ—λŸ¬λŠ” λ°˜λ“œμ‹œ λ‹€μ‹œ μœ„λ‘œ throw λ˜μ–΄μ•Ό ν•œλ‹€. λ”°λΌμ„œ throws keyword λ₯Ό μΆ”κ°€ν•΄ buyFavoriteSnack(person:vendingMachine:) ν•¨μˆ˜ μ—­μ‹œ Throwing Functions둜 λ§Œλ“€μ–΄μ•Όν•œλ‹€.


Propagating Errors Using Throwing Initializers

Throwing Initializers μ—­μ‹œ μ—λŸ¬λ₯Ό propagation ν•  수 있으며, 방법은 Throwing Functions와 κ°™λ‹€.

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

2. Handling Errors Using Do-Catch

Syntax

do {
    try expression
    statements
} catch pattern 1(let errorConstant) {
    statements
} catch pattern 2 where condition {
    statements
} catch pattern 3, pattern 4 where condition {
    statements
} catch {
    statements
}

do-catch statement λŠ” μ½”λ“œ λΈ”λŸ­μœΌλ‘œ μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•œλ‹€. do clause μ—μ„œ μ—λŸ¬κ°€ thrown 되면, catch clauses μ—μ„œ μΌμΉ˜ν•˜λŠ” μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•œλ‹€.

pattern 이 μΌμΉ˜ν•˜κ±°λ‚˜, pattern κ³Ό condition 이 μΌμΉ˜ν•  λ•Œ trigger 되며 μ•„λ¬΄λŸ° pattern 도 μΆ”κ°€ν•˜μ§€ μ•ŠμœΌλ©΄ λ‚˜λ¨Έμ§€ λͺ¨λ“  μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•œλ‹€.

pattern 은 catch λ‹€μŒμ— μž‘μ„±ν•˜λ©°, condition 은 pattern λ‹€μŒμ— 'where' λ₯Ό μ΄μš©ν•΄ μΆ”κ°€ν•œλ‹€. λ˜ν•œ , λ₯Ό μ΄μš©ν•΄ Multiple Patterns λ₯Ό ν•˜λ‚˜μ˜ catch clause 에 μ—°κ²°ν•˜κ±°λ‚˜ catch is λ₯Ό μ΄μš©ν•΄ μ—¬λŸ¬ cases λ₯Ό ν•˜λ‚˜μ˜ catch μ—μ„œ μ²˜λ¦¬ν•  수 μžˆλ‹€. λ˜ν•œ Switch Value Bindings 와 같이 catch clause λ‚΄λΆ€ context μ—μ„œ μ‚¬μš©ν•  값을 binding ν•˜λŠ” 것도 κ°€λŠ₯ν•˜λ‹€.

λͺ¨λ“  catch clause λŠ” λ³„λ„λ‘œ μ—λŸ¬ μƒμˆ˜λ₯Ό μ •μ˜ν•˜μ§€ μ•ŠμœΌλ©΄, default 둜 errorλ₯Ό local constant둜 μ‚¬μš©ν•œλ‹€.


1 ) do-catch examples 1

let favoriteSnacks = [
    "Alice": "Chips",
    "Queen": "Licorice",
    "Eve": "Pretzels"
]

func buyFavoriteSnack(person: String, vendingMachine: VendingMachine) throws {
    let snackName = favoriteSnacks[person] ?? "Candy Bar"
    try vendingMachine.vend(itemNamed: snackName)
}
var vendingMachine = VendingMachine()
vendingMachine.coinsDeposited = 8
do {
    try buyFavoriteSnack(person: "Alice", vendingMachine: vendingMachine)
    print("Success! Yum.")
} 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.")
} catch {
    print("Unexpected error: \(error).")
}
Insufficient funds. Please insert an additional 2 coins.

catch clauses λŠ” do clause κ°€ throw ν•  수 μžˆλŠ” λͺ¨λ“  μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•  ν•„μš”λŠ” μ—†λ‹€. λ§Œμ•½, catch clauses κ°€ μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•˜μ§€ μ•ŠμœΌλ©΄, μ—λŸ¬λŠ” μ£Όλ³€ scope 둜 propagation λœλ‹€. μ΄λ ‡κ²Œ propagation 된 μ½”λ“œλŠ” λ°˜λ“œμ‹œ 이것을 λ‘˜λŸ¬μ‹Ό β€˜scope’ 에 μ˜ν•΄ 처리`λ˜μ–΄μ•Όν•œλ‹€.

  • Nonthrowing Functions: λ°˜λ“œμ‹œ do-catch statement 둜 감싸 μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•΄μ•Όν•œλ‹€.
  • Throwing Functions : do-catch statement 둜 κ°μ‹Έκ±°λ‚˜, μ—λŸ¬λ₯Ό 던져 올리고 callerκ°€ μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•΄μ•Όν•œλ‹€.

λ§Œμ•½ μ—λŸ¬λ₯Ό μ–΄λ””μ„œλ„ μ²˜λ¦¬ν•˜μ§€ μ•Šμ„ 경우, Runtime Errorκ°€ λ°œμƒλœλ‹€. λ”°λΌμ„œ Throwing Functions λŠ” do-catch κ°€ λͺ¨λ“  μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•  ν•„μš” 없이 μœ„λ‘œ throw ν•  수 μžˆμ§€λ§Œ Nonthrowing Functions λŠ” λ°˜λ“œμ‹œ do-catch κ°€ λͺ¨λ“  μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•΄μ•Ό ν•œλ‹€.


2 ) do-catch examples 2

catch isλ₯Ό μ΄μš©ν•΄ μ—°κ΄€λœ μ—λŸ¬λ₯Ό ν•œ λ²ˆμ— μ²˜λ¦¬ν•  수 μžˆλ‹€.

func buySnack(with item: String) throws {
    do {
        try vendingMachine.vend(itemNamed: item)
    } catch is VendingMachineError {
        print("Couldn't buy that from the vending machine.")
    }
}

do {
    try buySnack(with: "Beat-Flavored Chips")
} catch {
    print("Unexpected non-vending-machine-related error: \(error)")
}
Couldn't buy that from the vending machine.

이 경우 VendingMachineErrorκ°€ λ°œμƒν•˜λ©΄ catch is VendingMachineError에 μ˜ν•΄ μ²˜λ¦¬κ°€ 되고, κ·Έ μ™Έ μ—λŸ¬κ°€ λ°œμƒν•˜λ©΄ throws λ˜μ–΄ caller에 μ˜ν•΄ μ²˜λ¦¬λœλ‹€.


3 ) do-catch examples 3

λ˜λŠ” catch is λŒ€μ‹  μ—°κ΄€λœ μ—λŸ¬λ₯Ό ν•„μš”ν•œ 만큼 , λ₯Ό μ΄μš©ν•΄ λ‚˜μ—΄ν•΄ μ²˜λ¦¬ν•  수 μžˆλ‹€.

func buySnack(with item: String) throws {
    do {
        try vendingMachine.vend(itemNamed: item)
    } catch VendingMachineError.invalidSelection,
            VendingMachineError.insufficientFunds,
            VendingMachineError.outOfStock {
        print("""
              Couldn't buy that from the vending machine
              because of invalid selection, out of stock, or not enough money.
              """)
    }
}

do {
    try buySnack(with: "Beat-Flavored Chips")
} catch {
    print("Unexpected non-vending-machine-related error: \(error)")
}
Couldn't buy that from the vending machine
because of invalid selection, out of stock, or not enough money.

3. Converting Errors to Optional Values

Optional Chaining always returns Optional Types 을 λ‹€μ‹œ λ– μ˜¬λ €λ³΄μž. Optional Chaining은 ?을 μ΄μš©ν•΄ Instance λ˜λŠ” Value κ°€ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” κ²½μš°μ—λ„ λ³„λ„μ˜ μ—λŸ¬ 처리 없이 μ½”λ“œλ₯Ό κ°„κ²°ν•˜κ²Œ μ²˜λ¦¬ν–ˆλ‹€. κ²°κ³Όλ₯Ό 항상 Optioanl둜 Wrapping ν•˜κ³  μ—λŸ¬κ°€ λ°œμƒν•˜λ©΄ nil을 λ‹΄μ•„ λ°˜ν™˜ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

Optional Chaining κ³Ό λ§ˆμ°¬κ°€μ§€λ‘œ Throwing Functions μ—­μ‹œ try λŒ€μ‹  try?λ₯Ό μ΄μš©ν•˜λ©΄ κ²°κ³Όλ₯Ό 항상 Optional둜 Wrapping ν•˜λ„λ‘ ν•œλ‹€. 그러면 Optional Chaining을 ν•  λ•Œμ™€ λ§ˆμ°¬κ°€μ§€λ‘œ 일반 μ½”λ“œλ₯Ό μž‘μ„±ν•˜λ“― μ²˜λ¦¬ν•  수 μžˆλ‹€.


try?λ₯Ό μ‚¬μš©ν•¨μœΌλ‘œμ¨ μ–»λŠ” μž₯점은 λͺ¨λ“  μ—λŸ¬λ₯Ό 같은 λ°©μ‹μœΌλ‘œ μ²˜λ¦¬ν•˜λŠ” 경우 do-catch 없이 짧고 κ°„κ²°ν•œ μ½”λ“œλ‘œ μž‘μ„±ν•  수 μžˆλ‹€λŠ” 것이고, 단점은 λͺ¨λ“  μ—λŸ¬λ₯Ό 같은 λ°©μ‹μœΌλ‘œ μ²˜λ¦¬ν•˜λ―€λ‘œ cases λ³„λ‘œ μžμ„Έν•œ μ—λŸ¬ 처리λ₯Ό ν•˜λŠ” 것이 λΆˆκ°€λŠ₯ν•˜λ‹€λŠ” 것이닀.

  • try? λŠ” Optional Chaining의 ?와 λ§ˆμ°¬κ°€μ§€λ‘œ 항상 Optional Types λ₯Ό λ°˜ν™˜ν•œλ‹€.
  • try! λŠ” Optional Chaining의 !와 λ§ˆμ°¬κ°€μ§€λ‘œ 항상 λ°˜ν™˜κ°’μ„ Forced Unwrapping ν•œλ‹€.
enum SomeError: Error {
    case zero
}

μ•„λž˜ 3개의 μΌ€μ΄μŠ€λŠ” λͺ¨λ‘ λ™μΌν•œ μž‘λ™μ„ ν•œλ‹€.


1 ) Optional Functions

throwsλ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³  ν•¨μˆ˜μ˜ return type 자체λ₯Ό Optional둜 λ§Œλ“€κ³ , μ—λŸ¬ λ°œμƒ 쑰건에 λŒ€ν•΄ λͺ…μ‹œμ μœΌλ‘œ nil을 return μ‹œμΌœ λ‹€μŒκ³Ό 같이 μž‘λ™μ‹œν‚¬ 수 μžˆλ‹€.

func someOptionalFunction(_ input: Int) -> Int? {
    if input == 0 {
        return nil
    } else {
        return input
    }
}
let x: Int? = someOptionalFunction(0)
print(x as Any)                         // nil
let y: Int? = someOptionalFunction(1)
print(y as Any)                         // Optional(1)

ν•˜μ§€λ§Œ μœ„ μΌ€μ΄μŠ€μ˜ 경우 LBYL(Look Before You Leap) λ°©μ‹μœΌλ‘œ, μ˜ˆμƒμΉ˜ λͺ»ν•œ μ—λŸ¬λ‘œ 인해 Runtime Errorκ°€ λ°œμƒλ  수 μžˆλ‹€.


2 ) Throwing Functions with try

do-catch와 tryλ₯Ό μ‚¬μš©ν•΄ μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•  것이기 λ•Œλ¬Έμ— 더 이상 return types κ°€ Int?일 ν•„μš”κ°€ μ—†λ‹€. return types λ₯Ό Int둜 λ°”κΎΈλ„λ‘ν•˜μž. μž μ‹œ ν›„ λ‹€μ‹œ μ„€λͺ…ν•˜κ² μ§€λ§Œ, Optional Wrapping 이 ν•„μš”ν•˜λ©΄ 단지 try? keyword λ₯Ό μ‚¬μš©ν•˜κΈ°λ§Œ ν•˜λ©΄ λœλ‹€.

func someThrowingFunction(_ input: Int) throws -> Int {
    if input == 0 {
        throw SomeError.zero
    } else {
        return input
    }
}

이제 LBYL 방식 λŒ€μ‹  EAFP(Easier to Ask for Forgiveness than Permission) λ°©μ‹μœΌλ‘œ μ½”λ“œλ₯Ό μž‘μ„±ν•  수 μžˆλ‹€. μ΄λ•Œ ν•¨μˆ˜κ°€ throwν•œ μ—λŸ¬λŠ” λ°˜λ“œμ‹œ λ‹€μ‹œ throw ν•΄ propagation ν•˜κ±°λ‚˜ do-catchλ₯Ό μ΄μš©ν•΄ μ²˜λ¦¬ν•΄μ•Όν•œλ‹€.

let v: Int?
do {
    v = try someThrowingFunction(0)
} catch {
    v = nil
}
print(v as Any)                         // nil

let w: Int?
do {
    w = try someThrowingFunction(1)
} catch {
    w = nil
}
print(w as Any)                         // Optional(1)

let x: Int
do {
    x = try someThrowingFunction(2)
} catch {
    x = -1
}
print(x)                                // 2 (Not Optional!!)

LBYL 방식과 달리 μ˜ˆμƒμΉ˜ λͺ»ν•œ μ—λŸ¬λ„ λͺ¨λ‘ catch clause λ₯Ό 톡해 μ²˜λ¦¬ν•  수 μžˆμœΌλ―€λ‘œ Runtime Error λ°œμƒμ„ 막을 수 μžˆκ²Œλ˜μ—ˆλ‹€.
ν•˜μ§€λ§Œ νŠΉλ³„ν•˜κ²Œ μ—λŸ¬λ₯Ό ꡬ뢄해 μ²˜λ¦¬ν•  ν•„μš”κ°€ 없을 경우 μœ„ 방식은 μ½”λ“œμ˜ 흐름이 λΆ„λ¦¬λ˜μ–΄ 가독성이 쒋지 λͺ»ν•œ λ¬Έμ œκ°€ μ‘΄μž¬ν•œλ‹€.


3 ) Throwing Functions with try?

try? keyword λ₯Ό μ‚¬μš©ν•˜λ©΄ μ—λŸ¬λ₯Ό Optional둜 Wrapping ν•΄ EAFP λ°©μ‹μœΌλ‘œ μ½”λ“œλ₯Ό μž‘μ„±ν•˜λ©΄μ„œ μœ„ 가독성 λ¬Έμ œλ„ ν•΄κ²°ν•  수 μžˆλ‹€.

let p = try? someThrowingFunction(0)
print(p as Any)                         // nil
let q = try? someThrowingFunction(1)
print(q as Any)                         // Optional(1)


예λ₯Ό λ“€μ–΄ fetch와 같은 ν•¨μˆ˜λŠ” try?λ₯Ό μ΄μš©ν•΄ λ‹€μŒκ³Ό 같이 κ°„κ²°ν•˜κ²Œ μž‘μ„±ν•  수 μžˆλ‹€.

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

4. Disabling Error Propagation

dot Syntaxλ₯Ό μ΄μš©ν•  λ•Œ μš°λ¦¬λŠ” 3가지 λ°©λ²•μœΌλ‘œ μ ‘κ·Όν•  수 μžˆμ—ˆλ‹€. Optional Chaining 을 ν•˜μ§€ μ•ŠλŠ” . κ³Ό Optional Chaining 을 ν•˜λŠ” ?., λ§ˆμ§€λ§‰μœΌλ‘œ Forced Unwrapping 을 ν•˜λŠ” !.이닀.

try도 λ§ˆμ°¬κ°€μ§€λ‘œ try, try?, try! 3가지 Syntax κ°€ μ‘΄μž¬ν•œλ‹€. μœ„μ—μ„œ try λŒ€μ‹  try?λ₯Ό μ‚¬μš©ν•΄ Optional or nil λ₯Ό λ°˜ν™˜ν•˜λ„λ‘ Throwing Functions ν˜ΈμΆœμ„ λ‹€λ£¨λŠ” 것을 λ³΄μ•˜λ‹€. try! μ—­μ‹œ Optional Chaining 의 !.와 μœ μ‚¬ν•˜λ‹€.

μ ˆλŒ€λ‘œ μ—λŸ¬κ°€ λ°œμƒν•˜μ§€ μ•ŠλŠ”λ‹€λŠ” 것을 μ•Œκ³  μžˆλŠ” 경우, Throwing Functions λ₯Ό ν˜ΈμΆœν•  λ•Œ try! λ₯Ό μ΄μš©ν•  수 μžˆλ‹€. 이 경우 λ‹€μŒ 두 가지가 μž‘λ™ν•˜μ§€ μ•ŠλŠ”λ‹€.

  • Error Propagation
  • λ°˜ν™˜ κ°’μ˜ Optional Wrapping
let x = try? someThrowingFunction(1)
print(x as Any)                         // Optional(1)
let y = try! someThrowingFunction(1)
print(y)                                // 1

try?λ₯Ό μ΄μš©ν•œ 호좜과 달리 Unwrapped된 값을 얻을 수 μžˆλ‹€.

단, 이 λ•Œ μ£Όμ˜ν•΄μ•Όν•  것이 throws -> Intκ°€ μ•„λ‹Œ throws -> Int?일 경우

func someThrowingFunction(_ input: Int) throws -> Int? {
    if input == 0 {
        throw SomeError.zero
    } else {
        return input
    }
}

throw에 ν•œ 번, Int?에 ν•œ 번 => 총 2번의 Optional Wrapping이 이루어진닀.
λ”°λΌμ„œ throw에 μ˜ν•΄ Wrapping 된 Optional 을 ν•΄μ œν•˜λ”λΌλ„ λ‹€μ‹œ Int? 에 μ˜ν•΄ Optional Wrapping 된 값을 μ–»λŠ”λ‹€. ν•¨μˆ˜κ°€ λ°˜ν™˜ν•œ 값이 Optional(Optional(1))이기 λ•Œλ¬Έμ΄λ‹€.

let y = try! someThrowingFunction(1)
print(y)                                // Optional(1)


둜컬 κ²½λ‘œμ—μ„œ 이미지λ₯Ό κ°€μ Έμ˜€λŠ” μ½”λ“œλ₯Ό μƒκ°ν•΄λ³΄μž.

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

이미지가 μ‘΄μž¬ν•  것이라 ν™•μ‹ ν•˜κ³  try!λ₯Ό μ‚¬μš©ν–ˆλŠ”λ° 이미지가 μ‘΄μž¬ν•˜μ§€ μ•Šκ±°λ‚˜ κ°€μ Έμ˜€λŠ” 데 μ‹€νŒ¨ν–ˆλ‹€λ©΄ Runtime Error κ°€ λ°œμƒν•œλ‹€. λ”°λΌμ„œ try!λ₯Ό μ‚¬μš©ν•  λ•ŒλŠ” μ ˆλŒ€ μ—λŸ¬κ°€ λ°œμƒν•˜μ§€ μ•ŠλŠ”λ‹€λŠ” 것에 λŒ€ν•œ 보증을 κ°œλ°œμžκ°€ ν•΄μ•Όν•˜λ―€λ‘œ μ‹ μ€‘ν•œ νŒλ‹¨μ΄ ν•„μš” ν•˜λ‹€.


3. Specifying Cleanup Actions πŸ‘©β€πŸ’»

Classes νƒ€μž…μ€ class instance 의 할당이 ν•΄μ œ(deallocate)되기 직전에 호좜될 μ½”λ“œλ₯Ό μ •μ˜ν•  수 μžˆλŠ” Deinitializer λΌλŠ” νŠΉλ³„ν•œ μ½”λ“œλΈ”λŸ­μ΄ 있고 이것은 deinit keyword λ₯Ό μ΄μš©ν•΄ μ •μ˜ν–ˆλ‹€.

그리고 μ΄λŸ¬ν•œ context λ₯Ό νƒˆμΆœν•  λ•Œ ν˜ΈμΆœλ˜λŠ” μ½”λ“œλŠ” Classes 보닀 더 μž‘μ€ λ‹¨μœ„μΈ μ–΄λ– ν•œ Block scope에 λŒ€ν•΄μ„œλ„ μ •μ˜ν•  수 μžˆλŠ”λ° Defer Statement라 ν•˜λ©°, defer keyword λ₯Ό μ΄μš©ν•΄ μ •μ˜ν•œλ‹€.

Closures, if, switch, …etc 와 같은 μ–΄λ– ν•œ Block scopeμ—μ„œλ‚˜ μ •μ˜ν•  수 μžˆλ‹€.

Defer Statement μ—­μ‹œ λ¦¬μ†ŒμŠ€λ₯Ό μ •λ¦¬ν•˜κΈ° μœ„ν•œ νŠΉλ³„ν•œ μ½”λ“œλ‘œ λ‹€μŒκ³Ό 같은 νŠΉμ§•μ„ κ°–λŠ”λ‹€.

  • μ½”λ“œ λΈ”λŸ­μ„ νƒˆμΆœν•˜κΈ° 직전에 호좜되며, throw, return, break 에 관계 없이 μž‘λ™ν•œλ‹€.
  • 내뢀에 throw, return, break λ₯Ό 포함할 수 μ—†λ‹€.
  • deinit κ³Ό 달리 ν•˜λ‚˜μ˜ μ½”λ“œ λΈ”λž™ 내에 μ—¬λŸ¬ κ°œκ°€ μ‘΄μž¬ν•  수 있으며, Stack을 μ΄μš©ν•΄ LIFO둜 μž‘λ™ν•œλ‹€.
func someDefer(number: Int) {
    if number == 0 {
        defer {
            print("zero")
        }
    }
    
    defer {
        print("first")
    }
    defer {
        print("second")
    }
    defer {
        print("third")
    }
}

someDefer(number: 0)
zero
third
second
first

그리고 Swift λŠ” scope 의 λ§ˆμ§€λ§‰μ— μ •μ˜λœ defer λŠ” 항상 μ¦‰μ‹œ μ‹€ν–‰λ˜λ―€λ‘œ deferλ₯Ό do둜 μž‘μ„±ν•˜λΌκ³  κ²½κ³ λ₯Ό λ„μš΄λ‹€. λ”°λΌμ„œ 이 κ²½κ³ λ₯Ό λ°›μ•„λ“€μ—¬ μœ„ μ½”λ“œλ₯Ό μˆ˜μ •ν•˜λ©΄ λ‹€μŒκ³Ό κ°™λ‹€. 즉, deferλŠ” block 의 μ‹œμž‘ λ˜λŠ” 쀑간에 μ •μ˜ ν›„ scope λ₯Ό νƒˆμΆœν•˜κΈ° 직전에 호좜될 μ½”λ“œλ₯Ό μ •μ˜ν•˜κΈ° μœ„ν•œ κ²ƒμž„μ„ μ•Œ 수 μžˆλ‹€.

func someDefer(number: Int) {
    if number == 0 {
        do {
            print("zero")
        }
    }
    
    defer {
        print("first")
    }
    defer {
        print("second")
    }
    do {
        print("third")
    }
}


λ§ˆμ§€λ§‰μœΌλ‘œ νŒŒμΌμ„ λ‹€λ£¨λŠ” ν•¨μˆ˜κ°€ λ¦¬μ†ŒμŠ€λ₯Ό ν•΄μ œν•˜κΈ° μœ„ν•΄ deferλ₯Ό μ–΄λ–»κ²Œ ν™œμš©ν•˜λŠ”μ§€ 보자.

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.
    }
}

μœ„ μ½”λ“œμ—μ„œ defer λŠ” if λΈ”λŸ­μ— 속해 μžˆμœΌλ―€λ‘œ, if λΈ”λŸ­μ„ νƒˆμΆœν•˜κΈ° 직전에 ν˜ΈμΆœλ˜μ–΄ νŒŒμΌμ„ λ‹«κ³  μˆ˜λ™μœΌλ‘œ λ©”λͺ¨λ¦¬ λ¦¬μ†ŒμŠ€λ₯Ό ν™•λ³΄ν•œλ‹€.
(μœ„ ν•¨μˆ˜μ— if μ™Έ λ‹€λ₯Έ μ½”λ“œκ°€ μ—†μ–΄μ„œ ν•¨μˆ˜κ°€ μ’…λ£Œλ  λ•Œ defer κ°€ ν˜ΈμΆœλ˜λŠ” κ²ƒμ²˜λŸΌ 보일 수 μžˆμœΌλ‚˜ if λΈ”λŸ­μ„ νƒˆμΆœν•˜κΈ° 직전에 ν˜ΈμΆœλ˜λŠ” κ²ƒμž„μ„ μ •ν™•νžˆ 이해해야햔닀).




Reference

  1. β€œError Handling.” The Swift Programming Language Swift 5.7. accessed Dec. 22, 2022, Swift Docs Chapter 16 - Error Handling.