티스토리 뷰

🍏/iOS

[Protocol] 프로토콜 뽀개기

eungding 2019. 1. 8. 19:48
728x90
반응형

@markdown 


티스토리에 마크다운을 적용해보았으나 예쁘지 않다 😧 코드 색깔이 살지 않는다 😔


예쁘게 보려면 깃헙으로~!~!~!~!~!~!~! 


[요기요기](https://github.com/eunjin3786/iOSStudy/blob/master/프로토콜.md ) 눌러보아요



# Protocol 


## 1. 프로토콜 이란


프로토콜은 특정 역할을 하기 위한 메서드, 프로퍼티, 기타 요구사항 등의 청사진.  

구조체, 클래스, 열거형 은 프로토콜을 채택해서 프로토콜의 요구사항을 실제로 구현한다  

프로토콜의 요구사항을 모두 구현한 타입을 '해당 프로토콜을 conform(준수)한다' 라고 표현한다 


```swift

protocol 프르토콜이름 {

   프로토콜 정의 

}

```




### 1.1 프로퍼티 요구사항


 var키워드를 사용한 변수 프로퍼티로 정의한다 


- 읽기 전용 프로퍼티는 { get }

- 읽기쓰기 모두 가능한 프로퍼티는 { get set }


```swift

protocol AProtocol {

    var property : String { get }

    var settableProperty : String { get set }

}

```


### 1.2 메소드 요구사항


```swift

protocol AProtocol {

    func someFunction()

    func someFunction(param1: String, param2 : String)

}

```


### 1.2.1 가변 메소드 요구사항


값 타입(구조체,열거형)의 인스턴스 메서드에서 자신 내부의 값을 변경하려고 하는 함수는 `mutating` 키워드를 써주기


```swift

protocol AProtocol {

    mutating func someFunction()

}

```




### 1.3 타입 프로퍼티 or 타입 메소드 요구사항 




> 타입 자체에 호출이 가능한 프로퍼티나 메소드 ( <-> 인스턴스 프로퍼티, 메소드 )  

>

> 클래스의 타입 프로퍼티, 메소드는 static 키워드와 class키워드를 사용할 수 있는데  

>

> static으로 정의하면 상속후 재정의(override)가 불가능하고 class로 정의하면 재정의가 가능하다. 




 `static` 키워드를 써준다 


```swift

protocol AProtocol {

    var property : String { get }

    static var someTypeProperty: String { get }

    static func someTypeMethod()

}


class ParentClass : AProtocol {

    

    var property: String {

        return "someText"

    }

    

    static var someTypeProperty: String {

        return "someText"

    }

    

    static func someTypeMethod() {

        

    }

}


// 이렇게 사용할 수 있다 

ParentClass.someTypeProperty

ParentClass.someTypeMethod()



class ChildClass : ParentClass {

    

    override var property: String {

        return "AnotherText"

    }

    

    // static 붙은 프로퍼티랑 메소드는 오버라이드 할 수 없다-!

}



// 이렇게 사용할 수 있다 

ChildClass.someTypeProperty

ChildClass.someTypeMethod()


```




## 2. 프로토콜 채택


타입 이름 뒤에 콜론(:)을 붙여준 후 채택할 프로토콜 이름을 쉼표( , ) 로 구분하여 명시해준다 


```swift

struct SomeStruct : AProtocol, BProtocol {

}


class SomeClass : AProtocol, BProtocol {

}


enum SomeEnum : AProtocol, BProtocol {

}


```




- 예제 1 


```swift

protocol AProtocol {

    var property : String { get }

    var settableProperty : String { get set }

}


class SomeClass : AProtocol {

    

    var property: String {

        return "someText"

    }

    

    var settableProperty: String{

        get {

            return "someText"

        }

        set {

            // newValue를 가지고 어떤 것을 한다 

        }

    }

    

}

```




- 예제 2


```swift

protocol Sendable {

    var from : String { get }

    var to : String { get }

    func sendMessage(_ message:String)

}


class Message : Sendable {

    var from: String {

        return "eunjin"

    }

    

    var to: String {

        return "heeyoun"

    }

    

     func sendMessage(_ message: String) {

        print("\(from) -> \(to)")

    }

}


class Mail : Sendable {

    var from: String

    var to: String

    

    init(from:String,to:String) {

        self.from = from

        self.to = to

    }

    

     func sendMessage(_ message: String) {

        print("\(from) -> \(to)")

    }

}

```




- 예제 3 -  `mutating` 


```swift

protocol Resettable {

    mutating func reset()

}


class Person : Resettable {

    var name: String?

    var age: Int?

    

    func reset() {

        self.name = nil

        self.age = nil 

    }

}


struct Point: Resettable {

    var x = 0

    var y = 0 

    

    mutating func reset() {

        self.x = 0 

        self.y = 0

    }

}


enum Direction : Resettable {

    case east, west, south, north, unknown

    

    mutating func reset() {

        self = Direction.unknown 

    }

}

```




## 3. 프로토콜의 이니셜라이저 요구 


프로토콜은 특정한 이니셜라이저를 요구할 수 도 있다 


```swift

protocol Named {

    var name : String { get }

    init(name: String)

}


struct Pet : Named {

    var name : String

    init(name: String) {

        self.name = name 

    }

}

```


클래스에서 이니셜라이저를 요구한 프로토콜을 채택할때는 조금 다르다  

`required` 식별자를 붙여줘야한다 


```swift

class Person : Named {

    var name : String

    

    required init(name: String) {

        self.name = name 

    }

}

```


만약 클래스가 상속받을 수 없는 final 클래스라면 `required` 식별자를 붙여줄 필요없다.  

상속할 수 없는 클래스의 요청 이니셜라이저 구현은 무의미하기 때문 


```swift

final class Person : Named {

    var name : String

    

    init(name: String) {

        self.name = name 

    }

}

```


만약 상속받은 클래스가 있는 상황에서 이니셜라이저를 구현해줘야하는 프로토콜을 채택한다면 `required`와 `override` 식별자를 모두 명시하여야한다 


```swift

class School {

    var name: String

    init(name: String) {

        self.name = name 

    }

}



class MiddleSchool : School, Named {

    required override init(name: String) {

        super.init(name: name)

    }

}

```


Shool 클래스에서 상속받은 init(name: ) 이니셜라이저를 재정의해야하고  

동시에 Named 프로토콜의 이니셜라이저 요구도 충족시켜주어야하기 때문에  

`required`와 `override` 식별자를 모두 표기해야한다  


`override required`하든 `required override`하든 순서는 상관없다 




## 4. 프로토콜의 상속


프로토콜은 하나 이상의 프로토콜을 상속받아 기존 프로토콜의 요구사항보다 더 많은 요구사항을 추가할 수 있다 


```swift

protocol Readable {

    func read()

}


protocol Writeable {

    func write()

}


protocol ReadWriteSpeakable : Readable, Writeable {

    func speak()

}


class SomeClass : ReadWriteSpeakable {

    func read() {

        

    }

    

    func write() {

        

    }

    

    func speak () {

        

    }

}

```




## 5. 클래스 전용 프로토콜의 정의 


해당 프로토콜이 클래스타입에만 채택될 수 있도록 `class` 키워드를 추가해서 제한할 수 있다.  프로토콜의 상속 리스트의 맨 처음에 class 키워드가 위치해야한다. 


```swift

protocol ClassOnlyProtocol : class, Readable, Writeable {

}


class SomeClass : ClassOnlyProtocol {  

}


//오류발생-! 

struct SomeStruct : ClassOnlyProtocol {

}

```


`

class ` 대신 `AnyObject`  키워드를 사용해도 된다.  


```swift

protocol ClassOnlyProtocol : AnyObject, Readable, Writeable {

}


class SomeClass : ClassOnlyProtocol {  

}


//오류발생-! 

struct SomeStruct : ClassOnlyProtocol {

}

```




## 6. 프로토콜 조합 (Composition)


하나의 매개변수가 여러 프로토콜을 모두 준수하는 타입이어야한다면  

& 를 사용하여 여러 프로토콜을 조합하여 요구할 수 있다  

더불어 특정 클래스의 인스턴스 역할을 할 수 있는지 함께 확인 할 수 있다 


```swift

var someVariable: AProtocol & BProtocol


var someVariable : AClass & AProtocol 


// 에러발생 

// 클래스 & 프로토콜 조합에서 클래스 타입은 한 타입만 조합할 수 있다

var someVariable : AClass & BClass & AProtocol 

```




## 7. 프로토콜의 선택적 요구


프로토콜의 요구사항 중 일부를 선택적 요구사항으로 지정할 수 있다. 선택적 요구를 하면 해당 요구사항을 필수로 구현할 필요가 없다 


- 제한조건 


  선택적 요구사항을 정의하고 싶은 프로토콜은 @objc 속성이 부여된 프로토콜이어야한다 


  ------


  @objc 속성 


  = 해당 프로토콜을 Objective-C코드에서 사용할 수 있도록 만드는 역할.


  하지만 Objective-C코드 공유와 상관없이 @objc 속성이 부여되어야만  선택적 요구사항을 정의할 수 있다고 한다. 


  그리고 @objc 속성이 부여된 프로토콜은 클래스에서만 채택가능하다. 열거형이나 구조체에서 채택할 수 없다 


  ------


선택적 요구사항은 optional 식별자를 요구사항의 정의 앞에 붙여주면 된다 


```swift

@objc protocol Moveable {

    func walk()

    @objc optional func fly()

}


class Tiger : Moveable {

    func walk() {

        

    }

}


class Bird : Moveable {

    func walk() {

        

    }

    

    func fly() {

        

    }

}

```




## 8. 위임을 위한 프로토콜 - delegate 패턴 


위임(delegation)은 클래스나 구조체가 자신의 책임이나 임무를 다른 타입의 인스턴스에게 위임하는 디자인 패턴.  

사용자의 특정행동에 반응하기 위해, 비동기처리를 위해 주로 사용한다 


ex) `UITableViewDelegate`  

