이 글은 Swift 호환성이 좋은 Objective-C 코드를 작성하는 방법에 관한 내용이다. Swift라는 언어가 워낙 쓰기 편하게 나와서 이제는 과연 얼마나 Objective-C 코드가 쓰일까 알 수 없는 요즘이다. 그래도 개인적으로는 쓸 수밖에 없는 상황이기도 하고 아직 쓰이는 곳이 분명 있으리라는 믿음으로 이런 글을 쓰게 되었다.
참고로 이 글의 핵심 키워드는 아마도 NS_SWIFT_NAME
인 것 같다.
C 함수 별명
Objective-C 코드로 작성한 C 함수의 인터페이스는 Swift 측에서 보면 좀 답답할 수도 있다. 예를 들어 아래 함수가 있다고 치자.
int SuperGoodAdditionFeature(int a, int b)
{
return a + b;
}
이 함수를 Swift에서 접근하려면 아래처럼 해야 한다.
let result = SuperGoodAdditionFeature(1, 2)
이름도 길고 필드명이 없어서 읽기가 불편하다. 물론 C 언어의 함수는 원래 이런 방식으로 호출하기는 하지만 말이다.
이럴 때를 위해서 Objective-C 코드에서 NS_SWIFT_NAME
을 이용해 Swift 언어에 맞는 별명(인터페이스)을 지어줄 수 있다.
위 예시의 함수의 인터페이스를 아래와 같이 수정해봤다.
int SuperGoodAdditionFeature(int a, int b) NS_SWIFT_NAME(add(a:b:));
그러면 이제 Swift에서는 아래와 같이 코딩할 수 있다.
let result = add(a: 1, b: 2)
당연히 이 코드가 좀 더 Swift 스럽다.
다만 이름 자체를 이렇게 완전히 바꿔버리는 것은 그다지 좋은 행위는 아닐 것 같지만 말이다.
클래스 정적 메서드의 별명
클래스를 좀 더 편하게 인스턴스화 할 수 있도록 생성자 대신 정적 메서드(static methods)를 구현하는 경우도 있다. 예를 들어 아래와 같은 클래스 정의를 보자.
@interface FooClass : NSObject
+ (FooClass *)fooClassWithName:(NSString *)name;
+ (FooClass *)ultraGoodObjectWithNickName:(NSString *)name;
@end
이 두 정적 메서드는 일단은(?) Swift에서 편리하게 쓸 수 있도록 기본 생성자 형태로 감싸 진다. 특히 여기서 fooClassWithName:
메서드는 아래와 같이 쓸 수 있다.
let f = FooClass(name: "James")
위 코드는 아무 추가 수정 없이도 Swift에 어울린다. 기본 번역(?)이 매우 친절하다고 느껴질 정도다.
하지만 불행히도 ultraGoodObjectWithNickName:
메서드는 위의 방식으로 호출할 수 없다. 번역기가 알아서 처리할 만한 힌트가 부족해서 아래와 같이 원래의 이름으로 호출하는 수밖에 없다.
let b = FomeClass.ultraGoodObject(withNickName: "Knight")
물론 위와 같은 방식은 그다지 Swift 스럽지 않다.
이럴 때도 좀 더 어울리는 이름을 가질 수 있도록 고칠 수 있다. 아래처럼 NS_SWIFT_NAME
을 이용해서 해당 메서드 정의를 고쳐보자.
+ (FooClass *)ultraGoodObjectWithNickName:(NSString *)name NS_SWIFT_NAME(init(nickName:));
이제 위의 정적 메서드는 아래와 같이 쓸 수 있게 된다.
let b = FooClass(nickName: "Knight")
역시 이런 인터페이스가 좀 더 Swift에 어울린다.
열거형(enumerations)
본래 C 언어의 열거형은 enum
으로 정의할 수 있다. 하지만 enum
을 통해 열거형을 정의하게 되면 Swift에는 어울리지 않게 각 필드의 이름이 그대로 심벌이 되어버린다.
이를 Swift에 어울리게 구현하려면 NS_ENUM
이라는 것을 이용할 수 있다.
typedef NS_ENUM(int, Foobar) {
FoobarDefault = 0,
FoobarGood,
FoobarBad
};
위와 같이 NS_ENUM
을 이용해 열거형을 구현하면 Swift에 어울리는 형태로 번역이 된다. 즉 Swift 코드에서 Foobar
타입 인스턴스의 값을 .default
, .good
, .bad
와 같이 머릿글자를 생략한 형태의 이름으로 쓸 수 있다는 말이다. 이 방식이 확실히 Swift에 어울린다고 볼 수 있다.
다만 위처럼 통일된 인터페이스가 아닌 특수한 이름을 써야 하는 상황이 있을 수도 있다. 예를 들어 아래와 같이 특이한 이름을 추가했다고 쳐보자.
typedef NS_ENUM(int, Foobar) {
FoobarDefault = 0,
FoobarGood,
FoobarBad,
IHaveNoIdea // 이상한 이름
};
위 예에서 마지막에 추가한 IHaveNoIdea
라는 이름은 다른 심벌의 이름들과 패턴이 일치하는 게 없다. 그래서 어울리지 않게 .IHaveNoIdea
와 같은 이름 전체로만 접근할 수가 없을 것이다.
이를 해결하기 위해 또 NS_SWIFT_NAME
을 동원할 수 있다.
typedef NS_ENUM(int, Foobar) {
FoobarDefault = 0,
FoobarGood,
FoobarBad,
IHaveNoIdea NS_SWIFT_NAME(unknown)
};
위와 같이 수정하면 마지막 IHaveNoIdea
심벌을 Swift 코드에서 .unknown
라는 이름으로 사용할 수 있게 된다.
뭐 약간은 억지 같은 예제였지만 이런 식으로 쓸 수도 있다는 말이다.
옵셔널(Optionals)
원래 Objective-C에는 옵셔널 개념이 없다. C 언어 자체가 그렇듯이 모든 인스턴스는 포인터를 이용한다. 그래서 Objective-C의 모든 인스턴스 변수는 nil
값을 가질 수 있다. 따라서 Objective-C로 코딩한 인스턴스 변수들은 Swift에서는 전부 옵셔널 인터페이스로 번역된다.
하지만 Swift의 세계에선 옵셔널이 아닌 변수들이 더 활발하게 쓰이고 당연히 더 편하다. 이를 위해 Objective-C 측에서 보증(?)을 서주는 방법이 있다. 아래의 예제를 보자.
@interface FooClass : NSObject
@property (nonatomic, strong, nullable) NSString *name;
@property (nonatomic, strong, nonnull) NSString *identifier;
- (nonnull NSString * nonnull)makeNickNameWithType:(nullable NSString *)type;
@end
위 코드에서 쓰인 nullable
과 nonnull
이라는 심벌에 주목하면 아마 이해가 될 것이다. nullable
이 기본 상태인 옵셔널로 번역된다. 그리고 nonnull
은 nil
을 가질 수 없는 일반적인 값을 의미하도록 번역된다. 이게 바로 "Non-Optional이라는 보증"이다. 그리고 이 모두 프로퍼티와 메서드 다 사용할 수 있다. 물론 보증에 문제가 없도록 코딩하는 것이 필수이겠지만 말이다.
이외에도 비슷하게 _Nullable
이나 _Nonnull
같은 밑줄이 붙은 형태의 심볼도 있지만 이것보단 위의 방식이 가독성 면에서는 나을 것 같아서 다른 방식은 다 생략했다. 필요하다면 직접 알아보자.
아예 광역으로 nullability를 해제하는 매크로도 있다.
NS_ASSUME_NONNULL_BEGIN
...
NS_ASSUME_NONNULL_END
이름의 의미에서 이해가 되겠지만 위 두 매크로 사이에 있는 포인터들은 모두 nonnull
로 선언한 것처럼 번역된다.
최근의 Xcode에서 Objective-C 클래스 코드를 생성하면 아예 위 매크로로 둘러싸인 보일러플레이트(boilerplate) 코드를 만들어 준다. nonnull이 주는 이점이 그만큼 크다는 의미일 것이다.
마무리
이외에도 Objective-C와 Swift 사이의 인터페이스 호환에 도움이 되는 여러 방법이 있을 수도 있다. 물론 발견하는 대로 이 글에 업데이트하거나 혹은 양이 많아질 것 같으면 별도의 글을 추가할 생각이다. 하지만 이 정도만 해도 제법 괜찮은 코드가 만들어지는 것 같기도 하다.
그런데 미래에는 과연 Objective-C가 살아남을 수 있을까? 점유율이 점점 줄어들기는 할 것이지만 아마도 흔적은 남지 않을까?
'기술적인 이야기 > 애플 플랫폼 개발' 카테고리의 다른 글
WKWebView에서 Page Down 구현하기 (268) | 2022.05.08 |
---|---|
Xcode Playground에서 Swift Package 이용하기 (277) | 2022.04.17 |
UserDefaults 기초 정리 (242) | 2021.10.17 |
SwiftUI로 액션시트 띄우기 (iOS 15, macOS Monterey) (273) | 2021.10.12 |
SwiftUI에서 경고창(Alert Dialog) 띄우기(iOS 15, macOS Monterey) (276) | 2021.10.08 |
댓글