[iOS] delegate 구현하기

[iOS] delegate 구현하기

·

4 min read

✍🏻 delegate 패턴 이란?

delegate 의 뜻은 위임이다. 나의 권한을 다른 사람에게 위임하다. 원래는 내가 사용하도록 가지고 있는 권한인데 다른 사람에게 내 권한을 사용할 수 있게 위임한다. 라는 의미로 사용 된다.

delegate pattern 의 의미도 같다. 원래 나의 오브젝트가 처리할 권한을 가지고 있어서 처리해야 할 일을 다른 오브젝트에게 권한을 넘겨주어서 다른 오브젝트에서 처리하도록 해주는 패턴이다.

✍🏻 Delegate 패턴 예시

👧🏻 예시를 통해서 알아보자. 👨🏻‍💼 사장 클래스와 👩🏻‍💼직원 클래스가 있다. 회식을 하고 결제를 하는 일은 원래 사장님이 권한을 가지고 있는 일이다. 그런데 사장님이 이번 회식에는 참여를 못하게 되었다. 그래서 직원 한명에게 카드를 긁을 수 있는 권한을 위임해 주었다. 직원이 사장님을 대신 해서 카드를 긁는 상황이다. 메뉴얼 만들기 우선 사장님이 직원에게 어떻게 카드를 긁는지 알려주기 위해서 결제메뉴얼 이라는 프로토콜을 만들었다. 이 프로토콜 안에는 결제를 하려면 어떤 메서드와 프로퍼티를 가지고 있어야 하는지 나와있다.

직원이 메뉴얼 배우기 직원은 결제메뉴얼을 채택하고, 결제메뉴얼을 준수하기 위해 Staff 클래스안에 회식계산하기 라는 메서드를 만든다.

사장님이 일을 맞길 직원자리 만들기 사장님은 일을 위임하기 위해서 Boss 클래스안에 delegate 라는 변수를 생성한다.(이름은 자유롭게 정할 수 있으나 보통 delegate로 많이 사용한다.) delegate 안에는 결제메뉴얼을 익혀서 알고 있는(결제 메뉴얼 프로토콜을 채택하고 있는) 직원 중 한명을 넣어줄 예정이다. 아직은 값이 없으니 옵셔널로 해준다.

사장님이 일을 맞길 사람은 나야나 Staff 클래스안에 Boss 인스턴스를 생성해준다. boss.delegate 를 self 로 설정해준다. 사장님이 결제 권한을 위임할 사람은 바로 나 라고 정해주는 것이다. 그럼 이제 Bossdelegate 안에는 Staff 가 들어가 있는 것이다.

직원에게 결제 시키기 이제 회식이 끝나고 결제를 할 시간이 왔다. 사장님이 직원에게 결제를 시킨다(delegate.회식계산하기()) 직원은 결제를 잘 처리한다.

그림을 보면 회식계산하기() 라는 함수는 Staff 가 가지고 있다. 호출은 Boss 에서 하고 있다.

이게 delegate pattern 이다. Boss 에서 해야할 일이지만 Staff에서 구현 해 주는 것. 그리고 Boss 는 지시만 하는 것.

사전 학습 해야할것

  • 프로토콜
  • 타입 캐스팅

    ✍🏻 Delegate 패턴 실제코드

    이제 실제 코드를 작성해 보자. 위의 예시 코드와 같은 순서를 따르면 된다. ✅ 프로토콜 정의하기 ✅ 위임자(대리자)가 프로토콜 채택하고 구현하기 ✅ 본책임자 안의 위임자 자리 만들기 (delegate) ✅ 위임자(대리자)안의 본책임자 인스턴스 생성후 delegate self로 지정하기 ✅ 본책임자가 일 지시하기

첫번째 뷰에서 버튼을 누르면 두번째 뷰로 이동한다. 값 증가하기를 누르면 두번째 뷰의 라벨이 업데이트 된다. 첫번째 뷰로 돌아올 때 첫번째 뷰의 라벨도 업데이트 된다. 이러한 코드를 구현할 것이다.

두번째 뷰에서 첫번째 뷰로 넘어올 때, viewWillDisappear 에서 첫번째 뷰의 라벨 값을 바꿔주고 싶다. 두번째 뷰에서 해야할 일이지만, 라벨 값을 가져오기도 힘들고 그래서 첫번째 뷰에서 일을 대신 해줬으면 좋겠다.

