πŸ› [SwiftUI] @Published λ³€μˆ˜ 값을 비동기λ₯Ό 톡해 λ³€κ²½ μ‹œ 'Publishing changes from background threads is not allowed’ κ²½κ³  λ©”μ‹œμ§€ ν•΄κ²° 방법

μ΄μ„œΒ·2023λ…„ 9μ›” 24일
1

μ‚‘μ‚‘ μ•ˆλ…•ν•˜μ„Έμš” μ΄μ„œμ—μš”πŸ₯Έ. SwiftUI의 @Publishedλ₯Ό μ‚¬μš©ν•˜μ—¬ λ„€νŠΈμ›Œν¬ 톡신을 톡해 λ°›μ•„μ˜¨ κ°’μœΌλ‘œ λ³€κ²½ν•˜λŠ” 도쀑 μ•„λž˜μ™€ 같은 κ²½κ³  λ©”μ‹œμ§€λ₯Ό ν™•μΈν•˜μ—¬ 이λ₯Ό ν•΄κ²°ν•˜λŠ” 방법에 λŒ€ν•΄ ν¬μŠ€νŒ…ν•˜κ³ μž ν•΄μš”.

κ²½κ³  λ©”μ‹œμ§€

Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.

λ°±κ·ΈλΌμš΄λ“œ μŠ€λ ˆλ“œμ—μ„œ λ³€κ²½ 사항을 λ°œν–‰ν•˜λŠ” 것은 ν—ˆμš©λ˜μ§€ μ•Šμ•„μš”. λͺ¨λΈ μ—…λ°μ΄νŠΈμ— λŒ€ν•œ 값을 λ°œν–‰ν•  λ•ŒλŠ” (receive(on:)κ³Ό 같은 μ—°μ‚°μžλ₯Ό 톡해) 메인 μŠ€λ ˆλ“œμ—μ„œ 싀행을 보μž₯ν•˜μ„Έμš”.

μ™œ XcodeλŠ” μœ„μ™€ 같은 κ²½κ³  λ©”μ‹œμ§€λ₯Ό 보여쀀 κ±ΈκΉŒμš”? μ•„λž˜μ—μ„œ κ²½κ³  λ©”μ‹œμ§€κ°€ λ°œμƒν•œ μ½”λ“œλ₯Ό ν•œ 번 보도둝 ν•΄μš”.

κ²½κ³  λ©”μ‹œμ§€ λ°œμƒ μ½”λ“œ

import Foundation

class CoinViewModel: ObservableObject {
    @Published var coin: String = ""
    @Published var price: String = ""
    
    init(coin: String) {
        self.coin = coin
        self.fetchPrice()
    }
    
    func fetchPrice() {
        let urlString: String = "https://api.coingecko.com/api/v3/simple/price?ids=\(self.coin)&vs_currencies=krw"
        guard let url: URL = URL(string: urlString) else { return }
        
        URLSession.shared.dataTask(with: url) { data, response, error in
            guard let data = data else { return }
            guard let jsonObject: [String : Any] = try? JSONSerialization.jsonObject(with: data) as? [String : Any] else { return }
            guard let values = jsonObject[self.coin] as? [String : Int] else { return }
            guard let price = values["krw"] else { return }
            
            // Publishing changes from background threads is not allowed;
            // make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
	          self.price = "β‚©\(price)"
        }.resume()
    }
}

μœ„μ˜ μ½”λ“œλ₯Ό 보면 priceλ₯Ό URLSession을 톡해 κ°€μ Έμ˜¨ data둜 값을 λ³€κ²½ν•΄μ£Όκ³  μžˆμ–΄μš”. 이 λ•Œ Xcodeμ—μ„œλŠ” μœ„μ™€ 같이 κ²½κ³  λ©”μ‹œμ§€λ₯Ό μ•Œλ €μ£Όκ³  μžˆμ–΄μš”.

URLSession은 Apple의 Foundation ν”„λ ˆμž„μ›Œν¬μ—μ„œ μ œκ³΅ν•˜λŠ” 클래슀둜, λ„€νŠΈμ›Œν¬ μš”μ²­μ„ μƒμ„±ν•˜κ³  κ΄€λ¦¬ν•˜λŠ” 데 μ‚¬μš©ν•΄μš”. 비동기 λ„€νŠΈμ›Œν¬ μž‘μ—…μ„ μˆ˜ν–‰ν•˜λŠ” 데 널리 μ‚¬μš©λ˜λ©°, μ›Ή μ„œλ²„μ™€ 데이터λ₯Ό μ£Όκ³ λ°›κΈ° μœ„ν•΄ 주둜 HTTP μš”μ²­μ„ μ‚¬μš©ν•΄μš”. 이λ₯Ό 톡해 앱은 인터넷을 톡해 데이터λ₯Ό κ°€μ Έμ˜€κ±°λ‚˜ μ„œλ²„μ™€ 톡신할 수 μžˆμ–΄μš”.

