[Swift Apple ] Swift 프로토콜 심화 과정

Swift 프로토콜(Protocol) 심화 과정 입니다. 기본적인 프로토콜 개념을 넘어서, 프로토콜 지향 프로그래밍(POP) 의 진짜 의미와 실전에서 프로토콜을 어떻게 활용해 유지보수성과 확장성을 높이는지 배워볼 거예요. Swift의 설계 철학은 “클래스 상속보다 프로토콜로 유연하게 설계하라!” 입니다. 이번 심화 과정을 통해 프로토콜을 마스터해보세요!


[Swift Apple ] 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 패턴 구현 – 사용자 입력 알림
 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 패턴 – 알고리즘 교체
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 테스트 객체 생성 – 네트워크 테스트용
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) 실현
다형성(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 설계에서 동작 표준화 – 버튼 인터페이스 정의
UI 설계에서 동작 표준화 – 버튼 인터페이스 정의





🧠 마무리 요약

예시

핵심 포인트

Delegate

사용자 → 컨트롤러 전달

Strategy

알고리즘 교체 가능성

Mock

테스트 유연성 증가

다형성

다양한 객체를 하나의 타입으로

모듈 분리

책임 분리, 유지보수 용이

UI 표준화

재사용성, 일관성 증가






🎯 마무리 – 프로토콜 마스터의 길

심화 개념

핵심 포인트

Extension 활용

기본 구현 제공으로 코드 재사용성 UP

AssociatedType

제네릭 프로토콜로 유연성 확보

프로토콜 합성

복잡한 타입 요구를 간단하게

DIP 실천

의존성 관리와 테스트 효율화

POP 실전 적용

클래스 상속보다 프로토콜 중심 설계

Swift 개발자로서 성장하려면

프로토콜을 자유자재로 설계하고 활용할 수 있어야 합니다.



댓글 쓰기