2015년 2월 24일 화요일

Swift - 'as!' 오퍼레이터 살펴보기

Swift 1.2 업데이트 내역에 관한 글을 쓰면서 as! 오퍼레이터에 대해 좀 오해한 것 같다. 그래서 이 오퍼레이터의 기능에 대해 간단히 살펴보고자 한다.

정체

이 녀석이 가지는 의미는 두 가지이다.
  • Guaranteed Conversion: 안전한 형변환
  • Forced Conversion: 강제 형변환
결국 형변환(Type Casting) 시에 사용된다는 의미인데, 무작정 형변환 하는 것 보다는 클래스의 상속 규칙에 맞게 형변환을 하도록 하는 오퍼레이터이다.

일반적인 형변환 예제

예제 코드를 보자. 이 코드는 애플의 공식 블로그에 올라온 내용과 비슷하지만 약간만 입맛에 맞게 수정하였다.
class Animal {
    let name: String?
    init(name: String) {
        self.name = name
    }
}

class Dog: Animal {
}

let someAnimal = Animal(name: "Some Animal")
someAnimal.name         // "Some Animal"

let someDog = Dog(name: "Some Dog")
someDog.name            // "Some Dog"

let someDogAsAnimal = someDog as Animal
someDogAsAnimal.name    // "Some Dog"

// 일반적인 형변환

let someAnimal2: Animal = someDog as Animal
someAnimal2.name        // "Some Dog"
이 코드에서 마지막 부분이 핵심이다. 특정 클래스의 오브젝트(Dog)를 상위클래스(Animal) 타입으로 형변환 시키는 예 이다.

이런 경우는 아무런 문제 없이 형변환이 된다. 사실 당연한 거다. 이런게 그냥 안되면 그건 OOP라고 부를 수가 없으니까.

안전하게 형변환하기

이어서, 약간은 좀 특수한 예를 보자. 계속 이어지는 코드이다.
let doggyAnimal: Animal = Dog(name: "Doggy")
doggyAnimal.name        // "Doggy"

let doggyDog = doggyAnimal as Dog   // Animal is not convertible Dog
Swift 1.2 부터는 이 코드의 마지막 라인은 컴파일 오류가 발생시킨다. 이런 경우를 다운캐스트(downcast)라고 부르는데 컴파일러 차원에서는 자신의 자식 타입을 파악하지는 않고 있는 것이라 생각된다. 실제로 부모클래스 명세에서는 자신의 자식 클래스들을 알지는 못 하니까.

어쨌거나 이런 경우를 위해서 도입된 것이 as! 오퍼레이터인 것 같다.
let doggyDog = doggyAnimal as! Dog
doggyDog.name           // "Doggy"
이런 식으로 다운캐스트가 가능해진다.

뭔가 불공평하고 C++ 등에 비해 부담스럽게 느껴질지도 모르겠지만, Swift의 Type-Safe 철학을 위해서 바뀐 것이라고 생각해야겠다.

강제로 형변환하기

이제 Animal을 상속받은 또다른 클래스를 만들어 볼 차례이다.
class Cat: Animal {
}

let variousAnimal: Animal = Cat(name: "Kuro")
variousAnimal as? Dog   // nil

variousAnimal as! Dog   // SIGABRT
마지막 라인은 강제로 사촌지간의 타입으로 변환하려 한 것이고 그래서 오류가 발생한다. SIGABRT(Signal Abort) 오류가 발생했는데, 실제로 돌려보면 앱이 죽어버릴 것이다.

강제로 형변환을 했으니 안되는 경우라면 죽어버리는 것이 당연한 것이다. 그리고 이렇게 죽는 경우라면 죽는 위치가 명확하기 때문에 오히려 디버깅에 도움이 된다고 할 수 있다.

이런 강제 캐스팅의 경우 기존의 옵셔널 벗기기 오퍼레이터(!)와 비교해보면 동일하다고 볼 수 있다.
let happy: Dog? = nil
happy?.name             // nil
happy!.name             // EXC_BAD_INSTRUCTION
위 예제의 아래 두 라인의 결과는 그 위의 예제와 동일하게 nil 이거나 죽거나이다.

옵셔널 체인이 안전한 이유는 nil 체크를 통해 인스턴스가 존재하는지 보고 명령을 수행한다. 따라서 as?? 를 이용한 옵셔널 체인과 동일한 모습이다.

반대로 강제로 벗기기(!)의 경우는 다르다. 값이 nil이면 그대로 죽어버린다. 이는 as! 가 죽어버리던 상황과 동일하다.

마무리

애초에 업데이트 내역을 읽어 볼 때 이런 의미는 전혀 찾을수가 없었는데 제대로 알아두지 않으면 큰일 날 뻔 했다. 완전히 의미가 달랐기 때문이다.

어쨌건간에, 기존 옵셔널 문법과 동일한 스타일이기 때문에 혼란스러울 일은 없을 것 같다.

다만, 강제 다운캐스팅이 필요한 때에는 Xcode가 as! 쓰라는 오류를 표시해주기 때문에 굳이 몰라도 코딩하는데 문제는 없을 것 같다. -_-;;;

[관련글] Swift - 옵셔널(Optionals)
[관련글] Swift - 옵셔널(Optional)액세스

댓글 3개 :

익명 :

Swift 공부하면서 궁금하다 싶어서 구글링하면 다 여기서 답이 나오네요 ㅎㅎㅎ

잘보고있습니다. 감사합니다.

이철 :
작성자가 댓글을 삭제했습니다.
익명 :

초보자로써 좋은 공부가 되었습니다 감사합니다~