URLSession의 경우 비동기 처리λ₯Ό ν•˜μ—¬ 메인 μŠ€λ ˆλ“œλ₯Ό μ°¨λ‹¨ν•˜μ§€ μ•Šμ•„μš”. λ”°λΌμ„œ μœ„μ˜ μ½”λ“œμ˜ self.price의 값을 λ³€κ²½ν•΄μ£ΌλŠ” 뢀뢄은 μ•±μ˜ 메인 μŠ€λ ˆλ“œμ—μ„œ μ²˜λ¦¬ν•˜μ§€ μ•Šκ³  λ³„λ„μ˜ μŠ€λ ˆλ“œμ—μ„œ 값을 λ³€κ²½ν•˜κ³  μžˆμ–΄μš”.

ν•΄λ‹Ή κ²½κ³  λ©”μ‹œμ§€λŠ” SwiftUI와 비동기 μ²˜λ¦¬μ—μ„œ ν”νžˆ λ°œμƒν•˜λŠ” 문제둜, λ°±κ·ΈλΌμš΄λ“œ μŠ€λ ˆλ“œμ—μ„œ UIλ₯Ό μ—…λ°μ΄νŠΈν•˜λ €κ³  ν•˜κ±°λ‚˜ λΉ„λ™κΈ°λ‘œ λ°œν–‰λœ κ°’μ—μ„œ UIλ₯Ό λ³€κ²½ν•˜λ €κ³  ν•  λ•Œ λ°œμƒν•˜λŠ” 였λ₯˜μ—μš”. SwiftUIλŠ” 주둜 메인 μŠ€λ ˆλ“œμ—μ„œ μ‚¬μš©ν•˜λ„λ‘ μ„€κ³„λ˜μ—ˆμœΌλ©°, λ°±κ·ΈλΌμš΄λ“œ μŠ€λ ˆλ“œμ—μ„œ UIλ₯Ό μ—…λ°μ΄νŠΈν•˜λ©΄ μ˜ˆμƒμΉ˜ λͺ»ν•œ λ™μž‘ λ˜λŠ” 좩돌이 λ°œμƒν•  수 μžˆμ–΄μš”.

λ”°λΌμ„œ 이 이슈λ₯Ό ν•΄κ²°ν•˜λ €λ©΄ SwiftUI 뷰의 μ—…λ°μ΄νŠΈ, 특히 @Published 속성 λ˜λŠ” Combine 퍼블리셔와 κ΄€λ ¨λœ μ—…λ°μ΄νŠΈλ₯Ό 메인 μŠ€λ ˆλ“œμ—μ„œ μˆ˜ν–‰ν•˜λ„λ‘ 보μž₯ν•΄μ•Ό ν•΄μš”.

DispatchQueue.main.async

DispatchQueue.main.asyncλŠ” GCD(Grand Central Dispatch)의 μΌλΆ€λ‘œ, iOS 및 macOS μ•±μ—μ„œ λΉ„λ™κΈ°μ μœΌλ‘œ μž‘μ—…μ„ 메인 μŠ€λ ˆλ“œμ—μ„œ μ‹€ν–‰ν•˜λ„λ‘ ν•˜λŠ” μ€‘μš”ν•œ νŒ¨ν„΄ 쀑 ν•˜λ‚˜μ—μš”.

메인 μŠ€λ ˆλ“œλŠ” λŒ€λΆ€λΆ„μ˜ UI κ΄€λ ¨ μž‘μ—… 및 μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€ μ—…λ°μ΄νŠΈλ₯Ό μ²˜λ¦¬ν•˜λŠ” μŠ€λ ˆλ“œμ—μš”. λ”°λΌμ„œ 메인 μŠ€λ ˆλ“œμ—μ„œ μ‹€ν–‰λ˜μ–΄μ•Ό ν•˜λŠ” μž‘μ—…μ—λŠ” UI μ—…λ°μ΄νŠΈ, λ·° 컨트둀러의 λ©”μ„œλ“œ 호좜 및 μ‚¬μš©μž 이벀트 μ²˜λ¦¬κ°€ ν¬ν•¨λΌμš”. κ·ΈλŸ¬λ‚˜ λ°±κ·ΈλΌμš΄λ“œ μŠ€λ ˆλ“œμ—μ„œ 비동기 μž‘μ—…μ„ μˆ˜ν–‰ν•˜κ³  λ‚˜μ€‘μ— κ²°κ³Όλ₯Ό 메인 μŠ€λ ˆλ“œλ‘œ 보내야 ν•  λ•Œ DispatchQueue.main.asyncλ₯Ό μ‚¬μš©ν•΄μš”.

