본문 바로가기

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

기술적인 이야기/애플 플랫폼 개발 2022. 3. 6.
반응형

이 글은 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라는 이름으로 사용할 수 있게 된다.

뭐 약간은 억지 같은 예제였지만 이런 식으로 쓸 수도 있다는 말이다.

728x90

옵셔널(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

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

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

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

NS_ASSUME_NONNULL_BEGIN
...
NS_ASSUME_NONNULL_END

이름의 의미에서 이해가 되겠지만 위 두 매크로 사이에 있는 포인터들은 모두 nonnull로 선언한 것처럼 번역된다.

최근의 Xcode에서 Objective-C 클래스 코드를 생성하면 아예 위 매크로로 둘러싸인 보일러플레이트(boilerplate) 코드를 만들어 준다. nonnull이 주는 이점이 그만큼 크다는 의미일 것이다.

마무리

이외에도 Objective-C와 Swift 사이의 인터페이스 호환에 도움이 되는 여러 방법이 있을 수도 있다. 물론 발견하는 대로 이 글에 업데이트하거나 혹은 양이 많아질 것 같으면 별도의 글을 추가할 생각이다. 하지만 이 정도만 해도 제법 괜찮은 코드가 만들어지는 것 같기도 하다.

그런데 미래에는 과연 Objective-C가 살아남을 수 있을까? 점유율이 점점 줄어들기는 할 것이지만 아마도 흔적은 남지 않을까?

728x90
반응형

댓글