IOS

Delegate를 이용하여 뷰 컨트롤러간의 데이터 전달(양방향)

레옹아범 2023. 1. 13. 00:26
반응형

delegate로 만드는 데이터 전달

  • 첫번째 뷰 컨트롤러에서 버튼을 누를 경우 두번째 뷰 컨트롤러로 푸쉬되며, 현재 라벨 값에 +1을 더하여 두번째 뷰 컨트롤러로 넘겨줄 것이다.
  • 두번째 뷰 컨트롤러에서 뒤로가기 버튼을 누를 시 현재 값의 +3을 하여 첫번째 뷰 컨트롤러로 넘겨줄 것이다.

FirstViewController

class ViewController: UIViewController {

    @IBOutlet weak var label: UILabel!

    var value: Int = 0

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)

        self.label.text = "\(self.value)"
    }

    @IBAction func didTapPlusOneButton(_ sender: UIButton) {
        guard let secondVC = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as? TestViewController else { return }

        self.delegate?.sendData(myData: "\(value + 1)")
        self.navigationController?.pushViewController(secondVC, animated: true)
    }
}

SecondViewController

class TestViewController: UIViewController {

    @IBOutlet weak var label: UILabel!

    var data = 0

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        self.label.text = "\(data)"
    }

    @IBAction func clickBackButton(_ sender: Any) {
        self.navigationController?.popViewController(animated: true)
    }
}

delegate를 위한 프로토콜

protocol SendData {
    func sendData(myData: String)
}

두번째 뷰 컨트롤러에서 첫번째 뷰 컨트롤러로 데이터 넘겨주기

두번째 뷰 컨트롤러

class TestViewController: UIViewController {
    var delegate: SendData?

    @IBAction func clickBackButton(_ sender: Any) {
        self.delegate?.sendData(myData: "\(data+3)")
        self.navigationController?.popViewController(animated: true)
    }
}
  • delegate를 선언해주고 백버튼을 클릭시 delegate의 sendData를 실행해준다.
  • 이 delegate는 첫번째 뷰 컨트롤러를 지칭하며, delegate에 첫번째 뷰 컨트롤러를 할당하는 일은 첫번째 뷰 컨트롤러에서 해준다.

첫번째 뷰 컨트롤러

class ViewController: UIViewController {
    @IBAction func didTapPlusOneButton(_ sender: UIButton) {
        guard let secondVC = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as? TestViewController else { return }

        secondVC.delegate = self

        self.navigationController?.pushViewController(secondVC, animated: true)
    }
}

extension ViewController: SendData {
    func sendData(myData: String) {

        guard let data = Int(myData) else { return }
        self.value = data
    }
}
  • 첫번째 뷰 컨트롤러의 sendData를 두번째 뷰 컨트롤러에서 실행해주므로 해당 프로토콜을 채택하고 구현해준다.
  • 또한 didTapPlusOneButton메소드 내에서 secondVC의 인스턴스를 만들어주는데 여기서 secondVC에 있는 delegate프로퍼티에 현재 self(첫번째 뷰 컨트롤러)를 할당해준다.

첫번째 뷰 컨트롤러에서 두번째 뷰 컨트롤러로 데이터 넘겨주기

첫번째 뷰 컨트롤러

class ViewController: UIViewController {
      var delegate: SendData? // 추가

    @IBAction func didTapPlusOneButton(_ sender: UIButton) {
        guard let secondVC = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as? TestViewController else { return }

        secondVC.delegate = self
        self.delegate = secondVC    // 추가

        self.delegate?.sendData(myData: "\(value + 1)")    // 추가
        self.navigationController?.pushViewController(secondVC, animated: true)
    }
}
  • 첫번째 뷰 컨트롤러에서 두번째 뷰 컨트롤러 이동할 시 새 인스턴스를 만드는데 여기서 현재 첫번째 뷰 컨트롤러의 delegate에 두번째 뷰 컨트롤러 인스턴스를 넣어서 sendData를 실행할 수 있게 해준다.
  • 해당 sendData값에 value + 1을해줘서 값을 넣는다.

두번째 뷰 컨트롤러

