Cannot convert return expression of type 'Task<[String], Error>' to return type '[String]' when using Task.init{} - swift

I'm learning how to iterate with async and await in swift.
My current stage is on:
import Foundation
import SwiftUI
import Darwin
enum MyError: Error {
case genError
}
let myString : String = """
https://httpbin.org/anything
https://httpbin.org/ip
https://httpbin.org/user-agent
https://httpbin.org/headers
https://httpbin.org/get
https://httpbin.org/post
https://httpbin.org/put
https://httpbin.org/delete
https://httpbin.org/gzip
https://httpbin.org/status/:code
https://httpbin.org/response-headers?key=val
https://httpbin.org/redirect/:n
https://httpbin.org/relative-redirect/:n
https://httpbin.org/cookies
https://httpbin.org/cookies/set/:name/:value
https://httpbin.org/basic-auth/:user/:passwd
https://httpbin.org/hidden-basic-auth/:user/:passwd
https://httpbin.org/digest-auth/:qop/:user/:passwd
https://httpbin.org/stream/:n
https://httpbin.org/delay/:n
"""
func fetchInfo(for url: String, with index:Int) async throws -> String {
let request = URLRequest(url: URL(string: url)!)
let (data, response) = try await URLSession.shared.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
print("error found\n" + String(index) + "\n" + (url))
throw MyError.genError }
let thisOutput = String(data: data, encoding: .utf8)!
return thisOutput
}
func fetchOnebyOne(urls: String) async throws -> [String] {
var count : Int = 0
var finalArray : [String] = []
for item in myString.components(separatedBy: "\n") {
count += 1
do {
let thisThis: String = try await fetchInfo(for: item, with: count)
finalArray.append(thisThis)
}
catch {
print("\(count) ---------------------------- error found\n\n\n\n")
}
} // : for
return finalArray
}
Task{
let finalOutput = try await fetchOnebyOne(urls: myString)
print(finalOutput)
}
For the fetchOneByOne(), on a webpage, I know I can use async to get the same results, so I rewrite this function:
func fetchOnebyOne(urls: String) {
async {
var count : Int = 0
var finalArray : [String] = []
for item in myString.components(separatedBy: "\n") {
count += 1
do {
let thisThis: String = try await fetchInfo(for: item, with: count)
finalArray.append(thisThis)
}
catch {
print("\(count) ---------------------------- error found\n\n\n\n")
}
} // : for
} //: async
}
fetchOnebyOne(urls: myString)
I'm successful to get the same output. But I got a yellow warning in Xcode, the async should be replaced with Task.init. So I change the async to Task.init. The output still same.
But actually you can see, the fetchOnebyOne() doesn't return a [String] anymore. Because I cannot solve the warnings if I make it return [String]. I tried the below code:
func fetchOnebyOne(urls: String) -> [String] {
Task.init {
var count : Int = 0
var finalArray : [String] = []
for item in myString.components(separatedBy: "\n") {
count += 1
do {
let thisThis: String = try await fetchInfo(for: item, with: count)
finalArray.append(thisThis)
}
catch {
print("\(count) ---------------------------- error found\n\n\n\n")
}
} // : for
return finalArray
} // : Task
}
the warning is:
No 'init' candidates produce the expected contextual result type
'[String]'
and I did some research and change the first line to
func fetchOnebyOne(urls: String) -> [String] {
Task.init {() async throws -> [String] in
var count : Int = 0
var finalArray : [String] = []
for item in myString.components(separatedBy: "\n") {
count += 1
do {
let thisThis: String = try await fetchInfo(for: item, with: count)
finalArray.append(thisThis)
}
catch {
print("\(count) ---------------------------- error found\n\n\n\n")
}
} // : for
return finalArray
} // : Task
}
I got warning:
Cannot convert return expression of type 'Task<[String], Error>' to
return type '[String]'
I stuck here and cannot find useful information about Task.init, especially about the error - 'Task<[String], Error>' on internet.
I did all of this for knowledge, for learning swift. No practical use. Hope people here could help. Thanks.

Related

async let does not compile because of the "Reference to captured var 'imageDescriptors' in concurrently-executing code"

