• Feed
  • Explore
  • Ranking
/
/
    🎨 UIKit & SwiftUI

    [UIKit] UI 업데이트 "해줘", 데이터 바인딩

    iosSwiftUIKitData Binding
    지
    지성
    2025.04.11
    ·
    8 min read

    앱을 만들다 보면 종종 아래와 같은 상황을 겪게 된다.

    버튼을 눌렀을 때 값을 어디에 알려줘야 하지?”

    “ViewModel에서 가져온 데이터를 어떻게 View에 알려주지?”

    “상태가 바뀌었는데, View는 왜 가만히 있지...?”

    이럴 때 필요한 게 바로 데이터 바인딩으로, 데이터가 바뀌면 UI에 반영되도록 연결해주는 일종의 "파이프 라인"이다.

    여기에서는 UIKit 환경에서 사용할 수 있는 대표적인 바인딩 방법을 알아보도록 하겠다.


    Delegate

    "너 무슨 일 생기면 나한테 알려줘!"

    Delegate는 한 객체가 다른 객체의 행동을 위임받아 처리하는 방식으로, UIKit에서 자주 사용되는 방법이다. TableView, CollectionView, TextField 등 UIKit 컴포넌트들이 많이 사용한다. 예를 들어, 버튼을 눌렀을 때 특정 액션을 처리하는 데 사용된다.

    protocol MyDelegate: AnyObject {
        func didTapButton()
    }
    
    class MyView: UIView {
        weak var delegate: MyDelegate?
        
        func buttonTapped() {
            delegate?.didTapButton()
        }
    }

    장점

    • 구조가 명확하고, 간단하다.

    • 메모리 관리에 익숙한 패턴이다. (weak delegate)

    단점

    • 한 번에 하나의 이벤트만 처리 가능하다. 즉, 여러 컴포넌트가 같은 이벤트를 받아야 한다면 다른 방식이 필요하다.

    • 프로토콜을 만들고 연결하는 게 귀찮다.

    • weak로 참조해야 메모리 누수를 방지할 수 있다.

    Closure

    "끝나면 이 함수 호출해줘!"

    Closure는 일회성 이벤트 처리를 위한 간단한 방법이다. 메서드나 함수에서 바로 인라인으로 사용할 수 있다. 버튼 클릭이나 네트워크 응답 처리 등에 사용된다.

    class MyView: UIView {
        var onTap: (() -> Void)?
        
        func buttonTapped() {
            onTap?()
        }
    }

    장점

    • Delegate보다 간단하고 깔끔하다.

    • 클로저 내에서 상태를 쉽게 캡처할 수 있다.

    • 일회성 이벤트 전달에 좋다.

    단점

    • 메모리 관리에 주의해야 한다.

    • 복잡한 데이터 흐름을 관리하기엔 한계가 있다.

    NotificationCenter

    "내가 한 일 듣고 싶은 사람 다 들어!"

    NotificationCenter는 여러 객체에 이벤트를 전달하고자 할 때 사용하는 방식이다. 이벤트를 발생시킨 객체와 수신 객체가 서로를 알지 못해도 된다. 전역적으로 상태 변경이 필요할 때, 예를 들어, 앱 테마 변경이나 로그인 상태 변경 등을 처리할 때 유용하다.

    NotificationCenter.default.post(name: .didLogin, object: nil)
    
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(handleLogin),
        name: .didLogin,
        object: nil
    )

    장점

    • N:1N:1N:1 또는 N:NN:NN:N 구조를 지원한다. 즉, 여러 객체가 동일한 이벤트를 수신할 수 있다.

    • Loose coupling(느슨한 결합)을 지원한다. 발행자와 구독자가 서로를 모른 채 통신할 수 있다.

    단점

    • 코드가 분산되어서 추적하기 어려운 경우가 있다.

    • 잘못 사용하면 앱이 복잡해져서 관리가 어려워질 수 있다.

    • 발행자와 구독자 간에 타입 검사가 컴파일 시점에 이루어지지 않기 때문에 타입 안정성이 떨어진다.

    Property Observer (didSet, willSet)

    "값 바뀌면 바로 반응해!"

    속성 값이 변경될 때 자동으로 실행되는 코드 블록이다. 상태 변경을 감지하고 즉시 반응할 수 있다. 단일 객체 내에서 상태를 추적하거나 UI를 즉시 업데이트할 때 유용하다.

    class MyClass {
        var score: Int = 0 {
            willSet {
                print("점수가 곧 \(newValue)점으로 바뀔 예정입니다.")
            }
            didSet {
                print("점수가 \(oldValue)점에서 \(score)점으로 바뀌었습니다!")
            }
        }
    }

    장점

    • 아주 간단하게 사용할 수 있다. 별도의 메서드나 설정없이 값을 변경할 수 있다.

    단점

    • 외부 객체와의 바인딩에는 부적합하고, 같은 클래스 내에서만 효과적이다.

    Reactive Programming (Combine, RxSwift)

    "데이터가 바뀌면, 자동으로 따라갈게!"

    반응형 프로그래밍은 선언형 프로그래밍 패러다임으로, 데이터 스트림과 구독을 통해 데이터를 자동으로 처리할 수 있다. 복잡한 비동기 이벤트 처리, 상태 변화 추적, 또는 여러 데이터를 조합해야 할 때 사용한다.

    // Combine
    let name = CurrentValueSubject<String, Never>("")
    let cancellable = name.sink { newName in
        print("이름이 \(newName)으로 바뀌었습니다!")
    }
    name.send("춘장")
    // RxSwift
    let name = BehaviorSubject<String>(value: "")
    name.subscribe(onNext: { newName in
        print("이름이 \(newName)으로 바뀌었습니다!")
    }).disposed(by: disposeBag)
    name.onNext("춘장")

    장점

    • 복잡한 데이터 흐름을 선언적으로 관리할 수 있다.

    • 비동기 작업, 이벤트 스트림을 간결하게 처리할 수 있다.

    단점

    • 학습에 시간이 걸릴 수 있다. (용어부터 헷갈리 수도..)

    • 작은 프로젝트에서는 오히려 오버 엔지니어링이 될 수 있다.







    - 컬렉션 아티클