Sitemap

[Swift] Protocol과 격리 컨텍스트 알아보기

feat. Protocol에 @MainActor를 붙이면 무슨 일이?

11 min readSep 5, 2025

--

들어가기 전에

개-하! 아래와 같은 코드에서 어떤 에러가 발생할까요? 뇌파일러 ㄱㄱ

@MainActor
var mainActorValue = 1

protocol MyProtocol {}
extension MyProtocol {
func getValue() -> Int {
return mainActorValue
}
}

바로 MainActor context 에 격리된 함수를 nonisolated context 에서 접근하려고 한다는 에러가 발생합니다

그럼 protocol 자체를 @MainActor protocol 로 만들자! 라는 생각을 하게 되고...

@MainActor // 📍 MainActor 로 마킹
protocol MyProtocol { }
extension MyProtocol {
func getValue() -> Int {
return mainActorValue
}
}

이러면 에러 없이 동작하죠? ㅎㅎ

😳 아니 잠깐 잠깐;;; @MainActor protocol 이 정확하게 의미하는 바가 뭔데?? 무조건 MainActor context 에서만 conform 해야한다.. 이런거야??

🙂‍↕️ 그게 제가 처음에 생각하고 있던 거였고요? 그래서 아래처럼 MainActor 가 아닌 타입에서 conform 하면 에러나는줄 알았는데요? ㅎ

class MyClass: MyProtocol {}

하지만 에러 안나죠?ㅋ 너무 잘 동작하죠?

class MyClass: MyProtocol {
func test() {
let value = getValue()
}
}

위 코드에 대한 제 의식의 흐름은 이랬답니다?

  1. class MyClass 자체는 nonisolated class 니까
  2. 인스턴스 함수인 func test() 도 nonisolated func 일테고
  3. 그럼 여기서 MainActor protocol 함수인 getValue() 를 호출하면
  4. context 안맞아서 에러나야하는거 아님??? (nonisolated -> MainActor)
  5. 왜 잘 동작하는데?? 그럼 도대체 이 프로토콜에 @MainActor 붙은 의미가 뭔데?

Protocol과 격리 컨텍스트가 어떻게 동작하는데요?

여러분 그거 아셈여? 아까 코드를 볼때 의식의 흐름이

  1. class MyClass 자체는 nonisolated class 니까

부터 시작했잖아요?

하지만 여기서부터 틀렸다는 사실 ㅋ

만약 타입 자체가 nonisolated 라면, protocol 을 conform 하는 순간! 해당 타입도 protocol 과 같은 격리 context 를 가지게 됩니다.

MyProtocol 이 MainActor protocol 이라면, 이를 conform 하는 MyClass 도 MainActor class 가 되어버리는거져 ㅎ

@MainActor 
protocol MyProtocol {} // 🎨 MainActor isolated protocol

class MyClass: MyProtocol {} // 🎨 MainActor isolated class

자동으로 해당 class 안에 있는 함수도 MainActor context 되고, MainActor protocol 의 getValue() 함수까지 무리 없이 호출 가능하게 됩니다

class MyClass: MyProtocol { // 🎨 MainActor isolated func
func test() { // 🎨 MainActor isolated func
let value = getValue() // 🎨 MainActor isolated func
}
}

ㅇㅋ 여기까지 이해됐음…. 그럼 이제부터 @MyCustomActor 까지 만들어서 여러가지 케이스 테스트를 해볼건데요?

@globalActor
public struct MyCustomActor {
public actor ActorType { }
public static let shared: ActorType = ActorType()
}

이때 각 코드에서 어떤 격리 context 를 가지고 있는지 주석으로 같이 적어두겠습니다. 이때 각 이모지가 의미하는바는 다음과 같습니다

  • 🎨 — MainActor context (UI 랑 관련있다는 의미에서 그림 이모지)
  • 🏝️ — MyCustomActor context (actor 는 동떨어져있다는 의미에서 섬 이모지)
  • 🕊️ — nonisolated context (자유롭다는 의미에서 새 이모지)

Case 1.

타입의 extension 에서 protocol 을 conform 하면, 타입이 nonisolated 더라도, 타입 자체를 MainActor 로 만들어주지 않습니다. 코드로 보져 ㄱㄱ

@MainActor protocol MyProtocol {} // 🎨 MainActor isolated protocol

class MyClass { // 🕊️ nonisolated class
func test() { // 🕊️ nonisolated func
let value = getValue() // 🎨 MainActor isolated func -> ❌ 에러
}
}

// extension 에서 protocol 을 conform 해도 MyClass 를 MainActor 로 만들지 않음​
extension MyClass: MyProtocol {}

또한 extension 내부의 context 도 MainActor 가 아닙니다. 따라서 MainActor protocol 함수를 호출하기 위해서는 별도의 MainActor context 를 지정해줘야합니다.

@MainActor 
protocol MyProtocol {} // 🎨 MainActor isolated protocol

