[TIL] Delegate Pattern

valse·2022년 8월 22일
0

iOSInterViewController

목록 보기
2/8
post-thumbnail

Delegate Pattern

한국어로 직역하면 '대리자 패턴'이다.
델리게이트는 프로토콜을 활용하여 객체 간 소통이 필요할 때 사용하는 디자인 패턴이다.
이벤트가 일어나는 객체의 정보를 다른 객체가 알고 싶을 때 사용할 수 있다.
콜렉션뷰를 써본 사람은 알겠지만 didSelectItemAt 또한 델리게이트 패턴으로 사용되고 있다.
뷰컨과 뷰의 관계에 따라 델리게이트 프로토콜을 직접 정의해야 할 필요가 있으며, 객체 간 참조가 강한 순환 참조를 일으킬 수 있기 때문에 메모리 누수에 주의해야 한다.

개인적으로 델리게이트 패턴은 설명을 들었을 때보다 실제로 사용할 때가 굉장히 헷갈렸다.


활용

델리게이트 패턴은 정말정말 다양한 방법으로 활용할 수 있다.
이번엔 필자가 진행하고 있는 사이드 프로젝트의 예시를 가져와서 살펴보겠다.
주어진 상황은 다음과 같다.

  1. GameViewControllerGameCollectionView를 갖고 있다.
  2. GameCollectionView는 25개의 GameViewCell을 갖고 있다.
  3. GameViewCell은 각각의 UITextField를 갖고 있다.
  4. GameViewCellUITextField에 입력된 값을 GameViewController에서 확인하고자 한다.

이벤트가 일어나는 곳은 GameViewCellUITextField고 변화를 알고 싶은 곳은 GameViewController다.

델리게이트 패턴의 핵심은 이벤트 발생 객체에서 delegate 속성을 정의하고, 이벤트 수신 객체에서 delegate의 메소드를 정의하는 것이다.
이 관계를 중개해주는 프로토콜이 있기 때문에 이러한 응용이 가능하다.

  • protocol 정의

아래에 정의된 프로토콜로 속성과 메소드는 서로 다른 객체에서 소통할 수 있게 된다.
이 프로토콜은 UITextFieldGameViewCell을 파라미터 타입으로 요구하는 메소드 하나를 갖는다.
클래스 객체만 채택할 수 있도록 AnyObject를 더한다.
직접 커스터마이징한 특정한 셀에서의 활동을 받아오기 때문에 구체화 했다.

protocol CustomCollectionViewCellDelegate: AnyObject {
	func collectionViewCell(valueChangedIn textField: UITextField,
    delegatedFrom cell: GameViewCell)
}
  • delegate 속성 정의

이벤트가 일어나는 객체에 delegate 속성을 정의한다.
위에서 정의한 프로토콜 타입으로 정의하여 delegate에 할당되는 또 다른 프로토콜 타입의 메소드를 호출할 수 있는 연관성을 얻는다.
UITextField 의 값 입력이 끝날 때 그 값을 전달해야 하기 떄문에 addTarget을 함께 활용한다.
addTarget 메소드가 트리거하는 메소드는 delegate 프로토콜의 메소드다.
이 시점까지 delegate의 메소드는 정의되어 있지 않기 때문에 아무런 일도 일어나지 않는다!
CharacterTextFieldself가 아규먼트로 전달되어 이제 delegate에 할당되는 객체는 이 이벤트에 접근할 수 있을 것이다.
또한 이벤트에 접근하고 처리하는 그 객체에서 프로토콜을 채택하고 구체화함으로써 delegate 메소드는 올바르게 동작할 것이다.

그러나 이 구조는 delegate가 참조하는 객체 내부의 메소드를 호출하는 것이다.
클래스 객체가 온다면 retain이 1회 발생할 것이다.
반대로 클래스 객체는 GameViewCelldelegate에 자기 자신을 할당하게 될테니 여기서도 retain이 발생한다.
서로 참조하는 강한 참조 순환이 일어날 수 있기 때문에 delegate 속성은 weak으로 선언했다.

// MARK: GameViewCell
public weak var delegate: CustomCollectionViewCellDelegate?

public let chracterTextField: UITextField = {
		let tf = UITextField()
		tf.backgroundColor = .systemGray4
		tf.layer.borderColor = UIColor.black.cgColor
		tf.layer.borderWidth = 0.5
		tf.font = UIFont.boldSystemFont(ofSize: 16)
		tf.autocapitalizationType = .allCharacters
		tf.autocorrectionType = .no
		tf.keyboardType = .alphabet
		tf.textAlignment = .center
		tf.addTarget(self, action: #selector(valueChanged), for: .editingDidEnd)
		return tf
}()

@objc func valueChanged(_ sender: UITextField) {
		delegate?.collectionViewCell(valueChangedIn: chracterTextField, delegatedFrom: self)
}
  • delegate 메소드 정의

위의 delegate가 전달받은 이벤트로 무엇을 할 것인지 정의하는 것으로 커스텀 델리게이트 패턴은 마무리된다.
이벤트를 수신하는 객체는 GameViewController다.
GameViewController 에서 이벤트를 수신하고 처리하기 때문에 프로토콜은 이 곳에서 채택된다.

위에서 정의한 GameViewCell을 생성하는 UICollectionViewDataSource에서 delegateGameViewController 로 할당한다.

GameViewController를 확장해서 CustomCollectionViewCellDelegate를 채택하고 수행할 메소드를 정의한다.
gameViewindexPath를 받아오고, 25개의 셀이 갖고 있는 textField 의 값이 어느 위치의 indexPath에 해당하는지 확인할 수 있다.

// MARK: GameViewController
private var gameView: UICollectionView?

// MARK: GameViewController: UICollectionViewDataSource
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
		let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! GameViewCell
		cell.delegate = self
		return cell
}

extension GameViewController: CustomCollectionViewCellDelegate {
	func collectionViewCell(valueChangedIn textField: UITextField, delegatedFrom cell: GameViewCell) {
		guard let gameView = gameView else { return }
		if let indexPath = gameView.indexPath(for: cell),
		   let text = textField.text, text != "" {
			print("textField text: \(text) from cell: \(indexPath))")
		}
	}
}


정리

  1. 델리게이트 패턴은 하나의 객체가 자신의 책임을 다른 객체로 위임하는 디자인 패턴이다. 다른 객체의 정보는 캡슐화되기 때문에 접근하여 수정할 수 없고 이에 책임을 위임할 필요가 있다.
  2. 델리게이트 패턴은 이벤트가 발생하는 객체의 정보나 활동을 수신 객체가 받아와서 처리할 때 사용할 수 있다.
  3. NotificationCenter로 구현하는 옵저버 패턴과 달리 일대일, 단방향 소통이다(일반적으로는).
  4. 개별 프로토콜을 직접 구현하여 커스터마이징이 가능하고, 강한 참조 순환이 발생할 수 있다.
    즉, retain count가 증가한다.

220822

profile
🦶🏻🦉(발새 아님)

0개의 댓글