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?
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]] {