Swift 심화(6)

EN·2022년 10월 6일
0

요번 시간에는 에러와 예외처리에 대한 것을 배워 볼 것이다.

에러 타입은 열거형(enum)이다.

import Foundation
enum FileTransferError: Error{
  case noConnection
  case lowBandwidth
  case fileNotFound
}
  • Error protocol을 따르는 열거형 내에서 표현되도록 할 수 있다.

에러 던지기

func transferFile() throws{

}
func transferFile() throws -> Bool{
  
}
  • throws라는 키워드를 사용한다. throws는 절대 프로토콜이 아니다.
  • 에러를 던질 수 있또록 메서드를 선언했으니 오류가 발생할 때 에러를 던지는 코드를 추가할수 있다.
func transferFile() throws{
  guard connectionOK else{
    throw FileTransferError.noConnection
  }
  guard connectionSpeed > 30 else{
    throw FileTransferError.lowBandwidth
  }
  guard fileFound else{
    throw FileTransferError.fileNotFound
  }
}
  • throw와 guard구문을 결합하여 사용하게 된다.
  • 메서드 또는 함수가 에러를 던지도록 선언했다면 일반적인 방법으로는 호출할 수 없다.
  • 이러한 메서드를 호출할 때는 다음과 같이 앞에 try구문을 붙여야 한다
  • do-catch구문 내에서 호출하는 방법도 있따.
func sendFile() -> String{
  do{
    try fileTransfer()
  }catch FileTransferError.noConnection{
    return "No Network Connection"
  }catch FileTransferError.lowBandwidth{
    return "File Transfer Speed too low"
  }catch FileTransferError.fileNotFound{
    return "File not Found"
  }catch {
    return "Unknown error"
  }
  return "Successful transfer"
}
  • Do-catch 구문에서 반드시 필요한건, 가능한 모든 에러에 대해 처리할수 있도록 구성되어야 한다.

여러 객체에 접근하기

  • 메서드 호출에 실패하면 반드시 실패한 원인을 구변할 수 있는 NSError객체가 반환될 것이다.
do{
  try filemgr.createDirectory(...)
}catch let error{
  print("...")
}
  • 이건 잘 안쓰고 catch문으로 쓰임. 그냥 개발하는 과정에서 참고만 하기.

에러캐칭 비활성화하기

  • Try!구문을 사용하면 do-catch구문 내에서 메서드가 호출되도록 감싸지 않아도 스로잉 메서드가 강제로 실행된다!
  • 이러한 방법을 사용하는 것은 컴파일러에게 이 메서드 호출은 어떠한 에러도 발생하지 않을 것이라고 알려주는 것과 동일하다

defer 구문 사용하기

  • do-catch구문에 있는 각각의 catch절은 호출하는 메서드에게 제어권을 반환하기 위하여 return 구문을 포함하였다.
  • 하지만, 에러의 종류와는 상관없이 제어권을 반환하기 전에 어떠한 별도의 작업을 수행하는 게 더 효과적인 경우가 있을 수 있다.(에러 수습 같은 경우..)
  • 예를 들어 sendFile메서드에서는 제어권을 반환하기 전에 임시 파일들을 지워야할 경우가 발생할 수 있다.
  • 이것은 defer구문을 이용하면 가능하다
func sendFile() -> String{
  defer{
    removeTmpFiles()
    closeConnection()
  }
  do{
    try fileTransfer()
  }catch FileTransferError.noConnection{
    return "No Network Connection"
  }catch FileTransferError.lowBandwidth{
    return "File Transfer Speed too low"
  }catch FileTransferError.fileNotFound{
    return "File not Found"
  }catch {
    return "Unknown error"
  }
  return "Successful transfer"
}
  • defer구문은 메서드가 결과를 반환하기 직전에 실행되어야 하는 일련의 코드를 지정할수 있게 해준다.
  • 이제 메서드가 어떠한 반환을 하든지 제어권을 반환하기 전에 removeTmpFiles메서드와 closeConnection메서드가 항상 호출될 것이다.

