티스토리 뷰

Delegate Pattern 활용하기 (ViewController끼리 Data 전달하기 - 2)

Swift Version 5.3
Xcode Version 12.3

 

드디어.. 마음의 짐으로 남아있던 이 Delegate Pattern에 대해 설명을 하려고 합니다.

이전 글은 아래를 참고해주세요!
2020/11/25 - [Swift] - ViewController끼리 Data 전달하기 - 1

Delegate가 위임자고 우리는 이 위임자의 일을 대신 받아서 처리하는 역할을 하는거고.. 뭐 이런건 너무 자주 듣는 이야기니까 패스하고,
저는 이렇게 이해했습니다.

1. 누군가가 지정해 놓은 일을
2. 내가 대신 하는 것

예를 들어 학창 시절에 주번이 되면 칠판 지우기라던지, 복도 청소라던지 그런걸 했었잖아요?
주번의 역할은 정해져있고, 내가 주번이 되면 그 역할을 하게 되는 것이죠.
자 이정도로 이해를 하고, 실제 예제들을 보도록 하겠습니다.


 

UITableViewDelegate

Delegate 하면 대표적으로 생각나는 UITableViewDelegate가 있습니다.

  1. UITableViewDelegate라는 프로토콜 안에 있는 함수들을
  2. tableView.delegate = self 를 통해 내가 대신 하겠다고 선언하는 것입니다.

1. UITableViewDelegate라는 프로토콜 안에 있는 함수들을

TableView를 사용하기 위한 함수들을 정의해 놓은 프로토콜로, Xcode에서 Jump to Definition 해서 보면

이 보다도 더 많은 함수들이 있습니다. 주번의 역할과도 같은 것이죠.

그럼 이 함수들을 내가 사용하려면 어떻게 해야 할까요?

 

2. tableView.delegate = self 를 통해 내가 대신 하겠다고 선언하는 것입니다.

우선 UITableViewDelegate 프로토콜을 채택합니다.

class ViewController: UIViewController, UITableViewDelegate

프로토콜의 채택과 준수에 대해서는 아래 포스트에 잘 설명이 되어 있으니 읽어보시는 것을 추천드려요!
https://daheenallwhite.github.io/swift/2019/06/02/Protocol-Conformance-Adoption/

그 다음 viewDidLoad()함수 안에 tableView.delegate = self 를 작성해서 ViewController가 그 일을 대신 하겠다고 선언합니다.

override func viewDidLoad() {
        super.viewDidLoad()

        testTableView.delegate = self
}

그러면 이제 UITableViewDelegate가 제공하는 함수들을 사용할 수 있게 됩니다.

다시 한 번 정리하자면,

  • UITableViewDelegate 안에 정의되어 있는 많은 함수들을 사용하기 위해
  • 해당 프로토콜을 채택하고,
  • 위임자를 나로 선택하는 것입니다.

그럼 이제 실제 Delegate를 구현해 보도록 하겠습니다.

 

Delegate Pattern 구현하기

UITableViewDelegate가 protocol이었던 것, 기억 하시나요?
우리가 데이터를 주고 받을 때 사용할 함수를 정의하기 위해 프로토콜을 하나 만듭니다.

protocol TestDelegate: class {
  func delegateFunction(data: String) -> String
}

프로토콜은 청사진을 제시하는 것이기 때문에 함수의 내용은 작성하지 않고 함수 형태만 작성해주면 됩니다.

여기서 살짝, 아주 살짝 어려워지는데요 !
현재 화면을 A, 새로운 화면을 B라고 했을 때 현재 화면에서 TestDelegate를 채택해주세요.

class ViewController: TestDelegate

그러면 아래 사진과 같이 빨간 불이 들어올 텐데요, TestDelegate 라는 프로토콜을 채택했으니 프로토콜을 준수하기 위해 꼭 지켜야 할 함수들을 작성하라는 뜻입니다.

우리가 위에 작성했던 함수를 구현해 줍니다.

func delegateFunction(data: String) -> String{

    // 두 번째 화면에서 입력한 data를 첫 번째 화면에서 사용 가능
        print("ViewController DelegateFunction: \(data)")
        return "I am \(data)"
}

새로운 화면 B(DelegateVC)에서 구현한 함수를 호출해줄겁니다.

class DelegateVC: UIViewController {

  // TestDelegate의 구현체를 만들어줍니다.
    weak var delegate: TestDelegate?

    override func viewDidLoad() {
        super.viewDidLoad()
    // viewDidLoad에서 해당 함수를 호출합니다.
    // 첫 번째 화면에서도 두 번째 화면으로 데이터를 전달 가능
        let data = self.delegate?.delegateFunction(data: "Hyeon")
        print("data: \(data)")
    }
}

위의 viewDidLoad에서 해당 함수를 호출했기 때문에
Hyeon 으로 들어갔던 데이터가 I am Hyeon 으로 나오게 됩니다.

