Danny의 iOS 컨닝페이퍼
article thumbnail
반응형

형 변환 (Type Casting)

Swift에서 타입 캐스팅is 및 as연산자로 나눠줄 수 있습니다.

 

is 연산자를 통하여 인스턴스에 대한 타입을 확인을 할 수가 있고

 

as 연산자는 클래스의 계층구조(Superclass Subclass)에서 변환할 타입의 인스턴스로 리턴합니다.

 

또한, 타입 캐스팅을 이용하여 특정 프로토콜을 따르는지 확인할 수도 있습니다.

 

 

is 연산자 - Type Checking

간단히 영어 표현식으로 생각해 보면 "is""~은 ~이다"라고 사용이 되죠?

 

영어를 읽는다 생각하고 아래 예제를 읽어보면 사용방법이 확 이해가 가실 거예요.

var score: Int = 100

score is Int         
"score" 는 "Int" 타입이다.
// true

score is Double      
"score" 는 "Double" 타입이다.
// false

is 연산자는 Bool 타입으로 리턴합니다.

 

만약 인스턴스가 동일한 타입이거나 동일한 타입에 대하여 Subclass 유형이면

 

리턴 값으로 true를 리턴하고 틀리면 false를 리턴합니다.

 

음... 동일한 타입에 대하여 Subclass?? 이게 무슨 말이냐 하면... 예제를 보면서 알아봅시다.

 

아래와 같이 DeveloperPerson을 상속받아 Developer 타입으로 danny라는 객체(인스턴스)를 생성해 줬습니다.

class Person {}
class Developer: Person {}

let danny = Developer()

danny is Developer
danny is Person

// true
// true

is타입 검사를 해보면 dannyDeveloper 타입이므로 당현히 true 리턴하겠죠?

 

그러면 Person은 어떻게 나오는지 볼까요?

 

Person에 대한 타입 검사를 해보면 true로 리턴하게 되네요.

 

이유는 DeveloperPerson자식 클래스(Subclass)이고,

부모 클래스Person의 모든 특성과 기능을 상속받아 만들어진 타입입니다.

 

이미 자식 클래스부모 클래스모든 기능을 갖고있어 타입이 같다고 취급해주는 거죠!!!

 

 

간단한 예제를 통해 어떤 식으로 사용할 수 있는지 알아봅시다.

class Person {
    let name: String
    init(name: String) {
        self.name = name
    }
}
class Developer: Person {}
class Doctor: Person {}


let people: [Person] = [
    Developer(name: "Danny"),
    Developer(name: "Jobs"),
    Doctor(name: "김사부")
]

Person상속받아서 "개발자 Danny, Jobs"와 "의사 김사부"를 만들어 줬습니다.

 

if문이나 switch문으로 분기처리를 통해 이와 같이 사용할 수 있습니다.

var developerCount = 0

for person in people {
    if person is Developer {
        print("개발자 : \(person.name)")
        developerCount += 1
    } else if person is Doctor {
        print("닥터 : \(person.name)")
    }
}

print("개발자의 수 : \(developerCount)명")

// 개발자 : Danny
// 개발자 : Jobs
// 닥터 : 김사부
// 개발자의 수 : 2명

 

for문으로 각각의 타입이 true일 경우 그 해당하는 이름을 프린트하게 하고

 

또한 Developertrue일 경우 developerCount의 숫자를 1씩 증가시키도록 만들었습니다.

var developerCount = 0

for person in people {
    switch person {
    case is Developer:
        print("개발자 : \(person.name)")
        developerCount += 1
    case is Doctor:
        print("닥터 : \(person.name)")
    default:
        break
    }
}
print("개발자의 수 : \(developerCount)명")

// 개발자 : Danny
// 개발자 : Jobs
// 닥터 : 김사부
// 개발자의 수 : 2명

 

 

as 연산자 (UpCasting / DownCasting)

 

as 연산자도 그림으로 이해해 보면 외우기가 편해집니다.

 

상속 관계에서

 

'자식 클래스' --> '부모 클래스'로 형 변환 시켜 줄 때는 Upcating (위로 올라가니까 up)

 

'부모 클래스' --> '자식 클래스'로 형 변환을 할 때는 Downcasting (아래로 내려가니까 down)

 

Casting은 메모리의 값을 수정하여 타입을 변경하는 게 아니라 단순히 캐스팅된 타입의 인스턴스로 사용한다고 하네요.
(Swift는 타입 취급에 대해 관대하지 않기 때문에 단순히 해당 타입인 것처럼 취급하려는 목적인 것 같습니다.)

 

 

