Swift에 어울리는 Objective-C 코드 작성하기
≡ 목차 (Table of Contents)
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
위 코드에서 쓰인 nullable
과 nonnull
이라는 심볼에 주목하면 아마 이해가 될 것이다. nullable
이 바로 옵셔널 형태가 된다. 그리고 nonnull
은 nil
을 가질 수 없는 일반적인 값을 의미한다. 이게 바로 Non-Optional 이라는 보증이다. 그리고 이 모두 프로퍼티와 메서드 다 사용할 수 있다. 물론 보증에 문제가 없도록 코딩하는 것이 필수이겠지만 말이다.
이외에도 비슷하게 _Nullable
이나 _Nonnull
같은 밑줄이 붙은 형태의 심볼도 있지만 이것보단 위의 방식이 가독성 면에서는 나을 것 같아서 다른 방식은 다 생략했다. 필요하다면 직접 알아보자.
아예 광역으로 nullability를 해제하는 매크로도 있다.
NS_ASSUME_NONNULL_BEGIN ... NS_ASSUME_NONNULL_END
이름의 의미에서 이해가 되겠지만 위 두 매크로 사이에 있는 포인터들은 모두 nonnull이 기본이 된다.