첫번째 뷰를 대신해주는뷰 두번째 뷰를 일을넘기는뷰 라고 부르겠다.

✅ 프로토콜 정의하기

protocol Updateable: AnyObject {
    func update(to data: Int)
}

✅ 위임자(대리자)가 프로토콜 채택하고 구현하기

class 대신해주는뷰: UIViewController, Updateable {
    //메인의 라벨
    @IBOutlet weak var mainLabel: UILabel!
    //Updateable 프로토콜을 채택하고 구현해준 메서드
    //메인라벨의 값을 파라미터 값으로 변경해 준다.
    func update(to data: Int) {
        mainLabel.text = String(data)
    }
    //우선 뷰가 로드 되면 라벨에는 0을 넣어준다
    override func viewDidLoad() {
        super.viewDidLoad()
        mainLabel.text = "0"
        }
 }

✅ 본책임자 안의 위임자 자리 만들기 (delegate)

class 일을넘기는뷰: UIViewController {
    //세컨드 라벨
    @IBOutlet weak var secondLabel: UILabel!
    // 위임자 자리 만들기
    weak var delegate: Updateable?
    //
    var someData: Int = 0
    // 버튼을 클릭하면 값이 someData 값이 1씩 증가함
    @IBAction func clickButton(_ sender: UIButton) {
        someData += 1
        secondLabel.text = String(someData)
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        secondLabel.text = "0"
        }
}

✅ 위임자(대리자)안의 본책임자 인스턴스 생성후 delegate self로 지정하기

class 대신해주는뷰: UIViewController, Updateable {
    @IBOutlet weak var mainLabel: UILabel!

    func update(to data: Int) {
        mainLabel.text = String(data)
    }
    // 이부분 보면됨.
    @IBAction func moveNextView(_ sender: UIButton) {
    //일을 넘기는 뷰를 만들고 타입을 일을만드는뷰로 다운캐스팅
        guard let 일을넘기는뷰 = self.storyboard?.instantiateViewController(withIdentifier: "nextView") as? 일을넘기는뷰 else { return }
        // 본인이 위임자인것을 알려주기
        일을넘기는뷰.delegate = self
        //뷰를 띄우기
        present(일을넘기는뷰, animated: true, completion: nil)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        mainLabel.text = "0"
        }

}

✅ 본책임자가 일 지시하기

class 일을넘기는뷰: UIViewController {

    @IBOutlet weak var secondLabel: UILabel!

    weak var delegate: Updateable?

    var someData: Int = 0

    @IBAction func clickButton(_ sender: UIButton) {
        someData += 1
        secondLabel.text = String(someData)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        secondLabel.text = "0"
        }
    // 이부분 보기 
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        //뷰가 사라질 때 update 호출하기
        delegate?.update(to: someData)
    }
}

➕ 참고사항

💎 delegate를 왜 weak 으로 선언해 주어야 할까?

위의 그림을 봤을 때, 서로가 서로를 참조하고 있다는 것을 알 수 있다. weak 를 쓰지 않으면, 순환 참조 안에서 메모리가 해지되지 않을 가능성이 있다.

💎 프로토콜 다운 캐스팅

        guard let 일을넘기는뷰 = self.storyboard?.instantiateViewController(withIdentifier: "nextView") as? 일을넘기는뷰 else { return }
        // 본인이 위임자인것을 알려주기
        일을넘기는뷰.delegate = self

이부분에서 다운캐스팅을 하지 않으면 delegate 의 접근할 수 없다. 다운캐스팅을 꼭 하자.

💎 segue로 뷰컨트롤러를 연결 할 때

위의 코드에서는 코드로 다음 뷰컨트롤러를 생성하고, 뷰컨트롤러의 값을 가져와서 사용했다. segue로 연결한 뷰를 불러오고 싶다면 prefared 메서드(segue로 연결해준 뷰컨트롤러로 가기 전에 호출되는 메서드)를 오버라이딩 해서 사용하면 된다. ios prepare 공식 문서

 override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let 일을넘기는뷰 = segue.destination as?  일을넘기는뷰 else { return }
        일을넘기는뷰.delegate = self
    }

이런 방식으로 segue.destination 으로 일을 넘기는 뷰를 받아올 수 있다.


참고문서

ios delegate 공식 문서

ios prepare 공식 문서