네트워크 요청을 통해 받은 response 결과를 CoreData에 저장시키려 했는데 다음과 같은 런타임 에러와 로그가 발생했다.


코드는 다음과 같았다.
AF.upload(multipartFormData: someMultipartFormData, to: requestURL, method: .post)
.responseString(queue: customQueue) { [weak self] response in
guard let self else { return }
switch response.result {
case .success(let result):
self.CoreData저장메소드()
case .failure(let error):
(...에러처리...)
}
}
에러의 원인은 CoreData저장메소드가 백그라운드 쓰레드 큐에서 실행되었기 때문이다.
코드를 보면, 나는 네트워크 요청을 customQueue를 통해 작업하도록 하였고, 해당 큐는 백그라운드 큐였다. 그리고 CoreData저장메소드()는 아래와 같이 viewContext를 통해 엔티티를 저장시키는 로직을 갖고 있었다.
func CoreData저장메소드() {
...
persistentContainer.viewContext.save()
}
공식 문서에 따르면,viewContext는 mainQueueConcurrencyType로 정의되어 이며, mainQueue에서만 사용할 수 있다고 나와있다.
하지만 나는 네트워크 요청에 대한 결과값을 전달받는 클로저 내에서 사용했기 때문에 mainQueue(즉 main 쓰레드)가 아닌 customQueue에서 viewContext가 실행되었던 것이다.
따라서 다음과 같이 CoreData저장메소드()를 mainQueue에서 실행할 수 있도록 해 에러를 해결하였다.
AF.upload(multipartFormData: someMultipartFormData, to: requestURL, method: .post)
.responseString(queue: customQueue) { [weak self] response in
guard let self else { return }
switch response.result {
case .success(let result):
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.CoreData저장메소드()
}
case .failure(let error):
(...에러처리...)
}
}
네트워크 요청 응답에 따라 CoreData에 저장시키는 로직을 mainQueue에 async로 보내더라도 동기화에 문제되는 부분은 없어 위와 같이 임시로 해결하였다.
하지만, 객체지향 관점에서 '네트워크 요청객체'가 '데이터 관리객체(CoreData)'의 메소드가 반드시 main쓰레드에서 실행되어야 한다는 것을 모르고 있어야 하지 않나 생각을 한다.
따라서 현재 방법에서 상황에 따라 CoreData를 viewContext, backgroundContext로 관리할 수 있도록 리팩토링을 진행해야 될 것 같다.
[참고링크]
StackOverFlow - Core Data crash attempt to insert nil with userInfo (null)
StackOverFlow - Fetching from background thread with Core Data
Apple Developer Documentation - Using Core Data in the background