본문 바로가기
iOS/Swift

[Swift] 타입 캐스팅(Type Casting)

by Dev.Andy 2023. 7. 28.

머리말

포스팅을 하게 된 이유

수업 시간에 UITableViewCell? 타입의 cell이라는 상수에 하위 클래스의 CustomTableViewCell 타입으로 다운캐스팅(as!)을 하는 것을 배웠다. as라는 키워드도 익숙하지 않은데 물음표(?)나 느낌표(!)까지 들어가 있어서 더더욱 무서웠다...

이후에 다운캐스팅을 포함한 타입 캐스팅에 대해 개념도 배우고 실습도 했지만 아직 긴가민가 해서 포스팅을 하게 됐다.

강의 시간의 코드 일부

class CustomTableViewCell: UITableViewCell {
    // ...
    
    func configureCell(row: ToDo) {
        // ...
    }
    
    // ...
}

class CustomTableViewController: UITableViewController {
    // ...

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
        // 다운캐스팅(Downcasting)
        let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier) as! CustomTableViewCell
        
        let row = todo.list[indexPath.row]
        
        cell.configureCell(row: row)
        
        return cell
    }
    
   // ...
}

타입 캐스팅의 개념과 사용

개념

Swift 영문 공식 문서에 적힌 타입 캐스팅의 정의와 이를 필자가 번역한 것은 아래와 같다.

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)은 인스턴스의 타입을 확인하거나, 해당 인스턴스를 자기자신의 클래스 위계 구조에서 상위 클래스나 하위 클래스의 인스턴스로 다루기 위한 방법이다.

종류

(1) 타입을 확인하는 is 연산자

is 연산자는 타입 확인 연산자(type check operator)인데, 해당 인스턴스가 하위 클래스의 타입인지 아닌지를 판별하여 그 결괏값으로 각각 true나 false를 반환한다.

(2) 다운캐스팅을 하는 as? 연산자와 as! 연산자

다운캐스팅(downcasting)이란, 특정한 클래스 타입의 상수/변수가 "하위 클래스"의 인스턴스를 참조하는 것이다. 이렇게 참조하여 상위 클래스 타입의 변수/상수를 하위 클래스의 상수/변수처럼 다룰 수 있다. 여기에는 as 연산자를 사용하는데, 두 가지 방식이 있다.

  1. as? - 조건적인 형식(conditional form)으로, 다운캐스팅을 시도하되 결괏값을 옵셔널 타입으로 반환하고, 실패 시 nil을 반환한다. 따라서 이를 언래핑하는 과정이 또한 필요하기에, 총 두 번의 과정(다운캐스팅 & 언래핑)을 거쳐야 한다.
  2. as! - 강제적인 형식(forced form)으로, 다운 캐스팅을 강제로 언래핑하여 한번에 다운캐스팅을 하는 것이다. nil이 안 나온다는 확신이 있을 때만 써야 하기에 각별한 주의가 필요하다. 실패 시 런타임 에러가 발생하기에 앱이 강제 종료된다.

(3) 업캐스팅을 하는 as 연산자

다운캐스팅과는 반대로 업캐스팅(upcasting)은 특정한 타입의 "상위 클래스" 인스턴스를 참조하는 것이다. 자기자신보다 상위 클래스를 접근하는 것이기에 캐스팅이 항상 성공할 수밖에 없어서, 다운캐스팅처럼 옵셔널 연산자(?)나 강제 언래핑 연산자(!)를 사용하지 않는다.

타입 캐스팅 실습

클래스 정의

class Mobile {
    let name: String
    
    init(name: String) {
        self.name = name
    }
}

class Google: Mobile {
    let paySystem = "Google Pay"
}

class Apple: Mobile {
    let paySystem = "Apple Pay"
}

클래스의 인스턴스 할당

let mobile = Mobile(name: "A Mobile Phone")
let pixel = Google(name: "A Google Pixel")
let iPhone = Apple(name: "An iPhone")

print(mobile.name) // A Mobile Phone
print(pixel.name) // A Google Pixel
print(iPhone.name) // An iPhone

is 연산자 활용

mobile is Mobile // true
mobile is Apple // false
mobile is Google // false

iPhone is Mobile // true
iPhone is Apple // true
iPhone is Google // false

let myPhone: Mobile = Apple(name: "iPhone 13 mini")
print(myPhone.name) // iPhone 13 mini
print(type(of: myPhone)) // Apple

as 연산자 활용 (1) - 다운캐스팅as?

if let value = myPhone as? Apple {
    print(value.paySystem)
}
// Apple Pay

if let value = myPhone as? Google {
    print(value.paySystem)
} else {
    print("TYPE CASTING ERROR")
}
// TYPE CASTING ERROR

as 연산자 활용 (2) - 다운캐스팅as!

let device = myPhone as! Apple
device.paySystem // Apple Pay

let hisPhone = iPhone as! Google
/// error: Execution was interrupted, reason: EXC_BREAKPOINT (code=1, subcode=0x102b3cf88).
hisPhone.paySystem

as 연산자 활용 (3) - 업캐스팅as

let herPhone = iPhone as Mobile
herPhone.paySystem // Value of type 'Mobile' has no member 'paySystem'

꼬리말

이렇게 타입캐스팅의 개념과 종류에 대해서 알아 보고, 코드를 통해 직접 실행해 보았다.

강의 코드 개선 - as! 대신 as?

아무래도 강제로 언래핑하여 다운캐스팅하는 as!보다는 as?가 안전할 것 같아서 옵셔널 바인딩(guard let)을 통해 cell 상수를 가져오는 것으로 코드를 개선해 보았다 :)

class CustomTableViewCell: UITableViewCell {
    // ...
    
    func configureCell(row: ToDo) {
        // ...
    }
    
    // ...
}

class CustomTableViewController: UITableViewController {
    // ...

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: CustomTableViewCell.identifier) as? CustomTableViewCell else {
            return UITableViewCell()
        }
        
        let row = todo.list[indexPath.row]
        
        cell.configureCell(row: row)
        
        return cell
    }
    
   // ...
}

참고 자료

스위프트 공식 문서 (영문)

Type Casting | Documentation

스위프트 공식 문서 한글 번역 사이트

타입 캐스팅 (Type Casting) - Swift

댓글