swift 기본문법 - 오류처리(error handling)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
인프런, 야곰의 스위프트 기본문법 강좌를 듣고 정리하였습니다.


오류처리(error handling)

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

enum 오류종류이름: Error {
  case 종류1
  case 종류2
  case 종류3
  case 종류4
  ...
}

예시는 아래와 같다.

enum VendingMachineError: Error {
  case invalidInput
  case insufficientFunds(moneyNeeded: Int)
  case outOfStock
}

함수에서 발생한 오류 던지기

이러한 오류들은 특정 함수안에서 오류가 발생했다고 (자신을 호출한 곳에) 오류를 던져준다.

오류 발생의 여지가 있는 메서드는 throws를 사용해 오류를 내포하는 함수임을 표시한다.

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)원 받음")
  }

  // 물건 팔기 메서드
  func vend(numberOfItems numberOfItemsToVend: Int) throws -> String {
    guard numberOfItemsToVend > 0 else {
      throw VendingMachineError.invalidInput
    }
    guard numberOfItemsToVend * itemPrice <= deposited else {
      let moneyNeeded: Int
      moneyNeeded = numberOfItemsToVend * itemPrice - deposited
      throw VendingMachineError.insufficientFunds(moneyNeeded: money)
    }
  }
}

오류 처리(try, do-catch)

  • 오류발생의 여지가 있는 throws 함수는 try를 사용해 호출해야한다. > try, try?, try!
  • 오류 발생의 여지가 있는 throws 함수는 do-catch 구문을 활용해 오류발생에 대비한다.
do {
  try machine.receiveMoney(0)
} catch VendingMachineError.invalidInput {
  print("입력이 잘못됐습니다")
} catch VendingMachine.insufficientFunds {
  print("\(moneyNeeded)원이 부족합니다")
} catch VendingMachineError.outOfStock {
  print("수량이 부족합니다")
}

반복된 catch가 별로라면 아래처럼 해도 된다.

do {
  try machine.receiveMoney(300)
} catch // let error 는 생략 가능하다
  switch error {
    case VendingMachineError.invalidInput:
      print("입력이 잘못되었습니다")
    case ....
    default:
    print("알수없는 오류 \(error)")
  }
}

그런데 이마저도 귀찮다.

do {
  result = tru machine.vend(numberOfItems: 4)
} catch {
  print(error)
}

혹은 아래로 한다

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!

  • 오류가 발생하지 않을 것이라는 강력한 확신을 가질 때
  • 정상 동작 후에 바로 결과값을 돌려받는다.
  • 오류가 발생하면 런타임 오류가 발생하여 애플리케이션 동작이 중지된다.
