일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- customclass
- ios
- jQuery
- Xcode
- 파이썬서버
- 계산기앱
- subscript
- Xib
- iOS배포
- 맥
- FileOwner
- FLASK
- 자바스크립트
- 스위프트
- iOS계산기
- spring
- MainScheduler
- Python
- Swift
- 앱배포
- iOS앱배포
- AJAX
- 딩동말씀
- 개발기록
- JavaScript
- 웹
- 스프링
- DispatchGroup
- 계산기앱만들기
- 앱버전구하기
- Today
- Total
개발하는 뚝딱이
[swift] 타입캐스팅 본문
책 <스위프트 프로그래밍 3판 ;야곰 지음> 을 정리한 글입니다.
타입캐스팅
- 스위프트는 데이터 타입 안전을 위해 다른 타입끼리의 값 교환을 엄격히 제한
- 다른 언어에서 제공하는 암시적 데이터 타입 변환 (Implicit Type Conversion)은 지원하지 않음
1. 스위프트 타입캐스팅
- 타입캐스팅을 이니셜라이저로 단순화함
- 스위프트의 타입캐스팅은 인스턴스의 타입을 확인하거나 자신을 다른 타입의 인스턴스 행세할 수 있는 방법으로 사용 가능
- is와 as 연산자로 구현됨
- is와 as 연산자로 값의 타입을 확인하거나 다른 타입으로 전환 가능
- 타입캐스팅을 통해 프로토콜을 준수하는지 확인 가능
// Coffee 클래스와 Coffee 클래스를 상속받은 Latte와 Americano 클래스
// Latte나 Americano는 Coffee인 척 할 수 있음
class Coffee {
let name: String
let shot: Int
var description: String {
return "\(shot) shot(s) \(name)"
}
init(shot: Int) {
self.shot = shot
self.name = "coffee"
}
}
class Latte: Coffee {
var flavor: String
override var description: String {
return "\(shot) shot(s) \(flavor) latte"
}
init(flavor: String, shot: Int) {
self.flavor = flavor
super.init(shot: shot)
}
}
class Americano: Coffee {
let iced: Bool
override var description: String {
return "\(shot) shot(s) \(iced ? "iced" : "hot") americano"
}
init(shot: Int, iced: Bool) {
self.iced = iced
super.init(shot: shot)
}
}
let coffee: Coffee = Coffee(shot: 1)
print(coffee.description) // 1 shot(s) coffee
let myCoffee: Americano = Americano(shot: 2, iced: false)
print(myCoffee.description) // 2 shot(s) hot americano
let yourCoffee: Latte = Latte(flavor: "green tea", shot: 3)
print(yourCoffee.description) // 3 shot(s) green tea latte
2. 데이터 타입 확인
2.0 is 연산자
- 타입 확인 연산자 is를 사용하여 어떤 클래스 (혹은 어떤 클래스의 자식클래스)의 인스턴스인지 타입 확인 할 수 있음
- 타입 확인 연산자는 인스턴스가 해당 클래스의 인스턴스거나 그 자식클래스의 인스턴스라면 true 반환, 아니면 false 반환
- is 연산자는 클래스의 인스턴스뿐만 아니라 모든 데이터 타입에서도 사용 가능
print(coffee is Coffee) // true
print(coffee is Americano) // false
print(coffee is Latte) // false
print(myCoffee is Coffee) // true
print(yourCoffee is Coffee) // true
print(myCoffee is Latte) // false
print(yourCoffee is Latte) // true
2.1 메타 타입(Meta Type) 타입
- 메타 타입 타입은 타입의 타입을 뜻함
- 클래스 타입, 구조체 타입, 열거형 타입, 프로토콜 타입 등의 타입의 타입
- 즉, 타입 자체가 하나의 타입으로 표현됨
- 클래스, 구조체, 열거형의 이름은 타입의 이름이며, 그 이름 뒤에 .Type을 붙이면 메타 타입을 나타냄
- 프로토콜의 경우 .Protocol를 붙여서 메타 타입을 나타냄
- self를 사용해 타입을 값처럼 표현 가능
- 예를 들어, SomeClass.self라고 표현하면 SomeClass의 인스턴스가 아니라 SomeClass 타입의 값으로 표현한 값을 반환
- SomeProtocol.self라고 표현하면 SomeProtocol을 준수하는 타입의 인스턴스가 아니라 SomeProtocol 프로토콜을 값으로 표현한 값을 반환
protocol SomeProtocol { }
class SomeClass: SomeProtocol { }
let intType: Int.Type = Int.self
let stringType: String.Type = String.self
let classType: SomeClass.Type = SomeClass.self
let protocolProtocol: SomeProtocol.Protocol = SomeProtocol.self
var someType: Any.Type
someType = intType
print(someType) // Int
someType = stringType
print(someType) // String
someType = classType
print(someType) // SomeClass
someType = protocolProtocol
print(someType) // SomeProtocol
2.2 type(of:) 함수
- 프로그램 실행 중에 인스턴스의 타입을 표현한 값을 알아볼 때 사용
- type(of: someInstance).self라고 표현하며 someInstance의 타입을 값으로 표현한 값을 반환
* 인스턴스 self와 타입 self의 의미
값.self : 그 값 자신을 의미
타입이름.self : 타입을 표현하는 값을 반환
print(type(of: coffee) == Coffee.self) // true
print(type(of: coffee) == Americano.self) // false
print(type(of: coffee) == Latte.self) // false
print(type(of: coffee) == Americano.self) // false
print(type(of: myCoffee) == Americano.self) // true
print(type(of: yourCoffee) == Americano.self) // false
print(type(of: coffee) == Latte.self) // false
print(type(of: myCoffee) == Latte.self) // false
print(type(of: yourCoffee) == Latte.self) // true
3. 다운캐스팅
- 어떤 클래스 타입의 변수 또는 상수가 해당 클래스의 인스턴스를 참조하지 않을 수도 있음
- 예를 들어 Latte 클래스의 인스턴스가 Coffee 클래스의 인스턴스인양 Coffee 행세를 할 수 있음
- actingConstant 상수는 Coffee 인스턴스를 참조하도록 선언했지만 실제로는 Coffee 타입인 척 하는 Latte 타입의 인스턴스를 참조하고 있음
- acintConstant가 참조하는 인스턴스를 진짜 타입인 Latte 타입으로 사용해야 할 때가 있음
- Latte 타입에 정의되어 있는 메서드를 사용하거나 프로퍼티에 접근해야 할 때 Latte 타입으로 변수의 타입을 변환해줘야 함
- 이것을 다운캐스팅 (Down Casting)이라고 함
- 부모클래스의 타입을 자식클래스의 타입으로 캐스팅
- 다운캐스팅은 클래스 뿐 아니라, Any 타입에서 다른 타입으로 캐스팅할 때도 사용됨
// Latte 타입의 인스턴스를 참조하는 Coffee 타입과 actingConstant 상수
let actingConstant: Coffee = Latte(flavor: "vanila", shot: 2)
print(actingConstant.description)
3.1 타입캐스트 연산자
- Type Cast Operator
- as? 와 as! 두 가지가 있음
- 다운캐스팅은 실패의 여지가 충분히 있기 때문에 ?와 ! 두 가지
- 조건부 연산자 as?는 다운캐스팅에 실패했을 때 nil을 반환
- 다운캐스팅을 강제하는 연산자 as!는 실패했을 때 런타임 오류가 발생
if let actingOne: Americano = coffee as? Americano {
print("This is Americano")
} else {
print(coffee.description)
}
// 1 shot(s) coffee
if let actingOne: Latte = coffee as? Latte {
print("This is Latte")
} else {
print(coffee.description)
}
// 1 shot(s) coffee
if let actingOne: Coffee = coffee as? Coffee {
print("This is just Coffee")
} else {
print(coffee.description)
}
// This is just Coffee
if let actingOne: Americano = myCoffee as? Americano {
print("This is Americano")
} else {
print(myCoffee.description)
}
// This is Americano
if let actingOne: Latte = myCoffee as? Latte {
print("This is Latte")
} else {
print(myCoffee.description)
}
// 2 shot(s) hot americano
if let actingOne: Coffee = myCoffee as? Coffee {
print("This is just Coffee")
} else {
print(myCoffee.description)
}
// This is just Coffee
// Success
let castedCoffee: Coffee = yourCoffee as! Coffee
// 런타임 오류, 강제 다운캐스팅 실패!
let castedAmericano: Americano = coffee as! Americano
- 항상 성공하는 다운캐스팅
- as!나 as? 대신에 as를 사용할 수 있음
- 항상 성공하는 것을 아는 경우는 캐스팅하려는 타입이 같은 타입이거나 부모클래스 타입이라는 것을 알 때
let castedAmericano: Coffee = yourCoffee as Coffee
* 타입 캐스팅의 의미
캐스팅은 실제로 인스턴스를 수정하거나 값을 변경하는 작업이 아니다. 인스턴스는 메모리에 똑같이 남아있고, 인스턴스를 사용할 때 어떠 타입으로 다루고 접근해야 할 지 판단할 수 있도록 컴퓨터에서 힌트를 주는 것 뿐
4. Any, AnyObject의 타입캐스팅
- 특정 타입을 지정하지 않고 여러 타입의 값을 할당할 수 있는 Any와 AnyObject라는 특별한 타입이 있음
- Any는 함수 타입을 포함한 모든 타입을 뜻하고, AnyObject는 클래스 타입만을 뜻함
- 어떤 타입의 데이터라도 전달할 수 있지만, 반환되는 타입도 Any나 AnyObject라면 전달받은 데이터가 어떤 타입인지 확인하고 사용해야 함 (스위프트는 타입에 엄격하므로)
* Any와 AnyObject를 사용하면 예기치 못한 오류가 발생할 확률이 높으로 사용을 지양할 것
// AnyObject의 타입확인
func checkType(of item: AnyObject) {
if item is Latte {
print("item is Latte")
} else if item is Americano {
print("item is Americano")
} else if item is Coffee {
print("item is Coffee")
} else {
print("Unknown Type")
}
}
checkType(of: coffee) // item is Coffee
checkType(of: myCoffee) // item is Americano
checkType(of: yourCoffee) // item is Latte
checkType(of: actingConstant) // item is Latte
// AnyObject의 타입캐스팅
func castTypeToAppropriate(item: AnyObject) {
if let castedItem: Latte = item as? Latte {
print(castedItem.description)
} else if let castedItem: Americano = item as? Americano {
print(castedItem.description)
} else if let castedItem: Coffee = item as? Coffee {
print(castedItem.description)
} else {
print("Unknown Type")
}
}
castTypeToAppropriate(item: coffee) // 1 shot(s) coffee
castTypeToAppropriate(item: myCoffee) // 2 shot(s) hot americano
castTypeToAppropriate(item: yourCoffee) // 3 shot(s) green tea latte
castTypeToAppropriate(item: actingConstant) // 2 shot(s) vanila latte
- AnyObject는 클래스의 인스턴스만 취할 수 있었지만 Any는 모든 타입의 인스턴스를 취할 수 있음
func checkAnyType(of item: Any) {
switch item {
case 0 as Int:
print("zero as an Int")
case 0 as Double:
print("zero as a Double")
case let someInt as Int:
print("an integer value of \(someInt)")
case let someDouble as Double where someDouble > 0:
print("a positive double value of \(someDouble)")
case is Double:
print("some other double value that I don't want to print")
case let someString as String:
print("a string value of \(someString)")
case let (x, y) as (Double, Double):
print("an (x, y) point at \(x), \(y)")
case let latte as Latte:
print(latte.description)
case let stringConverter as (String) -> String:
print(stringConverter("Jason"))
default:
print("something else : \(type(of: item))")
}
}
checkAnyType(of: 0) // zero as an Int
checkAnyType(of: 0.0) // zero as a Double
checkAnyType(of: 42) // an integer value of 42
checkAnyType(of: 3.14159) // a positive double value of 3.14159
checkAnyType(of: -0.25) // some other double value that I don't want to print
checkAnyType(of: "hello") // a string value of hello
checkAnyType(of: (3.0, 5.0)) // an (x, y) point at 3.0, 5.0
checkAnyType(of: yourCoffee) // 3 shot(s) green tea latte
checkAnyType(of: coffee) // something else : Coffee
checkAnyType(of: {(name: String) -> String in "Hello, \(name)"}) // Hello, Jason
* 옵셔널과 Any
Any 타입은 옵셔널을 포함한 모든 값 타입을 표현한다. 그런데 Any 타입의 값이 들어와야 할 자리에 옵셔널 타입의 값이 위치한다면 스위프트 컴파일러는 경고를 한다. 의도적으로 옵셔널 값을 Any 타입 값으로 사용하고자 한다면 as 연산자를 사용하여 명시적 타입 캐스팅을 해주어 경고 메시지를 받지 않도록 한다.
// print의 정의
public func print(_ items: Any..., separator: String = " ", terminator: String = "\n")
let optionalValue: Int? = 100
print(optionalValue) // 컴파일러 경고 발생
print(optionalValue as Any) // 경고 없음
'swift' 카테고리의 다른 글
[swift] 서브스크립트 (0) | 2020.06.25 |
---|---|
[swift] 모나드 (0) | 2020.06.23 |
[swift] 맵, 필터, 리듀스 (0) | 2020.05.13 |
[swift] 옵셔널 체이닝과 빠른 종료 (0) | 2020.05.08 |
[swift] 클로저 (0) | 2020.05.07 |