← 기술 지식

기술 지식 / 웹·모바일 / iOS

iOS 콜백·방송과 현재 상태 구독

delegate·완료 클로저·Notification의 관계를 나누고 구독 수명·재진입·등록과 snapshot 사이의 사건 누락을 설명합니다.

모바일 · 원리·구현·운영
기술 지식 트리

iOS의 delegate, 완료 클로저, Notification은 문법 선택지가 아니라 결과의 소유권과 관찰 수명에 관한 서로 다른 계약입니다. 이 노트는 현재 상태 snapshot과 과거 사건을 나누고, 등록·초기 상태 설정·해제 사이에서 사건이 빠지거나 중복되지 않게 만드는 경계를 설명합니다.

단일 작업 결과와 다중 화면 상태 변화

다운로드를 한 번 시작한 화면이 성공·실패·취소 중 하나의 결과만 받는다면 완료 클로저를 고려할 수 있습니다. 진행률·질의·완료처럼 같은 협력자와 여러 번 주고받아야 한다면 delegate를, 로그인 변화처럼 발신자가 어느 화면이 듣는지 몰라도 여러 소비자에게 사건을 알려야 한다면 Notification을 고려합니다. 선택할 때는 구독자 수보다 결과의 수명, 과거 사건을 다시 재생할 필요, 구독을 끝낼 시점을 먼저 판단합니다.

구독자 수만으로 선택하지 않습니다. 응답이 필요한지, 과거 사건을 재생해야 하는지, 누가 결과를 보존하고 언제 구독을 끝내는지도 판단해야 합니다.

delegate·완료 클로저·Notification 계약 비교

방식 적합한 관계 별도 관리
delegate 진행·질의·완료를 주고받는 명시적 협력자 참조 수명·호출 executor·필수 메서드
완료 클로저 시작한 작업의 성공·실패·취소 결과 한 번 전달·캡처·늦은 완료
Notification 독립 소비자에게 사건 방송 구독 토큰·순서·재진입·놓친 사건

서비스와 화면이 서로 소유하면 delegate를 약하게 두거나 관찰 클로저의 캡처를 조정할 수 있습니다. 그러나 화면이 없어도 남겨야 하는 다운로드 결과는 서비스나 저장소가 소유해야 합니다. 메모리 순환을 끊었다고 작업 보존 문제가 해결되는 것은 아닙니다.

Notification 사건과 현재 상태 원본

화면이 로그인 알림을 등록하기 전에 사용자가 로그인하면, 새 화면은 이미 지나간 사건을 받지 못해 로그인되지 않은 화면을 그릴 수 있습니다. 따라서 Notification을 세션의 원본으로 삼지 말고 현재 상태와 버전을 조회할 수 있는 저장소를 둡니다. 다만 상태 조회 → 구독을 두 동작으로 나누면 그 사이 변경을 놓칠 수 있으므로, 저장소가 구독 등록과 초기 snapshot을 한 직렬 구간에서 함께 처리하거나 먼저 구독한 뒤 초기 snapshot 이전의 이벤트를 버퍼링해야 합니다.

등록과 현재값을 한 경계에서 제공합니다새 화면: 현재 로그인 상태 필요. 세션 상태 저장소: 현재값 · 버전 · 구독자. 초기 snapshot v10. 후속 이벤트 v11…구독 시작현재 상태 반환이후 변경 전달새 화면현재 로그인 상태 필요세션 상태 저장소현재값 · 버전 · 구독자초기 snapshot v10후속 이벤트 v11…
등록과 현재값을 한 경계에서 제공합니다 화살표는 구독 시작 흐름입니다. 상태 저장소가 등록·snapshot을 한 직렬 경계에서 제공하고, 이후 이벤트에는 증가 버전을 붙입니다.
그림을 글로 읽기
  • 새 화면: 현재 로그인 상태 필요
  • 세션 상태 저장소: 현재값 · 버전 · 구독자
  • 초기 snapshot v10
  • 후속 이벤트 v11…
  1. 새 화면 → 세션 상태 저장소: 구독 시작
  2. 세션 상태 저장소 → 초기 snapshot v10: 현재 상태 반환
  3. 세션 상태 저장소 → 후속 이벤트 v11…: 이후 변경 전달

snapshot·이벤트 도착 순서와 버전 처리

상태 변경을 snapshot으로 보낼지 delta로 보낼지 먼저 고정해야 버전 처리도 정해집니다. snapshot v10 뒤 snapshot v11은 늦은 v10을 버려도 되지만, delta v11은 v10에서 적용해야 하므로 누락 감지와 원본 재조회가 필요합니다. 같은 버전 숫자라도 payload 의미가 다르면 같은 병합 규칙을 적용할 수 없습니다.