class MyClass {} // 🕊️ nonisolated class

extension MyClass: MyProtocol { // 🕊️ nonisolated
func extensionFunc() { // 🕊️ nonisolated func
getValue() // 🎨 MainActor isolated func -> ❌ 에러
}
}

Case 2.

MainActor protocol 을 conform 하더라도, 명시적으로 타입을 다른 actor 로 격리하면, 타입은 선언한 context 에 우선적으로 격리됩니다

@MainActor  
protocol MyProtocol {} // 🎨 MainActor isolated protocol

@MyCustomActor
class MyClass: MyProtocol {} // 🏝️ MyCustomActor isolated class

위 코드에서 MyClassMyCustomActor 에 격리됩니다 (명시적인 선언 없을땐 protocol 과 동일한 MainActor 에 격리됐음)

그래서 getValue() 같은 @MainActor protocol 로 격리된 함수를 사용하려면

@MyCustomActor
class MyClass: MyProtocol { // 🏝️ MyCustomActor isolated class
func test() { // 🏝️ MyCustomActor isolated func
let value = getValue() // 🎨 MainActor isolated func
}
}

아래와 같은 에러가 발생합니다 (🏝️ MyCustomActor -> 🎨 MainActor)

❌ Call to main actor-isolated instance method 'getValue()' in a synchronous global actor 'MyCustomActor'-isolated context

따라서 getValue() 라는 MainActor protocol 함수를 호출하는 곳에서도 MainActor context 를 따로 지정해야합니다

@MyCustomActor
class MyClass: MyProtocol { // 🏝️ MyCustomActor isolated class
@MainActor
func test() { // 🎨 MainActor isolated func
let value = getValue() // 🎨 MainActor isolated func
}
}

Case 3.

만약 @MyCustomActor 를 타입의 extension 에 붙이면, 해당 extension 범위만 MyCustomActor 에 격리됩니다

class MyClass: MyProtocol { // 🎨 MainActor isolated class
func test() {} // 🎨 MainActor isoltaed func
}
extension MyClass { // 🎨 MainActor isolated
func extensionFunc() {} // 🎨 MainActor isoltaed func
}

@MyCustomActor
extension MyClass { // 🏝️ MyCustomActor isolated
func actorExtensionFunc() {} // 🏝️ MyCustomActor isolated func
}

동일한 맥락으로 만약 @MyCustomActor 를 protocol 의 extension 에 붙이면, 해당 extension 범위만 custom actor 에 격리되기 때문에

@MainActor
protocol MyProtocol {} // 🎨 MainActor isoltaed

@MyCustomActor
extension MyProtocol {} // 🏝️ MyCustomActor isolated

protocol 자체가 MainActor 더라도, 별도의 custom actor 로격리된 protocol extension 함수에서 MainActor 값을 접근하려고 하면 에러가 발생합니다

@MainActor
protocol MyProtocol {} // 🎨 MainActor isoltaed

@MyCustomActor
extension MyProtocol { // 🏝️ MyCustomActor isolated func
func getValue() -> Int { // 🏝️ MyCustomActor isolated func
return mainActorValue // 🎨 MainActor isoltaed value -> ❌ 에러
}
}

만약 extension 에 아무런 actor 도 지정해주지 않으면, 원래 protocol 의 격리 context 를 따릅니다

@MainActor
protocol MyProtocol {} // 🎨 MainActor isoltaed
extension MyProtocol {} // 🎨 MainActor isoltaed

Case 4.

위와 동일한 맥락으로 protocol 의 extension 에만 actor 가 지정된 경우, protocol 자체는 nonisolated 입니다.

protocol MyProtocol {} // 🕊️ nonisolated protocol

@MainActor
extension MyProtocol { // 🎨 MainActor isoltaed
func getValue() -> Int { // 🎨 MainActor isoltaed func
return mainActorValue // 🎨 MainActor isoltaed value
}
}

따라서 nonisolated 타입에서 해당 protocol 을 conform 하더라도, MainActor 타입이 되는것이 아니라 여전히 nonisolated 타입으로 남아있습니다

따라서 MainActor 에 격리된 protocol extension 함수를 사용하려면 에러가 발생합니다

class MyClass: MyProtocol { // 🕊️ nonisolated class
func test() { // 🕊️ nonisolated func
getValue() // 🎨 MainActor isoltaed func
}
}

마무리

오키 이번 글은 여기까지!

다 쓰고나니 protocol conformance 의 격리 규칙은 class inheritance 의 격리 규칙과 비슷하게 동작한다… 같은데 애초에 왜 이상하게 생각했던건지 모르겠네여?

이번 내용은 이것저것 테스트하면서 알아본거라서.. 직접적으로 관련된 문서 있으면 좀 찾고 싶은데…. 알려주시면 감사합니다!?

Press enter or click to view image in full size
이 정도 밖에 못찾음..

그럼 20000!

참고

--

--

No responses yet