extension TestViewController: SendData {
    func sendData(myData: String) {
        guard let data = Int(myData) else { return }

        self.data = data
    }
}
  • 첫번째 뷰 컨트롤러에서 두번째 뷰 컨트롤러로 이동할 시 sendData를 실행해줘야 하기 때문에 프로토콜을 채택하고 메소드를 구현해준다.

실행화면

delegate의 메모리 누수

두번째 뷰 컨트롤러

class TestViewController: UIViewController {
    deinit {
        print("Second View Deinit")
    }
}
  • 두번째 뷰 컨트롤러에서 deinit을 해줄 경우 예상하기로는 첫번째 뷰 컨트롤러로 넘어갈 경우 deinit이 되어야 하지만 다시 두번째 뷰 컨트롤러로 넘어가기 전까지에는 deinit이 실행되지 않고 메모리에 남아있게 된다.
  • 해당 이유는 Swift는 클래스에 관해 메모리 관리를 위해 ARC를 사용하는데 현재 첫번째 뷰 컨트롤러의 delegate프로퍼티가 두번째 뷰 컨트롤러를 참조하고 있기 때문에 해당 delegate가 다른것을 참조하거나 nil이 아닌이상 끝까지 메모리에 남아 있는것이다.

메모리 누수 해결법

첫번째 해결법

첫번째 뷰 컨트롤러

@IBAction func didTapPlusOneButton(_ sender: UIButton) {
    guard let secondVC = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as? TestViewController else { return }

    secondVC.delegate = self
    self.delegate = secondVC
    self.delegate?.sendData(myData: "\(self.value + 1)")
    self.delegate = nil
    self.navigationController?.pushViewController(secondVC, animated: true)
}
  • 첫번째 뷰 컨트롤러의 delegate의 sendData를 실행시켜주고 바로 nil을 줄 경우 두번째 뷰 컨트롤러에서 첫번째 뷰 컨트롤러로 이동할 시 바로 deinit이 실행된다.
  • 첫번째 뷰 컨트롤러의 delegate가 두번째 뷰 컨트롤러를 참조하지 않고 있기 때문에 자동적으로 deinit이 실행되는것이다.

두번째 해결법

protocol SendData: AnyObject {
    func sendData(myData: String)
}

weak var delegate: SendData?
  • SendData의 프로토콜에 AnyObject타입을 상속? 채택?해주며 weak(약한참조)의 어노테이션을 붙여줄 경우 약한참조가 된다.

첫번째 뷰 컨트롤러에서 두번째 뷰 컨트롤러로 넘겨줄 필요가 없는 이유

  • 구글링하다보면 전부 두번째 뷰 컨트롤러에서 첫번째 뷰 컨트롤러로만 delegate전달을 한다.
  • 양방향 데이터 전달을 하는 예제를 찾아볼 수가 없었는데 그 이유는 우선 이렇게 구현할 경우 결합도가 높아진다.
  • 또한, 첫번째 뷰 컨트롤러에서 두번째 뷰 컨트롤러의 인스턴스를 만들어주기 때문에 두번째 뷰 컨트롤러에서 메소드를 만들어 실행시켜 주면 되기 때문이다.

첫번째 뷰 컨트롤러

class ViewController: UIViewController {
    @IBAction func didTapPlusOneButton(_ sender: UIButton) {
        guard let secondVC = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as? TestViewController else { return }

        secondVC.delegate = self
        secondVC.plusOnedata("\(self.value + 1)")
        self.navigationController?.pushViewController(secondVC, animated: true)
    }
}
  • 두번째 뷰 컨트롤러의 인스턴스에서 해당 메소드를 실행시켜 값을 업데이트 한다.

두번째 뷰 컨트롤러

class TestViewController: UIViewController {
    func plusOnedata(_ data: String) {
        guard let data = Int(data) else { return }

        self.data = data
    }
}
  • 해당 뷰 컨트롤러에서 이전 sendData에서 값을 업데이트 시켜줬던것과 똑같은 메소드를 구현하였다.

추후 해야할 것

  • ARC공부
  • weak, strong과 같은 변수 어노테이션 설정
반응형