자, 그러면 TestDelegate에 해야 할 일을 지정은 해놨는데
내가 대신 하겠다고 선언하는 부분이 필요하겠죠?

두 번째 화면의 일을 첫 번째 화면이 대신 해줘야 하기 때문에
선언 부분은 첫 번째 화면에 있어야겠죠?
저는 첫 번째 화면에서 버튼을 눌렀을 때 두 번째 화면으로 이동하려고 했기 때문에
버튼 탭 함수 안에서 아래와 같이 선언 함수를 입력했습니다.

let nextVC = DelegateVC()
nextVC.delegate = self
self.present(nextVC, animated: false, completion: nil)

 

이렇게 해서 우리는

  • TestDelegate 프로토콜을 만들어서 해당 프로토콜에 함수들을 정의하고
  • 해당 프로토콜을 채택하고, 호출할 곳을 정한 후
  • 위임자를 선택을 했습니다.

 

Delegate Pattern이 무엇인지는 이해를 했는데, 지난 글에서 설명 드렸던 Initialize Parameter로 데이터 넘기는 것보다는 더 어려워보이지 않나요?
그래서 A -> B 로 단방향 데이터 전달일 때는 Initialize Parameter가 더 편하지만, A <-> B 로 양방향 데이터를 전달할 때는 Delegate Pattern을 사용하는 것이 더 유용하답니다!

위에서 사용한 예제는 깃헙에서 확인하실 수 있습니다.

 

 

Delegate Pattern 응용하기

이 Delegate Pattern을 좀 더 깊게 응용해보도록 하겠습니다.

UIViewController와 UIView 사이를 자유롭게 오가기 위해서는 Delegate Pattern을 이해하는게 정말 필수적입니다.

알람을 예로 들어보겠습니다.

알람을 보여주는 화면은 하나(AlarmVC)지만, 알람은 여러 개가 있을 수 있기 때문에 View로 그려야 합니다(AlarmView).

알림의 내용은 서버에서 받아오는 데이터를 화면에 그리면서 같이 설정하면 되지만, 스위치 버튼은 어떻게 해야할까요?

스위치 버튼은 AlarmView에 있기 때문에 AlarmView에서 삭제 처리를 하고 싶은데, 알림 내용은 AlarmVC에 있기 때문에 내가 지금 누르는 버튼이 몇 번째 뷰에 있는 알림인지 알 수가 없습니다.

바로 이 때! Delegate를 이용하면 편리하게 구현할 수 있습니다.

struct Alarm {
  let id: Int
  let isActive: Bool
}

protocol AlarmDelegate: class {
  func editAlarmStatus(alarm: Alarm)
}

스위치 버튼을 누르면 editAlarmStatus 함수가 실행되어야 합니다.

이 프로토콜이 원래 실행되어야 하는 곳은 AlarmView이기 때문에 AlarmView 안에서 delegate 구현체를 만들어 줍니다.

class AlarmView: UIView {
  private let alarm: Alarm
  weak var delegate: AlarmDelegate?
}

또한 스위치 버튼을 누를 경우 AlarmDelegate 안에서 editAlarmStatus가 실행되도록 설정해 줍니다.

@IBAction func switchTapped(_ sender: Any) {
  self.delegate?.editAlarmStatus(alarm: alarm)
}

이제 AlarmVC입니다.
우선 AlarmVC에서 AlarmDelegate를 채택합니다.

class AlarmVC: UIViewController, AlarmDelegate {
  func editAlarmStatus(alarm: Alarm) {
    // AlarmView에서 스위치가 눌렸을 때 실제로 함수가 어떻게 실행될 지를 구현합니다.
    alarm.isActive = !alarm.isActive
  }
}

그리고 마지막으로 AlarmDelegate를 채택하는 것뿐만 아니라 AlarmDelegate의 일을 내가 대신하겠다고 선언도 해야 한다고 했죠?

AlarmVC에서 알람의 수에 따라 AlarmView를 구현하는 부분에서 아래와 같이 선언해주면 됩니다!

for alarm in alarmList {
  let alarmView = AlarmView(alarm: alarm)
  alarmView.delegate = self
}

 

이렇게

  • 위임하고자 하는 함수들을 프로토콜로 작성
  • 해당 함수가 원래 실행되어야 할 부분들에서 호출
  • 함수를 대신 실행할 곳에서 프로토콜을 채택, 준수
  • 위임자 선택

의 행위를 통해 ViewController 와 View 사이를 관리할 수 있게 되었습니다.

 

후기

이렇게 제 첫 시리즈? 글이었던 ViewController끼리 Data 전달하기 작성을 완료하였습니다.
제가 제시해드린 세 가지 외에도 더 다양한 내용을 확인하고 싶으시다면 아래 블로그를 확인해주세요!

https://learnappmaking.com/pass-data-between-view-controllers-swift-how-to/#back-delegation

수정 내용이나 비판은 언제든지 감사히 받겠습니다 : )

댓글