DispatchQueue.global().async {
    // λ°±κ·ΈλΌμš΄λ“œ μŠ€λ ˆλ“œμ—μ„œ 비동기 μž‘μ—…μ„ μˆ˜ν–‰
    // ...

    DispatchQueue.main.async {
        // 메인 μŠ€λ ˆλ“œμ—μ„œ μ‹€ν–‰λ˜μ–΄μ•Ό ν•˜λŠ” μž‘μ—…
        // 예: UI μ—…λ°μ΄νŠΈ
    }
}

μœ„ μ½”λ“œμ—μ„œλŠ” DispatchQueue.global().asyncλ₯Ό μ‚¬μš©ν•˜μ—¬ λ°±κ·ΈλΌμš΄λ“œ μŠ€λ ˆλ“œμ—μ„œ 비동기 μž‘μ—…μ„ μˆ˜ν–‰ν•œ ν›„, DispatchQueue.main.async λ‚΄λΆ€μ—μ„œ 메인 μŠ€λ ˆλ“œλ‘œ μ „ν™˜ν•˜μ—¬ 메인 μŠ€λ ˆλ“œμ—μ„œ μ‹€ν–‰λ˜μ–΄μ•Ό ν•˜λŠ” μž‘μ—…μ„ μˆ˜ν–‰ν•΄μš”.

DispatchQueue.main.asyncλ₯Ό μ‚¬μš©ν•˜λ©΄ 메인 μŠ€λ ˆλ“œμ—μ„œ μ‹€ν–‰ν•΄μ•Ό ν•˜λŠ” μ½”λ“œ 블둝을 μ •μ˜ν•˜κ³ , 메인 μŠ€λ ˆλ“œμ—μ„œ λΉ„λ™κΈ°μ μœΌλ‘œ μ‹€ν–‰λ˜λ„λ‘ μ˜ˆμ•½ν•  수 μžˆμ–΄μš”. μ΄λ ‡κ²Œ ν•˜λ©΄ UI μ—…λ°μ΄νŠΈ 및 λ‹€λ₯Έ 메인 μŠ€λ ˆλ“œμ—μ„œ μˆ˜ν–‰ν•΄μ•Ό ν•˜λŠ” μž‘μ—…μ„ μ•ˆμ „ν•˜κ²Œ μ²˜λ¦¬ν•  수 있으며, μ•±μ˜ 응닡성을 μœ μ§€ν•  수 μžˆμ–΄μš”.

ν•΄κ²° 방법

λ”°λΌμ„œ μš°λ¦¬λŠ” URLSession이 비동기 처리λ₯Ό μ™„λ£Œν•˜κ³  UIλ₯Ό λ³€κ²½ν•  수 μžˆλ„λ‘ DispatchQueue.main.asyncλ₯Ό 톡해 ν•΄λ‹Ή κ°’μ˜ 변경을 메인 μŠ€λ ˆλ“œμ—μ„œ μ²˜λ¦¬ν•  수 μžˆμ–΄μš”.

func fetchPrice() {
    let urlString: String = "https://api.coingecko.com/api/v3/simple/price?ids=\(self.coin)&vs_currencies=krw"
    guard let url: URL = URL(string: urlString) else { return }
    
    URLSession.shared.dataTask(with: url) { data, response, error in
        guard let data = data else { return }
        guard let jsonObject: [String : Any] = try? JSONSerialization.jsonObject(with: data) as? [String : Any] else { return }
        guard let values = jsonObject[self.coin] as? [String : Int] else { return }
        guard let price = values["krw"] else { return }
        
	      // 메인 μŠ€λ ˆλ“œμ—μ„œ 값을 λ³€κ²½ν•˜λ„λ‘ ν–ˆμ–΄μš”.
        DispatchQueue.main.async {
            self.price = "β‚©\(price)"
        }
    }.resume()
}

SwiftUI의 UI μ—…λ°μ΄νŠΈμ™€ κ΄€λ ¨ν•œ μΈμ‚¬μ΄νŠΈκ°€ λΆ€μ‘±ν•΄μ„œ 생긴 μ΄μŠˆμ˜€μ–΄μš”. 이번 νŠΈλŸ¬λΈ” μŠˆνŒ…μ„ 톡해 또 ν•œλ²ˆ μ„±μž₯ν–ˆλ‹€κ³  μƒκ°ν•΄μš”πŸ˜Ž

profile
πŸŽοΈπŸ’¨ Beep Beep

0개의 λŒ“κΈ€