드디어 10월의 첫번째 수업이다! 아직은 잘 모르겠지만 지금처럼 열심히 살다보면 끝날 때 쯤에는 멋진 개발자가 되어 있겠죠?
<앞서 배운 것>
- 프로퍼티 래퍼
- 배열 딕셔너리 컬렉션
<오늘 배울 것>
- Swift를 이용한 에러 핸들링
- 에러 타입(error type)
- 스로잉 메서드(throwing method)와 함수(function)
- guard와 defer 구문
- do-catch 구문
1. 오전일정 : SWIFT 에러 핸들링
<에러의 종류>
1) 컴파일 불가 -> 문법이 잘못된 경우
2) 런타임 에러 -> 작동중 오류
3) 논리적 오류 -> 예상하지 못한 오류
-> 어떤 경우의 문제인지 파악, 접근, 대응을 해야한다.
에러 핸들링
1. Swift에서 에러 처리 2단계
1) iOS 앱의 메서드 내에서 원하는 결과가 나오지 않을 경우 에러 발생(즉, 스로잉(throwing)하기
2) 메서드가 던진(throwing) 에러를 잡아서 처리하기
* 에러를 던진다 = 에러가 발생한다.
- 에러 타입 값은 Error 프로토콜을 따르는 모든 값이 될 수 있다.
- 앱 내의 메서드에서 오류가 발생하도록 구현하는 것도 중요하지만, iOS SDK의 많은 API 메서드(특히, 파일처리와 관련된 메서드)도 앱의 코드 내에서 처리되어야 할 에러를 던진다는 것도 알아두어야 한다.
에러 타입 선언
- enum 열거형으로 구성되어있다.
- 에러는 어려 원인으로 인하여 발생하기 때문에, Error 프로토콜을 따르는 열거형 내에서 표현되도록 할 수 있다.
enum FileTransferError: Error {
case noConnetion
case lowBandwidth
case fileNotFound
}
에러 던지기 -> 강제로 오류상황을 직접 발생시킨다.
- 메서드나 함수에서 에어를 발생시키기 위해서 throws 키워드를 이용한다.
- throws는 프로토콜이 아닌 키워드이다.
func transferFile() throws {
}
- 결과를 반환하는 메서드나 함수의 경우, throws 키워드는 다음과 같이 반환 타입 앞에 위치하게 된다.
func transferFile() throws -> Bool {
}
- 오류가 발생할 수 있도록 메서드를 선언했으니 오류가 발생할 때 에러를 던지는 코드를 추가할 수 있다.
- throw 구문과 guard 구문을 결합하여 사용
- 메서드 내에 있는 각각의 guard 구문은 각 조건이 참인지 검사한다.
- 만약 거짓이라면 else 구문에 포함된 코드가 실행된다.
- 이 코드에서는 throw 구문을 사용하여 FileTranserError 열거형에 있는 에러 값들 중 하나를 던지고 있다.
let connectionOK = ture
let connectionSpeed = 30.00
let fileFound = false
enum FileTransferError: Error { //FileTransferError = 오류이름
case noConnetion //구체적 상황들
case lowBandwidth
case fileNotFound
}
func transferFile() throws { //해당 함수를 실행하면 오류가 발생할 수 있음
guard connectionOK else { //connectionOK가 false일때 실행
throw FileTransferError.noConnection
}
guard lowBandwidth > 30 else {
throw FileTransferError.lowBandwidth
}
guard fileNotFound > 30 else {
throw FileTransferError.fileNotFound
}
}
오류상황을 알려주는 메서드와 함수 호출하기
- 메서드 또는 함수가 에러를 던지도록 선언했다면 일반적인 방법으로는 호출할 수 없다.
- 이러한 메서드를 호출할 때는 앞에 try 구문을 붙여야 한다.
//함수 선언
func transferFile() throws {
}
//try를 써서 호출
try tranferFile()
- try 구문을 이용하는 방법 외에도 발생된 모든 에러를 잡아서 처리하는 do-catch 구문 내에서 호출하는 방법도 있다.
- do-catch는 에러만을 제어하기 위한 제어문이다.
func sendFile() -> String {
do {
try fileTrnasfer()
} catch FileTransferError.noConnection {
return "NO Network Connection"
} catch FileTransferError.fileNotFound {
return "File Not Found"
} catch {
return "Unknown error" //예상하지 못한 에러
}
return "Successful transfer"
}
에러 객체에 접근하기
- 메서드 호출이 실패하면 반드시 실패한 원인을 구별할 수 있는 NSError 객체가 반환될 것이다.
- catch 구문에서 가장 필요한 것은 이 객체에 대해 접근하여 앱 코드 내에서 취할 수 있는 가장 적절한 동작을 실시
do {
try filemgr.createDirectory()
} catch let error { //let error = NSError
print("Error: \(error.localizedDescriprtion)" //localizedDescriprtion = property
}
에러 캐칭 비활성화하기 = 경고, 위험함, 웬만하면 쓰지말 것
- try! 구문을 사용하면 do-catch 구문 내에서 메서드가 호출되도록 감싸지 않아도 스로인 메서드가 강제로 실행된다.
- 이러한 방법을 사용하는 것은 컴파일러에게 이 메서드 호출은 어떤 에러도 발생하지 않을 것이라고 알려주는 것도 동일하다.
- 이러한 방법을 사용했는데도 에러가 발생한다면 런타임 에러가 될 것이기에 가급적 사용은 자제해야 하낟.
try! transferFile()
defer 구문 사용하기
- do-catch 구문에 있는 각각의 catch 절은 호출하는 메서드에 제어권을 반환하기 위해 return 구문을 포함하였다.
- 하지만 에러의 종류와는 상관없이 제어권을 반환하기 전에(return) 어떠한 변도의 작업을 수행하는게 더 효과적인 경우가 있을 수 있다.
func sendFile() -> String {
do {
try fileTrnasfer()
} catch FileTransferError.noConnection {
return "NO Network Connection"
} catch FileTransferError.fileNotFound {
return "File Not Found"
} catch {
return "Unknown error" //예상하지 못한 에러
}
return "Successful transfer"
}
- ex) sendFile() 메서드에서 제어권을 반환하기 전에 임시 파일들을 지워야 할 경우(=사태 수습)가 발생할 수 있다. 이런 경우 defer 구문을 이용하면 가능하다.
- defer 구문은 메서드가 결과를 반환하기 직전에 싱행되어야 하는 일련의 코드를 지정할 수 있게 해준다.
func sendFile() -> String {
defer {
removeTmpFiles() //return 직전에 실행되는 부분
closeConnection()
}
do {
try fileTrnasfer()
} catch FileTransferError.noConnection {
return "NO Network Connection"
} catch FileTransferError.fileNotFound {
return "File Not Found"
} catch {
return "Unknown error" //예상하지 못한 에러
}
return "Successful transfer"
}
- defer 구문을 추가했으니 이 메서드가 어떠한 반환을 하든지 제어권을 반환하기 전에 removeTmpFiles 메서드와 closeConnection 메서드가 항상 호출될 것이다.
2. 오후일정 : Swift 더 알아보기
Swift 콜렉션 타입
https://bbiguduk.gitbook.io/swift/language-guide-1/collection-types
콜렉션 타입 (Collection Types) - Swift
let oddDigits: Set = [1, 3, 5, 7, 9] let evenDigits: Set = [0, 2, 4, 6, 8] let singleDigitPrimeNumbers: Set = [2, 3, 5, 7] // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
bbiguduk.gitbook.io
열거형
- 값들의 집합으로 이루어진 타입으로, 값들이 겹친는 것은 있을 수 없다.
enum PieType {
case Apple
case Orange
}
let favoritePie = PieType.Apple //PieType = 타입, Apple = 값
let name: String
switch favoritePire {
case .Apple:
name = "Apple"
case .Orange:
name = "Orangae"
}
- Swift의 enum은 case에 연관된 원시값(=rawValue)을 가질 수 있다.
- 타입이 명시된 enum을 가지고 rawValue를 사용하여 PieType의 인스턴스를 요청할 수 있다.
- 그리고 그 값을 enum 타입을 초기화할 수 있다.
- 이것은 enum의 실제 case에 상응하는 원시값이 없을 수 있기 때문에 옵셔널을 반환한다. 이 경우가 옵셔널 바인딩의 좋은 예다.
enum PieType: Int {
case Apple = 0
case Cherry = 1
case Pecan = 2
}
let pieRawValue = PieType.Pecan.rasValue
if let pieType = PieType(rawValue: 2) {
//"PieType"!이 유효한 값을 가지면 출력
}
if let pieType = PieType(rawValue: 4) { //pieType에 없는 값 = 옵셔널 바인딩
//"PieType"!이 유효한 값을 가지면 출력
}
프로퍼티 관찰자
- 프로퍼티 관찰자 : 프로퍼티의 값이 변경되는지 관찰하고 응답
- 프로퍼티 관찰자는 프로터피의 현재 값이 새로운 값과 같더라도 프로퍼티의 값이 설정(바뀌기 이전, 이후)될 때 호출된다.
- willSet(값이 저장되기 직전에 호출), didSet(새로운 값이 저장되지마자 호출)만 존재
* Get의 경우 변경이 일어나지 않기 때문에 없는 것이다.
* 제너릭
https://bbiguduk.gitbook.io/swift/language-guide-1/generics
제너릭 (Generics) - Swift
상황별 Where 절 (Contextual Where Clauses)
bbiguduk.gitbook.io
- 제너릭 코드(Generic code)는 정의한 요구사항에 따라 모든 타입에서 동작할 수 있는 유연하고 재사용 가능하 함수와 타임을 작성할 수 있다.
- 중복을 피하고 명확하고 추상적인 방식으로 의도를 표현하는 코드를 작성할 수 있다.
- Swift의 표준 라이브러리 대부분은 제너릭 코드로 되어 있다.
- 함수명 + <T> 로 작성하면 제너릭 함수로 선언한 것이다. ex) Array<T>
* inout : 함수의 매개변수를 함수 안에서 직접 접근하며 함수 밖에도 영향을 주기 위해 선언하는 방식
타입 파라미터
- 매개변수의 타입에 따라 타입이 유추된다.
func swapTwoValues<T>(_ a: inout T, _ b: inout T) {
let temporaryA = a
a = b
b = temporaryA
}
var firstInt = 3
var secondInt = 173
swapTwoValues(&firstInt, &secondInt) //&는 함수 정의에 inout 때문에 사용 필요
프로토콜 지향 프로그래밍
https://developer.apple.com/videos/play/wwdc2015/408/
Protocol-Oriented Programming in Swift - WWDC15 - Videos - Apple Developer
At the heart of Swift's design are two incredibly powerful ideas: protocol-oriented programming and first class value semantics. Each of...
developer.apple.com
- Talkable 프로토콜은 Person이라는 구조체 타입에만 채택이 되었으므로 여러 프로퍼티와 메서드를 구현하더라도 Person에만 구현하면 되므로 큰 문제가 없다.
- class로 구현했다면 Animal 클래스의 하위클래스로 Person과 Monkey를 생성했겠지만, strunct는 그럴 수 없다.
class: 내가 누구의 속성을 물려받았는가 | strunct: 내가 무엇을 하는 존재인가 - 대문자 Self는 타입 자체를 정의할 때 사용하고, 소문자 self는 타입의 객체를 의미한다고 한다. 프로토콜에서의 Self는 자기 자신을 채택하는 "타입 자체"를 의미한다고 한다. 그래서 소문자 self로 접근해야 그 객체 내부의 속성이나 메소드에 접근할 수 있는 것 같다.
protocol Talkable {
var topic: String { get set }
func talk(to: Self)
}
struct Person: Talkable {
var topic: String
var name: String
func talk(to: Person){
print("\(topic)에 대해 (\to.name)에게 말합니다.")
}
}
strunc Monkey: Talkable {
var topic: String
func talk(to: Monket) {
print("우끼끼 \(topic")
}
}
- 프로토콜이 요구하는 사항을 미리 모두 한꺼번에 구현해 둘 수 있다면 중복된 코드를 피할 수 있다. 이는 extension으로 구현 가능하다.
- extension으로 미리 talk를 구현했기 때문에 Person과 Monkey에서 Talkable의 요구사항인 talk를 구현하지 않았음에도 오류가 전혀 발생하지 않는다.
protocol Talkable {
var topic: String { get set }
func talk(to: Self)
}
extension Takable {
func talk(to: Self) {
print("\(to)! \(topic)")
}
}
struct Person: Talkable {
var topic: String
var name: String
}
strunc Monkey: Talkable {
var topic: String
}
- 만일 프로토콜 초기 구현화 다른 동작을 해야 한다면, 타입 내에서 요구사항을 재정의해주면 된다.
protocol Talkable {
var topic: String { get set }
func talk(to: Self)
}
extension Takable {
func talk(to: Self) {
print("\(to)! \(topic)")
}
}
struct Person: Talkable {
var topic: String
var name: String
}
strunc Monkey: Talkable {
var topic: String
func talk(to: Monkey) {
print("\(to)! 우끼끼")
}
}
객체 지향과 프로토콜 지향의 차이
3. 오늘의 리뷰
연휴 끝나고 첫 시간인데 확실히 조금 놓쳐도 앞에 클래스, 구조체 강의를 들으면서 배웠던 개념이 반복되는 것 같아서 이해가 연속적으로 가능한 것 같다는게 좋은고 뿌듯한 느낌이 든다!
'멋쟁이사자처럼 앱스쿨 1기' 카테고리의 다른 글
[멋쟁이사자처럼] 앱스쿨 1기 - Objective-C (15일차 22.10.06) (2) | 2022.10.06 |
---|---|
[멋쟁이사자처럼] 앱스쿨 1기 - 클로저&이스케이프 클로저 (14일차 22.10.05) (0) | 2022.10.06 |
[멋쟁이사자처럼] 앱스쿨 1기 - 구조체(Struct)&Swift 컬렉션&property wrapper (12일차 22.09.29) (2) | 2022.09.30 |
[멋쟁이사자처럼] 앱스쿨 1기 - 클래스&프로토콜 (10일차 22.09.27) (0) | 2022.09.27 |
[멋쟁이사자처럼] 앱스쿨 1기 - Swift 함수&클로저 표현식(closure expression) (9일차 22.09.26) (2) | 2022.09.26 |