🐦 Swift

[Swift] 프로토콜 (protocol)

dev_zoe 2023. 6. 20. 23:03
반응형

본 포스팅은 '스위프트 프로그래밍 (3판)' 도서 앨런 Swift 문법 마스터스쿨 강의를 참고하여

Swift 프로그래밍에 대해 정리하는 글입니다.

혹시 틀린 부분이 있거나 질문이 있으시다면 언제든지 댓글 달아주시면 정말 감사하겠습니다 :)


프로토콜

프로토콜이란 특정 역할을 하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진(그래서 구현은 없고 형태만 있음)이다.

구조체, 클래스, 열거형은 프로토콜을 채택하여 프로토콜을 구현할 수 있다.

 

protocol 프로토콜 이름(대문자로 시작){

}

 

❓ 상속 vs 프로토콜

 

1) 상속은 클래스만 상속 가능하며, 다중 상속이 불가하다.

프로토콜은 클래스, 구조체, 열거형에서 채택 가능하며 다중 채택이 가능하다.

-> 꼭 클래스가 아니라 구조체에서도 공통된 메서드를 실행하게 하고싶다면?

 

2) 상속은 상위 클래스의 속성과 메소드를 모두 따라갈 수 밖에 없어서 필요하지 않은 속성/메소드들도 같이 상속되는 불편한 점이 존재하여

프로토콜을 사용하면 해당 인스턴스에 필요한 기능만 구현할 수 있다.

-> 꼭 모든 속성/메서드를 상속을 하지 않고도 일부만 사용하고 싶다면?

 

✅ 채택, 구현

 

어떠한 클래스, 구조체, 열거형이 프로토콜을 따르고자 할 때 ": 프로토콜이름" 으로 지정하는 것을 "채택" 이라고 한다.

그리고 프로토콜의 요구사항을 구현하는 것을 "구현" 이라고 한다.

protocol MyProtocol {   // 정의
	func doSomething() { }
}

class MotherClass {
	func doSomething() { }
}

// 채택 (클래스, 구조체, 열거형 다 가능)
class MyClass: MotherClass, MyProtocol {    // 상위클래스인 FamilyClass를 먼저 선언
    override func doSomething() { // 재정의
    
    }
    
    // MyProtocol 구현
    func doSomething() {
    	print("do")
    }
}

 

프로토콜 요구사항 정의

1) 속성

- var로 선언 (let이 안되는 이유는, 프로토콜은 최소 요구사항을 구현하는 곳이고 채택한 곳에서 let 상수로 구현할 수 있으므로 변할 수 있다는 점에서 var로 선언해야함) 

- 읽기 전용 속성 ==> 채택: let/var 저장속성, 읽기 계산 속성, 읽기/쓰기 계산 속성

- 읽기/쓰기 속성 ==> 채택: var 저장 속성, 읽기/쓰기 계산 속성

- 타입 속성 ==> 채택: static(타입 저장/계산)/class (타입 계산) 속성

 

protocol RemoteControl {
    
    var id: String { get }                // ===> let 저장속성 / var 저장속성 / 읽기계산속성 / 읽기,쓰기 계산속성
    
    var name: String { get set }          // ===> var 저장속성 / 읽기,쓰기 계산속성

    static var type: String { get set }   // ===> 타입 저장 속성 (static)
                                          // ===> 타입 계산 속성 (class)
                                          
                                          
    // func getId() -> Int       // 리턴타입까지가 헤드부분임
}

struct TV: RemoteControl {
    
    var id: String {   // 계산 속성 -> setter가 추가될 수 있는 가능성때문에 var 선언
        return "hi"
    }
    // var id: String = "hi" 가능
    // let id: String = "hi" 가능
    
    var name: String = "삼성티비"
    
    static var type: String = "리모콘"  // class 불가 -> 상속은 class만 가능하니까
  //  static var type: String {
  //  	return "리모콘"
  //  }
}

 

2) 메소드

- mutating: 구조체도 자기 자신의 속성을 변경하도록 하는 키워드 (클래스에서도 사용 가능함)

- 이외에 메소드 요구사항 정의 제한은 크게?없는듯

protocol Togglable {
    mutating func toggle()        // 클래스에서는 Mutating 안붙여도 됨
}


enum OnOffSwitch: Togglable {
    case on
    case off
    
    mutating func toggle() {
        switch self {   // .on   .off
        case .off:
            self = .on
        case .on:
            self = .off
        }
    }
}
protocol DataList {
    subscript(idx: Int) -> Int { get }     // (서브스크립트 문법에서) get 필수 (set 선택)
}

struct DataStructure: DataList {
    
    subscript(idx: Int) -> Int { 
        get {
            return 0
        }
        set { 
            
        }
    }
}

 

3) 생성자 (프로젝트 할때 흔하지는 않음)

- 클래스에서 생성자 채택시, required를 붙여야함 (왜냐? 하위 클래스가 해당 클래스를 상속할 때 해당 생성자를 반드시 구현해야 함을 명시하는 것임)

- 이 때, 지정생성자를 따로 만들지 않으면 required init은 자동 상속됨

- 만약에 class 앞에 final을 붙이면 required를 붙이지 않아도 무관 (왜냐 final을 붙이면 상속을 못하니까)

 

타입으로써의 프로토콜 (일급 객체로서의 프로토콜)