요약

  • Swift2의 등장 덕분에 에러 처리 작업 쉬워짐
  • 에러 타입들은 Error 프로토콜을 따르는 값들을 이용하여 생성. 열거형처럼 구현
  • 에러 던지는 메서드와 함수는 throw 키워드를 이용하면 선언된다.
  • guard와 throw구문은 에러 타입을 리기반으로 한 에러들을 던지기 위하여 메서드나 함수 코드 내에서 사용된다.
  • 에러는 던질수 있으나 메서드는 try구문을 이용하여 호출되며, 반드시 do-catch구문을 이용한다.
import Foundation

let connectionOk: Bool = true
let connectionSpeed: Double = 30.00
let fileFound: Bool = false
let fileSize: Int = 30
//Error 프로토콜을 따르는 나만의 에러 타입 
enum FileTransferError: Error{
  //에러가 갖게 될 상황들을 값으로 정리함
  case noConnection
  case lowBandwidth
  case fileNotFound
  case tooBigSize
}

//이 함수는 실행중에 오류가 발생할 수 있음
func transferFile() throws {
  //함수 안에서는 최대한 모든 오류 상황들을 throw로 주고 받을 수 있어야 한다.


  //connectionOK가 false이면 else구문 실행되고 함수 종료
  guard connectionOk else{
    throw FileTransferError.noConnection  
  }

  guard connectionSpeed > 30 else{
    throw FileTransferError.lowBandwidth
  }

  guard fileFound else{
    throw FileTransferError.fileNotFound
  }

  guard fileSize < 20 else{
    throw FileTransferError.tooBigSize
  }
}

//transferFile함수를 호출하는 응용 코드
//파일 전송을 "시도"하고 결과를 문자열로 반환
func sendFile() -> String{
  //오류가 발생할수 있는 함수를 사용하는 쪽에서는 모든 오류 내용에 대해 못하더라도 최대한 많이 대응 가능하도록 해줄 필요가 있따.
  //try! transferFile() -> 위험한 방법
  do{
    try transferFile()

  }catch FileTransferError.noConnection{
    return "No Network Connection"
  }catch FileTransferError.lowBandwidth{
    return "File Transfer Speed too low"
  }catch FileTransferError.fileNotFound{
    return "File not Found"
  }catch let error{//여기는 tooBigSize가 걸린거임
    return "Unknown error: \(error)"
  }
  return "Successful transfer"
}

