공부/iOS

UIKit에서 Delegate Pattern를 쓰는 이유

안토니1 2023. 12. 21. 14:38

# 시작하기 전...

UIKit을 처음 접했을 때 가장 당황했던 것은 호출하지 않고도 실행되는 함수였습니다.

무슨 소리인가? 싶겠지만 정말 func 로 구현은 되어있는데 어디에도 호출은 하지 않았지만 분명 그 코드는 동작하고 있었던 것이죠.

그 미스테리의 정체는 오늘 알아볼 Delegate입니다.

 

# Delegate란?

Delegate를 직역하면 위임자란 뜻입니다.

(이걸 처음 봤을 때 나의 심정)

도대체 무슨 소리인가 싶고, 설명도 잘 이해가 되지 않죠...

 

뭔가 위임자라고 하면 뭔가 대신 일을 처리해주는 종속된 관계가 아닐까 생각했다.

하지만 정 반대로 일종의 Delegate를 채택하는 쪽이 팀장님, 요청하는 쪽이 직원에 가깝습니다.

 

Delegate를 생성하는 과정을 비유를 섞어 설명해볼게요.

 

1. Delegate 생성 및 채택

회사에는 작업이 완료될 때 마다 보고를 하는 프로세스가 있습니다.

구체적인 내용은 각  팀장의 재량에 맡깁니다.

protocol WorkerDelegate: AnyObject {
// 작업 종료 후에 보고하기
    func didFinishWork()
}

 

2. WorkerDelegate 프로토콜 채택

한 팀장님이 이 체계가 마음에 들어 자신의 팀에도 도입하기로 합니다.

보고를 할 때는 PPT를 만들어야 한다는 것을 추가합니다.

extension ManagerViewController: WorkerDelegate {

    func didFinishWork() {
        // 작업이 완료된 후에 업무를 보고합니다.
        // PPT를 만든 뒤에 발표하세요.
        print("업무 보고")
    }
}

 

3. WorkerDelegate를 선언

위에서 시키면 시키는대로 할 준비가 된 성실한 직원입니다. 

class Worker {
    weak var delegate: WorkerDelegate?
    
}

 

4. ManagerViewController가 worker의 대행자

위 직원을 팀장님(ManagerViewController)이 "직접" 직원(Worker)을 담당하기로 합니다.

class ManagerViewController: UIViewController {

    private let worker = Worker()

    override func viewDidLoad() {
        super.viewDidLoad()

        // self = ManagerViewController
        worker.delegate = self
    }
}

 

5. Worker에서 작업 구현

Worker의 일을 정의합니다. 여기에는 업무가 끝난 뒤에 보고하는게 포함되어있습니다. 

여기서 didFinishWork()는 팀장님이 시켰던 PPT를 포함한 업무보고입니다.

class Worker {
    weak var delegate: WorkerDelegate?

    func doWork() {
        // 업무를 수행합니다.
        //
        // 업무 수행이 끝난 뒤에 일이 다 끝났다고 관리자에게 보고를 합니다.
        delegate?.didFinishWork()
    }
}

 

6. 구현한 작업을 실행

worker에게 작업을 시킵니다.

위에서 구현한대로 worker는 업무의 마지막 단계에서 2번에서 구현한 함수를 실행하면서 보고를 할 것입니다.

class ManagerViewController: UIViewController {

    private let worker = Worker()

    override func viewDidLoad() {
        super.viewDidLoad()

        worker.delegate = self
        
        // 직원에게 일을 시킵니다.
        worker.doWork()
    }
}

 

 

# 이해하기

이 모든 과정은 팀장님과 직원의 1:1 커뮤니케이션으로 진행됩니다.

 

만약 팀장님이 바뀌어 보고체계에 변화가 생긴다면?

Worker의 코드는 바꿀 필요가 없습니다. 그저 하던 작업을 똑같이 수행하면 됩니다!

Delegate를 채택한 팀장(NewViewConroller)에서 didFinishWork의 구현부만 수정하면 된다는 뜻이니까요 ㅎㅎ

 

만약 보고를 할 담당자가 아무도 없다면? (worker.delegate = nil)

그럼 업무만 딱 수행하고 별도의 보고는 안해도 되겠죠!

 

# 활용하기

이런 특이한 구조가 실전에서 어떻게 활용할 수 있을까요?

 

UITextFieldDelegate를 예시로 들어서 설명해볼게요.

평소 텍스트필드만 사용할 때는 이용할 수 없던 메서드를 Delegate를 채택하면 사용할 수 있게 됩니다.

그럼 shouldChangeCharactersIn을 활용해 텍스트의 최대 길이를 10으로 제한해보겠습니다!

extension ViewController: UITextFieldDelegate {
    
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        
        let length = textField.text!.count + string.count - range.length
        
        textField.layer.borderWidth = 3.0
        textField.layer.borderColor = length <= 10 ? UIColor.green.cgColor : UIColor.red.cgColor
        return length <= 10
    }
}

1) textField.text!.count: 텍스트필드에 입력되어있는 텍스트의 길이입니다.

2) string.count: 사용자가 입력에 새롭게 추가될 텍스트의 길이 입니다.

3) range.length: 사용자의 입력에 기존 텍스트의 변화하게 될 텍스트의 길이입니다.

 

이 메서드는 텍스트가 입력될 때 마다 위 함수를 호출해 입력 여부를 결정하게 됩니다.

만약 사용자가 10글자를 초과한 입력을 하게 된다면 입력이 불가능하겠죠.

ViewController에서 최대 길이에 대한 제한을 걸 수도 있겠지만, 이렇게 간단하게 함수를 구현만 하더라도 제약을 걸 수 있는 것이죠.

또한 이 함수가 사용자가 입력할 때 마다 호출된다는 점을 이용해 컬러 변경과 같은 효과를 유동적으로 적용시킬 수도 있겠네요.

 

# 결론

오늘은 UIKit의 Delegate 패턴에 대해 알아봤습니다.

예제에서 파라미터를 따로 지정해주지 않았지만 UITextFieldDelegate처럼 파라미터를 지정해주면 데이터를 전달하는 역할로도 사용할 수 있게 됩니다. 이를 활용하면 UIViewController와 UIView, UIViewController와 다른 UIViewController 사이에 데이터를 전달하는 용도로 사용할 수 있겠죠.

 

결국 1:1 관계에선 강력하게 작동한다고 볼 수 있을 것 같습니다.

다만 depth가 길어지거나 공통으로 사용되는 영역이 많은 경우, 통신과 같은 비동기 처리가 잦은 경우에는 부적절해보입니다.

물론 didset 같은거로 막 도배하면 되기는 하겠지만...