[Swift 5.7] Actor 관련 warning들 원인 파악해보기
Xcode 14.2로 올리고 Actor관련 warning 3종류를 보게 되었다.
Swift 5.7에 대한 변경사항때문인지는 확실하지는 않지만;; 일단 그런것으로 생각하기로함
일단 내가 파악해본것을 써보려고 한다.
1. Actor-isolated instance method 'testMethod()' can not be referenced from a non-isolated context; this is an error in Swift 6
2. Actor-isolated property 'name' can not be mutated from a non-isolated context; this is an error in Swift 6
3. Cannot access property 'disposeBag' here in non-isolated initializer; this is an error in Swift 6
1. Actor-isolated instance method 'testMethod()' can not be referenced from a non-isolated context; this is an error in Swift 6
actor Person {
init() {
testMethod() // ⚠️ Actor-isolated instance method 'testMethod()' can not be referenced from a non-isolated context; this is an error in Swift 6
}
func testMethod() {}
}
나는 actor의 프로퍼티, 인스턴스 메소드는 전부 isolation하니 initializer도 그런 줄 알았는데, 아닌 것 같다.
testMethod -> isolation
initializer -> non-isolation
non-isolation한 곳에서 isolation한 메소드를 호출하는것은 암시적으로 비동기로 호출된다고 한다.
Calls to instance method 'testMethod()' from outside of its actor context are implicitly asynchronous
해결방법 1 - init을 async하게 만든다. (비동기로 호출되고 있다고 했으니까)
actor Person {
init() async {
testMethod()
}
func testMethod() {}
}
해결방법 2 - Task & await 조합
Task로 묶어주고..
init() {
Task {
self.testMethod()
}
}
원래 Task로 묶어줘도 async가 아닌 메소드는 await을 안써도 되는 것은 알고있을것이다.
testMethod는 async메소드가 아니지만.. 암시적으로 비동기로 호출되고 있기 때문에 await을 붙혀주지 않으면 컴파일 에러가 난다.
최종!
actor Person {
init() {
Task {
await self.testMethod()
}
}
func testMethod() {}
}
2. Actor-isolated property 'name' can not be mutated from a non-isolated context; this is an error in Swift 6
actor Person {
var name: String = ""
let disposeBag = DisposeBag()
init() {
Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.asyncInstance)
.subscribe(onNext: { _ in
self.name = "Zedd" // ⚠️ Actor-isolated property 'name' can not be mutated from a non-isolated context; this is an error in Swift 6
}).disposed(by: disposeBag)
}
}
대충 이런 컨텍스트에서 warning이 나고있는데, 설명으로 아래와 같이 나오고 있었다.
Mutation of this property is only permitted within the actor
이 프로퍼티의 변경은 actor 내에서만 허용된다..
처음에는 actor안에서 변경중인데 왜이래;; 하면서 너무 헷갈렸는데,
저렇게 비동기 컨텍스트를 가지면서 저게 actor바깥에서 실행되기 때문인 것 같다.
actor Person {
var name: String = ""
init() {
Task {
self.name = "zedd" // ⛔️ error! Actor-isolated property 'name' can not be mutated from a non-isolated context
}
}
}
Task로 하면 warning이 아니라 error가 뜬다. RxSwift로 짠 코드도 Swift 6가면 에러가 된다고 하니.. 미리미리 수정해두면 좋을 것 같다.
당연히 위의 Task, RxSwift 코드를 actor의 인스턴스 메소드에서 실행하면 아무 warning도 뜨지 않는데, initializer안에서 해서 그런 것 같다.
해결방법
굳이 initializer에서 name을 바꿔야한다면
actor Person {
var name: String = ""
let disposeBag = DisposeBag()
init() {
Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.asyncInstance)
.subscribe(onNext: { _ in
Task {
await self.setName("Zedd")
}
}).disposed(by: disposeBag)
}
func setName(_ name: String) async {
self.name = name
}
}
initializer내부에서 수정하는게 아니라 name만 처리하는 인스턴스 메소드 하나 만들어서 처리하기
그게 아니라면 인스턴스 메소드로 하나 빼고 1번의 해결방법으로 수정하기
actor Person {
var name: String = ""
let disposeBag = DisposeBag()
init() {
Task {
await bind()
}
}
func bind() {
Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.asyncInstance)
.subscribe(onNext: { _ in
self.name = "Zedd"
}).disposed(by: disposeBag)
}
}
3. Cannot access property 'disposeBag' here in non-isolated initializer; this is an error in Swift 6
actor Person {
var name: String = ""
let disposeBag = DisposeBag()
init() {
Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.asyncInstance)
.subscribe(onNext: { _ in
self.name = "Zedd"
}).disposed(by: disposeBag) // ⚠️ Cannot access property 'disposeBag' here in non-isolated initializer; this is an error in Swift 6
}
}
2번과 똑같은 예제에서 disposeBag부분에서 warning이 뜨고 있었다.
자세한 설명을 보면
After making a copy of 'self', only non-isolated properties of 'self' can be accessed from this init
'self'의 복사본을 만든 후에는 이 initializer에서(== non-isolated initializer) 'self'의 non-isolated 프로퍼티에만 액세스할 수 있다고 나온다.
subscribe부분에서 self가 캡쳐되어 self의 복사본이 만들어진 후에 actor의 isolated 프로퍼티에 접근하는것이 안되는 것 같다?..
nonisolated 키워드를 붙히고 싶은 욕구가 들지만 computed와 메소드에만 붙힐 수 있다. 즉 date race를 일으키지 않는것들만 붙힐 수 있고,
var같은 mutable property는 non-isolated가 되면 data race를 일으킬 수 있기 때문에 붙힐 수 없다.
위 RxSwift 예제 말고 좀 더 쉽게 보면..
actor Person {
var name: String = ""
init() {
Task {
await self.testMethod()
}
self.name = "Zedd" // ⚠️ Cannot access property 'name' here in non-isolated initializer; this is an error in Swift 6
}
func testMethod() {}
}
Task내부에서 self가 복사된 후에 name에 접근하는 이런 흐름..
결국 initializer가 non-isolated하기 때문에 이런 일들이 일어나는 것 같다.
아무튼 3번문제도 2번의 해결방법으로 자연스럽게 해결됨.
SE-0327을 이해하는데 너무 어려웠어서 내가 우아하게 해결한건지는 잘 모르겠는데..
(status는 Accepted이나 Xcode 14 Release Notes를 보면 SE-0327에 대한 내용이 일부 구현이 된 것 같다.)
내가 틀린 말을 했거나;; 더 좋은 방법이 있다면 댓글 부탁드립니다.