I am trying to use withThrowingTaskGroup to get some data from the web. I found that my code runs a bit slow, so I tried to use actors with async/await. I use Descriptors to get the
func getAllStocksList() async throws -> [SingleStockViewModel] {
var stockViewModels = [SingleStockViewModel]()
let urlString = URLBuilder.getAllStocks.makeString()
let (data, response) = try await URLSession.shared.data(from: URL(string: urlString)!)
if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode != 200 {
throw "Invalid HttpResponseCode"
}
let dataResponse = try JSONDecoder().decode([StockDetails].self, from: data)
let stocksDetailsList = dataResponse[..<25]
let stockSymbolsList = stocksDetailsList.map {
$0.title
}
let dataResponseDict = dataResponse.toDictionary {
$0.title
}
var stockPrices: [String: Double] = [:]
let imageUrlStringsDict = try await imageService.makeStockImageUrlStringsList(for: stockSymbolsList)
let nonEmptyImageUrlStringsList = imageUrlStringsDict.filter {
!$0.value.isEmpty
}
var imageDescriptors = [Descriptor]()
var chartDescriptors = [Descriptor]()
for item in nonEmptyImageUrlStringsList {
imageDescriptors.append(
Descriptor(
stockSymbol: item.key,
stockImageUrlString: nonEmptyImageUrlStringsList[item.key]!,
type: .image)
)
chartDescriptors.append(
Descriptor(stockSymbol: item.key,
stockImageUrlString: "",
type: .marketData)
)
let price = try await fetchStockPrice(for: item.key)
stockPrices[item.key] = price
}
print(chartDescriptors)
async let taskResults = try fetchGroupedStocksInfo(descriptors: imageDescriptors)
async let marketDataTaskResult = try fetchGroupedStocksInfo(descriptors: chartDescriptors)
let (taskResultsDict, marketDataTaskResultDict) = try await (taskResults, marketDataTaskResult)
}
and the stock descriptor is passed to
func fetchGroupedStocksInfo(descriptors: [Descriptor]) async throws -> [String: TaskResult] {
try await withThrowingTaskGroup(of: (String, TaskResult).self, returning: [String: TaskResult].self) { group in
for descriptor in descriptors {
group.addTask { [self] in
switch descriptor.type {
case .image:
let (_, image) = try await self.imageService.makeStockImageTuple(descriptor.stockImageUrlString)
let (symbol, marketResponse) = try await merketInfoSerice.fetchMarketInfo(descriptor.stockSymbol, numberOfDays: 3)
print(image)
return (descriptor.stockSymbol, TaskResult.image(image))
case .marketData:
let (symbol, marketResponse) = try await merketInfoSerice.fetchMarketInfo(descriptor.stockSymbol, numberOfDays: 3)
print(marketResponse.close)
return (symbol, TaskResult.marketData(marketResponse))
}
}
}
return try await group.reduce(into: [:]) {
$0[$1.0] = $1.1
}
}
}
and the errors found in compile time are
(63, 73) Reference to captured var 'imageDescriptors' in concurrently-executing code
(64, 82) Reference to captured var 'chartDescriptors' in concurrently-executing code
How can I make these error disappear, though I sense that my code can cause some problems at runtime.
You received this error:
Reference to captured var 'imageDescriptors' in concurrently-executing code
The issue is that you are supplying a mutable array to async let, an expression that will be await’ed later. As SE-0317 async let bindings says:
The right-hand side of a async let expression can be thought of as an implicit #Sendable closure ... the closure is #Sendable and nonisolated, meaning that it cannot access non-sendable state of the enclosing context.
Bottom line, the problem goes away if you replace it with an immutable array.
You can initialize them to constants directly:
let imageDescriptors = items.map { item in
Descriptor(
stockSymbol: item.key,
stockImageUrlString: nonEmptyImageUrlStringsList[item.key]!,
type: .image
)
}
let chartDescriptors = items.map { item in
Descriptor(
stockSymbol: item.key,
stockImageUrlString: "",
type: .marketData
)
}
You can make your own copy:
let descriptors = imageDescriptors
async let taskResults = try fetchGroupedStocksInfo(descriptors: descriptors)
Instead of async let, you can make your own Task with an explicit capture list:
let subtask1 = Task { [imageDescriptors] in
await self.fetchGroupedStocksInfo(descriptors: imageDescriptors)
}
let subtask2 = Task { [chartDescriptors] in
await try fetchGroupedStocksInfo(descriptors: chartDescriptors)
}
let (taskResultsDict, marketDataTaskResultDict) = try await (subtask1.value, subtask2.value)
NB: This opts out of structured concurrency.

Swift Async let with loop

