Swift(스위프트) nil과 Type Casting(타입캐스팅)에 대한 기록 with RxSwift
Swift를 다시 공부하던 도중 기록하면 좋을만한 부분이 있어서 작성한다.
Any?, AnyObject? 같은 옵셔널이 아닌 Any, AnyObject 등에는 nil을 직접적으로 넣을 수 없다. 하지만 아래와 같은 경우에는 가능하다.
let a: Int? = nil
let b: Any = a as Any //syntax error가 뜨지 않는다. 'Any?'로 쓰십시오!라고 뜰 줄 알았는데..
//또는
let c: NSObject? = nil
let d: AnyObject = c as AnyObject
b는 실질적으로 nil이지만 nil이 아닌 것처럼 보인다. 이런 부분에 있어 유의하도록 하자. (d도 마찬가지. 값 자체가 복사됐느냐 참조가 복사됐느냐의 차이는 존재)
위와 같은 경우가 있을 수 있다는 것은 RxSwift Github - ‘CLLocationManager+Rx’ Example을 통해 알았다.
private func castOptionalOrThrow<T>(_ resultType: T.Type, _ object: Any) throws -> T? {
if NSNull().isEqual(object) { // object == nil 로 조건문을 바꾸면 warning 표시.
return nil
}
//NSNull: nil을 허용하지 않는 콜렉션에서 null value를 대표하는 싱글톤 오브젝트
//nil이지만 nil이 아닌것처럼 쓸 수 있다는 건가?
guard let returnValue = object as? T else {
throw RxCocoaError.castingError(object: object, targetType: resultType)
}
return returnValue
}
링크 맨 밑에 위와 같은 함수가 있는데 ‘nil일 수 있는 인자를 Any?가 아닌 Any타입의 object로 받는 것’과 내부에서 ‘nil 체크를 NSNull().isEqual()로 하는 것’을 보고.. 처음에 굉장히 혼란스러웠다. 왜 object 파라미터가 Any?타입이 아닌 Any일까 하면서.. 물론 외부에서 넘어오는 것 자체가 [Any]에서 하나를 고른 Any이기는 하지만.
delegate.methodInvoked()에 의해 메소드 파라미터들은 Any로 캐스팅되고 [Any] 배열로 반환된다. 따라서 특정 파라미터는 Any타입이라고 보여도 사실은 nil일 수 있다. (이를테면 CLLocationManagerDelegate - didFinishDeferredUpdatesWithError의 두번째 파라미터는 Error? 옵셔널인데, Any로 캐스팅되어도 사실 nil일 수 있다는 것)
왜 methodInvoked()에 의해 반환되는 Observable시퀀스가 Observable<Any?>가 아닌 Observable<Any>인걸까?
다른 타입캐스팅(업캐스팅)에도 되는걸까?
결론부터 말하자면 안된다. Any와 AnyObject에만 된다.
//struct 테스트
protocol ParentProtocol {}
struct DescendantStruct: ParentProtocol {}
let descendant: DescendantStruct? = nil
let parent: ParentProtocol = descendant as ParentProtocol
Value of type 'DescendantStruct?' does not conform to 'ParentProtocol' in coercion
//class, protocol 테스트
protocol ParentProtocol {}
class DescendantClass: ParentProtocol {}
let descendant: DescendantClass? = nil
let parent: ParentProtocol = descendant as ParentProtocol
Value of type 'DescendantClass?' does not conform to 'ParentProtocol' in coercion
//class 테스트
class ParentClass {}
class DescendantClass: ParentClass {}
let descendant: DescendantClass? = nil
let parent: ParentClass = descendant as ParentClass
Value of optional type 'DescendantClass?' must be unwrapped to a value of type 'DescendantClass'
세가지 경우 모두 컴파일러에서 오류를 띄웠다. Optional 타입을 Any타입으로 캐스팅 하는 것은 Swift 공식문서 맨 밑에도 나와있기는 하다. 그런데 왜 가능하게 한걸까?
이 궁금증에 대한 해답은 아래 링크를 참조하도록 하자.
Why non optional Any can hold nil? - StackOverflow Non-Optional Any가 nil인지 체크하기 Casting from Any to Optional
댓글남기기