몇 달 전에 개인프로젝트를 시작할 때 궁금해서 정리해 봤던 주제에 대해 새싹 세션에서도 언급이 되어서 이 기회에 블로그에도 정리해 본다!
개인 프로젝트 당시, 레퍼런스로 봤던 레포지토리에서 namespace를 enum의 타입 프로퍼티 (static let)으로 작성해 둔 것을 보고 왜 굳이 enum안에서 타입프로퍼티로 정리를 해놓는 걸까 생각했었던 적이 있었다.
이유가 궁금해서 찾아보게 되었고 네임스페이스를 정의하는 몇 가지 방법들과 각 방법들의 단점과 장점에 대해 정리했었는데, 블로그에도 남겨놓으려 한다
이전까지 나는 enum의 rawValue를 활용해서 case로 각각 필요한 문자열 같은 것에 대해 정리해주고 있었었다. 기본적으로 우리가 알고 있는 것처럼 enum은 같은 주제의 값들을 모아놓기 좋은 형식이기 때문에 포괄적인 주제를 enum의 name으로 놓고 그 안에서 각 case들을 정리해 주었던 것이다.
enum EfeedbackItems : String {
case overall = "Overall & Recommendation"
case impact = "Impact"
case problemSolving = "Problem Solving"
case communication = "Communication"
}
이번 글에서는 우리가 자주 사용하는 값들에 대해 의미 있는 단위로 모아둘 수 있는 여러 가지 방법에 대해 살펴보고
상황별로 다르겠지만 내가 생각했을 때 가장 적절한 방법을 안내해보고자 한다!
https://bicycleforthemind.tistory.com/26
해당 블로그에서 namespace 정의에 대한 다양한 소개가 있어서 이 글을 바탕으로 작성했습니다.
일단 나조차 몇 달 전까지는 생소했던 단어인 namespace에 대해 먼저 알아보면!
# Namespace란?
- 연관된 값들을 한 공간에 이름 지어 모아둔 공간 ((서랍에 라벨링 하여 관련된 것들만 모아두는 것과 유사))
- 관련 있는 값들을 모아둠으로써 유지보수와 재사용이 편리하고, 필요한 문자열의 스펠링이나 숫자를 잘못 입력할 확률도 낮아진다.
이렇게 알고 있으면 되고,
코드를 작성할 때 하드코딩, 즉, 임의의 문자와 숫자를 코드에 그냥 넣어놓는 것은 매우 리스크가 크다는 것은 모두가 알 테니, 하드코딩을 줄이고 이러한 것들을 의미 있는 단위로 모아두어서 필요할 때 꺼내서 쓰는 보관함정도로 이해하면 된다.
# Namespace를 만드는 방법 6가지
처음에는 뭔 방법이 6가지나 있어.. 했는데
개발자마다 취향도 있고 개발 팀에서 선호하는 방법도 있고 방법마다 사용하는 이유가 다르니 알아두고 필요에 따라 상황에 따라 사용하면 될 것 같다!
((( 많은 옵션들을 안 상태에서 한 가지를 선택해서 사용하는 거랑 몰라서 쓰는 것만 고수하는 것은 다르니까!)))
내 기준에서 핵심인 내용은 1, 5, 6에 담겨 있는 것 같으니 바쁘다면 그것만 봐도 무방할 듯하다!
📌 1. 열거형에 원시값(rawValue)을 사용
글 초반에 내가 옛날에 썼다고 말했던 방법이다.
나는 직전 개인 프로젝트를 하기 전까지도 이 방법을 사용하고 있었고 아래 여러 방법을 알기 전까지는 무리 없이 네임스페이스로서 사용해 왔다.
// 열거형에 원시값(rawValue) 사용
enum enumWithRawValue: String {
case introMessage = "반갑습니다."
case exitMessage = "종료합니다."
}
print(enumWithRawValue.introMessage.rawValue) // 반갑습니다.
print(enumWithRawValue.exitMessage.rawValue) // 종료합니다.
근데 이런 원시값이 있는 enum으로 정의된 경우에, 호출할 때는 실제 원시값( 이 코드에서는 문자열)을 사용하고 싶을 때에는 .rawValue를 추가해야 내가 정의한 문자열이 나오기 때문에 번거로울 수 있다.
그리고 원시값이 독립적이고 유니크해야 하기 때문에 겹치는 rawValue가 있다면 정의가 불가하다!

