[UIKit] view 간 데이터 송수신

Kihyun Lee·2023년 2월 21일
0

UIKit

목록 보기
1/1

SwiftUI 에선 State+Binding, ObservableObject, EnvironmentObject 등을 사용하여 서로 다른 view 간에 데이터를 주고 받았다.

UIKit 에선 크게 4가지 방식으로 데이터를 주고 받는다.

  1. 프로퍼티 접근
  2. delegate
  3. closure
  4. Notification

Initial

FirstViewController 의 textField 값을 SecondViewController 로 전달하고자 하는 간단한 초기 상황이다.

First 엔 textField 변수를, Second 엔 받은 데이터를 보여줄 label 변수를 스토리보드와 연결한 상태이다.

import UIKit


class FirstViewController: UIViewController {
    
    @IBOutlet weak var textField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
}

class SecondViewController: UIViewController {
    
    @IBOutlet weak var label: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

    }

}

이제 데이터를 송수신해보자. 핵심 원리 파악을 위해 모든 코드를 쓰진 않았다.

Solution

1. 프로퍼티 접근

프로퍼티 전달 방법에는 다시 작게 3가지 방법이 있다. 이 방법들은 모두 간단하지만, 외부 뷰컨의 프로퍼티에 직접 값을 넣어 보내는 방식이기 때문에 결합도가 높다는 단점이 있다.

1) segue + prepare
2) present or push
3) 뷰컨트롤러 전달

1) 스토리보드에서 segue 로 Next 버튼과 SecondViewController 를 연결하고 prepare 함수로 전달하는 방식이다.

class FirstViewController: UIViewController {
    
    @IBOutlet weak var textField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    	// segue 의 목적지가 Second 라면
        if let vc = (segue.destination as? SecondViewController) {
        	// 그 뷰컨의 str 프로퍼티에 값 전달
            vc.str = textField.text
        }
    }
}

class SecondViewController: UIViewController {
    
    @IBOutlet weak var label: UILabel!
    var str: String!

    override func viewDidLoad() {
        super.viewDidLoad()

        label.text = str // 받은 str 을 label 에 적용
    }
}

segue 가 실행되는 과정에서 prepare 라는 함수가 자동적으로 호출되는데 이를 사용하는 것이다.

2) present or push
스토리보드가 아닌 present 나 navigation 의 pushView 로 띄워 준다. 뷰컨트롤러의 프로퍼티에 접근한다는 점은 segue 와 같다.

@IBAction func nextButtonAction() {
	guard let vc = self.storyboard?.instantiateViewController(identifier: "SecondViewController") as? SecondViewController else { return }

	vc.str = textField.text
	present(vc, animated: true)
}

3) 뷰컨트롤러 전달
SecondVC 에 VC 타입의 프로퍼티를 선언해 두고 뷰컨 자체를 전달 받는 방식이다. FirstVC 를 받은 것이기에 그 안의 함수들이나 변수들을 사용할 수 있다.

// firstVC
@IBAction func nextButtonAction() {
    guard let vc = storyboard?.instantiateViewController(identifier: "SecondViewController") as? SecondViewController else { return }
    vc.callerVC = self
    present(vc, animated: true)
}

// secondVC
var callerVC: FirstViewController!

2. delegate

프로토콜을 사용하여 결합도를 낮춤과 동시에 데이터를 전달하는 방식이다. FirstVC 는 프로토콜을 채택하여 이벤트를 구현하고 SecondVC 의 대리자를 본인으로 지정한다. 그러면 SecondVC 가 가지고 있는 대리자의 함수를 적재적소에 호출하여 데이터를 읽고 쓸 수 있다. 프로토콜을 따로 선언해야 하기 때문에 코드량이 조금 더 많아진다는 단점이 있지만 '결합도'를 낮출 수 있다는 장점이 중요하기 때문에 애용되는 방식이다.

protocol MyDelegate {
    func hello()
}

class FirstViewController: UIViewController, MyDelegate { 
	// hello() 구현
}

@IBAction func nextButtonAction() { // firstVC
    guard let vc = storyboard?.instantiateViewController(identifier: "SecondViewController") as? SecondViewController else { return }
    vc.delegate = self
    present(vc, animated: true)
}

//secondVC
var delegate: MyDelegate!

3. closure

FirstVC 의 클로저를 SecondVC 에서 받아서 사용하는 방식이다. delegate 와 유사하지만 따로 protocol 을 선언하지 않고 간결하게 쓸 수 있다는 장점이 있다.

func hello() { }

@IBAction func nextButtonAction() {
    guard let vc = storyboard?.instantiateViewController(identifier: "SecondViewController") as? SecondViewController else { return }
    vc.closure = hello
    present(vc, animated: true)
}

// SecondVC 에서 받는 부분 생략

4. Notification

NotificationCenter 라는 방송국에 옵저버를 등록해 두고 broadcast 받는 방식이다. 외부 공간의 메모리를 사용한다는 단점 대신, 모든 뷰컨트롤러에서 주파수만 맞추면 이벤트를 받을 수 있다는 장점이 있다.

// firstVC
NotificationCenter.default.addObserver(self, selector: #selector(trigger), name: NSNotification.Name(rawValue: "14hz"), object: nil)
// 내 옵저버를 14hz 라는 이름으로 방송국에 등록, 브로드캐스트 발생 시 호출할 함수는 trigger 로 지정

// 이벤트 액션은 Objective-C 코드 어노테이션 필요
@objc func trigger(_ notification: NSNotification) {
	// 받은 노티피케이션의 오브젝트를 보면 데이터가 담겨있다.
    if let s = notification.object as? String {
        textField.text = s
    }
}

// secondVC
@IBAction func broadcast() {
	// 오브젝트를 포함한 신호를 14hz 로 송출
    NotificationCenter.default.post(name: NSNotification.Name(rawValue: "14hz"), object: "~packet~")
}

Done

Kotlin 에선 intent 로 데이터를 주고 받는 방법이 있다. Swift 에선 데이터 송수신 개념이 약간 다르다고 느꼈다.
기본적으로 데이터를 주고 받는 주체는 뷰컨트롤러들이다. 그런데 뷰컨트롤러는 결국 class 다. 그러므로 내 클래스에서 다른 클래스의 데이터를 수정하고 읽을 수 있으면 그것이 데이터 송수신이 되는 것이다. 그런데 이게 어떻게 가능할까?
클래스는 참조 타입으로, 대입 시 값이 복사되는 것이 아니라 레퍼런스 카운트가 1증가 되면서 주소 값이 복사가 된다. 이 말은, 전달 받은 것이 전달 한 것과 같은 것을 가리키게 된다는 말이다. 그래서 클로저나 뷰컨을 전달하여 참조를 주고 데이터를 송수신 할 수 있다.
클로저 자체도 레퍼런스 타입이고, 클로저 안의 변수들도 캡쳐 시 레퍼런스 캡쳐가 되기 때문에 결국은 같은 값을 볼 수 있게 된다.

Reference

profile
실패도 배우는 게 있으면 성공이다.

0개의 댓글