[iOS | Swift] 자동 관찰 추적 (@Observable)

someng·2025년 9월 5일
0

iOS

목록 보기
38/38

iOS 18에 숨겨진 보석

SwiftUI가 처음 나왔을 때, @Published 속성이 바뀔 때마다 뷰가 자동으로 업데이트되는 모습에 모두 감탄했죠? 애플이 조용히 UIKit과 AppKit에도 같은 마법을 심어두고 있었습니다. 더 놀라운 점은, 이 기능이 iOS 18/macOS 15에 이미 포함되어 있다는 사실. Xcode 26 같은 최신 도구도 필요하지 않고, 오직 간단한 Info.plist 설정만 해주면 됩니다.

🎃 기존의 문제

UIKit에서 데이터 모델과 UI를 동기화하는 일은 항상 번거로운 일이었습니다. 흔히 아래와 같은 코드를 쓰면서 UI가 최신 상태를 유지하도록 신경 써야 했죠.

class ProfileViewController: UIViewController {
    var user: User? {
        didSet {
            updateUI()
        }
    }

    func updateUI() {
        nameLabel.text = user?.name
        avatarImageView.image = user?.avatar
        // … 추가로 여러 줄의 수동 업데이트 코드
        setNeedsLayout()
    }
}

updateUI() 호출을 깜빡하면 UI는 “멈춘” 상태가 되고, 너무 자주 호출하면 성능에 문제가 발생할 수 있습니다. 귀찮고 실수하기 쉬운 방식이죠.


🎃 자동 관찰 추적 (Automatic Observation Tracking)의 등장

이제는 모든 게 바뀝니다. 새로운 관찰 프레임워크를 도입하면 위의 패턴이 필요 없어집니다. 아래처럼 작성만 하면 끝입니다:

import Observation

@Observable
class User {
    var name: String = ""
    var avatar: UIImage?
    var unreadCount = 0

    var hasUnread: Bool {
        unreadCount > 0
    }
}

class ProfileViewController: UIViewController {
    let user = User()

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        // UIKit이 여기에 접근한 속성을 자동으로 추적합니다!
        nameLabel.text = user.name
        avatarImageView.image = user.avatar
        badgeView.isHidden = !user.hasUnread
    }
}

이제 앱 어디에서든 user.name을 변경하면, 해당 변경이 자동으로 반영되어 라벨이 갱신됩니다. 수동 호출, 누락된 업데이트, 불필요한 성능 부담 없이 “그냥 작동”합니다.


🎃 관찰 추적이 지원되는 메서드들

자동 관찰 트래킹은 다양한 UIKit과 AppKit 메서드에서 지원됩니다. 주로 아래 메서드들이 사용됩니다:

UIKit — UIView

  • updateProperties() (iOS 26+)
  • layoutSubviews()
  • updateConstraints()
  • draw(_:)

UIKit — UIViewController

  • updateProperties() (iOS 26+)
  • viewWillLayoutSubviews()
  • viewDidLayoutSubviews()
  • updateViewConstraints()
  • updateContentUnavailableConfiguration(using:)

UIKit — UIPresentationController

  • containerViewWillLayoutSubviews()
  • containerViewDidLayoutSubviews()

UIKit — UIButton

  • updateConfiguration()
  • configurationUpdateHandler 실행 시

UICollectionViewCell, UITableViewCell, UITableViewHeaderFooterView

  • updateConfiguration(using:)
  • configurationUpdateHandler 실행 시

AppKit — NSView

  • updateConstraints()
  • layout()
  • draw(_:)
  • updateLayer()

AppKit — NSViewController

  • updateViewConstraints()
  • viewWillLayout()
  • viewDidLayout()

🎃 Info.plist 설정

이 기능은 기본으로 활성화되어있지는 않습니다. 수동으로 Info.plist에 다음 키를 추가해야 합니다.

  • UIKit (iOS 18+)

    <key>UIObservationTrackingEnabled</key>
    <true/>
  • AppKit (macOS 15+)

    <key>NSObservationTrackingEnabled</key>
    <true/>

iOS 26/macOS 26부터는 이 키가 없어도 기본적으로 활성화되어, 이 설정은 무시됩니다. ([steipete.me][1])


🎃 iOS 26 이후의 추가 개선점

iOS 26에서는 updateProperties()라는 신규 메서드를 통해 더 효율적인 업데이트 루프를 지원합니다. layoutSubviews()보다 먼저 실행되며, 다음과 같은 기능을 제공하죠: ([steipete.me][1])

  • 속성 업데이트 전용 메서드로, 레이아웃과 분리된 깔끔한 구조
  • setNeedsUpdateProperties()로 수동 플래그 호출 가능
  • updatePropertiesIfNeeded()로 즉각적 실행 가능
  • 자동으로 Observable 속성을 추적하고 UI 갱신 트리거

🎃 참고: 구현 예제 프로젝트

이 기능들을 테스트 해볼 수 있는 예제 프로젝트도 공개되어 있습니다: ObservationTrackingExample

  • 지원 플랫폼: iOS 18+/macOS 15+ (수동 활성화 필요), iOS 26+/macOS 26에서는 기본 활성화

  • 모델 예시:

    @Observable
    @MainActor
    final class SharedDataModel: Sendable {
        var message = "Hello!"
        var counter = 0
        // ... 기타 속성
    }
  • UIKit 예시:

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        textLabel.text = model.message
        counterLabel.text = "\(model.counter)"
    }
  • AppKit 예시:

    override func viewWillLayout() {
        super.viewWillLayout()
        textField.stringValue = model.message
        label.intValue = model.counter
    }

이 프로젝트는 SwiftUI, UIKit, AppKit 간에 Observable 모델을 공유하며, 수동 코드 없이 UI가 자동 동기화되는 사례를 보여줍니다.

🦄 내가 만든 예제

위 예제를 참고하여 버튼 클릭에 따라 UILabel 이 바뀌는 간단한 기능을 구현해보았다!

@Observable
class User {
    var name: String = "테스트 이름"
}

class ViewController: UIViewController {
    @IBOutlet weak var nameLabel: UILabel!
    
    let user = User()
    var testFlag: Bool = true

    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        // UIKit이 여기에 접근한 속성을 자동으로 추적합니다!
        nameLabel.text = user.name
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
            
    }

    @IBAction func updateBtnClicked(_ sender: Any) {
        testFlag.toggle()
        user.name = testFlag ? "테스트 이름" : "조이"
    }
    
}

정리

  • iOS 18/macOS 15에 이미 포함된 자동 관찰 추적 기능을 사용하면 UIKit과 AppKit에서도 SwiftUI처럼 자동 UI 업데이트가 가능합니다.
  • Info.plist에 UIObservationTrackingEnabled 또는 NSObservationTrackingEnabled 키를 추가하면 활성화할 수 있습니다.
  • iOS 26/macOS 26부터는 기본적으로 활성화되며, 더 세밀한 업데이트 제어를 위해 updateProperties() 메서드도 지원됩니다.
  • 예제 프로젝트를 통해 실제 작동 방식을 직접 확인해볼 수 있습니다!

출처
https://steipete.me/posts/2025/automatic-observation-tracking-uikit-appkit

profile
👩🏻‍💻 iOS Developer

0개의 댓글