이렇게 돌아보니 단점이 꽤나 많은 방법이었다...
📌 2. 열거형에 인스턴스 메서드(Method)를 사용
// 열거형에 인스턴스 메서드 사용
enum enumWithInstanceMethod {
case intro
case exit
func message() -> String {
switch self {
case .intro:
return "반갑습니다."
case .exit:
return "종료합니다."
}
}
}
print(enumWithInstanceMethod.intro.message()) // 반갑습니다.
print(enumWithInstanceMethod.exit.message()) // 종료합니다.
처음에는 굳이..? 번거롭게..? 라고 생각했지만
원래 enum의 주요 기능인 case로 분기하는 기능과 각 case별로 어떤 문자열을 리턴할지 정해주는 메서드를 함께 가지는 게 좋다고 생각할 때 사용할 수 있을 것 같다. 근데 사실 1번 방법도 case로 사용할 수도 있고 원시값으로도 사용할수 있는 거라서
내 기준 굳이..이긴 한 것 같다!
📌 3. 열거형에 인스턴스 연산 프로퍼티(Computed Property)를 사용
// 열거형에 인스턴스 연산 프로퍼티 사용
enum enumWithComputedProperty {
case intro
case exit
var message: String {
switch self {
case .intro:
return "반갑습니다."
case .exit:
return "종료합니다."
}
}
}
print(enumWithComputedProperty.intro.message) // 반갑습니다.
print(enumWithComputedProperty.exit.message) // 종료합니다.
연산 프로퍼티도 어떻게 보면 메서드와 비슷하게 동작하기 때문에 2번 방법과 거의 동일하다고 볼 수 있다.
접근할 때 호출만 안 해준다 뿐!
📌 4. 열거형에 CustomStringConvertible 프로토콜을 사용
이 방법은 전혀 생각해보지 못했고 CustomStringConvertible라는 프로토콜도 처음 접해보았기 때문에 흥미로운 방법이었다.
CustomStringConvertible 프로토콜을 채택하지 않은 평범한 열거형의 케이스를 그냥 print 한다고 했을 때 case 이름 자체가 String 으로 출력된다.
// 평범한 열거형의 케이스를 그냥 print 하면 case 이름 자체가 String 으로 출력된다.
enum ordinaryEnum {
case case1
case case2
}
print(ordinaryEnum.case1) // case1
print(ordinaryEnum.case2) // case2
내가 원하는 String 이 출력되도록 해주려면?! 이걸 가능하게 해주는 것이 CustomStringConvertible 프로토콜이다!
이 프로토콜을 준수하기 위해선 반드시 description 프로퍼티를 정의해야 하는 것도 기억해 두자
https://developer.apple.com/documentation/swift/customstringconvertible
CustomStringConvertible | Apple Developer Documentation
A type with a customized textual representation.
developer.apple.com
// 열거형에 CustomStringConvertible 프로토콜 채택하고 description구현해주면
//case로 접근했을 때 우리가 원하는 문자열을 출력할 수 있다.
enum enumWithCustomStringConvertible: CustomStringConvertible {
case introMesssage
case exitMessage
var description: String {
switch self {
case .introMesssage:
return "반갑습니다."
case .exitMessage:
return "종료합니다."
}
}
}
print(enumWithCustomStringConvertible.introMesssage) // 반갑습니다.
print(enumWithCustomStringConvertible.exitMessage) // 종료합니다.
📌 5. 열거형에 타입 프로퍼티(static let)를 사용
이 방법은 쉽게 말하면, case 없는 열거형이라고 할 수 있다.
// 열거형에 타입 프로퍼티(static let) 사용
enum enumWithTypeProperty {
static let introMessage = "반갑습니다."
static let exitMessage = "종료합니다."
}
print(enumWithTypeProperty.introMessage) // 반갑습니다.
print(enumWithTypeProperty.exitMessage) // 종료합니다.
가장 눈에 띄는 것은 타입 프로퍼티의 호출이 그렇듯이 타입에서 바로 접근하고 있어 가독성이 매우 좋다는 것.
그리고 타입 프로퍼티는 기본적으로 lazy하게 동작합니다. 즉, 사용을 할 때 비로소 메모리에 올라갑니다. 프로퍼티를 호출하기 전엔 메모리에 이 값이 없는 거죠?!
이 말은 작게나마 메모리 세이빙할 수 있다는 것을 의미하기도 합니다.
지연 저장 프로퍼티는 항상 lazy var ⭕️ (lazy let❌)로 선언해주었어야 하는데..
왜 타입 프로퍼티(static let)는 lazy하게 동작하는데도 let 선언이 가능한 것인가?
- 인스턴스 프로퍼티는 초기화 시점에 값없음을 나타내야 하므로 값없음 & 나중에 메모리에 올라갈 값 이렇게 둘 다 적용이 되어야 하기 때문에 var로 선언이 되어야 했던 것!
- 타입 프로퍼티는 타입에서 직접 접근하기 때문에 초기화 시점이랑 아얘 상관이 없고 사용될 때 그 값이 메모리에 올라가면 되므로 let으로 선언 가능한 것!
소들이님 글 참고!
📌 6. 구조체(struct)에 타입 프로퍼티(static let)를 사용
열거형에 타입 프로퍼티를 넣을 수 있다면, 구조체 또한 가능하다.
5번 방법과의 차이는 enum이냐 struct이냐 뿐이다.
// 구조체에 타입 프로퍼티(static let) 사용
struct structWithTypeProperty {
static let introMessage = "반갑습니다."
static let exitMessage = "종료합니다."
}
print(structWithTypeProperty.introMessage) // 반갑습니다.
print(structWithTypeProperty.exitMessage) // 종료합니다.
그렇다면 타입 프로퍼티를 써서 네임스페이스를 만드는 방법인 5번, 6번 중에서는 뭐가 더 나을까? 라는 의문에 자연스레 드는데!!
이 지점에서 초기화 구문을 사용할 수 있는지 없는지를 생각해 볼 수 있다.
struct는 초기화 구문을 사용해서 인스턴스를 만들 수 있는 반면, enum은 init()으로 인스턴스를 만들 수 없다.
우리가 지금 이 형태를 만들고 있는 목적을 다시 한번 상기해 보면 어떤 방법이 좀 더 적절한지 알 수 있는데, 어차피 네임스페이스 목적으로 만든 struct라서 인스턴스를 만들어도 할 수 있는 게 없다.
그리고 다른 개발자와 같이 작업한다고 했을 때 이 구조체의 목적을 잘 모르는 개발자가 불필요하게 init()을 통해 인스턴스를 만들 가능성도 있는 것..!
// 인스턴스를 만들 수 없도록 막아보자
struct structWithTypeProperty {
static let introMessage = "반갑습니다."
static let exitMessage = "종료합니다."
private init() {} // private 접근제어 적용
}
print(structWithTypeProperty.introMessage) // 반갑습니다.
print(structWithTypeProperty.exitMessage) // 종료합니다.
let initTest = structWithTypeProperty()
// 'structWithTypeProperty' initializer is inaccessible due to 'private' protection level
다른 동료 개발자가 이렇게 불필요하게 인스턴스를 만들지 않도록 하기 위해 init에 접근을 막아줄 수도 있겠지만
어차피 네임스페이스 목적으로 만드는 거라면 6번보다는 5번이 더 적합할 것 같다.
이렇게 namespace를 관리할 수 있는 여러 방법들에 대해 알아봤는데,
이전에는 문자열들을 그냥 아무 생각 없이 enum의 rawValue로 관리해주고 있었지만, 생각보다 다양한 방법이 있다는 것을 알았고, 이제는 상황별로 필요에 따라 선택해서 잘 사용해 줄 수었을 것 같다!
역시 뭐 하나를 하더라도 이유 있는 코딩을 해야겠다는 생각을 다시 한번 하게 된다.
참고
[Swift] NameSpace(네임스페이스)란 무엇이고, 어떻게 만들면 좋을까?
https://bicycleforthemind.tistory.com/26
[Swift] NameSpace(네임스페이스)란 무엇이고, 어떻게 만들면 좋을까?
네임스페이스(NameSpace) 란? 네임스페이스는 연관된 값들을 한 공간에 이름을 지어 모아둔 공간을 말한다. 쉽게 예를 들면, 우리가 '서랍'에 물건을 보관할 때, 그 안에 뭐가 들었는지 '라벨링'하는
bicycleforthemind.tistory.com