-
Swift Protocol 다중 채택Swift 2025. 2. 26. 19:54
안녕하세요. 그린입니다 🍏
이번 포스팅에서는 Swift Protocol과 다중 채택에 대해 정리해보겠습니다 🙋🏻
사실, 내용 자체는 기초적이고 간단하지만 처음 개발을 시작하고 Swift를 배우시는 분들은 헷갈려 하거나 의문을 품는 지점이 간혹 있어 한번 쉽게 정리해보려고 합니다!
Protocol?
Swift에서 프로토콜은 특정 요구사항을 정의하는 청사진이죠.
클래스, 구조체, 열거형이 해당 프로토콜을 채택하면 프로토콜이 정의한 요구사항을 충족해야 합니다.
protocol Greetable { var name: String { get } func greet() -> String } struct Person: Greetable { var name: String func greet() -> String { return "Hello, my name is \(name)." } } let person = Person(name: "Green") print(person.greet()) // Hello, my name is Green.
보시는것처럼 기본적으로 이렇게 사용되죠 😃
기본적으로 프로토콜을 안다고 가정하겠습니다.
그럼 오늘 주제인 다중 채택에 대해 슬슬 넘어갈께요~
Protocol 다중 채택
Swift에선 클래스를 다중 상속하는게 불가능해요.
반면, Swift에서는 하나의 타입이 여러 개의 프로토콜을 동시에 채택할 수 있습니다.
이를 통해서 코드의 유연성과 재사용성을 높일 수 있어요.
코드 하나 볼까요?
protocol Runnable { func run() } protocol Swimmable { func swim() } struct Athlete: Runnable, Swimmable { func run() { print("Running fast!") } func swim() { print("Swimming well!") } } let athlete = Athlete() athlete.run() // Running fast! athlete.swim() // Swimming well!
이렇게 Athlete는 Runnable과 Swimmable을 동시에 채택해 다중 프로토콜 구현을 보여주고 있죠.
더 들어가볼까요?
Protocol 다중 채택의 활용
다중 프로토콜 채택은 유지보수성이 뛰어난 코드를 작성하는데 유용해요.
예를 들어서, 네트워크 요청을 수행하는 객체와 로깅 기능을 분리해 테스트 가능성을 높여줍니다.
protocol NetworkService { func fetchData() -> String } protocol Logger { func log(message: String) } struct APIService: NetworkService { func fetchData() -> String { return "Fetched data from API" } } struct ConsoleLogger: Logger { func log(message: String) { print("LOG: \(message)") } } struct DataManager: NetworkService, Logger { let networkService: NetworkService let logger: Logger func fetchData() -> String { let data = networkService.fetchData() log(message: "Data received: \(data)") return data } func log(message: String) { logger.log(message: message) } } let apiService = APIService() let consoleLogger = ConsoleLogger() let dataManager = DataManager(networkService: apiService, logger: consoleLogger) dataManager.fetchData() // LOG: Data received: Fetched data from API
위 코드를 보면 DataManager가 NetworkService와 Logger를 다중 채택해 의존성을 주입하는 방식으로 구현할 수 있어요.
다음으로 다중 프로토콜과 프로토콜 확장에 대해 한번 살펴볼께요.
다중 프로토콜 & 프로토콜 확장
프로토콜 확장을 이용해 다중 프로토콜을 채택한 타입에 기본 구현을 제공할 수 있어요.
protocol Identifiable { var id: String { get } } protocol Displayable { func displayInfo() } extension Identifiable { var id: String { return UUID().uuidString } } extension Displayable where Self: Identifiable { func displayInfo() { print("ID: \(id)") } } struct User: Identifiable, Displayable {} let user = User() user.displayInfo() // ID: (자동 생성된 UUID 출력)
코드를 보면 Identifiable 프로토콜은 id의 기본 구현을 제공하고 Displayable 프로토콜은 Identifiable을 채택한 타입에 대해서만 기본 displayInfo() 메서드 구현을 제공하고 있어요.
이렇게도 활용될 수 있죠.
또한 믹스인 패턴으로 이용될 수도 있습니다.
Mixin Pattern with in Multiple protocol
Swift의 다중 프로토콜 채택은 Mixin 패턴을 구현하는데 효과적이에요.
믹스인 패턴은 공통 기능을 조합해 타입에 동적으로 기능을 추가해주는 패턴으로 사용될 수 있습니다.
protocol Drivable { func drive() } protocol Flyable { func fly() } protocol Amphibious: Drivable, Flyable {} struct SuperCar: Amphibious { func drive() { print("Driving on the road!") } func fly() { print("Flying in the sky!") } } let vehicle = SuperCar() vehicle.drive() // Driving on the road! vehicle.fly() // Flying in the sky!
이렇게 Amphibious 프로토콜이 Drivable과 Flyable을 상속받아 여러 프로토콜을 조합하는 Mixin 패턴을 구현할 수 있습니다.
이렇게 유용한 다중 프로토콜 채택도 주의할 부분이 있어요.
다중 프로토콜 채택 시 주의할 부분
메서드 충돌이 날 수 있습니다.
코드부터 볼까요?
protocol A { func doSomething() } protocol B { func doSomething() } struct Example: A, B { func doSomething() { print("Implemented doSomething") } }
이렇게 두 개 이상의 프로토콜이 동일 메서드를 요구하는 경우라면 명확한 구현이 필요해요.
즉, 충돌을 일으키죠.
코드처럼 Example 타입에서 명확하게 구현을 하면 충돌은 해결할 수 있습니다.
즉, 두 프로토콜이 동일 메서드를 요구한다 하더라도 하나의 구현으로 통합한다면 컴파일 에러가 나지 않아요.
또한 다음 방법도 있어요.
protocol A { func doSomething() } protocol B { func doSomething() } struct Example: A, B { func doSomething() { print("Default Implementation") } } extension A { func doSomething() { print("A's Implementation") } } extension B { func doSomething() { print("B's Implementation") } } let example = Example() as A example.doSomething() // A's Implementation
다형성을 이용해 프로토콜을 캐스팅해 호출하는 방식을 사용하면 특정 프로토콜의 구현을 선택할 수 있습니다.
이런 상황도 볼까요?
protocol A { func hello() } protocol B { func hello() } extension A { func hello() { print("A") } } extension B { func hello() { print("B") } } struct Hello: A, B { ... }
이렇게 된다면 hello 구현이 없어 모호하기에 에러가 발생합니다.
즉, A, B는 기본 구현을 제공하고 있지만 어떤걸 사용해야할지 모르니까요.
그래서 이렇게 타입 캐스팅을 하는걸로 해결할 수 있어요.
let hello = Hello() (hello as A).hello() // A (hello as B).hello() // B
만약 Hello 객체에서 hello 메서드에 무언갈 채워넣었다면, 그 로직이 돌겠죠~?
간단하죠!?
마무리
Swift에서 프로토콜 다중 채택은 유연하고 확장 가능한 코드를 작성해주는데 아주 도움을 줍니다.
이를 활용해 의존성 주입, 확장, 믹스인 패턴 등 다양하게 구현할 수 있죠.
단, 주의할 부분들도 있기에 이런 부분들을 고려한다면 더 깔끔한 설계를 할 수 있습니다.
레퍼런스
Documentation
docs.swift.org
'Swift' 카테고리의 다른 글
Property Observers (willSet, didSet) (7) 2025.01.27 Swift로 효율적인 디버그 로깅 시스템 구축하기 (11) 2025.01.06 RangeSet (feat. Set, IndexSet) (29) 2024.12.26 NSObject에 대하여 (31) 2024.12.19 Explore the Swift on Server ecosystem (feat. WWDC 2024) (34) 2024.12.02