[iOS] delegate pattern, 데이터 주고 받기

ungchun·2022년 6월 29일
0
post-thumbnail

delegate pattern 은 정말 자주 쓰이기 때문에 정말 중요하다. 나도 빈번하게 사용했지만 확실히 정리가 되어있지 않아서 이번 기회에 정리하려한다.

delegate 가 무엇일까? 사전적 정의는 위임하다. 즉 어떤 작업을 다른 사람에게 위임해서 요청한다 라는 느낌으로 이해하면 쉽다. delegate pattern 을 쓰기 위해서는 송신자와 수신자가 필요하다. 쉽게 생각하면 데이터를 주고 받는 ViewController 2개가 필요하다고 생각하자.

delegate 패턴은 보통 되돌아오는 과정 (B -> A) 경우에 사용한다고 한다. 반대인 A -> B의 경우에는 프로퍼티 접근으로 쉽게 데이터를 전달할 수 있다. 프로퍼티 접근에 관해서는 맨 밑에서 간단히 설명하겠다.


프로토콜 선언

protocol TapDelegate: AnyObject {
    func tapAction(value: String)
}

delegate는 보통 protocol 로 만들기 때문에 본인이 필요한 func을 담은 protocol을 하나 만들어 주자. 나는 간단한 테스트를 위해 string value 를 담은 func을 하나 만들었다.


첫 번째 뷰 (수신자) 작성

protocol을 만들었으면 첫 번째 뷰, 데이터를 받아야하는 뷰에 protocol을 채택해주자. 보통 extension 으로 많이 구성해서 나도 extension으로 작성했다. protocol을 채택하면 위에서 만든 func에 대해서 무조건 작성해주어야한다.

// TapDelegate protocol 채택, tapAction func 작성
extension FirstViewController: TapDelegate { 
    func tapAction(value: String) {
        print("receive data \(value)")
    }
}

Controller 본문에는 가운데에 클릭하면 두 번째 뷰로 이동하는 버튼을 하나 만들었다. 여기서 중요한 점은 secondViewController.delegate = self 이 부분인데 간단히 설명하면 secondViewController에 선언되어있는 delegate 에 자신을 전달해서 secondViewController.delegate 가 자기 대신 func 을 실행할 수 있게 해준다.

// 첫 번째 뷰, 수신자
class FirstViewController: UIViewController {

	@IBOutlet weak var btn: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func btnAction(_ sender: Any) {
        let storyboard: UIStoryboard = UIStoryboard(name: "SecondView", bundle: nil)
        guard let secondViewController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return }
        
        // secondViewController (대리자) 에게 자신을 전달
        secondViewController.delegate = self 
        
        self.present(secondViewController, animated: true, completion: nil)
    }
}

두 번째 뷰 (송신자) 작성

두 번째 뷰도 동일하게 가운데에 버튼이 하나 있는데, 그 버튼을 누르면 delegate 를 통해 첫 번째 뷰에 정의된 func 함수가 실행된다. 여기서 알아두면 좋은점은 weak var 인데, weak 없이 그냥 var delegate... 해도 오류없이 돌아간다. 하지만 weak 없이 진행하면 뷰 끼리 왔다 갔다 하면서 생기는 메모리가 해제되지 않아서 메모리가 누수되고 심하면 앱이 죽을수도 있다. 그래서 메모리 누수를 방지하기 위해 weak 를 써주자.
weak 를 쓰기 위해서는 protocol에 AnyObject나 class를 상속받아야 사용이 가능하다.

// 두 번째 뷰, 송신자
class SecondViewController: UIViewController {
    
    @IBOutlet weak var btn: UIButton!
    
    weak var delegate: TapDelegate?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func btnAction(_ sender: Any) {
        print("SecondViewController btn Action")
        
        // 첫 번째 뷰에서 선언한 함수를 통해 데이터 전달
        delegate?.tapAction(value: "delegate practice") 
    }
}

결과화면

두 번째 뷰에서 버튼을 클릭하면 정상적으로 첫 번째 뷰에서 정의된 tapAction 함수가 실행이 된다.


전체 코드

protocol TapDelegate: AnyObject {
    func tapAction(value: String)
}

extension FirstViewController: TapDelegate {
    func tapAction(value: String) {
        print("receive data \(value)")
    }
}

// 첫 번째 뷰, 수신자
class FirstViewController: UIViewController {

	@IBOutlet weak var btn: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func btnAction(_ sender: Any) {
        let storyboard: UIStoryboard = UIStoryboard(name: "SecondView", bundle: nil)
        guard let secondViewController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return }
        
        // secondViewController (대리자) 에게 자신을 전달
        secondViewController.delegate = self 
        
        self.present(secondViewController, animated: true, completion: nil)
    }
}

// 두 번째 뷰, 송신자
class SecondViewController: UIViewController {
    
    @IBOutlet weak var btn: UIButton!
    
    weak var delegate: TapDelegate?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func btnAction(_ sender: Any) {
        print("SecondViewController btn Action")
        
        // 첫 번째 뷰에서 선언한 함수를 통해 데이터 전달
        delegate?.tapAction(value: "delegate practice") 
    }
}


프로퍼티 접근

위에서 잠깐 설명했듯이 delegate pattern은 되돌아오는 과정(B -> A)에서 쓰이고 반대로 데이터 전달 과정이 A -> B 인 경우에 쓰이는 프로퍼티 접근에 대해 간단한 코드로 설명하려한다.

// 첫 번째 뷰
class FirstViewController: UIViewController {

	@IBOutlet weak var btn: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func btnAction(_ sender: Any) {
        let storyboard: UIStoryboard = UIStoryboard(name: "SecondView", bundle: nil)
        guard let secondViewController = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return }
        
        // 버튼 클릭 -> secondViewController의 receiverData 에 접근해서 데이터 세팅
        secondViewController.receiverData = "데이터 넘김"
        
        self.present(secondViewController, animated: true, completion: nil)
    }
}

// 두 번째 뷰
class SecondViewController: UIViewController {

	var receiverData: String = ""
    
    override func viewDidLoad() {
        super.viewDidLoad()
        print("receiverData = \(receiverData)"
    }
}

정말 간단한다. 두 번째 뷰에서 받을 데이터를 미리 객체를 만들어두고 첫 번째 뷰에서 두 번째 뷰로 이동할 때 객체에 직접 접근해서 데이터를 세팅해주는 것이다.

0개의 댓글