Swift

Swift :: delegate패턴 알아보기

상어(shark) 2019. 3. 18. 17:18

안녕하세요! 상어입니다.

요새 방문자 수가 늘어서 기뻐욤'ㅁ'

이제 누군가가 제 글을 읽고 있단 생각을 하니까 더 책임감이 막중해지는거 있죻ㅎㅎ

앞으로도 계속계속 제 글을 많이 읽어주셔야 해용!!

 

오늘은 delegate패턴을 소개하고자 합니다.

사실 protocol을 먼저 소개하고 해야하지만, protocol은 넘나 방대하기 때문에 천천히..ㅎㅎㅎ

그럼 시작하겠습니다.

 

Delegate 패턴

 

delegate를 설명하기 이전에 잠시 protocol을 언급하고 넘어가겠습니당.

delegate는 protocol로 구현되기 때문에 잠깐만 소개해드릴게요!

 

protocol 붕어빵 기계라고 생각하시면 됩니다.

붕어빵 기계에는

 

이 있어야 하고

반죽 도 있어야 하고

도 붙어야 하고

익혀야 하고

 

등등이 있습니다.

그런데 붕어빵에는 슈크림 붕어빵이 있고, 팥 붕어빵도 있고, 고구마 붕어빵 등 여러 종류가 있습니다.

그리고 이 모든 종류의 붕어빵들은 붕어빵 기계를 공통적으로 가지고 있지요.

 

즉, 붕어빵이라는 것은 저 붕어빵 기계를 무조건적으로 가지고 있어야만 완성됩니다.

 

그래서 붕어빵 기계를 protocol이라고 본다면,

붕어빵 기계 protocol을 채택한 붕어빵들은 무조건! 틀, 반죽, 불, 익힘을 구현해야합니다.

 

여기서 하나 더 알아야할건! 

슈크림 붕어빵과 팥 붕어빵의 불의 세기가 다를 수 있고, 틀의 모양이 다를 수 있습니다.

 

다시 말해, 틀과 반죽, 불, 익힘 전부 구현해야하지만 

각 붕어빵 마다 기능을 다르게 구현 할 수 있습니다.

 

그래서 protocol에는 기능을 전부 구현하는 것이 아니라, 선언만 해둡니다.

 

 

그럼 이제 delegate를 알아볼까요?

 

delegate는 아르바이트생(대리자)이라고 생각하면 좋을 것 같아요.

붕어빵 가게에서 사장님이 바쁘니 아르바이트생이 업무를 대신합니다.

 

즉, 아르바이트생은 메뉴얼(구현)대로 일을 처리합니다.

멋대로 하면 싸장님께 혼나거등요 흑흑 

 

조금 헷갈리실 분들을 위해 덧붙이자면,

protocol은 아르바이트생의 메뉴얼이라면,

delegate패턴은 사장님과 메뉴얼을 익힌 아르바이트 생

이랍니다☺️

 

휴,, 개념은 언제나 어려운 것 같아요.

그래도 이 개념을 가지고 예제를 본다면 이해가 더더더더더더 잘되실거에요><

 

예제) 

 

A, B viewcontroller이 있는데

A뷰에서 B뷰로 이동한 후, B뷰에서 A뷰로 다시 이동할 시

데이터를 넘기고자 해요.

이 때! delegate를 이용하여 데이터를 넘길 겁니다.

 

그럼 첫번째로 protocol을 생성해 볼까요?

 

 

protocol DeliveryDataProtocol: class {
    func deliveryData(_ data: String)
} 

protocol의 기본 형태는

 

protocol 이름 {

// property 가능

// method 가능

}

이렇습니다. 저는 이번 예제에서 method만 작성을 해보았어요.

 

DeliveryDataProtocol이라는 이름을 지정해주고, class를 상속받았습니다.(추후 설명) 

그리고 그 안에 deliveryData라는 메소드를 만드는데 String타입의 data parameter를 받습니다.

 

아까도 말씀 드렸다시피 protocol에서는 선언만 해두고 구현은 따로 하지 않습니다.

 

 

 

위의 그림은 대략적으로 어떻게 protocol을 구현해 나갈건지에 대해 그려보았어요.

이걸 토대로 구현하자면

 

데이터를 받을 view 에서는 protocol을 채택해야한다고 되어있지요?

그럼 채택해봅시다.

  

아아아아 그전에!!!

저의 스토리보드는 

 

 

이렇습니다 :)

데이터를 받을 view는 FirstViewController라고 했고,

데이터를 전달 할 view는 SecondViewController라고 했습니다.

 

앞으로는 편의상 first, second라고 할게요!

 

firstViewController에서 전달받을 데이터라고 되어있는 label을 처음에 hidden시켜주고,

데이터를 전달받으면 보여줄 겁니다..

그리고, next버튼을 누르면 second로 이동되게끔 present 해줄겁니다.

 

class FirstViewController: UIViewController, DeliveryDataProtocol {

    // MARK: - IBOutlet 
    
    @IBOutlet weak var dataLabel: UILabel!
    
    override func viewDidLoad() {
    	super.viewDidLoad() 
        dataLabel.isHidden = true 
    } 
    