`UITableView` 타입의 인스턴스가 해야하는 일을 위임받아 처리하는 인스턴스는  

`UITableViewDelegate` 프로토콜을 준수하면 된다. 그러면 `UITableView`의 인스턴스가 해야하는 일을 대신 처리해줄 수 있다. 




- 예제 1


```swift

class Dice {

    

}


protocol DiceGame {

    var dice: Dice { get }

    func play()

}

protocol DiceGameDelegate: AnyObject {

    func gameDidStart(_ game: DiceGame)

    func gameDidEnd(_ game: DiceGame)

}



class SnakesAndLadders: DiceGame {

    let dice = Dice()

    weak var delegate: DiceGameDelegate?

    func play() {

        delegate?.gameDidStart(self)

        // some code

        delegate?.gameDidEnd(self)

    }

}


class DiceGameTracker: DiceGameDelegate {

    func gameDidStart(_ game: DiceGame) {

        print("gameDidStart")

    }

    

    func gameDidEnd(_ game: DiceGame) {

        print("gameDidEnd")

    }

}



let tracker = DiceGameTracker()

let game = SnakesAndLadders()

game.delegate = tracker

game.play()



// 출력

// gameDidStart

// gameDidEnd 

```


DiceGameDelegate 프로토콜은 DiceGame의 진행 상황을 추적하기 위해 채택될 수 있다. 

