# Swift 프로토콜(Protocol) 완벽 가이드

jeongmuyamette·2025년 7월 26일

TIL

목록 보기
72/72
post-thumbnail

🎯 프로토콜이란?

프로토콜은 특정 작업이나 기능의 청사진(blueprint)을 정의하는 것입니다. 클래스, 구조체, 열거형이 반드시 구현해야 할 요구사항들을 명시합니다.

// 프로토콜 정의
protocol Flyable {
    var altitude: Double { get set }
    func fly()
    func land()
}

🔍 왜 프로토콜을 사용할까?

1. 다중 상속의 한계 해결

// Swift는 클래스 다중 상속 불가능
class Vehicle { }
class FlyingVehicle: Vehicle { } // ✅ 가능
// class AmphibiousVehicle: Vehicle, WaterVehicle { } // ❌ 불가능

// 하지만 프로토콜은 여러 개 채택 가능
protocol Flyable { }
protocol Swimmable { }

class Duck: Flyable, Swimmable { } // ✅ 가능!

2. 코드의 유연성과 확장성

protocol Drawable {
    func draw()
}

class Circle: Drawable {
    func draw() {
        print("원을 그립니다")
    }
}

class Rectangle: Drawable {
    func draw() {
        print("사각형을 그립니다")
    }
}

// 프로토콜 타입으로 배열 생성
let shapes: [Drawable] = [Circle(), Rectangle()]
shapes.forEach { $0.draw() } // 각각의 draw() 메서드 호출

📝 프로토콜 문법

1. 프로퍼티 요구사항

protocol PersonProtocol {
    var name: String { get }           // 읽기 전용
    var age: Int { get set }          // 읽기/쓰기
    static var species: String { get } // 타입 프로퍼티
}

struct Person: PersonProtocol {
    let name: String        // get만 요구하므로 let 가능
    var age: Int           // get set 요구하므로 var 필요
    static let species = "Homo sapiens"
}

2. 메서드 요구사항

protocol Calculable {
    func add(_ a: Int, _ b: Int) -> Int
    mutating func reset() // 구조체에서 프로퍼티 변경시 mutating 필요
    static func getVersion() -> String
}

struct Calculator: Calculable {
    var result: Int = 0
    
    func add(_ a: Int, _ b: Int) -> Int {
        return a + b
    }
    
    mutating func reset() {
        result = 0
    }
    
    static func getVersion() -> String {
        return "1.0"
    }
}

3. 이니셜라이저 요구사항

protocol Initializable {
    init(name: String)
}

class MyClass: Initializable {
    let name: String
    
    required init(name: String) { // required 키워드 필요
        self.name = name
    }
}

🚀 언제 프로토콜을 사용할까?

1. 공통 인터페이스가 필요할 때

protocol NetworkService {
    func fetchData(from url: String) -> Data?
}

class APIService: NetworkService {
    func fetchData(from url: String) -> Data? {
        // REST API 호출 로직
        return Data()
    }
}

class MockService: NetworkService {
    func fetchData(from url: String) -> Data? {
        // 테스트용 가짜 데이터 반환
        return Data("test data".utf8)
    }
}

// 의존성 주입으로 유연한 설계
class DataManager {
    private let service: NetworkService
    
    init(service: NetworkService) {
        self.service = service
    }
    
    func loadData() {
        let data = service.fetchData(from: "https://api.example.com")
        // 데이터 처리
    }
}

2. 델리게이트 패턴 구현

protocol TableViewCellDelegate: AnyObject {
    func didTapButton(in cell: CustomTableViewCell)
}

class CustomTableViewCell: UITableViewCell {
    weak var delegate: TableViewCellDelegate?
    
    @IBAction func buttonTapped(_ sender: UIButton) {
        delegate?.didTapButton(in: self)
    }
}

class ViewController: UIViewController, TableViewCellDelegate {
    func didTapButton(in cell: CustomTableViewCell) {
        // 버튼 탭 처리
        print("셀의 버튼이 탭되었습니다")
    }
}

3. 프로토콜 확장으로 기본 구현 제공

protocol Loggable {
    func log(_ message: String)
}

extension Loggable {
    func log(_ message: String) {
        print("[\(type(of: self))] \(message)")
    }
    
    func logError(_ error: String) {
        print("🚨 ERROR: \(error)")
    }
}

class NetworkManager: Loggable {
    func connect() {
        log("연결을 시도합니다") // 기본 구현 사용
        logError("연결 실패")    // 확장에서 추가된 메서드 사용
    }
}

4. 제네릭과 함께 사용

protocol Identifiable {
    var id: String { get }
}

struct User: Identifiable {
    let id: String
    let name: String
}

struct Product: Identifiable {
    let id: String
    let title: String
}

class Repository<T: Identifiable> {
    private var items: [T] = []
    
    func find(by id: String) -> T? {
        return items.first { $0.id == id }
    }
    
    func add(_ item: T) {
        items.append(item)
    }
}

let userRepo = Repository<User>()
let productRepo = Repository<Product>()

🎨 실제 iOS 개발에서의 활용

1. UIKit 델리게이트들

// UITableViewDataSource, UITableViewDelegate
class MyViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // 셀 구성
        return UITableViewCell()
    }
}

2. 커스텀 프로토콜로 모듈화

protocol UserServiceProtocol {
    func login(email: String, password: String) async throws -> User
    func logout() async throws
    func getCurrentUser() -> User?
}

class FirebaseUserService: UserServiceProtocol {
    func login(email: String, password: String) async throws -> User {
        // Firebase 로그인 로직
        return User(id: "123", name: "John")
    }
    
    func logout() async throws {
        // Firebase 로그아웃 로직
    }
    
    func getCurrentUser() -> User? {
        // 현재 사용자 정보 반환
        return nil
    }
}

💡 프로토콜 사용 팁

1. 프로토콜 컴포지션

protocol Named {
    var name: String { get }
}

protocol Aged {
    var age: Int { get }
}

// 여러 프로토콜을 조합
func greet(_ person: Named & Aged) {
    print("안녕하세요, \(person.age)\(person.name)님!")
}

2. 옵셔널 프로토콜 메서드

@objc protocol OptionalProtocol {
    @objc optional func optionalMethod()
    func requiredMethod()
}

class MyClass: NSObject, OptionalProtocol {
    func requiredMethod() {
        print("필수 메서드")
    }
    // optionalMethod는 구현하지 않아도 됨
}

🎯 정리

프로토콜은 다음과 같은 상황에서 사용합니다:

  1. 공통 인터페이스가 필요할 때
  2. 델리게이트 패턴을 구현할 때
  3. 의존성 주입으로 유연한 설계를 할 때
  4. 테스트 가능한 코드를 작성할 때
  5. 모듈 간 결합도를 낮추고 싶을 때

프로토콜을 잘 활용하면 확장 가능하고 유지보수하기 쉬운 코드를 작성할 수 있습니다! 🚀

0개의 댓글