(Swift) Objctive-C와 Swift 타입의 브릿징

브릿징

Swift의 많은 기본 타입들은 Objective-C의 파운데이션 타입에 대응되는 것들이 있고 (e.g. String <-> NSString) 이렇게 카운터파트가 존재하는 타입들은 파운데이션타입으로 브릿징된다. 브릿징은 Objective-C 타입이 Swift 타입으로 행동하거나 혹은 반대로 Swift 타입이 Objective-C 타입처럼 행동하여 이 둘을 상호간에 바꿔쓸 수 있음을 말한다. 다시 말해 파운데이션 API를 사용하면서 NSString 을 사용해야 할 때 String을 쓰거나 반대로 Swift 함수/메소드 호출 시 String 을 써야 할 때 NSString을 쓰는 것이 허용된다는 말이다. 1

Swift는 어떤 Objective-C 타입들을 자동으로 Swift 타입으로 변환하며, 그 반대의 동작도 자동으로 수행합니다. 이렇게 Objective-C와 Swift 사이에서 전환되는 타입들을 “bridged types”라 부릅니다. 예를 들어, Swift 코드에서 NSString 파라미터를 요구하는 Objective-C API에 String 타입의 값을 넘겨줄 수 있습니다. 또한 많은 코코아 프레임워크들은 API를 좀 더 Swift 스럽게 새로 정의했습니다. 예를 들어 NSCoderdecodeObjectOfClass(_:forKey:)는 Swift의 제네릭 타입을 통해 보다 강한 타입시그니처를 갖게 됐습니다.2

사실 이 브릿징은 Objective-C에 익숙하다면 낯선 개념은 아니다. 코코아에는 Objective-C가 아닌 C로 제작된 코어 파운데이션 프레임워크가 있고, 이미 NS* 클래스들은 CF* 타입들과 브릿징되고 있다.

Swift와 Objective-C 타입간의 브릿징에 대해서는 아직 많이 알려진 바가 없는데, Objective-C와 C간의 브릿징 (엄밀히는 파운데이션과 코어파운데이션간의 브릿징)이 어떻게 이루어지는지에 대해 살펴보자.

Toll-Free Bridging

코어파운데이션 프레임워크와 파운데이션 프레임워크에는 서로 바꿔쓸 수 있는 (can be used intercahngeably) 많은 데이터 타입들이 있습니다. 이러한 능력은 “toll-free bridging”이라 불리는데 코어파운데이션의 함수 호출과 파운데이션의 메시지 호출의 파라미터로 같은 타입을 사용할 수 있다는 뜻입니다. 예를 들어 NSLocale은 코어 파운데이션의 대응타입인 CFLocale과 서로 바꿔쓸 수 있으며, NSLocale* 인스턴스는 CFLocaleRef에 해당됩니다. 따라서 어떤 메소드에서 NSLocale *타입의 파라미터를 받는다면, 여기에 CFLocaleRef 타입의 값을 보낼 수 있고 그 반대도 가능합니다. 이 때 컴파일러의 경고를 없애기 위해서는 타입을 그대로 캐스팅해버리면 됩니다. 3

파운데이션과 코어파운데이션간의 브릿징은 toll-free (비용이 들지 않는)로 표현된다. 이는 Objective-C의 메타클래스 컨셉과 Objective-C 또한 C 언어라는 두가지 지점에서 출발한 아이디어로 묘사된다. 여기에 파운데이션의 기본 컨셉 중 하나인 클래스 클러스터가 사용된다.

코어파운데이션 타입이 Objective-C 클래스로 브릿징되는 방법은 의외로 간단하다. 모든 브릿지 가능한 클래스는 실제로는 클래스 클러스터이다. 이는 무슨 뜻이냐면 실제 퍼블릭한 클래스는 추상 클래스이며 실제 코어 기능은 private한 서브 클래스에서 구현하는 것이다. 코어 파운데이션 클래스는 프라이빗 서브클래스 중 하나와 매치하는 메모리 레이아웃을 얻게된다. 이 서브 클래스는 코어파운데이션 클래스의 Objective-C 카운터 파트가 된다. 외부에서 보면 이 둘은 완전히 동일한 인터페이스를 공유하기 때문에 똑같아 보이고 똑같은 일을 한다.

자세한 예를 들기위해 NSString을 보자. NSString은 일종의 추상 클래스이다. 이 클래스의 인스턴스를 만들 때마다 사실은 그 서브클래스의 인스턴스 하나를 얻게 된다.

그러한 서브 클래스 중 하나가 NSCFString이다. 이는 CFString의 직접적인 카운터파트이며, CFString의 첫번째 필드는 isa 포인터로, 이 포인터는 NSCFString을 가리키고 있어서 마치 Objective-C 객체처럼 동작하게 되는 것이다.

NSCFStringNSString 객체로 동작하는데 필요한 메소드들을 구현하고 있다. 이러한 구현에는 두가지 전략이 있을 수 있는데, 하나는 코어파운데이션 카운터파트에서 호출될 수 있는 스텁으로 구현하는 방법과 다른 하나는 코어파운데이션이 구현한 것과 똑같이 동작하도록 구현하는 것이다. 실제 프레임워크에서는 이 두가지 방법이 섞여서 사용되고 있다.

따라서 CFString 객체는 NSCFString인 셈이고 이는 NSString의 서브클래스이므로 NSString처럼 행동하기 위해 필요한 모든 메소드를 다 가지고 있다.

Swift의 클래스 역시 Objective-C의 클래스처럼 메타 타입의 일종이며, Swift와 Objective-C 간에 중간 연결자 역할을 하는 클래스/타입이 내부적으로 정의되어 있는지도 모른다. 이는 현재의 두 언어 모두 LLVM 컴파일러를 사용하여 동일한 중간언어로 번역된다는 점에서 어느정도 가능하리라 예상은 된다.

비단, Swift의 클래스 타입 뿐만아니라 값 시멘틱 타입들도 NS* 타입으로부터 브릿징하는 경우 데이터 전체가 복사되기 보다는 NS* 클래스의 내부 스토리지 기반으로 생성된다. 따라서 아마 내부적으로는 class와 struct는 메타 타입의 상속 단계에서 동일한 조상을 가지고 있고, 그 하위에서 각각의 시멘틱에 따라 동작 방식이 결정되는 형태로 운용되는게 아닌가 생각된다.


참고

  1. Toll-Free Bridging in Concepts in Objective-C Programming : Apple Developer
  2. Friday Q&A 2010-01-22: Toll Free Bridging Internals by Mike Ash: mikeash.com
  1. Swift 버전이나 플랫폼에 따라서 조금 다르게 이해해야 할 필요는 있다. 예를 들어 리눅스 플랫폼에서는 StringNSString으로 자동 변환되지 않으며, 경우에 따라서는 as 연산자를 통해서 명시적으로 캐스팅해야 할 수도 있다. 
  2. https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WorkingWithCocoaDataTypes.html#//apple_ref/doc/uid/TP40014216-CH6-ID61 
  3. https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaEncyclopedia/Toll-FreeBridgin/Toll-FreeBridgin.html