@MainActor함수를 이미 main actor에서 호출하면await에서 실제로 suspend 하나요?- 백그라운드 작업 중 일부만 메인에서 실행해야 할 때의 선택지는?
답변 ① — hop은 없지만, suspend는 callee에 달렸다 🔥 핵심
이 질문은 두 가지를 구분해야 제대로 답이 됩니다.
- actor 이동(hop) — 이미 main actor 위에 있으면 같은 격리 도메인이므로 hop은 절대 없습니다. 이건 항상 보장됩니다.
- suspend 여부 — 이건 호출당하는 함수(callee)에 달렸습니다. callee가 내부에서 진짜 suspension point(다른 actor를
await,Task.sleep, continuation 등)에 닿으면 caller도 그 지점에서 suspend됩니다. 그리고 그 사이 main actor는 다른 main-actor 작업을 실행할 수 있습니다(재진입).
그러므로 "이미 메인이니 무조건 안 멈춘다"는 과장입니다. 정확히는 callee가 아무것도 기다리지 않고 동기적으로 끝날 때만 실제 suspend가 없습니다. await 키워드가 여전히 필요한 이유도 여기에 있습니다 — "여기서 멈추고 재진입이 일어날 수도 있다"는 표시이기 때문입니다.
이 구분을 놓치면 "메인이니까 상태가 안 바뀐다"고 가정한 코드를 쓰게 됩니다. suspend가 일어나면 main actor도 재진입되므로, await 전후로 self의 상태가 바뀌어 있을 수 있습니다(01번 문항과 같은 함정).
@MainActor
final class ViewModel {
var items: [Item] = []
func onTap() async {
await refresh() // 같은 main actor → hop 없음(항상 보장)
// 단, refresh 안에서 진짜 기다리면 여기서 suspend
// → 그 틈에 다른 main-actor 작업이 끼어들 수 있다(재진입)
}
func refresh() async {
// 이 줄까지는 동기로 진행 → 위 await는 아직 실제 suspend가 아니다
let data = await api.load() // ← 진짜 suspension point: 여기서 멈춘다
items = data
}
}답변 ② — 좁게 격리하거나, 그 구간만 메인으로 올린다
백그라운드 작업 중 일부만 메인에서 실행하는 선택지는 셋입니다.
- 좁은
@MainActor표시 — UI를 만지는 함수·프로퍼티에만 붙입니다. 그 함수를 호출하면 자동으로 hop이 일어나므로 호출부는await만 쓰면 됩니다. 가장 선언적이고 권장되는 방법입니다. await MainActor.run { ... }— 타입 전체를 바꾸기 곤란할 때, 그 구간만 감싸 메인에서 실행합니다.DispatchQueue.main.async의 현대적 대체재입니다.- @MainActor 프로퍼티/메서드 접근 — 이미 격리된 대상을 건드리면 컴파일러가 알아서 hop을 삽입합니다.
피해야 할 것은 타입 전체를 통째로 @MainActor로 덮는 것입니다. 무거운 계산까지 메인에서 돌아 화면이 버벅입니다. 원칙은 "무거운 일은 백그라운드, 화면을 실제로 바꾸는 짧은 순간만 메인"입니다.
MainActor.assumeIsolated는 컴파일 타임 보장이 아니라 런타임 어서션입니다. 실제로 메인이 아니면 크래시합니다. 편의로 쓰다 사고가 나므로 명시적 격리를 우선하세요. app 타깃이라면 Swift 6.2의 Default Actor Isolation = MainActor가 더 나은 선택입니다.
func work() async {
let processed = await heavyCompute() // 백그라운드에서 무거운 계산
await MainActor.run { view.update(processed) } // 이 구간만 메인 무대
}
// 또는 — UI 갱신 함수 쪽에 좁게 격리를 선언(더 권장)
@MainActor
func apply(_ data: [Item]) { view.update(data) }
func work2() async {
let processed = await heavyCompute() // 백그라운드
await apply(processed) // 호출 시 자동으로 메인으로 hop
}쉽게 이해하기
@MainActor는 "이 일은 꼭 메인 무대 위에서 해"라는 뜻이에요.
(질문 ①) 내가 이미 메인 무대에 서 있으면, 다른 일을 부를 때 무대를 옮길 필요가 없어요. 그게 "hop이 없다"는 말입니다. 하지만 그 일이 중간에 "택배 올 때까지 기다려" 같은 걸 만나면, 그동안 나는 잠깐 무대를 비웁니다. 그 틈에 다른 배우가 올라올 수 있죠. 그러니 "메인에 있으니 절대 안 멈춘다"고 단정하면 안 되고, 그 일이 뭔가를 기다리느냐에 달렸다고 답해야 맞아요.
(질문 ②) 무거운 일까지 메인 무대에서 하면 무대가 꽉 차서 화면이 버벅여요. 그래서 계산 같은 건 뒷무대(백그라운드)에서 하고, 화면을 실제로 바꾸는 짧은 순간만 잠깐 메인 무대에 올렸다 내려옵니다. 방법은 "이 일은 메인에서만"이라고 그 함수에만 딱지를 붙이거나(@MainActor), 그 구간만 감싸는(MainActor.run) 거예요.
무대를 바꾸는 것(hop)과 잠깐 자리를 비우는 것(suspend)은 다른 얘기다. 같은 무대라도 기다리면 자리는 비워진다.