DiceGameDelegate protocol은 class-only이고 strong reference cycle을 피하기 위해 weak로 선언한다.  




- 예제 2 (옛날에 `delegate` 써보았던 예제)  


```swift

protocol SettingDelegate{

    func settingsDone(vm:SettingsViewModel)

}


class SettingsTableViewController: UITableViewController {


    private var settingsViewModel = SettingsViewModel()

    var delegate:SettingDelegate?

    

    // settingsDone 액션을 delegate한테 위임한다 

    @IBAction func done(){

        // weatherList VC 에서 넘겨준 delegate가 있으면  

        // weatherList VC에서 구현해놓은 delegate의 settingsDone 함수를  

        // 새로운 settingsViewModel을 넣어서 실행하도록 위임해줄게 -!   

        if let delegate = self.delegate{

            delegate.settingsDone(vm: settingsViewModel)

        }

        self.dismiss(animated: true, completion: nil)

    }

}




```


```swift

class WeatherListTableViewController: UITableViewController {

    

    private var weatherListViewModel = WeatherListViewModel()

    

    // SettingsTVC로 넘어갈 때 

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {  

        

       guard let nav = segue.destination as? UINavigationController else { fatalError("Navigation Controller not fiound") }

        

      guard let settingsTVC = nav.viewControllers.first as? SettingsTableViewController else{ fatalError("SettingsTableViewController not found") }

        

        settingsTVC.delegate = self

    }

}


extension WeatherListTableViewController: SettingDelegate {

    

    func settingsDone(vm: SettingsViewModel) {

        self.weatherListViewModel.updateUnit(to: vm.selectedUnit)

        tableView.reloadData()

    }

}



```




## 9. extension 


`extension` 을 통해 필요한 기능을 구현해두면, 그 프로토콜을 채택하는 인스턴스들에서 공통으로 그 기능을 사용할 수 있다 




```swift

Protocol AProtocol {

    

}


extension AProtoco {

    func someFunction () {

        

    }

}

```




`extension`에 `where` 절을 사용하면 `where`절 뒤에 적혀있는 특정 프로토콜을 준수하는 타입에만   


이 `extension`이 적용될 수 있도록 제약을 줄 수 있다. 


```swift

//  AProtocol을 채택하는 타입 중 BProtocol도 채택하는 타입에만 이 익스텐션이 적용될 수 있다 

extension AProtocol where Self : BProtocol {


}



// AProtocol을 채택하는 타입 중 BProtocol,CProtocol 도 채택하는 타입에만 이 익스텐션이 적용될 수 있다

extension AProtocol where Self : BProtocol, Self : CProtocol {


}

```




where 써주는 것이 귀찮아서 콜론(:) 으로 해보았으나 


```

extension AProtocol: BProtocol {

    

}

```


📌 ' Extension of protocol 'AProtocol' cannot have an inheritance clause  '   


이라고 컴파일 에러가 뜬다 🤔  




## 10. Protocol Type 


프로토콜을 타입으로 사용할 수 있다  

그리고 `array` 나 `dictionary` 같은 `collection`에 저장될 유형으로 사용할 수 있다.


```swift

protocol TextRepresentable {

    

}


class SomeClass : TextRepresentable {

    

}


struct SomeStruct : TextRepresentable {

    

}


let someClass = SomeClass()

let someStruct = SomeStruct()

let array: [TextRepresentable] = [someClass, someStruct]

```






### Reference 


- 야곰의 스위프트 프로그래밍 

- https://docs.swift.org/swift-book/LanguageGuide/Protocols.html

반응형
댓글