result = try! machine.vend(numberOfItems" 2)
result  // 1개 제공함

result = try! machine.vend(numberOfItems" 2) 

swift 기본문법 - 익스텐션(extension)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
인프런, 야곰의 스위프트 기본문법 강좌를 듣고 정리하였습니다.


익스텐션(extension)

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

기능을 추가하려는 타입의 구현된 소스코드를 알지 못하거나 볼수 없다해도 타입만 알고있다면 그 타입의 기능을 확장할 수 있다.

  • 연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티
  • 타입 메서드 / 인스턴스 메서드
  • 이니셜라이저
  • 서브스크립트
  • 중첩 타입
  • 특정 프로토콜을 준수할 수 있도록 기능 추가

기존에 존재하는 기능을 재정의 할 수는 없다.

extension 확장할 타입 이름 {
  타입에 추가될 새로운 기능 구현
}

익스텐션은 기존에 존재하는 타입이 추가적으로 다른 프로토콜을 채택할 수 있도록 확장할 수도 있다.

extension 확장할 타입 이름: 프로토콜1, 프로토콜2, 프로토콜3... {
  프로토콜 요구사항 구현
}

익스텐션 구현 / 연산 프로퍼티 추가

extension Int {
  var isEven: Bool {
    return self % 2 == 0
  }
  var isOdd: Bool {
    return self % 2 == 1
  }
}
// 그냥 숫자를 써주게 되더라도 정수 타입(리터럴 문법) >> 일반 숫자로도 표현 가능
print(1.isEven)  // false

var number: Int = 3

익스텐션 구현 / 메서드 추가

extension Int {
  func multiply(by n: Int) -> Int {
    return self * n
  }
}

// 3이라는 Int타입의 리터럴 문법, Int 타입의 인스턴스로 취급
print(3.multiply(by: 3))  // 9

익스텐션 구현 / 이니셜라이저 추가

extension String {
  init(intTypeNumber: Int) {
    self = "\(intTypeNumber)"
  }
}

// 기존에 없던 이니셜라이저를 추가할 수도 있다.

swift 기본문법 - 프로토콜(protocol)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
인프런, 야곰의 스위프트 기본문법 강좌를 듣고 정리하였습니다.


프로토콜(protocol)

특정 역할을 수행하기 위한 메서드, 프로퍼티, 이니셜라이저 등의 요구사항을 정의한다.

어떤 타입에 이 기능이 필요해! 그러니 꼭 그 기능을 구현해놨어야해! 라고 강요하는 것과 같다.

구조체, 클래스, 열거형은 프로토코를 채택(adopted)하여 프로토콜의 요구사항을 실제로 구현 가능하다.
어떤 프로토콜의 요구사항을 모두 따르는 타입은 그 프로토콜을 준수한다(Conform)고 표현한다.
프로토콜의 요구사항을 충족시키려면 프로토콜이 제시하는 기능을 모두 구현해야 한다.

protocol 프로토콜 이름 {
  정의부
}

예시는 아래와 같다

protocol Talkable {
  // 프로퍼티 요구 >> 항상 var 키워드를 사용한다.
  var topic: String { get set }  // 읽기 쓰기 모두 가능한 프로퍼티
  var language: String { get }  // 읽기만 가능한 프로퍼티

  // 메서드 요구
  func talk()

 // 이니셜라이저 요구
  init(topic: String, language: String)
}

직접 구현을 하는것은 아니고 요구만 한다.

프로토콜의 채택 및 준수

// Person 구조체는 Talkable 프로토콜을 채택했다
struct Person: Talkable {

  // 저장 프로퍼티
  var topic: String
  var language: String

  // 위 저장 프로퍼티는 연산프로퍼티로 대체가 가능하다.
  var language: String { return "한국어" }

  var subject: String = ""
  var topic: String {
    set {
      self.subject = newValue
    }
    get {
      return self.subject
    }
  }
}

프로토콜 상속

클래스와 다르게 다중 상속이 가능하다.

protocol 프로토콜 이름: 부모 프로토콜 이름 목록 {
  정의부
}

프로토콜이 상속한 모든 메서드들을 구현하지 않으면 오류가 발생한다.

클래스 상속과 프로토콜

클래스에서 상속과 프로토콜 채택을 동시에 하려면 상속 받으려는 클래스를 먼저 명시하고 그 뒤에 채택할 프로토콜 목록을 작성한다.

순서가 바뀌면 안된다!

프로토콜 준수 확인

인스턴스가 특정 프로토콜을 준수하는 지 확인 가능하다 » is, as 연산자 사용

swift 기본문법 - assert & guard, assertion, early exit

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
인프런, 야곰의 스위프트 기본문법 강좌를 듣고 정리하였습니다.


assert & guard

애플리케이션이 동작하는 도중에 생성하는 다양한 결과값을 동적으로 확인하고 안전하게 처리할 수 있도록 도와준다.

Assert

어떤 결과들을 확인해보는데 사용한다.
디버깅 모드에서만 동작 하고, 배포하는 애플리케이션에서는 동작하지 않는다.

디버깅 중에서도 예상했던 조건들이 확실히 맞는가를 확인할 때 사용한다.

var someInt: Int = 0

// someInt가 0이 맞으면 그냥 지나가고 아니면 someInt != 0 메시지를 반환
assert(someInt == 0), "someInt != 0")  

Early Exit

빠른 종료를 위해 사용한다.

디버깅 구문에서만 사용가능한 assert와는 다르게 guard는 어떤 조건에서도 동작 을 한다.

guard를 사용하여 잘못된 값이 전달됐을 시 특정 실행구문을 빠르게 종료시킨다.
guard의 else 블럭 내부 에는 특정 코드블럭을 종료하는 지시어(return, break 등)가 꼭 있어야 한다.
타입 캐스팅, 옵셔널과도 자주 사용되며 그 외 단순 조건 판단 후 빠르게 종료할 때도 용이다.

func functionWithGuard(age: Int?) {
  guard let unwrapperdAge = age,
      unwrapperdAge < 130,
      unwrapperdAge >= 0 else {
        print("나이값 입력이 잘못되었습니다.")
        return
      }
  print("당신의 나이는 \(unwrapperdAge)세 입니다")
}

if let은 블럭 안에서만 사용가능했지만 guard let은 블럭 바깥에서도 계속 사용할 수 있다.

var count = 1
while true {
  guard count < 3 else {
    break
  }
  print(count)
  count +=1
}

// 혹은
func someFunction(info: [String: Any]) {
  guard let name = info["name"] as? String else {
    return
  }

  guard let age = info["age"] as? Int, age >= 0 else {
    return
  }

  print("\(name): \(age)")
}
  • 딕셔너리에서 나오는 값은 모두 옵셔널(key에 해당하는 값이 없기 때문)
  • info라는 딕셔너리에서 name이라는 값을 가져와 캐스팅을 시도함
    • Any 타입이기 때문에 실질적으로 사용하기 위해서는 String으로 캐스팅을 시도함

swift 기본문법 - 타입 캐스팅(type casting)

|

개인공부 후 자료를 남기기 위한 목적임으로 내용 상에 오류가 있을 수 있습니다.
인프런, 야곰의 스위프트 기본문법 강좌를 듣고 정리하였습니다.


타입 캐스팅(type casting)

스위프트의 타입캐스팅은 인스턴스의 타입을 확인하는 용도로 사용한다. 뿐만 아니라 클래스의 인스턴스를, 부모 혹은 자식클래스의 타입으로 사용할 수 있는 지 확인하는 용도로 사용한다.

is, as를 사용한다.

let someInt: Int = 100
let someDouble: Double = Double(someInt)

이는 스위프트에서 타입캐스팅이 아니다. 그저 Double타입의 인스턴스를 더 생성해준 것일 뿐이다.

스위프트의 타입캐스팅은 클래스의 인스턴스에서 사용할 있다.

class Person {
  var name: String = ""
  func breath() {
    print("숨을 쉽니다")
  }
}

class Student: Person {
  var school: String = ""
  func goToSchool() {
    print("등교를 합니다")
  }
}

class UniversityStudent: Student {
  var major: String = ""
  func goToMT() {
    print("멤버쉽 트레이닝을 갑니다!")
  }
}

var zehye: Person = Person()
var hana: Student = Student()
var jason: UniversityStudent = UniversityStudent()

타입확인 - is

var result: Bool

result = zehye is Person  // true
result = zehye is Student  // false
result = zehye is UniversityStudent  // false

result = hana is Person   // true
result = hana is Student   // true
result = hana is UniversityStudent  // false

result = jason is Person    // true
result = jason is Student   // true
result = jason is UniversityStudent   // true

업 캐스팅 - as

as를 사용하여 부모클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 타입정보를 전환해준다. Any 혹은 AnyObject로도 타입 정보를 변환할 수 있다. > 암시적으로 처리되므로 생략해도 무방하다

var mike: Person = UniversityStudent() as Person
var jenny: Student = Student()
var jina: Any = Person() // as Any 생략가능

var jina: UniversityStudent = Person() as UniversityStudent  // 컴파일 오류
  • Person타입이라고 하더라도 UniversityStudent의 인스턴스가 들어올 수 있다 » 왜냐면 대학생은 사람이기 때문

다운 캐스팅 - as? 또는 as!

자식 클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 인스턴스의 타입정보를 전환해준다.(사람으로 지정했는데 학생일수도 있느냐?)

  • as? 조건부 다운캐스팅
  • as! 강제 다운캐스팅

조건부 다운캐스팅 as?

var optionalCasted: Student?

optionalCasted = mike as? UniversityStudent
optionalCasted = jenny as? UniversityStudent  // nil
optionalCasted = jina as? UniversityStudent   // nil
optionalCasted = jina as? Student  // nil
  • mike: Person 타입으로 되어잇어도 UniversityStudent로 캐스팅될 수 있다.
  • 나머지: 사람이거나 학생이었기 때문에 캐스팅이 될 수 없다.  » nil이 반환

조건부 다운캐스팅을 하게 되면 결과값이 옵셔널값이 나온다.

강제 다운캐스팅 as!

var forcedCasted: Student

optionalCasted = mike as! UniversityStudent
optionalCasted = jenny as! UniversityStudent  // 런타임 오류
optionalCasted = jina as! UniversityStudent   // 런타임 오류
optionalCasted = jina as! Student  // 런타임 오류
  • mike: 원래 대학생 타입이었기 때문에 문제없이 진행된다.
  • 나머지: 대학생이 될 수 없기 때문에 오류가 발생한다.

강제 다운캐스팅은 위험요소가 있지만 반환타입이 옵셔널이 아닌 일반 타입이기 떄문에 편하게 사용가능하다.

주로 함수로 전달되는 경우 확인해볼 수 있다.

func doSonethingWithSwitch(someone: Person) {
  switch someone {
  case is UniversityStudent:
    (someone as! UniversityStudent).goToMT()
  }
  case is Student {
    (someone as! Student).goToSchool()
  }
  case is Person {
    (someone as! Person).breath()
  }
}

switch구문을 할때는 확인만 할 뿐이지 실제 캐스팅을 하고싶다면 직접 실행을 해야한다.

그렇기 때문에 if let구문을 사용한다.  » 옵셔널 결과값을 받아와 옵셔널 추출을 한다.

func doSonethingWithSwitch(someone: Person) {
  if let universityStudent = someone as? UniversityStudent {
    universityStudent.goToMT()
  } else if let student = someone as? Student {
    student.goToSchool()
  } else if let person = someone as? Person {
    person.breath()
  }
}