Swift에서의 프로토콜 지향 프로그래밍: 프로토콜을 사용하여 유연하고 재사용 가능한 코드 작성.

작성일 :

Swift에서의 프로토콜 지향 프로그래밍

Swift에서 프로토콜 지향 프로그래밍(POP, Protocol-Oriented Programming)은 클래스 기반의 객체 지향 프로그래밍(OOP, Object-Oriented Programming)을 보완하거나 대체할 수 있는 방법론으로 주목받고 있습니다. 이 글에서는 POP의 기본 개념부터, 유용한 사용 사례, 그리고 실제 코드를 통해 프로토콜을 어떻게 활용할 수 있는지 자세히 설명합니다.

프로토콜의 기본 개념

프로토콜은 특정 기능을 수행하기 위한 메서드, 프로퍼티, 기타 요구사항의 집합입니다. 클래스, 구조체, 열거형 등이 하나 이상의 프로토콜을 채택(adopt)하여 그 요구사항을 구현해야 합니다. 이는 Java나 C#의 인터페이스와 유사한 개념입니다.

예를 들어, 프로토콜은 다음과 같이 정의될 수 있습니다:

swift
protocol Drivable {
    var maximumSpeed: Int { get }
    func drive()
}

위의 Drivable 프로토콜은 maximumSpeed라는 읽기 전용 프로퍼티와 drive라는 메서드를 요구합니다. 다음과 같이 이 프로토콜을 사용할 수 있습니다:

swift
struct Car: Drivable {
    var maximumSpeed: Int
    func drive() {
        print("Driving at \(maximumSpeed) km/h")
    }
}

이렇게 Car 구조체가 Drivable 프로토콜을 채택하여 요구사항을 구현하면, Car 인스턴스는 drive() 메서드를 호출할 수 있습니다.

프로토콜의 장점

유연성

프토토콜은 클래스 상속과 달리 다중 상속을 지원합니다. 이는 하나의 타입이 여러 개의 프로토콜을 채택할 수 있음을 의미합니다. 이렇게 다중 상속의 문제를 해결하면서도, 코드의 유연성을 높일 수 있습니다.

swift
protocol Flyable {
    var maximumAltitude: Int { get }
    func fly()
}

struct FlyingCar: Drivable, Flyable {
    var maximumSpeed: Int
    var maximumAltitude: Int
    func drive() {
        print("Driving at \(maximumSpeed) km/h")
    }
    func fly() {
        print("Flying at \(maximumAltitude) meters")
    }
}

위의 예제에서 FlyingCarDrivableFlyable 두 개의 프로토콜을 채택하여 두 가지의 기능을 모두 구현합니다.

재사용성

프로토콜 지향 프로그래밍은 코드 재사용성을 높이는 데 매우 유리합니다. 공통 기능을 프로토콜로 정의하고 여러 타입에서 이를 구현함으로써, 코드 중복을 줄이고 유지보수를 용이하게 합니다.

swift
protocol Workable {
    func doWork()
}

extension Workable {
    func doWork() {
        print("Work in progress")
    }
}

struct Robot: Workable {}
struct Employee: Workable {}

let robot = Robot()
robot.doWork() // Work in progress

let employee = Employee()
employee.doWork() // Work in progress

위의 예제에서 Workable 프로토콜에 기본 구현을 제공하여, RobotEmployee가 중복된 코드를 작성하지 않도록 합니다. 대신, 필요시 각 타입에서 doWork()를 재정의할 수 있습니다.

동일한 인터페이스 제공

애플리케이션에서 다양한 타입이 하나의 일관된 인터페이스를 사용하도록 할 수 있습니다. 이는 특히 콜렉션 타입에서 유용합니다. 예를 들어, Equatable 프로토콜을 사용하면 타입 간 비교 기능을 일관되게 제공합니다.

swift
protocol Equatable {
    static func == (lhs: Self, rhs: Self) -> Bool
}

struct Person: Equatable {
    var name: String
    var age: Int

    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.name == rhs.name && lhs.age == rhs.age
    }
}

let person1 = Person(name: "John", age: 30)
let person2 = Person(name: "John", age: 30)

if person1 == person2 {
    print("They are the same person.")
}

실제 사용 사례

데이터 소스와 델리게이트 패턴

iOS 개발에서 프로토콜은 주로 데이터 소스(data sources)와 델리게이트(delegate) 패턴에 많이 사용됩니다. 예를 들어, UITableViewDataSourceUITableViewDelegate 프로토콜은 테이블뷰의 데이터 공급 및 이벤트 처리를 담당합니다.

swift
protocol UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
}

protocol UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
}

개발자는 이를 채택하여 원하는 동작을 구현할 수 있습니다. 이렇게 하면 테이블뷰 행동을 유연하게 조정할 수 있습니다.

네트워킹

네트워크 요청을 처리할 때도 프로토콜을 사용할 수 있습니다. 네트워크 레이어를 보다 추상화하고, 모킹(mocking)과 같은 테스트 작업을 용이하게 합니다.

swift
protocol NetworkRequest {
    func fetchData(completion: @escaping (Data?, Error?) -> Void)
}

class APIClient: NetworkRequest {
    func fetchData(completion: @escaping (Data?, Error?) -> Void) {
        // 네트워크 요청 작업...
    }
}

APIClient 클래스는 NetworkRequest 프로토콜을 구현하여 네트워크를 통해 데이터를 가져오며, 이는 테스트 시 모킹을 통해 실제 네트워크 요청 없이도 동작을 검증할 수 있는 유연한 구조를 제공합니다.

결론

Swift에서 프로토콜 지향 프로그래밍은 코드의 유연성, 재사용성, 일관성을 크게 향상시킵니다. 프로토콜을 통해 다양한 기능을 추상화하고, 코드 중복을 줄이며, 타입을 일관된 방식으로 처리할 수 있습니다. 프로토콜을 적절히 활용하면 더 깨끗하고, 유지보수하기 쉬운 코드를 작성할 수 있습니다.