[Swift] 익스텐션, 오류처리, 고차함수

최승원·2022년 6월 8일
0

TIL (Today I Learned)

목록 보기
19/21

익스텐션

구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가할 수 있는 기능
추가할 수 있는 기능은 다음과 같다.

  • 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티
  • 타입 메서드 / 인스턴스 메서드
  • 이니셜라이저
  • 서브스크립트
  • 중첩 타입
  • 프로토콜
// Int 타입에 연산 프로퍼티 추가
extension Int {
    var isEven: Bool {
        return self % 2 == 0
    }
    var isOdd: Bool {
        return self % 2 == 1
    }
}

print(1.isEven) // false
print(2.isEven) // true
print(1.isOdd) // true
print(2.isOdd) // false

var number: Int = 3
print(number.isEven) // false
print(number.isOdd) // true

// Int 타입에 메서드 추가
extension Int {
    func multiply(by n: Int) -> Int {
        return self * n
    }
}

print(3.multiply(by: 2)) // 6
print(4.multiply(by: 5)) // 20

// String 타입에 이니셜라이저 추가
extension String {
    init(intTypeNumber: Int) {
         self = "\(intTypeNumber)"
    }
}

let stringFromInt: String = String(intTypeNumber: 100) // "100"

오류표현

Error 프로토콜과 열거형을 통해서 오류를 표현한다.

// 자판기 동작 오류의 종류를 표현한 VendingMachineError 열거형
enum VendingMachineError: Error {
case invalidInput
case insufficientFunds(moneyNeeded: Int)
case outOfStock
}

class VendingMachine {
    let itemPrice: Int = 100
    var itemCount: Int = 5
    var deposited: Int = 0
    
    
    // 사용자의 돈을 받는 메서드
    func receiveMoney(_ money: Int) throws {
        
        // 입력한 돈이 0 이하면 오류를 던진다.
        guard money > 0 else {
            throw VendingMachineError.invalidInput
        }
        
        // 오류가 없으면 정상처리를 한다.
        self.deposited += money
        print("\(money)원 받음")
    }
    
    
    // 자판기가 물건을 파는 메서드
    // throws는 error를 처리할 수 있는 함수라는 의미
    func vend(numberofItems numberOfItemsToVend: Int) throws -> String {
        
        // 원하는 아이템의 수량이 잘못 입력되었으면 오류를 던진다.
        guard numberOfItemsToVend > 0 else {
            throw VendingMachineError.invalidInput
        }
        
        // 구매하려는 금액보다 넣은 금액이 적으면 오류를 던진다.
        guard numberOfItemsToVend * itemPrice <= deposited else {
            let moneyNeeded: Int
            moneyNeeded = numberOfItemsToVend * itemCount - deposited
            throw VendingMachineError.insufficientFunds(moneyNeeded: moneyNeeded)
        }
        
        // 구매하려는 수량보다 재고가 적으면 오류를 던진다.
        guard itemCount >= numberOfItemsToVend else {
            throw VendingMachineError.outOfStock
        }
        
        // 오류가 없으면 정상처리를 한다.
        let totalPrice = numberOfItemsToVend * itemPrice
        
        self.deposited -= totalPrice
        self.itemCount -= numberOfItemsToVend
        
        return "\(numberOfItemsToVend)개 제공함"
    }
}

// 자판기 인스턴스
let machine: VendingMachine = VendingMachine()

// 판매 결과를 전달받을 변수
var result: String?

오류처리

오류 발생의 여지가 있는 throws 함수는 try를 사용하여 호출해야 한다.

// do-catch 구문 활용
do {
    try machine.receiveMoney(0)
} catch VendingMachineError.invalidInput {
    print("입력이 잘못되었습니다")
} catch VendingMachineError.insufficientFunds(let moneyNeeded) {
    print("\(moneyNeeded)원이 부족합니다")
} catch VendingMachineError.outOfStock {
    print("수량이 부족합니다")
} // 입력이 잘못되었습니다

