Weekoding
[Swift] ViewController간 데이터 주고받기 - Delegate pattern 본문
ViewController간 데이터를 주고받는 방법은 6가지가 있다.
1. 직접 프로퍼티에 접근
2. 함수를 통한 접근
3. Segue
4. Delegate
5. Closure
6. NotificationCenter
그 중 1, 4번을 간단한 예제를 통해 정리해보려고 한다. ( 나머지는 추후에..! ㅎㅎ )
먼저 잠깐 알아보자면 1~3번의 방법들은 남발하게 되면 좋지 않은데,
이 방법들은 다른 ViewController에 직접적으로 의존하게 되므로 강한 결합이 되어있는 형태가 된다.
이는 객체 간 모듈화에도 좋지 않으며 스파게티 코드가 될 위험이 있다고 한다.
4~6번의 방법은 이러한 강한 결합 형태의 문제점을 벗어나 서로 의존하지 않는 구조로 전달할 수 있다.
이번 예제에서는 FirstViewController, SecondViewController를 두고 FirstVC → SecondVC → FirstVC 의 과정에서
FirstVC → SecondVC 에서는 직접 프로퍼티에 접근하여 데이터를 전달하고,
SecondVC → FirstVC 에서는 Delegate pattern을 이용하여 protocol을 통해 데이터를 전달해 볼 것이다.
(Layout은 Snapkit과 코드를 이용해 작성했다.)
📂 FirstVC → SecondVC (직접 프로퍼티에 접근해 데이터를 넘겨주는 방법)
먼저, SecondVC에 변수를 하나 생성해준다.
FirstVC에서 직접 접근할 변수이다.
//SecondViewController
class SecondViewController: UIViewController{
var dataFromFirst: String = ""
//중략
}
그 후 FirstVC에서 SecondVC로 넘어갈 수 있도록
인스턴스 객체를 생성해주고, 직접 프로퍼티에 접근한다.
callBtn을 누르면 openSecondViewController내의 코드를 통해 SecondVC가 push된다.
//FirstViewController
private lazy var callBtn: UIButton = {
let button = UIButton()
button.setTitle("Next", for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 16.0, weight: .semibold)
button.layer.cornerRadius = 6.0
button.backgroundColor = .systemBlue
button.addTarget(self, action: #selector(openSecondViewController), for: .touchUpInside)
return button
}()
@objc func openSecondViewController(){
guard let myString = textField.text else { return }
let vc = SecondViewController()
vc.dataFromFirst = "\(myString) - I'm from firstVC"
self.navigationController?.pushViewController(vc, animated: true)
}
FirstVC의 textField에 내가 "hello world"를 입력했다면,
SecondVC의 dataFromFirst에는 "hello world - I'm from firstVC"가 저장될 것이다.
Label을 통해 데이터가 잘 전달되었는지 확인하였다.
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
self.setupView()
//data from FirstViewController
resultLabel.text = dataFromFirst
}
직접 프로퍼티에 접근하는 방식은 이처럼 매우 간단하다 !
📂 SecondVC → FirstVC (Delegate pattern을 이용한 방법)
Delegate Pattern: 디자인 패턴 중 하나로, 일부 클래스의 책임을 다른 클래스에 위임하는 패턴.
sender(송신 측), receive(수신 측), protocol이 준비물이다. 바로 사용방법에 대해 알아보자.
✉️ SecondViewController(데이터를 보낼 쪽): protocol을 선언해주어야 한다.
protocol: A protocol defines a blueprint of methods, properties, and other requirements
that suit a particular task or piece of functionality
= 특정 기능 수행에 필요한 요소들을 정의한 청사진
청사진에 대한 세부 내용은 해당 protocol을 채택한 class에서 오버라이딩 하므로, 내부 함수는 선언만 해주면 된다.
프로토콜은 주는쪽과 받는쪽에서의 코드 작성이 헷갈릴 수 있다.
이번에는 SecondVC → FirstVC로 데이터가 흘러가는 상황이기 때문에 SecondVC에 해당 프로토콜을 선언해준다.
protocol SendStringData{
func sendData(mydata: String)
}
⌘ 보통 되돌아오는(B → A) 경우에 Delegate pattern을 사용한다.
모듈화에 좋지는 않지만 A → B의 경우에는 프로퍼티 접근이 훨씬 간결하기 때문이다.
그리고 아직 구현까지는 못하지만 MVVM구조도 이를 해결하는데 도움이 되는 모양이다.
SecondVC의 CheckBtn의 #selector함수에
SendStringData protocol의 sendData를 '실행만' 시킨다.
함수 내부 내용은 해당 protocol을 채택한(데이터를 받을) 쪽에서 작성될 것이다.
확실히 데이터변화를 체감하기 위해, 파라미터에 ": checked"를 추가로 더 붙여서 데이터를 보냈다.
//SecondViewController
protocol SendStringData{
func sendData(mydata: String)
}
class SecondViewController: UIViewController{
var dataFromFirst: String = ""
//protocol 변수 생성
var delegate: SendStringData?
//...
@objc func checkBtnTabbed(){
guard let resultText = resultLabel.text else { return }
//protocol func
delegate?.sendData(mydata: "\(resultText): checked")
self.navigationController?.popViewController(animated: true)
}
//중략
}
📬 FirstViewController(데이터를 받을 쪽): 이제 FirstVC에서 해당 Protocol을 채택해준다.
class FirstViewController: UIViewController, SendStringData{
class에서 상속받는 것과 동일하다. 다만 상속받을 class가 더 있다면, 프로토콜은 맨 뒤에 위치한다.
'does not confirm to protocol~'이라는 에러가 발생하는데,
프로토콜 채택만 하고 내부의 프로퍼티나 함수 등을 구현하지 않아서 발생하는 에러이다.
Fix 버튼을 누르면 프로토콜 내부에 구현해야하는 것들을 구현까지 해주는 친절함을 보인다.
이제 생성된 sendData()의 내부를 채워주자.
//FirstViewController
func sendData(mydata: String) {
textField.text = mydata
textField.isUserInteractionEnabled = false
callBtn.setTitle("Checked!", for: .normal)
callBtn.backgroundColor = .systemGray3
callBtn.isUserInteractionEnabled = false
}
아직 끝이 아니다. SendStringData의 프로퍼티인 vc.delegate를 self로 지정해주어야 한다. (내가 자주 까먹는 부분이다..)
//FirstViewController
@objc func openSecondViewController(){
guard let myString = textField.text else { return }
let vc = SecondViewController()
vc.delegate = self //까먹지 말자!!!
vc.dataFromFirst = "\(myString) - I'm from firstVC"
self.navigationController?.pushViewController(vc, animated: true)
}
이러한 과정을 통해 SecondVC가 실행한 sendData()를
FirstVC에서 재정의하여 파라미터의 형태로 데이터를 전달받고, 이에대한 처리를 완료할 수 있게 된다.
당연한 것이지만 NavigationItem으로 뒤로 가면
관련된 함수가 없으므로 데이터 전달이 이루어지지 않는다.
📌 마치며
매번 헷갈리던 delegate 사용을 드디어 정리했다.
내가 놓치는 부분이 있었고, 어느 시점에 사용해야 하는지 정확히 알게 되었다.
만족스러운 공부가 되었고, Delegate 사용을 좀 더 자연스럽게 사용할 수 있게끔 손에 익혀야겠다.
오류 및 지적사항은 댓글로 남겨주시면 감사하겠습니다!
참고 :
'공부 노트(Swift)' 카테고리의 다른 글
[Swift] weak self, guard let self = self, self?. (0) | 2022.06.06 |
---|---|
[Swift] 생명주기 정리( App / ViewController ) (0) | 2022.05.22 |
[Swift] lazy Variable (0) | 2022.05.04 |
[Swift] UIKit코드로 SwiftUI Preview 사용해 보기 (0) | 2022.05.03 |
[Swift] Snapkit 맛보기(코드 UI, layoutIfNeeded) (0) | 2022.04.27 |