subscribeWithSnapshot(observer):
    within_store_serial_execution:
        token = register(observer)
        initial = (currentVersion, currentState)
        return token, initial

onState(version, snapshot):
    within_UI_serial_execution:
        if version <= displayedVersion: return
        displayedVersion = version
        render(snapshot)

이 모형은 이벤트가 전체 상태 snapshot을 담는다고 가정합니다. 예를 들어 초기 snapshot v10을 적용하기 전에 후속 이벤트 v11이 먼저 UI에 도착하면, UI는 v11을 그린 뒤 늦게 온 v10을 버릴 수 있습니다.

반대로 v11이 “포인트 +10” 같은 증분이면 v10에서 v11로 가는 변화 자체를 적용해야 하므로, 버전 숫자가 더 크다는 이유로 앞선 증분 이벤트를 버리면 상태가 틀어질 수 있습니다. 이때는 기대하는 다음 순번이 올 때까지 누락 이벤트를 재생하거나 원본에서 전체 상태를 다시 읽는 절차가 필요합니다.

등록·snapshot이 원자적이지 않은 API에서는 먼저 구독하며 이벤트를 버퍼링하고 snapshot 버전 이후만 적용하는 등 별도 절차를 둡니다. “일단 알림을 붙였다”는 사실만으로 race가 사라지지 않습니다.

구독 중 등록·해제와 재진입 규칙

멀티캐스트 delegate 집합을 순회하던 첫 번째 콜백이 자기 자신이나 다른 구독자를 제거하면, 같은 방송에서 그 변경을 즉시 반영할지 다음 방송부터 반영할지 정해야 합니다. 구현에서는 호출을 시작할 때 구독자 목록의 snapshot을 복사하거나, 직렬 실행기에서 추가·해제를 다음 순서로 예약하는 방식 중 하나를 택합니다. 약한 참조가 이미 nil이면 건너뛰되, 콜백을 호출하는 동안 필요한 객체의 수명은 별도로 확보합니다.

구독자 순서가 결과에 영향을 준다면 순서를 명시한 직접 협력 구조가 더 적합할 수 있습니다. Notification이 모든 수신자의 실패를 격리하거나 모든 API에서 같은 비동기 순서를 보장한다고 가정하지 않습니다. 실제 사용하는 observer API의 queue·동기 전달·토큰 수명 규칙을 확인해야 합니다.

UI 갱신과 필수 외부 효과의 책임 분리

UI는 적절한 MainActor 경계에서 바꾸고, 느린 파일 처리·네트워크를 동기 알림 콜백에 넣어 발신자를 막지 않도록 합니다. 동시에 큐로 넘기면 실행 완료 순서가 달라질 수 있으므로 버전과 도메인 조건을 유지합니다.

알림 payload에는 필요한 ID·버전만 넣고 비밀번호·원문 자격 등 불필요한 민감값을 방송하지 않습니다. 토큰 해제와 화면 종료를 연결하되, 필수 감사 기록·작업 완료를 UI 구독자 존재에 의존시키지 않습니다.

구독 등록 전후 상태 변경 재현

재현 로그에는 등록 시각, 초기 snapshot 버전, 이벤트 버전, 해제 토큰, UI가 실제로 적용한 마지막 버전을 함께 남깁니다. 이 기록이 있으면 화면이 오래된 상태를 그린 원인이 사건 누락인지, 순서 역전인지, 중복 등록인지 빠르게 구분할 수 있습니다.

현재값 조회 직후 로그인 변경, 등록 직후 로그아웃, v11 이벤트가 v10 snapshot보다 먼저 도착, 콜백 중 자기 해제, 중복 등록을 시험합니다. 최종 UI가 최신 상태와 맞고 같은 행동이 두 번 실행되지 않아야 합니다.

delegate·클로저·Notification 중 어떤 문법을 골랐는지보다 결과 수명·관찰 순서·복구 원본이 분명한지가 좋은 설계의 기준입니다. 플랫폼별 API 실험과 메모리 검사는 실제 iOS 환경에서 별도로 수행해야 합니다.

후속·관련 개념

이 개념의 확장

함께 읽을 문서

전체 기술 지식
선택 연습 · 관련 질문 3개

질문은 이해를 확인하는 보조 자료입니다. 학습 경로와 문서의 범위는 질문 수와 독립적입니다.