프로토콜은 타입이므로 1) 변수에 할당하고, 2) 파라미터로 전달하며 반환할 수 있다.

따라서 타입캐스팅을 통해 구체적인 타입으로 사용하는 것 또한 가능하다.

tv is Remote
sbox1 is Remote

// 업캐스팅(as)
let newBox = sbox1 as Remote
newBox.turnOn()
newBox.turnOff()

// 다운캐스팅(as?/as!)
let sbox2: SetTopBox? = electronic[1] as? SetTopBox
sbox2?.doNetflix()
// 인수로 전달 가능
protocol Drawable {
    func draw()
}

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

class Rectangle: Drawable {
    func draw() {
        print("네모를 그리다")
    }
}

// 인수로 전달 가능
func drawShapes(_ arr:[Drawable]) {
    arr.forEach { $0.draw() }
}

let circle = Circle()
let rec = Rectangle()

drawShapes([circle, rec])

 

프로토콜의 상속, 클래스 전용 프로토콜

프로토콜은 다중 상속이 가능하며, 다중 상속 시 다른 프로토콜의 모든 요구 사항을 구현해야한다.

AnyObject: 클래스 전용 프로토콜로 만들고자 한다면 AnyObject 프로토콜을 상속하면 된다.

 

프로토콜 조합(합성)

protocol1 & protocol2 로 프로토콜을 조합하여 한꺼번에 채택할 수 있게 할 수 있으며

타입으로 지정하고자 할 때 해당 프로토콜 2개 모두를 따르는 타입만 저장 가능함

 

프로토콜의 선택적 요구

@objc 키워드를 붙여서 프로토콜에서 요구사항 구현 시, 선택적인 멤버로 구현 가능하도록 할 수 있음

- objc: Objective-C 코드에서 사용할 수 있도록 하는 키워드

프로토콜 앞에는 @objc 키워드를, 메서드 앞에는 @objc optional 을 붙이면 해당 멤버는 선택적 구현이 가능하다. (구현하지 않아도 무관하도록 바뀜)

 

단, 유의할 점은 @objc 키워드를 붙이면 해당 프로토콜이 클래스 전용 프로토콜로 바뀌기 때문에 구조체/열거형에서는 사용 불가하다.

@objc protocol Flyable {
    @objc optional var wing: Int { get }
    @objc optional func fly()
    func sayName()
}

class Fly: Flyable {
    var wing = 5
    func sayName() {
        print("name")
    }
}

let fly = Fly()
fly.sayName()

 

프로토콜 지향 프로그래밍

애플은 2015년 6월, WWDC에서 스위프트는 "프로토콜 지향 언어" 라고 선언하였음.

*프로토콜 지향 프로그래밍이란?

 

 

https://developer.apple.com/videos/play/wwdc2015/408/

 

Protocol-Oriented Programming in Swift - WWDC15 - Videos - Apple Developer

At the heart of Swift's design are two incredibly powerful ideas: protocol-oriented programming and first class value semantics. Each of...

developer.apple.com

- 프로토콜을 채택하는 타입들이 모두 같은 메소드를 쓴다면 ...? => 많은 코드 중복과 유지보수의 어려움 발생

- 익스텐션과 프로토콜의 결합은 이러한 코드의 중복 구현 불편함을 제거해줌 => 애플은 프로토콜의 채택을 확장에서 구현하는 것을 권장하고 있음.

 

익스텐션과 프로토콜의 결합

- 프로토콜 초기 구현

protocol Remote {
    func turnOn()
    func turnOff()
}

class TV1: Remote {
    func turnOn() {
        // 구현
    }
    func turnOff() {}
    func doAnotherAction(){   
        // 구현
    }
}

// => 만약에 TV가 여러개라면...? 언제다 turnOn/turnOff를 다 구현하냐! 너무 귀찮고 코드 중복이 심하다.
// => 익스텐션과 결합함으로써 해결!

extension Remote {                         
    func turnOn() { print("리모콘 켜기") }   
    func turnOff() { print("리모콘 끄기") }  
    
    func doAnotherAction() {             
        print("리모콘 또 다른 동작")
    }
}

var tv1: Remote = TV1()
tv1.turnOn()   // 리모콘 켜기
tv1.turnOff()  // 리모콘 끄기
tv1.doAnotherAction()

- 해당 예시와 같이 코드의 중복을 없애줌으로써 유지보수할 수 있는 코드로 만들 수 있다.

이렇게 했을 때 메서드가 실행되는 규칙이 존재함

 

- 프로토콜 확장의 적용 제한

프로토콜을 확장할 시, 특정 프로토콜을 채택한 프로토콜에만 확장이 적용되도록 제한할 수 있음

protocol Remote {
    func turnOn()        
    func turnOff()       
}

protocol Bluetooth {
    func blueOn()
    func blueOff()
}

extension Bluetooth where Self: Remote {   
    func blueOn() { print("블루투스 켜기") }   
    func blueOff() { print("블루투스 끄기") }
}

 

위임 패턴(델리게이트 패턴)

클래스나 구조체가 자신의 책임이나 임무를 다른 타입의 인스턴스에게 위임하는 것

보통 UITableView나 UITextField 등에서 이들의 기능을 뷰컨트롤러가 대신 수행하고싶을 때 사용

 

 

반응형