Swift에 어울리는 Objective-C 코드 작성하기

2021년 12월 12일 수정

C 함수 별명

예를 들어 아래 함수가 있다고 치자.

int SuperGoodAdditionFeature(int a, int b)
{
    return a + b;
}

이 함수를 Swift에서 접근하려면 아래처럼 해야 한다.

let result = SuperGoodAdditionFeature(1, 2)

근데 이름도 길도 필드명이 없어서 읽기가 불편하다는 점이 있을 수 있다. 이럴 때를 위해서 NS_SWIFT_NAME 을 이용해 별명을 지어줄 수 있다.

위 함수의 인터페이스를 아래와 같이 정의해주자.

int SuperGoodAdditionFeature(int a, int b) NS_SWIFT_NAME(add(a:b:));

그러면 이제 Swift에서는 아래와 같이 코딩할 수 있다.

let result = add(a: 1, b: 2)

클래스 정적 메서드의 별명

구현한 클래스에서 여러 편의를 위해 생성자 대신 정적 메서드를 제공할 수도 있다. 예를 들어 아래와 같은 클래스 정의를 보자.

@interface FooClass : NSObject

+ (FooClass *)fooClassWithName:(NSString *)name;
+ (FooClass *)ultraGoodObjectWithNickName:(NSString *)name;

@end

이 두 정적 메서드는 Swift에서 편리하게 쓸 수 있기 기본 생성자 형태로 감싸진다. 특히 여기서 fooClassWithName 메서드는 아래와 같이 쓸 수 있다.

let f = FooClass(name: "James")

친절하게 만들어 주는 것 같다.

하지만 불행히도 ultraGoodObjectWithNickName: 메서드는 위의 방식으로 호출할 수 없다. 그저 그런 정적 메서드 방식으로 호출하는 수밖에 없다.

let b = SomeClass.ultraGoodObject(withNickName: "Knight")

이걸 위와 비슷한 형식으로 고쳐줄 수도 있다. 아래처럼 해당 메서드 정의를 고쳐보자.

+ (FooClass *)ultraGoodObjectWithNickName:(NSString *)name NS_SWIFT_NAME(init(nickName:));

이제 위의 정적 메서드는 아래와 같이 쓸 수 있게 된다.

let b = SomeClass(nickName: "Knight")

열거형(enumerations)

본래 C 언어의 열거형은 enum 으로 정의할 수 있다. 하지만 enum 을 통해 정의하게 되면 Swift에는 어울리지 않게 각 필드의 이름이 그대로 심볼이 되어버린다.

이를 Swift에 어울리게 구현하려면 NS_ENUM 이라는 것을 이용할 수 있다.

typedef NS_ENUM(int, Foobar) {
    FoobarDefault = 0,
    FoobarGood,
    FoobarBad
};

이렇게 구현하면 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)
};

이제 추가했던 마지막 이름은 Swift 코드에선 .unknown 으로 사용할 수 있게 된다.

옵셔널(Optinals)

원래 Objective-C에는 옵셔널 개념이 없다. C 언어 자체가 그렇듯이 모든 인스턴스는 포인터를 이용한다. 그래서 Swift에서는 해당 타입은 전부 옵셔널로 처리해버린다. 즉 nil 이 될 수 있게 말이다.

하지만 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

위 코드에서 쓰인 nullablenonnull 이라는 심볼에 주목하면 아마 이해가 될 것이다. nullable 이 바로 옵셔널 형태가 된다. 그리고 nonnullnil 을 가질 수 없는 일반적인 값을 의미한다. 이게 바로 Non-Optional 이라는 보증이다. 그리고 이 모두 프로퍼티와 메서드 다 사용할 수 있다. 물론 보증에 문제가 없도록 코딩하는 것이 필수이겠지만 말이다.

이외에도 비슷하게 _Nullable 이나 _Nonnull 같은 밑줄이 붙은 형태의 심볼도 있지만 이것보단 위의 방식이 가독성 면에서는 나을 것 같아서 다른 방식은 다 생략했다. 필요하다면 직접 알아보자.

아예 광역으로 nullability를 해제하는 매크로도 있다.

NS_ASSUME_NONNULL_BEGIN
...
NS_ASSUME_NONNULL_END

이름의 의미에서 이해가 되겠지만 위 두 매크로 사이에 있는 포인터들은 모두 nonnull이 기본이 된다.