// switch를 활용하여 표현도 가능
do {
    try machine.receiveMoney(300)
} catch /*(let error)*/ { // let error 생략 가능
    
    switch error {
    case VendingMachineError.invalidInput:
        print("입력이 잘못되었습니다")
    case VendingMachineError.insufficientFunds(let moneyNeeded):
        print("\(moneyNeeded)원이 부족합니다")
    case VendingMachineError.outOfStock:
        print("수량이 부족합니다")
    default:
        print("알수없는 오류 \(error)")
    }
} // 300원 받음

// case별로 오류처리할 필요가 없으면 생략 가능
do {
    result = try machine.vend(numberOfItems: 4)
} catch {
    print(error)
} // insufficientFunds(100)

// error를 catch하는걸 생략하는 것도 가능
do {
    result = try machine.vend(numberOfItems: 4)
}

try?와 try!

// try?
// 별도의 오류처리 결과를 통보받지 않고
// 오류가 발생했으면 결과값으로 nil을 돌려받음

result = try?machine.vend(numberofItems: 2)
result // Optional("2개 제공함")

result = try?machine.vend(numberofItems: 2)
result // nil

// try!
// 오류가 발생하지 않을 것이라는 강력한 확신을 가질 때
// try!를 사용하면 정상동작 후에 바로 결과값을 돌려받음
// 오류가 발생하면 런타임 오류가 발생

result = try! machine.vend(numberOfItems: 1)
result // 1개 제공함

result = try! machine.vend(numberOfItems: 1) // runtime error

고차함수

전달 인자로 함수를 전달 받거나, 함수 실행 결과를 함수로 반환하는 함수

map

컨테이너 내부의 기존 데이터를 변형하여 새로운 컨테이너 생성
이 때, 기존 데이터는 변하지 않음

let numbers: [Int] = [0, 1, 2, 3, 4]
var doubledNumbers: [Int]
var strings: [String]

doubledNumbers = numbers.map({ (number: Int) -> Int in
    return number * 2
}) // 클로저가 매개변수로 들어감

strings = numbers.map({ (number: Int) -> String in
    return "\(number)"
})

print(doubledNumbers) // [0, 2, 4, 6, 8]
print(strings) // ["0", "1", "2", "3", "4"]

// 매개변수, 반환 타입, return 생략, 후행 클로저
doubledNumbers = numbers.map { $0 * 2 }

filter

조건에 맞는 값만 걸러서 새로운 컨테이너로 추출

var filtered: [Int] = [Int]()
let numbers: [Int] = [0, 1, 2, 3, 4]

for number in numbers {
    if number % 2 == 0 {
        filtered.append(number)
    }
}

print(filtered) // [0, 2, 4]

// filter를 사용한 상수 선언도 가능함
// 클로저 형태
let evenNumbers: [Int] = numbers.filter {
    (number: Int) -> Bool in
    return number % 2 == 0
}

print(evenNumbers) // [0, 2, 4]

// 매개변수, 반환 타입, return 생략, 후행 클로저
let oddNumbers: [Int] = numbers.filter { $0 % 2 != 0 }

reduce

컨테이너 내부의 콘텐츠를 하나로 통합한다.
배열의 각 항목들을 재귀적으로 클로저를 적용시켜 하나의 값을 만든다.

let someNumbers: [Int] = [2, 8, 15]

// 초기값을 0으로 설정한 클로저
let sum: Int = someNumbers.reduce(0, { (first: Int, second: Int) -> Int in
    print("\(first) + \(second)")
    return first + second
})
// 0 + 2
// 2 + 8
// 10 + 15

print(sum) // 25

// 매개변수, 반환 타입, return 생략, 후행 클로저
// 초기값을 3으로 설정한 클로저
let sumFromThree = someNumbers.reduce(3) { $0 + $1 }

print(sumFromThree) // 28
profile
문의 사항은 메일로 부탁드립니다🙇‍♀️

0개의 댓글