I want to get data in parallel. I found an example to call API in parallel but I want to store async let variables with loop.
Async let example. However, this example doesn't use a loop.
async let firstPhoto = downloadPhoto(named: photoNames[0])
async let secondPhoto = downloadPhoto(named: photoNames[1])
async let thirdPhoto = downloadPhoto(named: photoNames[2])
let photos = await [firstPhoto, secondPhoto, thirdPhoto]
show(photos)
I want to do something like the following.
let items = photoNames.map({ photo in
async let item = downloadPhoto(named: photo)
return item
})
let photos = await items
show(photos)
You can use a task group. See Tasks and Task Groups section of the The Swift Programming Language: Concurrency (which would appear to be where you got your example).
One can use withTaskGroup(of:returning:body:) to create a task group to run tasks in parallel, but then collate all the results together at the end.
E.g. here is an example that creates child tasks that return a tuple of “name” and ”image”, and the group returns a combined dictionary of those name strings with their associated image values:
func downloadImages(names: [String]) async -> [String: UIImage] {
await withTaskGroup(
of: (String, UIImage).self,
returning: [String: UIImage].self
) { [self] group in
for name in names {
group.addTask { await (name, downloadPhoto(named: name)) }
}
var images: [String: UIImage] = [:]
for await result in group {
images[result.0] = result.1
}
return images
}
}
Or, more concisely:
func downloadImages(names: [String]) async -> [String: UIImage] {
await withTaskGroup(of: (String, UIImage).self) { [self] group in
for name in names {
group.addTask { await (name, downloadPhoto(named: name)) }
}
return await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
}
}
They run in parallel:
But you can extract them from the dictionary of results:
let stooges = ["moe", "larry", "curly"]
let images = await downloadImages(names: stooges)
imageView1.image = images["moe"]
imageView2.image = images["larry"]
imageView3.image = images["curly"]
Or if you want an array sorted in the original order, just build an array from the dictionary:
func downloadImages(names: [String]) async -> [UIImage] {
await withTaskGroup(of: (String, UIImage).self) { [self] group in
for name in names {
group.addTask { await (name, downloadPhoto(named: name)) }
}
let dictionary = await group.reduce(into: [:]) { $0[$1.0] = $1.1 }
return names.compactMap { dictionary[$0] }
}
}
Rob's answer is good. You can use an Array instead of a Dictionary too, to preserve order.
let photos = await photoNames.map(downloadPhoto)
public extension Sequence where Element: Sendable {
func mapWithTaskGroup<Transformed: Sendable>(
priority: TaskPriority? = nil,
_ transform: #escaping #Sendable (Element) async throws -> Transformed
) async rethrows -> [Transformed] {
try await withThrowingTaskGroup(
of: EnumeratedSequence<[Transformed]>.Element.self
) { group in
for (offset, element) in enumerated() {
group.addTask(priority: priority) {
(offset, try await transform(element))
}
}
return try await group.reduce(
into: map { _ in nil } as [Transformed?]
) {
$0[$1.offset] = $1.element
} as! [Transformed]
}
}
}
If the order of result doesn't matter here, use a TaskGroup instead.

Firebase Storage listAll() body not executed

I am new to Firebase and Swift. My previous question was very vague due to a misunderstanding on my part. In a class named "A" for example I am trying to create an object of class "B" that contains the fetchARImageTargets function that I have below. I am trying to assign the array ARImageTargets to a var in class "A" however, the listAll completion is not returned in time, which results in the var being empty. Is there a way that I can edit my function or class to avoid the var being set prematurely?
let ARImageTargetStorageRef = Storage.storage().reference().child("ImageTargets")
self.fetchARImageTargets(ref: ARImageTargetStorageRef)
func fetchARImageTargets(ref: StorageReference) {
ref.listAll { (result, error) in
if let error = error {
print(error)
}
for prefix in result.prefixes {
self.fetchARImageTargets(ref: prefix)
}
for item in result.items {
item.getMetadata { (metadata, error) in
if let error = error {
print(error)
} else {
var imageTarget = ARImageTarget()
item.downloadURL(completion: { (url, error) in
imageTarget.ImageURL = url
})
imageTarget.Id = metadata?.customMetadata?["Id"] as String?
let width = metadata?.customMetadata?["PhysicalWidth"] as String?
imageTarget.PhysicalWidth = CGFloat(truncating: NumberFormatter().number(from: width!)!)
self.ARImageTargets.append(imageTarget)
}
}
}
}
}

How to merge nil cases to Failing case

