iOS 개발 중 화면간 데이터를 주고받아야 하는 과정은 매우 기본적인 상황일 것이다!
나역시 처음엔 무작정 segue만을 붙들고 다녔지만, 이 방법 저 방법 써보게 되었고 구글링 중 잘 정리된 포스팅이 있어 참고해서 정리해보려한다!
전달하고자 하는 데이터를 뷰 컨트롤러 프로퍼티에 직접 접근해서 넘기는 방식이다. GreenVC
에서 BlueVC
로 "hello blue"
라는 String 데이터를 넘기려 한다고 해보자.
이때 GreenVC
에서 BlueVC
의 프로퍼티 data
에 직접 접근해서 "hello blue" 를 넘겨주고 BlueVC
는 이 스트링을 화면 Label 에 띄운다.
import UIKit
class GreenViewController: UIViewController{
@IBOutlet weak var sendButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func sendData(_ sender: Any) {
guard let vc = self.storyboard?.instantiateViewController(identifier: "BlueViewController") as? BlueViewController else {
return
}
vc.data = "hello blue"
self.navigationController?.pushViewController(vc, animated: true)
}
}
import UIKit
class BlueViewController: UIViewController {
@IBOutlet weak var dataLabel: UILabel!
var data : String = ""
override func viewDidLoad() {
super.viewDidLoad()
self.dataLabel.text = data
}
}
이 때 GreenVC의 버튼을 눌렀을 때,
vc.data = "hello blue"
에서 스트링이 데이터가 할당 되는 것이 아니다!navigation stack
에 쌓일 BlueVC에 넘긴다는 의미간단한 이 방법이지만 이 방법은 push, present 방식으로 화면을 전환하는 경우에만 데이터가 정상적으로 넘어간다!
이 방식은 1번을 조금더 변형시킨 방법이다. 프로퍼티에 직접 접근하지 않고 함수를 통해서 접근한다.
GreenVC -> BlueVC 이동 후 다시 GreenV
C로 "recieve success"
를 보내려 한다.
BlueVC
가 프로퍼티로 GreenVC
를 가지고 있고 GreenVC
의 메소드를 통해 이를 전달하는 방식이다.
import UIKit
class GreenViewController: UIViewController{
@IBOutlet weak var sendButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func sendData(_ sender: Any) {
guard let vc = self.storyboard?.instantiateViewController(identifier: "BlueViewController") as? BlueViewController else {
return
}
vc.data = "hello blue"
vc.greenVC = self
self.navigationController?.pushViewController(vc, animated: true)
}
func checkRespose(str : String) -> Void {
print("잘 받았나? :\(str)")
}
}
import UIKit
class BlueViewController: UIViewController {
@IBOutlet weak var dataLabel: UILabel!
var data : String = ""
var greenVC : UIViewController?
override func viewDidLoad() {
super.viewDidLoad()
self.dataLabel.text = data
guard let vc = self.greenVC as? GreenViewController else {
return
}
vc.checkRespose(str: "recieve success")
}
}
결과는, GreenVC의 메소드가 정상적으로 실행된 걸 확인 가능하다!
세번째 방법은 storyboard
에서 Segue
를 이용하는 방법이다.
GreenVC -> BlueVC 로 향하는 segue를 먼저 생성하고 이름을 GreenToBlue
로 지정해주었다.
GreenVC -> Segue -> BlueVC 과정에서 Segue 실행 시 prepare
함수가 호출되는데 이 함수에서 전달하고자 하는 데이터를 함께 넘길 수 있도록 한다
import UIKit
class GreenViewController: UIViewController{
@IBOutlet weak var sendButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func sendData(_ sender: Any) {
performSegue(withIdentifier: "GreenToBlue", sender: nil)
}
// segue 실행 전 전달하려는 데이터 set
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.destination is BlueViewController {
guard let vc = segue.destination as? BlueViewController else { return }
vc.data = "hello blue"
}
}
}
위의 세 방법은 간단하고 바로 이해도 되지만 사실 다른 뷰 컨트롤러의 프로퍼티에 접근하고 해당 컨트롤러에 직접적으로 의존하고 있는 형태가 좋을리가 없다. 이러한 형태의 구조는 VC 끼리 강하게 결합되어 있도록 하고 이는 결국 스파게티 코드로 이어져 많은 수정을 초래할 것이다.
이러한 VC간의 강한 결합 형태를 보완하기 위한 방법이다. Delegate
를 이용하면 데이터를 주고 받는 VC가 서로 의존하지 않고 떨어져 있는 구조를 유지하게 된다.
이번엔 BlueVC에서 GreenVC로 "delegate works well~"
이라는 String을 보내려 하는 상황이다. 먼저 Delegate Protocol
을 먼저 정의하고
protocol SendDataDelegate {
func recieveData(response : String) -> Void
}
GreenVC 는 SendDataDelegate
를 채택해서 func recieveData(response : String) -> Void
를 VC 내에 정의한다. 이후 BlueVC 로 이동 전 BlueVC의 SendDataDelegate 프로퍼티를 self로 지정하고
class GreenViewController: UIViewController, SendDataDelegate {
@IBOutlet weak var sendButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func sendData(_ sender: Any) {
guard let vc = self.storyboard?.instantiateViewController(identifier: "BlueViewController") as? BlueViewController else { return }
vc.dataDelegate = self
self.navigationController?.pushViewController(vc, animated: true)
}
func recieveData(response: String) {
print("response : \(response)")
}
}
BlueVC 는 SendDataDelegate를 프로퍼티의 delegate 메소드를 실행시켜 "delegate works well~"
를 전달하면,
import UIKit
class BlueViewController: UIViewController {
@IBOutlet weak var dataLabel: UILabel!
var data : String = ""
var dataDelegate : SendDataDelegate?
override func viewDidLoad() {
super.viewDidLoad()
self.dataLabel.text = data
dataDelegate?.recieveData(response: "delegate works well~")
self.navigationController?.popViewController(animated: true)
}
}
stirng이 잘 전달되어 GreenVC 에서 print 될 수 있다!
(후,, 생각보다 긴데,,? 그만하고 싶다 ^^,,)
또 다른 방법은 Swift의 Closure
를 이용하는 것이다! 나는 swift의 모든 개념중에서 가장 마지막에 이해할 것 같은걸 고르라면 이놈의 클로져다 ㅋ,, 그치만 너무 중요한 개념이고 빈번히 사용되기 때문에 외면할 수 없다..!
암튼 Closure를 사용해서 데이터를 전달하는 방법은,,!
BlueVC -> GreenVC 로 navigation pop
되기 이전에 "hello green"
이라는 메세지를 함께 보내보자!
import UIKit
class GreenViewController: UIViewController {
@IBOutlet weak var sendButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func sendData(_ sender: Any) {
guard let vc = self.storyboard?.instantiateViewController(identifier: "BlueViewController") as? BlueViewController else { return }
vc.completioHandler = {
msg in
print("messgae : \(msg)")
}
self.navigationController?.pushViewController(vc, animated: true)
}
}
GreenVC -> BlueVC 로 navigation push
되기 이전에, GreenVC 는 BlueVC 에서 메세지를 받고 출력할 수 있도록 BlueVC 의 completionHander
를 이용해 먼저 작성 해 두고,
import UIKit
class BlueViewController: UIViewController {
@IBOutlet weak var responseButton: UIButton!
var data : String = ""
var completioHandler : ((String) -> (Void))?
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func responseToGreen(_ sender: Any) {
completioHandler?("hello green~")
self.navigationController?.popViewController(animated: true)
}
}
BlueVC 는 String을 받아 반환값 없이 실행되는 클로저 ((String) -> Void)
completioHandler
를 프로퍼티로 가지고 있다가 navigation pop 해서 GreenVC로 넘어가기 전 해당 클로저의 매개변수로 "hello green~"
을 넘기면,
GreenVC 에서 print가 잘 되는걸~!
Closure
는 Delegate
와 유사한 형태로 동작하지만 Closure는 delegate 보다 간결하게 작성해서 사용할 수 있고 프토로톨, 메소드 없이 지역 스코프 내에서 바로 처리할 수 있다는 장점을 가지고 있다!
한눈에 보기에도 매우 간결한 모습이다!
(후,,, 마지막이야,, ^^ 정신 똑바로 차리자,,)
마지막 방법은 NotificationCenter
를 이용하는 것이다!
NotificationCenter 는 iOS 앱 전체에 하나만 존재하는 일종의 방송국 같은 역할을 한다. 어떠한 변화를 관찰하고 싶을 때 사전에 NotificationCenter 에 observer
를 등록해 두고, 해당 변화가 일어났을 때는 NotificationCenter에 post
를 보내서 observer 등록시 함께 연결? 해둔 작업들(selector 메소드)이 실행되도록 할 수 있다.
나의 경우 앱의 bluetooth 권한이 변경되어 이를 감지하는 용도로 사용해보는 식으로 사용해 보았다.
NotificationCenter 역시 VC 들이 독립적으로 존재할 때 사용하는 방법이다.
import UIKit
class GreenViewController: UIViewController {
@IBOutlet weak var sendButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(printMessage), name: Notification.Name(rawValue: "SendMessage"), object: nil)
}
@IBAction func sendData(_ sender: Any) {
guard let vc = self.storyboard?.instantiateViewController(identifier: "BlueViewController") as? BlueViewController else { return }
self.navigationController?.pushViewController(vc, animated: true)
}
@objc func printMessage(_ notification : Notification) {
let msg = notification.object as? String
print("message : \(msg!)")
}
}
BlueVC 로 이동하기 전 사전에 NotificationCenter에 "SendMessage"
라는 이름으로 printMessage()
가 실행되도록 observer를 등록해 두고,
import UIKit
class BlueViewController: UIViewController {
@IBOutlet weak var responseButton: UIButton!
var data : String = ""
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func responseToGreen(_ sender: Any) {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "SendMessage"), object: "hello green")
self.navigationController?.popViewController(animated: true)
}
}
BlueVC 에서 GreenVC 로 돌아가기 전 NotificationCenter에 post를 보내면서 "hello green"을 함께 전달한다.
NotificationCenter 를 이용하면 왠만한 데이터 전달이 모두 가능하다고 하지만, 사실 NotificationCenter로 모든 데이터를 넘긴다면 여러 변화를 observe 해야 하므로 상당히 비효율적인 방식이 된다고 한다.
때문에 간단한 데이터를 넘기거나, 어떠한 변화에 대응하는 작업을 할 때 사용하는 것이 보다 적절하다.
이렇게 총 6가지의 데이터 전달 방법에 대해 정리 해 보았다!
기본적인 내용들이고 여러번 봐 왔지만 중구난방 쓰는 것 보다 정리해두고 용도에 따라 효율적으로 각 방법을 사용해서 개발해야겠다~!
저는 개인적으로 delegate를 주로 사용합니다 ! ㅎㅎ
그런데 delegate를 사용할 때는 ARC 때문에 weak var delegate 로 만들어줘야 하더라구요 !