Swift 기본 문법 (3)
25) Type Casting (타입 캐스팅)
•
타입 캐스팅은 인스턴스의 타입을 확인하는 용도, 또는 클래스의 인스턴스를 부모 혹은 자식 클래스의 타입으로 사용할 수 있는지 확인하는 용도로 사용한다.
•
is, as를 사용한다.
ex) 스위프트의 타입 캐스팅이 아닌 예
let someInt: Int = 100
let someDouble: Double = Double(someInt)
Swift
복사
→ 실질적으로 Swift에서는 타입 캐스팅이 아니다.
→ Double 타입에 인스턴스를 하나 더 생성해준 것일 뿐.
import Swift
// 타입 캐스팅을 위한 클래스 정의
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("MT 가자")
}
}
var yagom: Person = Person()
var hana: Student = Student()
var jason: UniversityStudent = UniversityStudent()
Swift
복사
타입 확인
•
is를 사용하여 타입을 확인한다.
var result: Bool
result = yagom is Person // true
result = yagom is Student // false
result = yagom 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
if yagom is UniversityStudent {
print("yagom은 대학생입니다")
}
else if yagom is Student {
print("yagom은 학생입니다")
}
else if yagom is Person {
print("yagom은 사람입니다")
} // yagom은 사람입니다
switch jason {
case is Person:
print("jason은 사람입니다")
case is Student:
print("jason은 학생입니다")
case is UniversityStudent:
print("jason은 대학생입니다.")
default:
print("jason은 사람도, 학생도, 대학생도 아닙니다")
} // jason은 사람입니다
switch jason {
case is UniversityStudent:
print("jason은 대학생입니다")
case is Student:
print("jason은 학생입니다")
case is Person:
print("jason은 사람입니다")
default:
print("jason은 사람도, 학생도, 대학생도 아닙니다")
} // jason은 대학생입니다
Swift
복사
업 캐스팅
•
as 를 사용하여 부모 클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 타입 정보를 전환해 준다.
•
Any 혹은 AnyObject로도 타입 정보를 변환할 수 있다.
•
암시적으로 처리되므로 생략해도 무방하다.
var mike: Person = UniversityStudent() as Person
var jenny: Student = Student()
// var jina: UniversityStudent = Person() as UniversityStudent // 컴파일 오류
var jina: Any = Person() // as Any 생략 가능
Swift
복사
다운 캐스팅
•
as? 또는 as! 를 사용하여 자식 클래스의 인스턴스로 사용할 수 있도록 컴파일러에게 인스턴스의 타입 정보를 전환해 준다.
조건부 다운 캐스팅
◦
as!
◦
결과값이 옵셔널로 나오게 된다.
var optionalCasted: Student?
optionalCasted = mike as? UniversityStudent
// casting에 성공하면 옵셔널로 반환된다 -> 실패 가능성을 내포하기 떄문에
optionalCasted = jenny as? UniversityStudent // nil
optionalCasted = jina as? UniversityStudent // nil
optionalCasted = jina as? Student // nil
Swift
복사
강제 다운 캐스팅
◦
as!
◦
반환 타입이 옵셔널 타입이 아니고, 일반 타입이기 때문에 편하게 활용 가능
var forcedCasted: Student
optionalCasted = mike as! UniversityStudent
// optionalCasted = jenny as! UniverstiyStudent // 런타임 오류
// optionalCasted = jina as! UniversityStudent // 런타임 오류
// optionalCasted = jina as! Student // 런타임 오류
Swift
복사
◦
활용
func doSomethingWithSwitch(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()
}
}
doSomethingWithSwitch(someone: mike as Person)
// MT 가자!
doSomethingWithSwitch(someone: mike)
// MT 가자!
doSomethingWithSwitch(someone: jenny)
// 등교를 합니다
doSomethingWithSwitch(someone: yagom)
// 숨을 쉽니다
Swift
복사
func doSomething(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()
}
}
doSomething(someone: mike as Person)
// MT 가자!
doSomething(someone: mkie)
// MT 가자!
doSomething(someone: jenny)
// 등교를 합니다
doSomething(someone: yagom)
// 숨을 쉽니다
Swift
복사
◦
if-let 구문을 사용하면, 캐스팅과 동시에 인스턴스를 반환 받아서 unwrapping을 할 수 있다 → 옵셔널을 추출해서 쓸 수가 있다.
26) assert와 guard
•
애플리케이션이 동작 도중에 생성하는 다양한 결과값을 동적으로 확인하고 안전하게 처리할 수 있도록 확인하고 빠르게 처리할 수 있다.
Assertion
assert(_:_:file:line:)
Swift
복사
함수 사용
•
assert 함수는 디버깅 모드에서만 동작한다.
•
배포하는 애플리케이션에서는 제외된다.
•
주로 디버깅 중 조건의 검증을 위하여 사용한다.
var someInt: Int = 0
assert(someInt == 0, "someInt != 0")
someInt = 1
// assert(someInt == 0) // 동작중지, 검증실패
// assert(someInt == 0, "someInt != 0") // 동작중지, 검증실패
// assertion failed: someInt != 0: file guard_assert.swift, line 26
func functionWithAssert(age: Int?) {
assert(age != nil, "age == nil")
assert((age! >= 0) && (age! <= 130), "나이값 입력이 잘못되었습니다")
print("당신의 나이는 \(age!)세 입니다")
}
functionWithAssert(age: 50)
functionWithAssert(age: -1) // 동작중지, 검증실패
functionWithAssert(age: nil) // 동작중지, 검증실패
Swift
복사
Early Exit
•
guard를 사용하여 잘못된 값의 전달 시, 특정 실행 구문을 빠르게 종료한다.
•
디버깅 모드 뿐만 아니라, 어떤 조건에서도 동작한다.
•
guard의 else 블럭 내부에는 특정 코드 블럭을 종료하는 지시어(return, break 등)가 꼭 있어야 한다.
•
타입 캐스팅, 옵셔널과도 자주 사용된다.
•
그 외, 단순 조건 판단 후, 빠르게 종료할 때도 용이하다.
func functionWithGuard(age: Int?) {
guard let unwrappedAge = age,
unwrappedAge < 130,
unwrappedAge >= 0 else {
print("나이값 입력이 잘못되었습니다")
return
}
print("당신의 나이는 \(unwrappedAge)세 입니다")
}
Swift
복사
•
guard let → 옵셔널 바인딩과 연결시켜 준다.
◦
age가 nil이라면, 실행이 되지 않고 바로 return 된다.
guard unwrappedAge < 100 else {
return
}
Swift
복사
◦
이것도 가능
var count = 1
while true {
guard count < 3 else {
break
}
print(count)
count += 1
}
// 1
// 2
Swift
복사
딕셔너리 같은 것들을 받아왔을 때도 굉장히 많이 활용!
func someFunction(info: [String: Any]) {
guard let name = info["name"] as? String else {
// 딕셔너리에서 나온 값들은 모두 옵셔널 -> Key에 해당되는 값이 없기 때문
// as? String -> 캐스팅
return
}
guard let age = info["age"] as? Int, age >= 0 else {
return
}
print("\(name): \(age)")
}
someFunction(info: ["name": "jenny", "age": "10"])
// age가 String이기 때문에 실행X
someFunction(info: ["name": "mike"])
// age가 없기 때문에, 바인딩X. 캐스팅X
someFunction(info: ["name": "yagom", "age": 10])
// 정상적으로 실행 된다
// yagom: 10
Swift
복사
27) 프로토콜
•
프로토콜은 특정 역할을 수행하기 위한 메서드, 프로퍼티, 이니셜라이저 등의 요구사항을 정의한다.
•
구조체, 클래스, 열거형은 프로토콜을 채택(adopted)해서 프로토콜의 요구사항을 실제로 구현할 수 있다.
•
어떤 프로토콜의 요구사항을 모두 따르는 타입은 그 ‘프로토콜을 준수한다(conform)’고 표현한다.
•
프로토콜의 요구사항을 충족시키려면 프로토콜이 제시하는 기능을 모두 구현하여야 한다.
정의
protocol 프로토콜 이름 {
// 정의부
}
Swift
복사
protocol Talkable {
// 프로퍼티 요구
// 프로퍼티 요구는 항상 var 키워드를 사용한다.
// get은 읽기만 가능해도 상관없다는 뜻이며,
// get과 set을 모두 명시하면, 읽기 쓰기 모두 가능한 프로퍼티여야 한다.
var topic: String { get set }
var language: String { get }
// 메서드 요구
func talk()
// 이니셜라이저 요구
init(topic: String, language: String)
}
Swift
복사
프로토콜 채택 및 준수
// Person 구조체는 Talkable 프로토콜을 채택하였다
struct Person: Talkable {
var topic: String
let language: String
/*
// 읽기 전용 프로퍼티 요구는 연산 프로퍼티로 대체가 가능하다
var language: String { return "한국어" }
// 물론 읽기 쓰기 프로퍼티도 연산 프로퍼티로 대체가 가능하다
var subject: String = ""
var topic: String {
set {
self.subject = newValue
}
get {
return self.subject
}
}
*/
func talk() {
print("\(topic)에 대해 \(language)로 말합니다")
}
init(topic: String, language: String) {
self.topic = topic
self.language = language
}
}
Swift
복사
프로토콜 상속
•
프로토콜은 클래스와 다르게 다중 상속이 가능하다
protocol 프로토콜 이름: 부모 프로토콜 이름 목록 {
// 정의부
}
Swift
복사
protocol Readable {
func read()
}
protocol Writeable {
func write()
}
protocol ReadSpeakable: Readable {
// func read() // 써줄 필요 없다. 이미 상속 받았기 때문에
func speak()
}
protocol ReadWriteSpeakable: Readable, Writeable {
// func read()
// func write()
func speak()
}
struct SomeType: ReadWriteSpeakable {
func read() {
print("Read")
}
func write() {
print("Write")
}
func speak() {
print("Speak")
}
} // 하나라도 구현하지 않으면 Error
Swift
복사
클래스 상속과 프로토콜
•
클래스에서 상속과 프로토콜 채택을 동시에 하려면, 상속 받으려는 클래스를 먼저 명시하고, 그 뒤에 채택할 프로토콜 목록을 작성한다.
class SuperClass: Readable {
func read() { print("read") }
}
class SubClass: SuperClass, Writeable, ReadSpeakable {
func write() { print("write") }
func speak() { print("speak") }
}
Swift
복사
프로토콜 준수 확인
•
인스턴스가 특정 프로토콜을 준수하는지 확인할 수 있다.
•
is , as 사용
let sup: SuperClass = SuperClass()
let sub: SubClass = SubClass()
var someAny: Any = sup
someAny is Readable // true
someAny is ReadSpeakable // false
someAny = sub
someAny is Readable // true
someAny is REadSpeakable // true
someAny = sup
if let someReadable: Readable = someAny as? Readable {
someReadable.read()
} // read
if let someReadSpeakable: Readable = someAny as? Readable {
someReadSpeakable.speak()
} // 동작하지 않음
someAny = sub
if let someReadable: Readable = someAny as? Readable {
someReadble.read()
} // read
Swift
복사
28) Extension (익스텐션)
•
익스텐션은 구조체, 클래스, 열거형, 프로토콜 타입에 새로운 기능을 추가할 수 있는 기능이다.
•
기능을 추가하려는 타입의 구현된 소스코드를 알지 못하거나 볼 수 없다해도, 타입만 알고 있다면 그 타입의 기능을 확장할 수 있다.
익스텐션으로 추가할 수 있는 기능
•
연산 타입 프로퍼티 / 연산 인스턴스 프로퍼티
•
타입 메서드 / 인스턴스 메서드
•
이니셜라이저
•
서브스크립트
•
중첩 타입
•
특정 프로토콜을 준수할 수 있도록 기능 추가
→ 기존에 존재하는 기능을 재정의 할 수는 없다.
정의
extension 확장할 타입 이름 {
// 타입에 추가될 새로운 기능 구현
}
Swift
복사
•
익스텐션은 기존에 존재하는 타입이 추가적이고 다른 프로토콜을 채택할 수 있도록 확장할 수도 있다.
extension 확장할 타입 이름: 프로토콜1, 프로토콜2, 프로토콜3 {
// 프로토콜 요구사항 구현
}
Swift
복사
익스텐션 구현
•
연산 프로퍼티 추가
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
number = 2
print(number.isEven) // true
print(number.isOdd) // false
Swift
복사
메서드 추가
extension Int {
func multiply(by n: Int) -> Int {
return self * n
}
}
print(3.multiply(by: 2)) // 6
print(4.multiply(by: 5)) // 20
number = 3
print(number.multiply(by: 2)) // 6
print(number.multiply(by: 3)) // 9
Swift
복사
이니셜라이저 추가
extension String {
init(intTypeNumber: Int) {
self = "\(intTypeNumber)"
}
init(doubleTypeNumber: Double) {
self = "\(doubleTypeNumber)"
}
}
let stringFromInt: String = String(intTypeNumber: 100) // "100"
let stringFromDouble: String = String(doubleTypeNumber: 100.0) // "100.0"
Swift
복사
29) Error Handling (오류 처리)
오류 표현
•
Error 프로토콜과 (주로) 열거형을 통해서 오류를 표현한다.
enum 오류종류 이름: Error {
case 종류1
case 종류2
case 종류3
...
}
Swift
복사
ex) 자판기 동작 오류의 종류를 표현한 VendingmachineError 열거형
enum VendingMachineError: Error {
case invalidInput
case insufficientFunds(moneyNeeded: Int)
case outOfStock
}
Swift
복사
함수에서 발생한 오류 던지기
•
자판기 동작 도중 발생한 오류 던지기
•
오류 발생의 여지가 있는 메서드는 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)
}
// 구매하려는 수량보다 요구하는 수량이 많으면 오류를 던진다
guard itemCount >= numberOfItemsToVend else {
throw VendingMachineError.outOfStock
}
// 오류가 없으면 정상처리
let totalPrice = numberOfItemsToVend * itemPrice
self.deposited -= totalPrice
self.itemCount -= numberOfItemsToVend
return "\(numberOfItemsToVend)개 제공함"
}
}
Swift
복사
// 자판기 인스턴스
let machine: VendingMachine = VendingMachine()
// 판매 겨로가를 전달받을 변수
var result: String?
Swift
복사
오류 처리
•
오류 발생의 여지가 있는 throws 함수(메서드)는 try를 사용하여 호출한다.
•
try , try? , try!
do-catch
•
오류 발생의 여지가 있는 throws 함수(메서드)는 do-catch 구문을 활용하여 오류 발생에 대비한다.
do {
try machine.receiveMoney(0)
}
catch VendingMachineError.invalidInput {
print("입력이 잘못되었습니다")
}
catch VendingMachineError.insufficientFuncs(let moneyNeeded) {
print("\(moneyNeeded)원이 부족합니다")
}
catch VendingMachineError.outOfStock {
print("수량이 부족합니다")
}
// 입력이 잘못되었습니다
Swift
복사
do {
try machine.receiveMoney(300)
}
catch // let error {
switch error {
case VendingMachineError.invalidInput:
print("입력이 잘못되었습니다")
case VendingMachineError.insufficientFunds(let moneyNeeded):
print("\(moneyNeeded)원이 부족합니다")
case VendingMachineError.outOfStock:
print("수량이 부족합니다")
default:
print("알수없는 오류 \(error)")
}
}
Swift
복사
do {
result = try machine.vend(numberOfItems: 4)
}
catch {
print(error)
} // insufficientFunds(100)
do {
result = try machine.vend(numberOfItems: 4)
}
Swift
복사
try?
•
별도의 오류처리 결과를 통보 받지 않고, 오류가 발생했으면 결과값을 nil로 돌려 받을 수 있다.
•
정상 동작 후에는 옵셔널 타입으로 정상 반환값을 돌려 받는다.
result = try? machine.vend(numberOfItems: 2)
result // Optional("2개 제공함")
result = try? machine.vend(numberOfItems: 2)
result // nil
Swift
복사
try!
•
오류가 발생하지 않을 것이라는 강력한 확신을 가질 때, try!를 사용하면 정상동작 후에 바로 결과값을 돌려 받는다.
•
오류가 발생하면 런타임 오류가 발생하여 애플리케이션 동작이 중지된다.
result = try! machine.vend(numberOfItems: 1)
result // 1개 제공함
// result = try! machine.vend(numberOfItems: 1) // 죽어버림..
Swift
복사
30) 고차 함수
•
전달 인자로 함수를 전달 받거나, 함수 실행의 결과를 함수로 반환하는 함수
•
map, filter, reduce
map
•
컨테이너 내부의 기존 데이터를 변형(transform)하여 새로운 컨테이너 생성
let numbers: [Int] = [0, 1, 2, 3, 4]
var doubledNumbers: [Int]
var strings: [String]
// for 구분 사용했을 때
doubledNumbers = [Int]()
strings = [String]()
for number in numbers {
doubleNumbers.append(number * 2)
strings.append("\(number)")
}
print(doubleNumbers) // [0, 2, 4, 6, 8]
print(strings) // ["0", "1", "2", "3", "4"]
// map 메서드 사용했을 때
// numbers의 각 요소를 2배하여 새로운 배열로 반환
doubleNumbers = numbers.map({ (number: Int) -> Int in
return number * 2
})
// numbers의 각 요소를 문자열로 반환하여 새로운 배열로 반환
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 }
print(doubleNumbers) // [0, 2, 4, 6, 8]
Swift
복사
filter
•
컨테이너 내부의 값을 걸러서 새로운 컨테이너로 추출
// for 구문 사용했을 때 // 변수 사용에 주목
var filtered: [Int] = [Int]()
for number in numbers {
if number % 2 == 0 {
filtered.append(number)
}
}
print(filtered) // [0, 2, 4]
// filter 메서드 사용
// numbers의 요소 중 짝수를 걸러내어 새로운 배열로 반환
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
}
print(oddNumbers) // [1, 3]
Swift
복사
reduce
•
컨테이너 내부의 컨텐츠를 하나로 통합
let someNumbers: [Int] = [2, 8, 15]
// for 구문 사용했을 때
var result: Int = 0
// someNumbers의 모든 요소를 더한다.
for (number in someNumbers {
result += number
}
print(result) // 25
// reduce 메서드 사용했을 때
// 초기값이 0이고, someNumbers 내부의 모든 값을 더한다.
let sum: Int = someNumbers.reduce(0, {
(first: Int, second: Int) -> Int in
return first + second
})
print(sum) // 25
// 초기값이 0이고, someNumbers 내부의 모든 값을 뺀다.
var subtract: Int = someNumbers.reduce(0, {
(first: Int, second: Int) -> Int in
return first - second
})
print(subtract) // -25
print(someNumbers.reduce(1) { $0 * $1 }) // 240
Swift
복사