Search code examples
swiftswiftuiasync-awaittask

How to solve this issue 'Actor-isolated property 'channels' can not be referenced from the main actor'


I wrote an actor that allows me to download and parse some data online, the function downloads the data and append the result into channels array.


actor BackgroundParser : ObservableObject {
   
    @Published var channels : [String: [PlaylistItem]] = [:]
    
    let parser = PlaylistParser()
 
    
   
    func fetchOnMemory(lista: PlayListModel,url: String ,_ completion: @escaping (_ isdownloading:Bool) -> Void) async  {
        guard let m3uURL = URL(string: url) else {return}
        completion(true)
        var counter = 0
        
        AF.request(m3uURL).responseString { response in
            switch response.result {
            case .success(let m3uContent):
                do{
                    let playlist = try self.parser.parse(m3uContent)
                    
                    let media = playlist.medias
                    print("QUESTA LISTA CONTIENE \(media.count)")
                    for item in media {
                        let  duration = item.duration
                        let attributes = item.attributes
                        let tvgId = attributes.id
                        let tvgName = attributes.name
                        let tgvcountry = attributes.country
                        let tvglanguage = attributes.language
                        let tvgLogo = attributes.logo
                        let tvgchno = attributes.channelNumber
                        let tvgShift = attributes.shift
                        let groupTitle = attributes.groupTitle
                        let seasonNumber = attributes.seasonNumber
                        let episodeNumber = attributes.episodeNumber
                        let kind = item.kind.rawValue
                        let url = item.url
                        let def : String = "XXX"
                        print("\(counter) of \(media.count)-- id: \(tvgId ?? def) -- name: \(tvgName ?? def) -Title: \(groupTitle ?? def)")
                        let playlistItem = PlaylistItem(duration: duration, tvgId: tvgId, tvgName: tvgName, tvgcountry: tgvcountry, tvglanguage: tvglanguage, tvgLogo: tvgLogo, tvgchno: tvgchno, tvgshift: tvgShift, groupTitle: groupTitle ?? "NOT CLASSIFIED", seasonNumber: seasonNumber, episodeNumber: episodeNumber, kind: kind, url: url)
                        
                        
                        self.updateChannelList(with: playlistItem)
                        
                        //                            print("parse-------\(counter)--\(String(describing: tvgName))")
                        counter += 1
                    }
                    
                    completion(false)
                    
                    
                } catch {
                    completion(true)
                    print("FAIL CATCH")
                }
            case .failure(let error):
                completion(true)
                print("Failed to fetch m3u content: \(error)")
            }
        }
    }
    
    
   
  
    
    func updateChannelList(with playlistItem: PlaylistItem) {
        // Assuming listofChannel is a class-level or instance-level variable
        // and you want to modify it in-place

        // Check if the groupTitle already exists in the dictionary
        if var existingPlaylist = channels[playlistItem.groupTitle] {
            // If it exists, append the new playlist item
            existingPlaylist.append(playlistItem)
            channels[playlistItem.groupTitle] = existingPlaylist
        } else {
            // If it doesn't exist, create a new key with an array containing the playlist item
            channels[playlistItem.groupTitle] = [playlistItem]
        }
    }
}


im try now to display the data in a view but getting the error 'Actor-isolated property 'channels' can not be referenced from the main actor'.

struct ListViewDetails: View {
   
   
  
    @State var isLoading = true
    @StateObject var parser = BackgroundParser()
    let columns = [
        GridItem(.fixed(300)),GridItem(.fixed(300)),GridItem(.fixed(300)),GridItem(.fixed(300)),GridItem(.fixed(300)),GridItem(.fixed(300)),
       ]
    
    var body: some View {
        
        
        if isLoading {
            ProgressView("Fetching Data...")
                .progressViewStyle(CircularProgressViewStyle())
        }
            ScrollView(.vertical){
                LazyVGrid(columns: columns,alignment: .center,spacing: 20) {
                  
                    ForEach(parser.channels.sorted(by: { $0.key < $1.key }), id: \.key)  { group , channels in
                        DetailsChannelGroup(group:  group, channels: channels)
                           
                    }
                }
            }
            
            .task {
               
            
                await parser.fetchOnMemory(lista: item, url: item.playlistUrl) { isdownloading in
                    
                    isLoading = isdownloading
                    
                }

                
            }

}

}

the error comes in for each loop because I try to access the published array.

how can i get that data?


Solution

  • It would be better if your func fetchOnMemory async returned a result that you can set on a @State then you can just remove the @StateObject which is what .task is designed to let you do.

    @State var channels : [String: [PlaylistItem]] = [:]
    @State var isDownloading = false
    let playlistModel: PlayListModel
    ...
     .task(id: playlistModel.id) {
        if channels == [:] { return } // exit early if already downloaded
        isDownloading = true
        let parser = BackgroundParser()
        channels = await parser.fetchOnMemory(list: playlistModel, url: playlistModel.url)
        isDownloading = false
    }
    
    struct BackgroundParser {
       
        func fetchOnMemory(lista: PlayListModel,url: String) async -> [String: [PlaylistItem]]  {