중첩 타입(Nested Type)
- Swift에서는 타입 내부에 다른 타입을 선언하는 것이 가능하다
- 이러한 방식을 중첩 타입(Nested Types) 이라고 하며, 특정 타입 내에서만 사용되는 타입을 정의할 때 유용하다
중첩 타입의 사용 목적
- 특정 타입과 밀접하게 관련된 타입을 내부에서 정의하여 범위를 명확히 할 수 있다
- 타입 간의 연관성을 명확히 표현하고, 내부 구조를 더욱 디테일하게 설계할 수 있다
- 코드의 가독성을 높이고, 구조를 더 잘 이해할 수 있도록 도와준다
- 외부에서 사용할 때 중첩된 경로를 사용해야 하므로, 의미가 명확해진다
- 문자열 실수 방지 및 데이터 관리 용이성을 제공한다
중첩 타입을 사용하지 않은 경우(타입의 관계가 명확하지 않다)
enum Style {
case full
case long
case medium
case none
}
struct DateFormatters {
var style: Style
}
var dateStyle = DateFormatters(style: .full)
dateStyle.style = .medium
중첩 타입을 사용한 경우(타입의 관계가 명확해진다)
struct DateFormatters {
enum Style {
case full
case long
case medium
case none
}
var style: Style
}
var dateStyle = DateFormatters(style: DateFormatters.Style.full)
dateStyle.style = .medium
- 중첩 타입을 사용하지 않은 경우 (전역 선언)
Style 열거형이 전역 선언되었기 때문에, 사용자가 어느 타입과 관련된 스타일인지 정확히 알기 어렵다
- 예를 들어,
Style.full 이라고 한다면, 어떤 용도로 사용되는 스타일인지 알기 어렵다
- 중첩 타입을 사용한 경우
DateFormatters.Style 로 사용하게 되면, 이 스타일이 DateFormatters 와 관련된 것임을 명확히 알 수 있다
- 외부에서 사용하려면 반드시 전체 경로(
DateFormatters.Style.full)를 지정해야 하기 때문에 의미가 명확해진다
- 타입 간의 관계를 명확히 표현할 수 있고, 코드의 의미도 더 분명해진다
중첩 타입을 배우는 목적
- 중첩 타입으로 선언된 API들을 볼 줄 알아야 한다
- 예시:
DateFormatter.Style.full
- 중간에 대문자가 나오면, 중첩 타입임을 인지해야 한다
- 실제 앱을 만들 때 중첩 선언을 잘 활용해야 한다
- 관련된 타입을 내부에 선언함으로써 타입 간의 관계를 명확히 표현한다
- 코드를 더욱 깔끔하고 이해하기 쉽게 작성할 수 있다
- 하나의 타입의 내부 구조 (계층 관계 등)를 디테일하게 설계할 수 있다
- 내부의 타입은 외부로부터의 접근을 제한하면서도 의미 있는 관계를 유지할 수 있다
간단한 중첩 타입 예제
class Zoo {
struct Animal {
enum AnimalType {
case mammal, bird, reptile, amphibian, fish, insect
}
var name: String
var type: AnimalType
}
}
let myZoo = Zoo()
let lion = Zoo.Animal(name: "Lion", type: .mammal)
let parrot = Zoo.Animal(name: "Parrot", type: .bird)
print("동물 이름: \(lion.name), 종류: \(lion.type)")
print("동물 이름: \(parrot.name), 종류: \(parrot.type)")
Zoo 클래스 내부에 Animal 구조체가 정의되었다
Animal 구조체 내부에는 AnimalType 이라는 열거형이 정의되었다
- 외부에서
Animal 구조체와 AnimalType 열거형을 사용하기 위해서는 전체 경로를 사용해야 한다 (Zoo.Animal, Zoo.Animal.AnimalType)
- 중첩 타입을 사용하면, 타입 간의 관계를 더욱 명확히 표현할 수 있다
복잡한 중첩 타입 예제
class GameCharacter {
enum WeaponType {
case sword, bow, staff
var description: String {
switch self {
case .sword: return "검 (근접 공격)"
case .bow: return "활 (원거리 공격)"
case .staff: return "지팡이 (마법 공격)"
}
}
}
struct Weapon {
struct Level {
let value: Int
var attackPower: Int {
return value * 10
}
}
var type: WeaponType
var level: Level
func weaponInfo() -> String {
return "무기 타입: \(type.description), 레벨: \(level.value), 공격력: \(level.attackPower)"
}
}
var name: String
var equippedWeapon: Weapon?
init(name: String) {
self.name = name
}
func equip(weapon: Weapon) {
self.equippedWeapon = weapon
print("\(name)이(가) \(weapon.type.description)을(를) 장착했습니다.")
}
func attack() {
if let weapon = equippedWeapon {
print("\(name)이(가) 공격합니다! 공격력: \(weapon.level.attackPower)")
} else {
print("\(name)은(는) 무기가 없습니다.")
}
}
}
GameCharacter 클래스(최상위 타입)
- 게임 캐릭터를 표현하며, 이름과 장착된 무기를 속성으로 가진다
equip(weapon:) 메서드를 통해 무기를 장착하고, attack() 메서드를 통해 공격을 수행한다
WeaponType 열거형(중첩 타입)
- 무기의 종류를 정의하며, 각 타입별 설명을 제공하는 계산 속성(
description)이 포함되어 있다
Weapon 구조체(중첩 타입)
- 무기의 타입(
type)과 레벨(level)을 속성으로 가진다
weaponInfo() 메서드를 통해 무기의 정보를 출력한다
Level 구조체(중첩 타입)
- 무기의 레벨을 표현하며, 레벨에 따른 공격력을 계산한다(
attackPower)
- 경로 사용의 중요성
- 외부에서 사용하려면 전체 경로를 지정해야 한다(
GameCharacter.Weapon, GameCharacter.Weapon.Level)
- 경로를 명확히 지정함으로써 의미를 분명히 할 수 있다
사용 예제 (코드 실행 예제)
let hero = GameCharacter(name: "Knight")
let sword = GameCharacter.Weapon(type: .sword, level: GameCharacter.Weapon.Level(value: 5))
hero.equip(weapon: sword)
hero.attack()
- Swift의 표준 라이브러리와 Apple의 프레임워크에서는 중첩 타입을 광범위하게 사용한다
- 특히
DateFormatter 클래스는 Style 이라는 중첩 타입을 사용하여 날짜와 시간을 관리한다
import Foundation
let formatter = DateFormatter()
formatter.dateStyle = .full
formatter.dateStyle = DateFormatter.Style.medium
let setting1: DateFormatter.Style = DateFormatter.Style.full
let setting2: DateFormatter.Style = DateFormatter.Style.short
print(setting1)
print(setting2)
DateFormatter 클래스
- 날짜와 시간을 문자열로 변환하거나, 문자열을 날짜와 시간으로 변환할 때 사용되는 클래스이다
- 중첩 타입(
Style 열거형)
DateFormatter 클래스 내부에 정의된 중첩 타입으로, 날짜와 시간을 표시하는 스타일을 정의한다
- 사용법:
DateFormatter.Style(하지만 DateFormatter 내부에서 사용하는 경우 .full, .medium 등으로 줄여서 사용 가능)
- 사용법의 차이점
.full 처럼 사용하면: DateFormatter 클래스 내부의 스타일을 의미(경로를 생략할 수 있다)
DateFormatter.Style.full 처럼 사용하면: 타입을 정확하게 지정하는 방식(더 명확한 표현)
- 이점
- 외부에서 사용하기 위해서는
DateFormatter.Style 이라는 경로를 명확히 지정해야 한다
- 중첩 타입을 사용함으로써, 관련 타입 간의 관계를 명확히 표현할 수 있다
사용자 정의 중첩 타입 예제(문자열 관리)
- 앱에서 자주 사용하는 문자열들을 별도의 파일에서 관리하는 대신, 중첩 타입으로 관리하면 더욱 안전하게 사용할 수 있다
struct AppConstants {
struct Info {
static let appName = "MyAmazingApp"
static let version = "1.0.0"
}
struct Colors {
static let red = "ThemeRed"
static let green = "ThemeGreen"
static let blue = "ThemeBlue"
}
struct Database {
static let collectionName = "users"
static let userField = "username"
static let messageField = "message"
static let timestampField = "timestamp"
}
struct Segues {
static let loginSegue = "LoginToMain"
static let registerSegue = "RegisterToMain"
}
}
let appName = AppConstants.Info.appName
let themeColor = AppConstants.Colors.blue
let collection = AppConstants.Database.collectionName
let loginSegue = AppConstants.Segues.loginSegue
print(appName)
print(themeColor)
print(collection)
print(loginSegue)
AppConstants 구조체는 앱의 전역 상수들을 관리하기 위해 사용되는 상위 타입이다
Info, Colors, Database, Segues 와 같은 중첩 타입으로 구성되어 있다
- 문자열을 상수로 관리하여 오타로 인한 오류를 방지하고, 코드의 의미를 명확히 표현할 수 있다
- 중첩 타입을 사용하여 관련 있는 데이터를 그룹화함으로써 코드의 유지보수성을 높일 수 있다
메시지 모델 설계(메신저 앱의 메시지 관리)
- 메신저 앱에서 메시지를 관리할 때, 상태 정보와 데이터를 중첩 타입으로 관리할 수 있다
import UIKit
class ChatMessage {
private enum Status {
case sent
case delivered
case read
}
let sender: String
let content: String
let timestamp: Date
private var status: Status = .sent
init(sender: String, content: String) {
self.sender = sender
self.content = content
self.timestamp = Date()
}
func displayMessageInfo() -> String {
return "보낸 사람: \(sender), 내용: \(content), 시간: \(timestamp)"
}
func statusColor() -> UIColor {
switch status {
case .sent: return .gray
case .delivered: return .blue
case .read: return .green
}
}
func updateStatus(to newStatus: Status) {
self.status = newStatus
}
}
let message = ChatMessage(sender: "Alice", content: "안녕하세요?")
print(message.displayMessageInfo())
ChatMessage 클래스는 메시지 정보를 관리하기 위한 타입이다
Status 라는 중첩 열거형을 사용하여 메시지의 상태를 관리한다(sent, delivered, read)
displayMessageInfo() 메서드는 메시지의 기본 정보를 반환한다
statusColor() 메서드는 메시지의 상태에 따라 다른 색상을 반환한다(UIColor)
updateStatus() 메서드를 사용하여 메시지의 상태를 업데이트할 수 있다
중첩 타입 사용의 이유
- 타입 간의 관계를 명확히 표현할 수 있다(캡슐화)
- 코드의 의미가 분명해진다(경로를 사용하여 타입을 구분)
- 확장성 (Extensibility) - 새로운 타입을 추가하거나 수정할 때 쉽게 변경 가능
- 코드의 가독성과 유지보수성이 향상된다(특정 타입 내부에서만 사용하는 타입 관리)
- Swift 표준 라이브러리에서도 널리 사용된다(
DateFormatter.Style, URLSession.Task 등)
요약
- 특정 타입 내에서만 사용되는 타입을 정의하여 범위를 명확히 지정할 수 있다
- 타입 간의 관계를 더욱 명확하게 표현할 수 있다
- 외부에서 사용할 때 경로를 모두 표시해야 하므로 의미가 명확해진다
- 코드의 가독성과 유지보수성이 향상된다