azhy의 iOS 이야기

[iOS/Swift] delegate pattern, 데이터 주고 받기 본문

Swift

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

azhy 2024. 11. 11. 21:38

2022년 6월 29일에 작성됨

 

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)"
    }
}

 

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