타입 캐스팅
은 지금까지 앱을 구현하면서 많이 사용했었는데 정확하게 짚고 넘어가려고 합니다. (블로그에서도 아주 간단하게 정리했었네요.)
스위프트에서의 타입 캐스팅은 다른 언어에서의 타입 캐스팅과는 의미가 다릅니다.
double value = 3.82;
int convertedValue = (int)value; // convertedValue = 3
convertedValue = 4.1; // convertedValue = 4
먼저 C언어에서의 타입 캐스팅을 확인 해보겠습니다.
C언어에서는 데이터 타입 변환을 통해서 double
타입의 변수를 int
타입으로 변환 할 수 있습니다.
var value: Double = 3.82
var convertedValue: Int = Int(value)
convertedValue = 4.1 // Error
Swift 에서도 유사한 형태로 Double -> Int
형태로 데이터 타입의 형태를 변경할 수 있습니다.
하지만 Int 구조체의 이니셜라이저를 통해서 새로운 Int 인스턴스를 생성하는 것이지 데이터 타입 변환으로 볼 수 없습니다.
Swift의 타입캐스팅은 인스턴스의 타입을 확인하거나 자신을 다른 타입의 인스턴스처럼 사용하는 것을 의미합니다.
(as, is)
예제를 통해서 살펴보겠습니다.
class Coffee {
let name: String
let shot: Int
init(shot: Int) {
self.shot = shot
self.name = "coffee"
}
}
class Latte: Coffee {
var flavor: String
init(flavor: String, shot: Int) {
self.flavor = flavor
super.init(shot:shot)
}
}
class Americano: Coffee {
let iced: Bool
init(shot: Int, iced: Bool) {
self.iced = iced
super.init(shot: shot)
}
}
위와 같이 Coffee
라는 클래스를 상속받는 Latte
,Americano
라는 두 클래스가 있습니다.
클래스를 상속받으면 부모 클래스의 고유한 프로퍼티들을 하위 클래스가 가지게 되므로 Coffee
클래스가 갖는 특성들을 Latte
,Americano
클래스도 모두 포함하고 있음을 알 수 있습니다.
다시 말하면 Latte
,Americano
는 Coffee
인 척할 수 있습니다.
반대의 경우는 불가능합니다. Latte
,Americano
의 고유한 프로퍼티를 Coffee
는 가지고 있지 않기 때문입니다.
이런 개념을 가지고 Swift 타입 캐스팅을 의미하는 1) 타입확인
2) 다른 타입의 인스턴스 처럼 사용
에 대해서 살펴 보겠습니다.
타입 연산자 is
를 사용해서 인스턴스가 어떤 클래스(어떤 클래스의 자식 클래스)
의 인스턴스 타입인지 확인할 수 있습니다.
let coffee: Coffee = Coffee(shot: 1)
let myCoffee: Americano = Americano(shot: 2, iced: false)
let yourCoffee: Latte = Latte(flavor: "good", shot: 2)
coffee is Coffee // true
coffee is Americano // false
coffee is Latte // false
myCoffee is Coffee // true
yourCoffee is Coffee // true
myCoffee is Latte // false
yourCoffee is Latte // true
위의 예제를 통해서 알 수 있듯이 인스턴스가 해당 클래스의 인스턴스
또는 해당 클래스의 자식클래스의 인스턴스
라면 true 를 반환합니다.
타입 연산자 is
이외에도 Meta Type
을 통해서 타입을 확인할 수 있습니다.
메타타입은 타입의 타입을 의미합니다.
타입의 타입이라니 이게 무슨 뜻일까요?? 별로 느낌이 와닿지 않지만 우리는 메타타입을 사용한 적이 있습니다!
tableView.register(BoardsTableViewCell.self, forCellReuseIdentifier: BoardsTableViewCell.identifier)
위와 같이 BoardsTableViewCell.self
이 BoardsTableViewCell
의 타입 자체의 변수 형태입니다.
간단한 예제를 통해서 개념을 공부해보겠습니다.
import Foundation
class Person {
static let nations = "korea"
let name: String
func printMajor(major: String) {
print("major is \(major)")
}
init(name: String) {
self.name = name
}
}
let sangwon: Person = Person(name: "sangwon")
let meta: Person.Type = type(of: sangwon)
let personType: Person.Type = Person.self
//print(sangwon.nations)
print(meta)
print(meta.nations)
print(personType)
print(personType.nations)
Person()
은 인스턴스, Person
은 인스턴스를 나타내는 type 입니다.
그래서 Person()
은 인스턴스 sangwon
에서는 타입 프로퍼티 nations 에 접근할 수 없습니다.
우리는 print(Person.nations)
와 같은 형태로 타입 이름을 이용해서 타입 프로퍼티를 사용해왔습니다.
하지만 위와같이 Some.Type
형태의 클래스 자체의 타입을 의미하는 메타타입을 사용해서 타입 프로퍼티에 접근할 수 있습니다.
Person.Type = Person 의 메타타입
Person.self = Person 메타타입의 값
앞선 예제를 메타타입을 통해서 타입을 확인 해보겠습니다.
type(of: coffee) == Coffee.self // true
type(of: coffee) == Americano.self // false
type(of: coffee) == Latte.self // false
type(of: yourCoffee) == Coffee.self // false
type(of: myCoffee) == Coffee.self // false
type(of: myCoffee) == Latte.self // false
type(of: yourCoffee) == Latte.self // true
is
와 달리 같은 타입일 때만 true 가 출력되는 것을 확인 할 수 있습니다.
다운캐스팅은 크게 두 가지로 나눌 수 있습니다.
첫번째 경우를 먼저 살펴보겠습니다.
// 다운캐스팅 실패하는 경우
if let actingOne: Americano = coffee as? Americano {
print("This is Americano")
} else {
print("Fail")
}
// 부모클래스로의 업캐스팅 (항상 성공)
if let actingTwo: Coffee = myCoffee as? Coffee {
print("This is Coffee")
} else {
print("Fail")
}
//Latte 인스턴스를 Coffee 로 업캐스팅
let castedCoffee: Coffee = yourCoffee as! Coffee
//업캐스팅된 castedCoffee를 다시 Latte 로 다운캐스팅
var recastedCoffee: Latte = castedCoffee as! Latte
as
를 통한 타입캐스트는 반환타입이 옵셔널인 as?
와 반환타입이 옵셔널이 아닌 as!
두 가지가 존재합니다.
따라서, 상황에 맞게 옵셔널 바인딩을 해주면 됩니다!
Swift에는 특정타입을 지정하지 않고 여러 타입의 값을 할당할 수 있는
Any, AnyObject
라는 타입이 있습니다.
Any
: 함수 타입을 포함한 모든 타입에 대한 인스턴스(구조체, 열거형)AnyObject
: 클래스 타입의 인스턴스Any, AnyObject
모두 어떤 타입으로 된 멤버를 가지고 있는지 컴파일 시점에서는 알 수 없고, 런타임 시점에 결정되기 때문에 타입 캐스팅을 하지 않으면 멤버에 대한 접근도 불가능합니다.
Any
를 이용해서 타입캐스팅을 하는 경우를 확인 해보겠습니다.
var things: [Any] = []
things.append(0)
things.append(0.0)
things.append(42)
things.append(3.14159)
things.append("hello")
things.append((3.0, 5.0))
things.append({ (name: String) -> String in "Hello, \(name)" })
for thing in things {
switch thing {
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 stringConverter as (String) -> String:
print(stringConverter("Michael"))
default:
print("something else")
}
}
// zero as an Int
// zero as a Double
// an integer value of 42
// a positive double value of 3.14159
// a string value of "hello"
// an (x, y) point at 3.0, 5.0
// a movie called Ghostbusters, dir. Ivan Reitman
// Hello, Michael
위와 같이 Any
를 사용하는 경우 모든 타입의 인스턴스를 멤버로 가질 수 있고, 타입 캐스팅을 통해서 값을 확인 할 수 있습니다.
새해가 밝으면 열심히 블로그도 쓰고 출시한 앱 업데이트도 주기적으로 하려고 했는데 게을러서 그런지 쉽지가 않습니다 ㅠㅠ
이제 수업시간에 배운 내용 중 블로그에 정리하지 않은 내용들은 한번씩 학습하고 벽을 느겼던 클로저, 동기/비동기, ARC
가 있습니다.
그래도 프로젝트 기간에 하나씩 천천히 이해해가면서 블로그에 정리하도록 하겠습니다!