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과 같은 변수 어노테이션 설정
반응형