import MVVMC
import RxSwift
import RxCocoa
import RTVModel
import RTVWebAPI
public class SettingsViewModel: ViewModel {
public var fetchedNotifications: Driver<[NotificationItem]> = .empty()
public var fetchedNotificationsFailed: Driver<String> = .empty()
public var notificationCount: Driver<Int> = .empty()
'''''''''''''''
public var userLoginName: Driver<String> = .empty()
///// userLoginName getting is a optional String.
'''''''''''''''''
public var fetchedUserLoginNameFailed: Driver<String> = .empty()
public func bindNotificationEvents(with trigger: Driver<Void>) {
let webService: Driver<RTVInformationListWebService> = trigger
.map { RTVInformationListParameters() }
.webService()
let result = webService.request()
notificationCount = result.success().map { $0.informationList.maxCount }
fetchedNotifications = result.success()
.map {$0.informationList.notifications}
-------> .map {$0.map {NotificationItem.init(notification: $0)}}
///////////////////////////////////////////////////////////////
Error (Value of optional type 'String?' must be unwrapped to a value of type 'String')
///////////////////////////////////////////////////////////////
fetchedNotificationsFailed = Driver.merge(fetchedNotificationsFailed, result.error().map { $0.message })
}
public func bindUserInfoEvents(with trigger: Driver<Void>) {
let webService: Driver<RTVMobileMenuWebService> = trigger
.map { RTVMobileMenuParameters() }
.webService()
let result = webService.request()
userLoginName = result.success().map { ($0.mobileMenuInfo.username) }
fetchedUserLoginNameFailed = Driver.merge(fetchedUserLoginNameFailed, result.error().map { $0.message })
}
}
extension RTVAPIError {
fileprivate var message: String {
var message = "\(self.localizedDescription)"
if let codeNumber = self.codeNumber {
message += "\n(\(codeNumber))"
}
return message
}
}
This is not really the way you should be using it, since the point of Driver is not to error, but you obviously have an error state, therefore, Observable or Signal would be better.
However, you need to split your signal into successful ones and error ones, something like this:
fetchedNotifications = result.success()
.map {$0.informationList.notifications}
.share(replay: 1)
let success = fetchedNotifications
.filter { $0 != nil }
.map { $0.map { NotificationItem.init(notification: $0) } }
let error = fetchedNotifications
.filter { $0 == nil } // Here would be your "error" state which you can merge later
I might be off with the syntax, I wrote this from memory.
I fixed it by using the catchOnNil
.catchOnNil { return }

How can I do array mapping with objectmapper?

I have a response model that looks like this:
class ResponseModel: Mappable {
var data: T?
var code: Int = 0
required init?(map: Map) {}
func mapping(map: Map) {
data <- map["data"]
code <- map["code"]
}
}
If the json-data is not an array it works:
{"code":0,"data":{"id":"2","name":"XXX"}}
but if it is an array, it does not work
{"code":0,"data":[{"id":"2","name":"XXX"},{"id":"3","name":"YYY"}]}
My mapping code;
let apiResponse = Mapper<ResponseModel>().map(JSONObject: response.result.value)
For details;
I tried this code using this article : http://oramind.com/rest-client-in-swift-with-promises/
you need to use mapArray method instead of map :
let apiResponse = Mapper<ResponseModel>().mapArray(JSONObject: response.result.value)
What I do is something like this:
func mapping(map: Map) {
if let _ = try? map.value("data") as [Data] {
dataArray <- map["data"]
} else {
data <- map["data"]
}
code <- map["code"]
}
where:
var data: T?
var dataArray: [T]?
var code: Int = 0
The problem with this is that you need to check both data and dataArray for nil values.
You need to change your declaration of data to an array, since that's how it is in the JSON:
var data: [T]?
let apiResponse = Mapper<ResponseModel>().mapArray(JSONObject: response.result.value)
works for me
Anyone using SwiftyJSON and if you want an object from JSON directly without having a parent class, for example, you want the "data" from it. You can do something like this,
if let data = response.result.value {
let json = JSON(data)
let dataResponse = json["data"].object
let responseObject = Mapper<DataClassName>().mapArray(JSONObject: dataResponse)
}
This will return you [DataClassName]? as response.
Based on Abrahanfer's answer. I share my solution. I wrote a BaseResult for Alamofire.
class BaseResult<T: Mappable> : Mappable {
var Result : Bool = false
var Error : ErrorResult?
var Index : Int = 0
var Size : Int = 0
var Count : Int = 0
var Data : T?
var DataArray: [T]?
required init?(map: Map){
}
func mapping(map: Map) {
Result <- map["Result"]
Error <- map["Error"]
Index <- map["Index"]
Size <- map["Size"]
Count <- map["Count"]
if let _ = try? map.value("Data") as [T] {
DataArray <- map["Data"]
} else {
Data <- map["Data"]
}
}}
The usage for Alamofire :
WebService.shared.request(url, params, encoding: URLEncoding.default, success: { (response : BaseResult<TypeData>) in
if let arr = response.DataArray
{
for year in arr
{
self.years.append(year)
}
}
}, failure: {
})
The request method is :
func request<T: Mappable>(_ url: String,_ parameters: [String : Any] = [:], _ method: HTTPMethod = .post,_ httpHeaders: HTTPHeaders? = nil, encoding: ParameterEncoding = JSONEncoding.default, success: #escaping (T) -> Void, failure: #escaping () -> () ) {
AF.request(newUrl, method:method, parameters:parameters, encoding:encoding, headers: httpHeaders)
.responseJSON { response in
if let res = response.value {
let json = res as! [String: Any]
if let object = Mapper<T>().map(JSON: json) {
success(object)
return
}
}else if let _ = response.error {
failure()
}
}
}
And TypeData class is :
class TypeData : Mappable
{
var Id : String = ""
var Title: String = ""
required init(map: Map){
}
func mapping(map: Map) {
Id <- map["ID"]
Title <- map["YEAR"]
}}