Reputation: 59
I am trying to download data from a school course catalog site. I have 64 Urls in the variable UrlBook. I have successfully written code to download a collection of courses and turn them into a single subject object from a single url using completion handler method. I don't really know how should I implement to collect all the subjects from 64 url and eventually turn them into a catalog object (it contains a list of subject objects).
I have read many articles and posts on asynchronous and synchronous processing, it's just so confusing to me. I would really appreciate easy and straight forward code to help me solve this problem. Thank you guys!
let urlBook = getUrlFromBook()
func fetchClassInfo(url:URL,completion: @escaping ([clase])-> Void){
let task = URLSession.shared.dataTask(with: url){(data, response, error) in
let jsonDecoder = JSONDecoder()
if let data = data,
let collection:[clase] = try? jsonDecoder.decode([clase].self, from: data){
completion(collection)
}else{
print("Either no data was returned, or data was not properly decoded.")
//completion(nil)
}
}
task.resume()
}
fetchClassInfo(url:urlBook.book[0]){(clase) in
let newSubject = makeNewSubject(subjectIndex: 0, collectionOfCourse: clase)
var masterCatalog = catalog(subjectCollection: [])
masterCatalog.addSubject(newSubject: newSubject)
}
Upvotes: 3
Views: 4670
Reputation: 1852
You can basically create a logic like below. This functions takes a list of url and return a list of Subject in completion. You can modify the models etc, as you need. In this function; DispatchGroup
is to wait all requests to be completed before calling completion
and DispatchQueue
is to prevent "data race" when appending subjects into array.
func downloadUrls(urls: [URL], completion: @escaping ([Subject]) -> Void) {
var subjectCollection: [Subject] = []
let urlDownloadQueue = DispatchQueue(label: "com.urlDownloader.urlqueue")
let urlDownloadGroup = DispatchGroup()
urls.forEach { (url) in
urlDownloadGroup.enter()
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
guard let data = data,
let subject = try? JSONDecoder().decode(Subject.self, from: data) else {
// handle error
urlDownloadQueue.async {
urlDownloadGroup.leave()
}
return
}
urlDownloadQueue.async {
subjectCollection.append(subject)
urlDownloadGroup.leave()
}
}).resume()
}
urlDownloadGroup.notify(queue: DispatchQueue.global()) {
completion(subjectCollection)
}
}
Upvotes: 4
Reputation: 333
This is a function that downloads all urls concurrently:
func downloadAllUrls(urls: [String]){
let dispatchGroup = DispatchGroup()
for url in urls {
dispatchGroup.enter()
// Here is where you have to do your get call to server
// and when finished call dispatchGroup.leave()
}
dispatchGroup.notify(queue: .main) {
// Do what ever you want after all calls are finished
}
}
it uses DispatchQueue to notify when all requests are finished and then you can do what ever you want with them. The Server could be your custom async call to the network. Mine is like that.
Upvotes: 2
Reputation: 13758
I would suggest to look on Promises approach since it has solutions for asynchronous tasks from the box. There are many implementations of Promises on swift. Even more you can try to use SDK's Combine framework but it is a little bit complex and works from iOS 13 only.
For instance there is a sample with my PromiseQ swift package:
Promise.all( paths.map { fetch($0) } ) // Download all paths concurrently
.then { dataArray in
// Parse all data to array of results
let results:[YourClass] = dataArray.compactMap { try? JSONDecoder().decode(YourClass.self, from: $0) }
}
Where:
paths:[String]
- array with http paths to download.fetch(_ path: String) -> Promise<Data>
- a special function to download data by a path that returns a promise.YourClass
- your class to parse.Upvotes: 1