print("\(sendFile()))

구조체와 열거형은 값타입, 클래스는 참조타입이다.

각 진수에 따라 정수를 표현하는 방법

  • 0b11100, 0o34, 0x1c

정수와 부동소수점 변환

  • 정수와 부동 소수점 숫자 타입의 변환은 명시적으로 변환해야 한다.
  • Double(3)이렇게

문자열

  • hasPrefix() 메소드를 이용해 확인
let hello = "Hello"
var hasPrefix: Bool = false
hasPrefix = hello.hasPrefix("He") // true
hasPrefix = hello.hasPrefix("HE") // false
  • hasSuffix()도 있다
  • Utf-16에서는 ‘안’이라는 글자는 한글자, 그리고 ‘a’라는 글자도 한 글자이다.
  • “안녕하세요”의 count는 5를 리턴한다.

집합

  • insert를 써서 set에다가 삽입할 수 있따.
  • remove를 써서 항목을 없앨 수 있다.
    • remove는 사전에 contains를 통해 값의 존재 여부를 확인해야 한다.
    • removeAll메서드를 이용하여 전체 아이템을 삭제할 수 있따.
  • For-in 루프와 함께 집합에 값을 반복할 수 있다.
  • swift의 set타입은 정의된 순서를 가지고 있지 않다.
  • 특정 순서로 집합의 값을 반복하려면 집합의 요소를 <연산자를 사용하여 정렬하여 반환하는 sorted메서드를 사용해야 한다.
  • a.intersection(b)
  • a.symmetricDifference(b)
  • a.union(b)
  • a.subtracting(b
  • a.isSubset(of: b)
  • houseAnimals.isSubset(of: farmAnimals)//true
  • farmAnimals.isSuperset(of: houseAnimals)//true
  • farmAnimals.isDisjoint(with: cityAnimals)//true
    이런연산도 있음

열거형

enum PieType{
  case Apple
  case Cherry
  case Pecan

}
let favoritePie = PieType.Apple
let name:String
switch favoritePie{
  case .Apple:
    name = "Apple"
  case .Cherry:
    name = "Cherry"
  case .Pecan:
    name = "Pecan"
}
  • 요런식으로 사용
  • 스위프트의 enum은 case에 연관된 원시 값을 가질 수 있다.
  • 타입이 명시된 enum을 가지고 rawValue를 사용하여 PieType의 인스턴스를 요청할 수 있다.
  • 그리고 그 값을 enum타입을 초기화 할 수 있다.
  • 이것은 enum의 실제 case에 상응하는 원시 값이 없을 수 있기 때문에 옵셔널을 반환한다.
enum PieType: Int{
  case Apple = 0
  case Cherry
  case Pecan
}

let pieRawValue = PieType.Pecan.rawValue // pieRawValue는 2를 값으로 가진 Int 타입이다

if let pieType = PieType(rawValue: pieRawValue){
  //'pieType'이 유효한 값을 가지면
}

프로퍼티 관찰자

  • 프로퍼티의 값이 변경되는지 관찰하고 응답한다.
  • 프로퍼티의 현재 값이 새로운 값과 같더라도 프로퍼티의 값이 설정될 때 호출된다.
    • 상속된 프로퍼티의 경우 하위 클래스의 프로퍼티를 재정의하여 프로퍼티 관찰자를 추가한다
    • 정의한 계산된 프로퍼티의 경우 관찰자를 생성하는 대신에 프로퍼티의 setter를 이용하여 값 변경을 관찰하고 응답한다.
    • 관찰자를 정의하는 방법은 2가지 선택사항을 가지며 둘다 정의할 수도 있다.
      • willSet: 갑싱 저장되기 직전에 호출
      • didSet: 새로운 값이 저장되자 마자 호출
    • willSet관찰자를 구현한다면 상수 파라미터로 새로운 프로퍼티 값이 전달된다.
      • willSet구현의 일부로 이 파라미터에 특정 이름을 가질 수 있다.
      • 파라미터 명과 구현 내에 소괄호를 작성하지 않으면 파라미터는 newValue의 기본 파라미터명으로 만들어 질 수 있다.
    • 유사하게 didSet 관찰자를 구현한다면 예전 프로퍼티 값을 포함한 상수 파라미터가 전달된다.
      • 파라미터 명을 사용하거나, oldValue인 기본 파라미터 명을 사용할 수 있다.
import Foundation
class StepCounter{
  var totalSteps: Int = 0{
    willSet(newTotalSteps){
      print("About to set totalSteps to \(newTotalSteps)")
    }
    didSet{
      if totalSteps > oldValue{
        print("Added \(totalSteps - oldValue) steps")
      }
    }
  }
}

let stepCounter = StepCounter()
stepCounter.totalSteps = 200

stepCounter.totalSteps = 360

stepCounter.totalSteps = 896
- 오브젝티브-C의 옵저버 개념과는 많이 다르다

제너릭

  • 제너릭 코드는 정의한 요구사항에 따라 모든 타입에서 동작할 수 있는 유연하고 재사용 가능한 함수와 타입을 작성할 수 있다.
  • 중복을 피하고 명확하고 추상적인 방식으로 의도를 표현하는 코드를 작성할 수 있다.
  • 제너릭은 Swift의 강력한 특징 중 하나이고 Swift표준 라이브러리 대부분은 제너릭 코드로 되어 있다.
  • 사실 Language Guide전체에서 제너릭을 사용한다
  • AnyType이랑은 다름.

제너릭 함수

func swapTwoValues<T>(_ a: inout T, _ b: inout T){
  샬라샬라
}
profile
iOS/JUJITSU

0개의 댓글