    // MARK: - IBAction
    
    @IBAction func nextButtonAction(_ sender: Any) { 
    	guard let vc = self.storyboard?.instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController else { return } 
        self.present(vc, animated: true, completion: nil) 
    }
 }

 

 

first에 DeliveryDataProtocol을 채택했습니다.

** 

protocol을 채택할 때, 앞에 상속받을 거 전부 받은 후, 마지막에 채택해주면 됩니다.

가령 -

FirstViewController가 ParentView를 상속받는다면,

FirstViewController: UIVIewController, ParentView, DeliveryDataProtocol 로 하면 됩니당 :D

**

 

위와 같이 protocol을 채택해주면, 아래와 같은 에러 메세지가 뜹니다.

 

 

해석해보면 

FirstViewController가 DeliveryDataProtocol 프로토콜을 준수하지 않는대용 'ㅁ'

 

아까 protocol설명할 때, protocol에 있는 것들은 모두 구현해야 한다는 말 기억하시나요?

현재 FirstViewController에는 protocol에 정의된 deliveryData메소드를 구현하지 않았기 때문에 에러가 뜬것입니다.

 

그럼 구현을 해볼까요?

 

저는, FirstViewController에서 데이터를 받으면 label의 hidden을 없애고, 

전달받은 데이터를 출력할 것입니다.

 

 

func deliveryData(_ data: String) { dataLabel.isHidden = false dataLabel.text = data }

 

 

별 것 없습니다. protocol에 선언한 그대로를 구현해주면 됩니다.

여기서 parameter이름이 달라진다거나, 타입이 달라진다면 다른 메서드로 인식을 하기 때문에

무조건 protocol에 선언한 그대로 적어야합니다.

 

그럼 이제 데이터를 전달 해줄 view(second)를 구현해볼까요?

 

second에서는 타입이 protocol인 property를 생성해주어야합니다.

 

 

weak var delegate: DeliveryDataProtocol?

 

이렇게요 ㅎㅎ 

property는 반드시 var로 해주셔야 합니다.

 

여기서 궁금하신 점이 있으실텐데,

weak을 왜 하냐!

 

사실 그냥 

var delegete: DeliveryDataProtocol? 로 해도 됩니다.

그치만, 

first -> second, second -> first 를 서로 참조하고 있는데 이럴 경우 메모리가 해제되지 않아서

좀비처럼 영원히 남아있게 됩니다.

이게 하나라면 큰 문제가 없을 수 있겠지만, 좀비가 많이 있다면 앱이 퀙 죽을 수 있어요.

(이부분은 ARC라는 메모리 관리 방법으로 인해서 그런것인데.. 자세한건 다음에 따로 설명하겠습니다.!)

 

근데 무작정 weak를 쓸 수 있는게 아니라,

protocol에서 오직 이 프로토콜은 클래스에서만 사용이 가능하다라는것이 명시가 되어있어야 합니다.

 

아까 제가 protocol을 생성할 때 class를 상속받은 거 기억나시나요?

protocol에 class 또는 AnyObject를 상속받는다면 

이 protocol은 오직 class에서만 사용 가능하다라는 뜻이 됩니다.

 

** protocol이 class 또는 AnyObject를 상속받지 않는다면 모든 곳에서 채택이 가능합니다. **

 

이해가 잘 안되시는 분들은 

weak을 붙이는 이유는 메모리누수를 방지하기 위함이라고 알아두시면 편하실 것 같아요 😁

 

자, 그럼 이전버튼을 눌렀을 때

first로 이동하면서 데이터를 전달해봅시당

@IBAction func preButtonAction(_ sender: Any) {
    delegate?.deliveryData("오늘 하루도 힘내세요!!") 
    self.dismiss(animated: true, completion: nil) 
}

 

사용하는 법은 간단합니다.

변수를 쓰고 그 후 사용할 기능을 쓰면 됩니닷 :)

 

 

이제, 가장 중요한 작업을 해야합니다.

first에서 second로 이동한다는 코드 부분에서 대리자를 설정해줘야 합니다.

vc.delegate = self // 대리자 설정

 

protocol을 채택하고 구현하면 채택한 protocol로 형변환이 가능해집니다.

second의 대리자를 '나'로 설정하는건데

다시 말해, 'second의 대리자는 나(first)야 (찡긋)'가 됩니다.

 

많은 분들께서 대리자를 위임해주는 과정을 생략(?)하셔서 안되는 경우가 많더라구요 ㅎㅎ

여러분들은 절대절대 까먹지 마세용!! (저도 자주 까먹어요..)

 

완성되면 아래와 같이 데이터를 잘 받을 수 있답니다!

 

 

생각보다 delegate 쉽지않나요?

처음에는 어.. 이게 모야 하면서 어려울 수 있는데

막상 익숙해지면 편하답니다 ㅎㅎ 

그럼 모두들 delegate 고수가 되었지요?!!!!

저는 뿌듯합니닷 헤헿

 

해당 소스는

https://github.com/shark-sea/BlogExam

에서 볼 수 있습니다~^^

 

안뇽!