Swift의 네임스페이스와 typealias를 어떻게 이용할 수 있을까라는 주제로 지난 Swift Korea Meetup에서 발표된 내용입니다.
소개
저는 ME2Day, BAND, LINE을 개발하고 있는 iOS 개발자로 Swift 1이 나올 때부터 프로젝트에 적용하고 있습니다.
네임스페이스
Why Namespace?
네임스페이스를 다루게 된 계기는 프로젝트 중간에 PIMS(Personal Information Managing System)를 추가하게 된 것이었습니다. 캘린더, 알람, 타이머, 메모, 리마인더를 만드는 것이었는데요. CRUD를 만들고 제네릭으로 프로토콜도 만들면 될 거라고 생각하다가 문득 재미로 네임스페이스를 넣기로 했습니다. 하지만 결과는 야근으로 돌아와 버렸죠.
꿈꾸던 세상
Structure/Feature | Calendar - Alarm - Timer - Memo - Reminder |
---|---|
Model | 모델 관점 개별 기능 |
CRUD | 저장 관점 개발 기능 |
TableView | 리스트 관점 개별 기능 |
UIViewController | 표현 관점 개별 기능 |
- Model: 모델 관점 개별 기능
- CRUD: 저장 관점 개발 기능
- TableView: 리스트 관점 개별 기능
- UIViewController: 표현 관점 개별 기능
처음에는 위와 같은 구조를 꿈꿨고, Objective-C를 사용하던 입장에서 기존의 불편함을 없앨 수 있을 것으로 생각했습니다. 실제로 Java는 강력하고 제약이 심한 네임스페이스를 가지고 있기도 했고요.
실제 네임스페이스
실제로 Swift의 네임스페이스가 어떤 것인지 찾아봤더니 모듈 네임을 네임스페이스에서 지원한다는 것이 전부였습니다. 즉, import ModuleA
, import ModuleB
를 하면 네임스페이스가 생기는 거죠. 그래서 이를 호출하려면 moduleA.foo()
와 같이 부릅니다. 하지만 이 기능만으로는 꿈꾸던 세상을 이룰 수 없었기 때문에 우회법을 찾아봤습니다.
네임스페이스 우회법
선구자들이 제시하는 우회법은 중첩 타입(Nested Type)을 쓰라는 것이었습니다. 클래스 안에 클래스를, 구조체 안에 구조체를 쓰는 형태죠. 구조체 안에 클래스를 넣을 수도 있습니다.
struct NameSpace {
class NameSpaceObject {
//
}
}
하지만 이 방식의 문제는 NameSpace()
를 인스턴스로 만들 수 있다는 점입니다. 따라서 이 문제를 해결하려면 중첩 타입이 아니라 중첩 열거형(Nested Enum)을 사용해야 합니다.
enum NameSpace {
class NameSpaceObject {
//
}
}
이를 적용해서 아래와 같은 코드를 만들었습니다.
enum PIMS {
}
extension PIMS {
enum Reminder {
static var reminders:[PIMS.Reminder] = []
}
}
extension PIMS.Reminder {
enum Error: Swift.Error {
case accessDefined
case invalidState
case nestedError(error: Swift.Error)
}
}
에러 역시 Swift.Error를 상속받아서 만들었습니다. 하지만 실전에서는 어떨까요?
실전에서의 네임스페이스
프로토콜 미지원
실전에서 네임스페이스를 사용하려니 여러 문제가 발견됐습니다. 그 중 첫 번째 문제는 프로토콜을 지원하지 않는다는 것이었습니다. 다만 중첩 타입에서 지원하지 않던 제네릭을 Swift 3.1에서 지원하게 개선된 것을 보면 프로토콜도 곧 지원할 가능성이 높다고 생각합니다.
스코프
두 번째 문제는 스코프였습니다. 실재하는 것을 참조하지 못하는 경우가 있었습니다. 아래처럼 익스텐션을 사용하면 Model
을 인식하지 못하면서 컴파일되지 않으므로 전체 이름인 PIMS.Reminder.Model
이라는 전체 이름을 모두 적어줘야 합니다.
// 컴파일되지 않는 코드
extension PIMS.Reminder {
struct Model {
var reminderID: String
var content: String
var done: Bool
}
}
extension PIMS.Reminder.Model: Equatable {
static func ==(lhs: Modle, rhs: Model) -> Bool {
return lhs.reminderID == rhs.reminderID
}
}
// 컴파일되도록 고친 코드
extension PIMS.Reminder {
struct Model {
var reminderID: String
var content: String
var done: Bool
}
}
extension PIMS.Reminder.Model: Equatable {
static func ==(lhs: PIMS.Reminder.Model,
rhs: PIMS.Reminder.Model) -> Bool {
return lhs.reminderID == rhs.reminderID
}
}
스코프 우회책: typealias
익스텐션은 별도의 스코프이므로 아래처럼 Coding
이라는 짧은 열거형을 사용하면 컴파일 에러가 납니다.
// 컴파일되지 않는 코드
extension PIMS.Alarm.Model {
var newCoding: PIMS.Alarm.Coding {
return Coding(alarm: self)
}
}
// 컴파일되도록 typealias 추가
private typealias Coding = PIMS.Alarm.Coding
extension PIMS.Alarm.Model {
var newCoding: PIMS.Alarm.Coding {
return Coding(alarm: self)
}
}
따라서 우회법으로 typealias를 쓰면 인식이 가능해집니다. 하지만 다시 PIMS.Alarm.Coding
이라고 길게 써야 하는 부분이 귀찮게 느껴지기에 해결 방법을 찾아봤습니다.
한 방법은 private typealias를 사용하면 것이었지만 public으로 변경하면 너무 많은 타입이 등장하므로 좋은 해결법은 아닌 것 같았습니다.
스토리보드
또한 스토리보드에서 커스텀 클래스를 지정할 때 중첩 타입으로 접근할 수 없다는 문제점이 있었습니다. 그래서 코드상으로는 클래스를 하나 만들고 중첩 타입을 쓰는 곳에서 typealias를 적용해서 접근할 수 있게 하고 스토리보드에서는 예전처럼 사용할 수 있도록 시도했습니다.
class PIMSReminderViewController: PIMS.BaseViewController {
private typealias Model = PIMS.Reminder.Model
}
extension PIMS.Reminder {
typealias ViewConroller = PIMSReminderViewController
}
이렇게 만든 결과 스토리보드에서 PIMSReminderViewController
라는 클래스를 인식할 수 있었습니다. 하지만 다시 아울렛이 인식되지 않는 문제가 발견됐습니다. 인터페이스 빌더를 새로 만들 수는 없으니 결국 포기할 수밖에 없었습니다.
네임스페이스 결론
결론을 말씀드리면 네임스페이스는 모듈만 지원한다는 것입니다. 중첩 타입을 네임스페이스로 쓸 수 있는데 이렇게 사용한 경우 열거형을 사용하는 것이 좋습니다. 하지만 전체 이름을 일일이 쳐야 하는 귀찮음이 있으므로 해당 객체를 선언한 익스텐션에서만 사용하는 것을 추천합니다. 필요하면 typealias로 전체 이름을 단축할 수 있지만, 아직 스토리보드나 xib 등에서 지원하지 않으므로 신중히 사용하셔야 할 것 같습니다.
보너스 영상
추가로 tuple과 typealias로 타입을 잘 만들어 나가는 방법에 대한 보너스 강연이 맨 뒤에 있으니 궁금한 분들은 영상을 확인해 주세요!
본 영상과 글은 Swift Korea Meetup의 비디오 스폰서인 Realm에서 제공합니다. 모바일 개발자가 더 나은 앱을 더 빠르게 만들도록 돕는 Realm 모바일 데이터베이스와 Realm 모바일 플랫폼을 통해 핵심 로직에 집중하고 개발 효율을 높여 보세요! 공식 문서에서 단 몇 분 만에 시작할 수 있습니다. 또한 Realm 홈페이지에서는 모바일 개발자를 위한 다양한 최신 기술 뉴스와 튜토리얼을 제공하고 있으니 즐겨찾기하고 자주 들러 주세요!
컨텐츠에 대하여
이 컨텐츠는 저자의 허가 하에 이곳에서 공유합니다.