Upcasting (as)

일단 Upcasting무조건 성공합니다.

 

예제를 보면서 알아봅시다.

class Person {
    let name = "Danny"
    let age = 20
}

class Developer: Person {
    let skill = "Swift"
}

 

메모리 구조에 대해서 조금 이해하고 넘어가면 좋은데,

 

한 개의 저장 속성마다 하나의 메모리 공간을 갖는다고 가정해 볼게요.

 

그러면 Person의 인스턴스에 대해서는 name, age 이렇게 2개의 메모리 공간이 생성되겠죠?

let person = Person()

person.name    // "Danny"
person.age     // 20

 

그리고, Developer의 인스턴스는 Person 상속을 받았으므로 

 

name, ageskill 이렇게 3개의 메모리 공간이 생기게 되죠.

let danny = Developer()

danny.name    // "Danny"
danny.age     // 20
danny.skill   // "Swift"

 

위에서 Upcasting'자식 클래스' --> '부모 클래스'형 변환을 시켜 준다고 말씀드렸어요.

 

자 간단히 생각해 봅시다. 3개의 메모리 공간에서 2개의 메모리 공간으로 변경되는 것은 크게 문제가 되지 않아요.

 

상속 관계에서 '자식 클래스'' 부모 클래스'멤버를 당연히 포함하고 있고 

 

또한, 메모리 공간없던 게 생기는 게 아니고 '자식 클래스'skill 속성만 빼주면 

 

Person 타입으로 사용이 가능하므로 Upcasting이 되는 거죠!

 

 

바로 DeveloperPerson으로 Upcasting을 해봅시다.

let castingDanny = danny as Person

castingDanny.name   // "Danny"
castingDanny.age    // 20
castingDanny.skill  // Error: Value of type 'Person' has no member 'skill'

참고. castingDannyPerson 타입이므로 Developer의 속성 skill은 접근이 불가능합니다.

 

아래와 같이 직접적으로 as 형변환 없이 타입을 선언만 해줘도 자동으로 캐스팅이 됩니다.

let castingDanny: Person = danny

castingDanny.name   // "Danny"
castingDanny.age    // 20

 

바로 Downcasting으로 넘어가겠습니다. 위의 메모리 개수를 생각하고 읽어주세요.

 

 

Downcasting (as? / as!)

Downcasting'부모 클래스' --> '자식 클래스'로 형 변환 해주는 겁니다.

 

Downcasting메모리 공간을 생각하면서 보면 '부모 클래스' 2개에서 '자식 클래스' 3개로 변경을 시켜줘야 되죠.

 

메모리 공간을 2개에서 3개로 변경을 시켜 줘야 하는데

 

이렇게 변환할 때는 새로운 메모리있을 수도 있고 없을 수도 있으므로 (실패가 가능하므로)

 

옵셔널"as" 뒤에다 붙여 주는 거예요. (as?)

 

 

위랑 같은 예제를 갖고 왔습니다.

class Person {
    let name = "Danny"
    let age = 20
}

class Developer: Person {
    let skill = "Swift"
}
let danny = Developer()
let castingDanny: Person = danny

castingDanny.name   // "Danny"
castingDanny.age    // 20
castingDanny.skill  // Error: Value of type 'Person' has no member 'skill'

이와 같이 Person 타입의 castingDanny가 있습니다.

 

Person 타입이므로 Developer속성skill접근이 불가능하죠.

 

이걸 사용하기 위해선 Person(부모) -> Developer(자식) 타입으로 Downcasting을 해줘야 됩니다.

let castingDeveloper = castingDanny as? Developer

 

as? 가 옵셔널이므로 현재 타입Developer? 가 됩니다.

 

사용 시 옵셔널 바인딩을 통해 풀어서 사용해 주면 됩니다.

if let castingDeveloper = castingDanny as? Developer {
    castingDeveloper.name    // "Danny"
    castingDeveloper.age     // 20
    castingDeveloper.skill   // "Swift"
}

 

만약, 부모 인스턴스가 자식 인스턴스를 갖고 있을 거라는 확신이 있을 때만

 

as!강제 옵셔널 언레핑을 사용하도록 하자.

let castingDeveloper = castingDanny as! Developer

castingDeveloper.name    // "Danny"
castingDeveloper.age     // 20
castingDeveloper.skill   // "Swift"

될 수 있으면 옵셔널 바인딩으로 풀어주는 게 안전합니다.

 

 

as? = 안전한 방법. 실패 시 nil을 리턴함

 

