Weekoding

[Swift] ViewController간 데이터 주고받기 - Delegate pattern 본문

공부 노트(Swift)

[Swift] ViewController간 데이터 주고받기 - Delegate pattern

Weekoding 2022. 5. 12. 16:33

ViewController간 데이터를 주고받는 방법은 6가지가 있다.

1. 직접 프로퍼티에 접근

2. 함수를 통한 접근

3. Segue

4. Delegate

5. Closure

6. NotificationCenter

 

그 중 1, 4번을 간단한 예제를 통해 정리해보려고 한다. ( 나머지는 추후에..! ㅎㅎ )

 

먼저 잠깐 알아보자면 1~3번의 방법들은 남발하게 되면 좋지 않은데,

이 방법들은 다른 ViewController에 직접적으로 의존하게 되므로 강한 결합이 되어있는 형태가 된다.

이는 객체 간 모듈화에도 좋지 않으며 스파게티 코드가 될 위험이 있다고 한다.

 

4~6번의 방법은 이러한 강한 결합 형태의 문제점을 벗어나 서로 의존하지 않는 구조로 전달할 수 있다.

 

 

 

 

이번 예제에서는 FirstViewController, SecondViewController를 두고 FirstVCSecondVCFirstVC 의 과정에서

FirstVCSecondVC 에서는 직접 프로퍼티에 접근하여 데이터를 전달하고,

SecondVCFirstVC 에서는 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에서 오버라이딩 하므로, 내부 함수는 선언만 해주면 된다.

 

프로토콜은 주는쪽과 받는쪽에서의 코드 작성이 헷갈릴 수 있다.

이번에는 SecondVCFirstVC로 데이터가 흘러가는 상황이기 때문에 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 사용을 좀 더 자연스럽게 사용할 수 있게끔 손에 익혀야겠다.

 

 

 

오류 및 지적사항은 댓글로 남겨주시면 감사하겠습니다!

참고 : 

https://velog.io/@nnnyeong/iOS-VC-%EA%B0%84-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A0%84%EB%8B%AC-%EB%B0%A9%EB%B2%95

Comments