티스토리 뷰

Swift

Swift - nonisolated 란

DevDiana 2026. 6. 30. 16:51

nonisolated 란?

nonisolated는 이 멤버는 actor의 격리(isolation)에서 빼달라 라는 선언입니다. Actor의 멤버는 기본적으로 격리되어 있으며 await로만 접근할 수 있는데, nonisolated를 붙이면 그 격리를 풀어 어디서나 동기적으로 호출할 수 있게 됩니다.

 

Actor는 자신의 가변상태(mutable state)를 데이터 레이스로부터 보호하기 위해 모든 멤버를 격리합니다. 그래서 Actor 밖에서 메서드나 프로퍼티에 접근하려고 하면 await가 필요하죠. 하지만 어떤 멤버는 Actor의 가변 상태를 전혀 건드리지 않았는데도 이 규칙에 묶여 불편할 때도 있습니다. 이때 우리가 사용하는게 nonisolated 입니다.


nonisolated 멤버는 Actor의 격리된 가변 상태에 접근할 수 없습니다. 접근 가능한건 불변(let) 프로퍼티, 다른 nonisolated 멤버, 그리고 자기 자신의 파라미터 뿐입니다. 이 제약 덕분에 await 없이 호출해도 data race가 발생하지 않는다는게 보장됩니다.

actor BankAccount {
    let id: UUID            // 불변(let)
    var balance: Decimal    // 격리된 가변 상태

    nonisolated var label: String {
        "Account \(id)"     // OK: id는 let으로 불변임
    }

    // nonisolated var current: Decimal { balance }  // 가변 상태이므로 nonisolated가 불가능
    // 격리된 가변 상태(balance)에는 접근할 수 없음
}

let account = BankAccount(id: UUID(), balance: 100)
print(account.label)   // await 없이 호출 가능

 

실무에서는 Hashable, Equatable, CustomStringConvertible, Identifiable, Codable 같은 프로토콜의 요구사항은 동기적이고 nonisolated 시그니처를 가집니다.

// Equatable
static func == (lhs: Self, rhs: Self) -> Bool   // 동기

// Hashable
func hash(into hasher: inout Hasher)            // 동기

// CustomStringConvertible
var description: String { get }                 // 동기

 

전부 await 없이 동기적으로 호출되도록 정의되어 있습니다. 여기서 충돌이 발생합니다.

Actor 멤버는 기본적으로 격리되며 호출하기 위해서는 await를 필요로 합니다. 하지만 위 프로토콜들은 동기 시그니처를 띄고 있기 때문에 await 를 붙일 수 없죠.

 

Actor가 이런 프로토콜을 준수하려면 해당 멤버를 nonisolated로 만들필요가 있습니다.

actor Player: CustomStringConvertible, Hashable {
    let id: UUID
    var score: Int

    nonisolated var description: String { "Player(\(id))" }

    nonisolated func hash(into hasher: inout Hasher) {
        hasher.combine(id)   // 불변 id만 사용
    }

    nonisolated static func == (lhs: Player, rhs: Player) -> Bool {
        lhs.id == rhs.id
    }
}

 

이때 주의해야 할 점은 nonisolated 멤버는 격리를 포기한 대신 가변 격리 상태(위의 코드에서는 var name)에 접근할 수 없습니다. 

nonisolated var description: String {
    "User(\(name))"   // 에러: 격리된 var name에 동기 접근 불가
}

 

✅ nonisolated(unsafe)

nonisolated(unsafe)는 Swift 5.10 에서 추가된 escape hatch 입니다. 주로 전역변수나 static 저장 프로퍼티에서 Data race 검사를 끄고 동기화를 스스로 책임지겠다고 선언할 때 사용합니다.

nonisolated(unsafe) var legacyCache: [String: Data] = [:]

 

주로 레거시 코드를 마이그레이션 하거나 직렬 큐 등으로 접근을 직접 보장하는 경우에 쓰입니다.
이름 그대로 unsafe이며 안전성을 개발자가 직접 떠안는 방식이기 때문에 남용에 주의해야 하죠.

 

nonisolated(unsafe)는 일반 nonisolated과 다릅니다.

일반 은 컴파일러가 멤버의 안전함을 검증해주는 반명 unsafe는 컴파일러가 검사를 포기하죠.

 

전역변수나 static 저장 프로퍼티에서 주로 unsafe를 사용해야하는 상황이 발생합니다. 이 둘은 앱 어디서든, 어느 스레드에서든 접근할 수 있기 때문에 컴파일러 입장에선 막아버리기 때문이죠.

 

하지만 기존에 잘 돌아가던 수십만 줄의 코드가 Swift6으로 옮기는데 변수 하나 때문에 컴파일이 안되면 안되니까 unsafe로 막아두고 나중에 고치자 하고 넘어가는 임시 방편인 것입니다.

 

또는 스스로 직렬 큐 등을 사용해 접근을 이미 직렬화 해놨을 때 컴파일러는 이 사실을 알지 못합니다.

이 경우 내가 보장하니 검사를 꺼달라는 식으로 사용도 가능합니다.

 

오늘은 이렇게 nonisolated에 대해 알아보았습니다.

 

감사합니다~

 

'Swift' 카테고리의 다른 글

Swift - WebRTC란?  (0) 2026.07.19
Swift - API 호출 실패 시 재시도  (0) 2026.07.07
Swift - 캐시 지역성(Cache Locality)이란  (0) 2026.06.29
Swift - Structured Concurrency(구조적 동시성)  (0) 2026.06.25
Swift - Swift Concurrency  (0) 2026.06.23
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/08   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
글 보관함