as! = 확신이 있을 때만 사용. 실패 시 에러를 발생

 

참고. 스위프트에서는 내부적으로 여전히 Objective-C의 프레임워크를 사용하는 것이 많아 서로 상호 호환이 가능하도록 설계해 둠

        ex) Sting <--> NSString

 

 

Any와 AnyObject를 위한 타입 캐스팅

Any 타입

  • 모든 타입을 표현할 수 있는 타입
  • 기본 타입(Int, String, Bool, ...) 등 포함, 클래스, 구조체, 열거형, 함수타입 까지도 포함 (옵셔널 타입도 포함)

 

장점: 모든 타입을 표현이 가능하다.

var closure: (String) -> String = { name in
    name
}

let array: [Any] = [5, "MacBook", 1.5, Developer(), closure]

 

단점: 저장된 타입의 메모리 구조를 알 수 없기 때문에

let array: [Any] = ["MacBook", 5, Developer(), closure]

let index0: Any = array[0]
let index1: Any = array[1]
let index2: Any = array[2]
let index3: Any = array[3]

사용하려면 항상 다운 캐스팅을 해줘야 된다.

let index0: String = (array[0] as! String)
let index1: Int = (array[1] as! Int)
let index2: Developer = (array[2] as! Developer)
let index3: (String) -> String = (array[3] as! (String) -> String)

 

 

AnyObject 타입

  • 모든 클래스 인스턴스도 표현할 수 있는 타입
let objcArray: [AnyObject] = [Person(), Developer()]

let objcIndex0: Person = (objcArray[0] as! Person)
let objcIndex1: Developer = (objcArray[1] as! Developer)

AnyObject 마찬가지로 다운 캐스팅을 해줘야 사용이 가능합니다.

let objcIndex0: Person = (objcArray[0] as! Person)
let objcIndex1: Developer = (objcArray[1] as! Developer)

 

 

참고. 옵셔널값의 Any 타입

  • Upcasting으로 컴퍼일러 경고를 없앨 수 있습니다.
let optionalNum: Int? = 10
print(optionalNum)          // 경고
print(optionalNum as Any)   // 경고 없음

 

 

지금까지 배운 것들을 응용하여 swich문으로 분기 처리를 해보고 마무리하겠습니다.

let array: [Any] = ["MacBook", 5, Developer(), {(str: String) in str}]
for (index, value) in array.enumerated() {
    switch value {
    case is String:
        print("Index \(index) - String 타입 입니다.")
    case let num as Int:
        print("Index \(index) - \(num), Int 타입입니다")
    case let danny as Developer:
        print("Index \(index) - 개발자 : \(danny.name), 나이 : \(danny.age), 스킬 : \(danny.skill)")
    case let closure as (String) -> String:
        print("Index \(index) - \(closure) 클로저 타입 입니다.")
    default: break
    }
}

// Index 0 - String 타입 입니다.
// Index 1 - 5, Int 타입입니다
// Index 2 - 개발자 : Danny, 나이 : 20, 스킬 : Swift
// Index 3 - (Function) 클로저 타입 입니다.

 

 

부족한 설명이지만, 조금은 이해 가셨나요?

틀린 내용이 있다면 언제든지 지적해 주시면 감사히 받겠습니다. 🫠
읽어주셔서 감사합니다 😃

 

 

참고

 

Type Casting — The Swift Programming Language (Swift 5.7)

Type Casting Type casting is a way to check the type of an instance, or to treat that instance as a different superclass or subclass from somewhere else in its own class hierarchy. Type casting in Swift is implemented with the is and as operators. These tw

docs.swift.org

 

앨런 Swift문법 마스터 스쿨 (온라인 BootCamp - 2개월과정) - 인프런 | 강의

Swift문법을 제대로 이해, 활용해보고자 하는 철학을 바탕으로 과정이 설계되었습니다. 코딩에 대해 1도 모르는 비전공자를 시작으로 네카라쿠배에 입사할 수 있는 초고급 수준까지 올리는 것을

www.inflearn.com

 

 

Swift) is, as - 타입 캐스팅 (Type Casting)

안녕하세요, 소들입니다!!!! 오늘 포스팅은 is와 as 즉, 타입 캐스팅에 대해 알아보려고 해요 :D 타입 캐스팅,,, 개발 하다보면 가끔 보이는.. is as as? as! 막 ... 이렇게... 단어 하나갖고 장난질이여;;;

babbab2.tistory.com

 

반응형
profile

Danny의 iOS 컨닝페이퍼

@Danny's iOS

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!