Swift 프로토콜(Protocol) 심화 과정 입니다. 기본적인 프로토콜 개념을 넘어서, 프로토콜 지향 프로그래밍(POP) 의 진짜 의미와 실전에서 프로토콜을 어떻게 활용해 유지보수성과 확장성을 높이는지 배워볼 거예요. Swift의 설계 철학은 “클래스 상속보다 프로토콜로 유연하게 설계하라!” 입니다. 이번 심화 과정을 통해 프로토콜을 마스터해보세요!
[Swift Apple ] Swift 프로토콜 심화 과정 |
🚀 Swift 프로토콜 심화 과정
– 프로토콜 지향 프로그래밍(POP)과 실전 설계 전략
✅ 프로토콜 지향 프로그래밍(POP) 이란?
Swift의 POP(Protocol-Oriented Programming) 은
기존의 객체지향 프로그래밍(OOP) 의 단점을 보완하기 위해 등장했어요.
구분 |
OOP (객체지향) |
POP (프로토콜 지향) |
---|---|---|
중심 개념 |
클래스 상속 |
프로토콜 + 확장성 |
유연성 |
낮음 (단일 상속) |
높음 (다중 채택 가능) |
재사용성 |
상속 구조에 의존 |
프로토콜 + Extension |
예시 |
Java, C++ |
Swift |
Swift에서는 클래스 상속보다 프로토콜 + 구조체(Struct) 를 조합해서
더 가볍고 안전한 설계를 권장합니다.
1️⃣ 프로토콜 기본 구현 (Protocol + Extension)
프로토콜은 규칙만 정의하는데,
Swift는 Extension 을 통해 기본 동작(Default Implementation) 도 제공할 수 있어요.
예제: 기본 동작 제공
protocol Greetable {
func greet()
}
extension Greetable {
func greet() {
print("Hello, 기본 인사입니다!")
}
}
struct Person: Greetable {}
let p = Person()
p.greet() // Hello, 기본 인사입니다!
-
필요한 경우에만 메서드를 오버라이드 해서 커스터마이징 가능!
2️⃣ 프로토콜 상속 & 다중 채택
-
프로토콜은 다른 프로토콜을 상속할 수 있고,
-
타입은 여러 프로토콜을 동시에 채택할 수 있어요.
protocol Flyable { func fly() }
protocol Swimmable { func swim() }
protocol DuckType: Flyable, Swimmable {}
struct Duck: DuckType {
func fly() { print("오리가 날아요!") }
func swim() { print("오리가 헤엄쳐요!") }
}
3️⃣ AssociatedType – 제네릭처럼 유연한 프로토콜 설계
프로토콜 내부에서 타입을 미리 정하지 않고,
채택하는 타입에서 나중에 결정하도록 만드는 기능이에요.
예제: 제네릭 프로토콜 설계
protocol Container {
associatedtype Item
func add(item: Item)
}
struct IntBox: Container {
func add(item: Int) {
print("\(item)를 추가했습니다.")
}
}
-
associatedtype 덕분에 재사용성이 매우 높아짐!
-
Swift 표준 라이브러리의 Collection, Sequence 도 이 방식을 사용
4️⃣ 프로토콜 합성 (Protocol Composition)
함수나 변수 타입을 정의할 때
여러 프로토콜을 동시에 요구할 수 있어요.
func startRace(participant: Flyable & Swimmable) {
participant.fly()
participant.swim()
}
-
& 연산자를 사용해 다중 요구사항 설정
5️⃣ 프로토콜을 통한 의존성 역전 (DIP 실천)
디자인 패턴의 핵심 원칙 중 하나인
의존성 역전 원칙(DIP) 을 실천할 때도 프로토콜이 사용됩니다.
예제: 네트워크 모듈 분리
protocol NetworkService {
func fetchData()
}
class APIManager: NetworkService {
func fetchData() { print("API 호출") }
}
class ViewModel {
let network: NetworkService
init(network: NetworkService) {
self.network = network
}
}
-
덕분에 테스트 시 Mock 객체도 쉽게 주입 가능!
6️⃣ 프로토콜 + 클래스 전용 (AnyObject)
클래스 타입에만 적용되는 프로토콜을 만들 수 있어요.
protocol DelegateProtocol: AnyObject {
func didFinish()
}
-
주로 Delegate 패턴에서 사용
-
weak 참조를 위해 반드시 클래스 전용 프로토콜 필요
7️⃣ 프로토콜 + Enum 활용
Swift에서는 Enum + Protocol 조합으로
더 강력한 상태 관리, 동작 분리가 가능합니다.
protocol Actionable {
func act()
}
enum CharacterState: Actionable {
case idle, attack
func act() {
switch self {
case .idle: print("대기 중")
case .attack: print("공격!")
}
}
}
✏️ 실전 활용 예시
-
Delegate 패턴 구현
-
Strategy 패턴 (알고리즘 교체)
-
Mock 테스트 객체 생성
-
다형성(Polymorphism) 실현
-
네트워크, 데이터베이스 모듈 분리
-
UI 설계에서 동작 표준화
✅ 1. Delegate 패턴 구현 – 사용자 입력 알림
// 1️⃣ 프로토콜 정의
protocol InputDelegate: AnyObject {
func didEnterText(_ text: String)
}
// 2️⃣ 입력 클래스
class TextField {
weak var delegate: InputDelegate?
func simulateTyping(text: String) {
print("⌨️ 입력: \(text)")
delegate?.didEnterText(text)
}
}
// 3️⃣ 델리게이트 구현 클래스
class ViewController: InputDelegate {
func didEnterText(_ text: String) {
print("✅ 입력받은 텍스트 처리 완료: \(text)")
}
}
// 사용
let textField = TextField()
let viewController = ViewController()
textField.delegate = viewController
textField.simulateTyping(text: "Hello")
Delegate 패턴 구현 – 사용자 입력 알림 |
✅ 2. Strategy 패턴 – 알고리즘 교체
// 전략 프로토콜
protocol PaymentStrategy {
func pay(amount: Int)
}
// 다양한 전략
class CreditCardPayment: PaymentStrategy {
func pay(amount: Int) {
print("💳 카드로 \(amount)원 결제")
}
}
class CashPayment: PaymentStrategy {
func pay(amount: Int) {
print("💵 현금으로 \(amount)원 결제")
}
}
// 컨텍스트
class PaymentContext {
var strategy: PaymentStrategy
init(strategy: PaymentStrategy) {
self.strategy = strategy
}
func process(amount: Int) {
strategy.pay(amount: amount)
}
}
// 사용
let context = PaymentContext(strategy: CreditCardPayment())
context.process(amount: 10000)
context.strategy = CashPayment()
context.process(amount: 5000)
Strategy 패턴 – 알고리즘 교체 |
✅ 3. Mock 테스트 객체 생성 – 네트워크 테스트용
// 프로토콜 정의
protocol APIService {
func fetchData(completion: @escaping (String) -> Void)
}
// 실제 구현
class RealAPIService: APIService {
func fetchData(completion: @escaping (String) -> Void) {
completion("실제 서버 데이터")
}
}
// 목(Mock) 객체
class MockAPIService: APIService {
func fetchData(completion: @escaping (String) -> Void) {
completion("🧪 테스트용 데이터")
}
}
// 사용하는 클래스
class ViewModel {
let apiService: APIService
init(apiService: APIService) {
self.apiService = apiService
}
func loadData() {
apiService.fetchData { data in
print("받은 데이터: \(data)")
}
}
}
// 테스트
let mockViewModel = ViewModel(apiService: MockAPIService())
mockViewModel.loadData()
Mock 테스트 객체 생성 – 네트워크 테스트용 |
✅ 4. 다형성(Polymorphism) 실현
protocol Animal {
func sound()
}
class Dog: Animal {
func sound() {
print("🐶 멍멍!")
}
}
class Cat: Animal {
func sound() {
print("🐱 야옹~")
}
}
// 다양한 동물 객체를 하나의 타입으로 관리
let zoo: [Animal] = [Dog(), Cat()]
zoo.forEach { $0.sound() }
다형성(Polymorphism) 실현 |
✅ 5. 네트워크, 데이터베이스 모듈 분리
// 공통 저장소 역할 정의
protocol DataRepository {
func loadData() -> String
}
// 네트워크 저장소
class NetworkRepository: DataRepository {
func loadData() -> String {
return "📡 서버에서 가져온 데이터"
}
}
// 로컬 저장소
class LocalRepository: DataRepository {
func loadData() -> String {
return "💾 로컬 DB에서 가져온 데이터"
}
}
// 사용하는 클래스
class DataManager {
let repo: DataRepository
init(repo: DataRepository) {
self.repo = repo
}
func display() {
print("📦 결과: \(repo.loadData())")
}
}
// 사용 예시
let manager = DataManager(repo: NetworkRepository())
manager.display()
let local = DataManager(repo: LocalRepository())
local.display()
네트워크, 데이터베이스 모듈 분리 |
✅ 6. UI 설계에서 동작 표준화 – 버튼 인터페이스 정의
protocol Clickable {
func onClick()
}
// 버튼 1
struct SaveButton: Clickable {
func onClick() {
print("💾 저장되었습니다.")
}
}
// 버튼 2
struct DeleteButton: Clickable {
func onClick() {
print("🗑️ 삭제되었습니다.")
}
}
// 표준화된 방식으로 처리
let buttons: [Clickable] = [SaveButton(), DeleteButton()]
buttons.forEach { $0.onClick() }
UI 설계에서 동작 표준화 – 버튼 인터페이스 정의 |
🧠 마무리 요약
예시 |
핵심 포인트 |
---|---|
Delegate |
사용자 → 컨트롤러 전달 |
Strategy |
알고리즘 교체 가능성 |
Mock |
테스트 유연성 증가 |
다형성 |
다양한 객체를 하나의 타입으로 |
모듈 분리 |
책임 분리, 유지보수 용이 |
UI 표준화 |
재사용성, 일관성 증가 |
🎯 마무리 – 프로토콜 마스터의 길
심화 개념 |
핵심 포인트 |
---|---|
Extension 활용 |
기본 구현 제공으로 코드 재사용성 UP |
AssociatedType |
제네릭 프로토콜로 유연성 확보 |
프로토콜 합성 |
복잡한 타입 요구를 간단하게 |
DIP 실천 |
의존성 관리와 테스트 효율화 |
POP 실전 적용 |
클래스 상속보다 프로토콜 중심 설계 |
Swift 개발자로서 성장하려면
프로토콜을 자유자재로 설계하고 활용할 수 있어야 합니다.
댓글 쓰기