swift async await pause task and waiting for some data - swift

I need to do infinity load while device not connected to the internet
Example: if user lost the internet connection when the app doing a request then app must waiting while internet connection will restore and trying again to do the request
I do it with the completions
func getData(completion: ((SomeData?) -> Void)?) {
apiService.getSomeData() { [weak self] result in
switch result {
case .success(let data):
return data
case .failure(let error):
switch error {
case .networkError:
let getDataTask: () -> Void = {
self?.getData()
}
// when internet connection will be restore then execute all tasks from onConnectionToTheInternetRestoredTasks
self?.onConnectionToTheInternetRestoredTasks.append(getDataTask)
default:
throw error
}
}
}
}
private func onConnectionToTheInternetRestored() {
onConnectionToTheInternetRestoredTasks.forEach { $0() }
onConnectionToTheInternetRestoredTasks = []
}
but now I need to refactor it in async/await
func getData() async throws -> SomeData {
let result = await apiService.getSomeData()
switch result {
case .success(let data):
return data
case .failure(let error):
switch error {
case .networkError:
// need to do wating for restore and try again
default:
throw error
}
}
}
I don't know how to process .networkError in getData() async throws to waiting for restore connection
Can u please explain it to me ?

Related

Failed to decode data coming from client

I am following Ray Wanderlich's book 'Server Side Swift with Vapor' and I am at chapter 26: Adding profile pictures.
First, I defined this struct:
struct ImageUploadData: Content {
var picture: Data
}
Then, in a route I try to decode it:
func postProfilePictureHandler(_ req: Request) throws -> EventLoopFuture<User> {
let data = try req.content.decode(ImageUploadData.self)
...
From the client side, I use Alamofire:
#discardableResult func uploadProfilePicture(for user: User, data: Data) async throws -> User {
enum Error: LocalizedError {
case missingUserID
}
guard let userID = user.id else {
throw Error.missingUserID
}
let appendix = "\(userID)/profilePicture"
let parameters = [
"picture": data
]
return try await withCheckedThrowingContinuation { continuation in
Task {
AF.request(baseUrl + appendix, method: .post, parameters: parameters).responseData { response in
switch response.result {
case .success(let data):
do {
let user = try JSONDecoder().decode(User.self, from: data)
continuation.resume(returning: user)
} catch {
continuation.resume(throwing: error)
}
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
}
In my integration tests, I create the picture's data like this:
guard let data = image?.pngData() else {
throw Error.missingPictureData
}
And then I pass it to the above method. The problem is that in the server side, the decoding fails with this error:
The data couldn’t be read because it is missing.
Just to understand if I was doing something else wrong, I tried the above methods with one difference: I replace the type 'Data' with 'String':
struct ImageUploadData: Content {
var picture: String
}
This wouldn't be useful for me because I need a data object, but just as a test to see if this doesn't produce an error, I tried and indeed this is decoded successfully. So I suspect that the problem is in how I encode the data before sending it to the server, but I don't know what's wrong.

How to return a function from inside a switch-case statement

How can I abort or return the parent function in this case:
enum MyError: String, Error {
case serverError = "This is a server error"
case condition1NotTrue = "Condition 1 is not true"
}
func myServerFunction(finishesSuccessful:Bool, completionHandler: #escaping (Result<Void, MyError>) -> Void) {
if finishesSuccessful {
completionHandler(.success(()))
} else {
completionHandler(.failure(.serverError))
}
}
func myMainFunction(test1:Bool, uploadToServer:Bool, completionHandler: #escaping (Result<Void,MyError>) -> Void) {
// check that condition test1 is true
if !test1 {
completionHandler(.failure(.condition1NotTrue))
return // abort function to not execute the rest
}
if uploadToServer {
myServerFunction(finishesSuccessful: false) { result in
switch result {
case .success():
print("Successfully uploaded to server")
case .failure(let error):
print("Error uploading to server: \(error.rawValue)")
completionHandler(.failure(error))
return // <<<--- Here is my problem
}
}
}
// if !test1 && upload to server successful -> continue and update local data
print("Local data will be updated now...")
}
myMainFunction(test1: true, uploadToServer: true) { result in
switch result {
case .success():
print("myMainFunction succeeded")
case .failure(let error):
print("myMainFunction failed: \(error.rawValue)")
}
}
Basically what I want to do is check some data, then decide if it should be uploaded to a server. If not it will be used locally only, but if it needs to be uploaded, I have to make sure that this succeeded. Otherwise I don't want to update my local data.
This outputs
Error uploading to server: This is a server error
myMainFunction failed: This is a server error
Local data will be updated now...
The return cancelling myMainFunction if test1 is not true works fine and it won't update the local data.
the return in case myServerFunction fails doesn't though, as it only stops the switch-case-statement.
Is there a convenient way to return myMainFunction right from there? The only workaround I could imagine is setting a bool in the switch-statement and then calling return after the switch-statement, but it somehow looks weird.
following #JoakimDanielson advice I changed myServerFunction to throw instead of failing a completion handler:
func myServerFunction(finishesSuccessful:Bool) throws {
if finishesSuccessful {
print("Upload to Server successful")
} else {
throw MyError.serverError
}
}
Now I can catch the error in myMainFunction
if uploadToServer {
do {
try myServerFunction(finishesSuccessful: false)
} catch {
completionHandler(.failure(.serverError))
return
}
}

Wrapping asynchronous code in Swift's Combine publisher

I have a class called QueryObserver that can produce multiple results over time, given back as callbacks (closures). You use it like this:
let observer = QueryObserver<ModelType>(query: query) { result in
switch result {
case .success(let value):
print("result: \(value)")
case .failure(let error):
print("error: \(error)")
}
}
(QueryObserver is actually a wrapper around Firebase Firestore's unwieldy query.addSnapshotListener functionality, in case you were wondering. Using modern Result type instead of a callback with multiple optional parameters.)
In an older project I am using ReactiveKit and have an extension that turns all this into a Signal, like so:
extension QueryObserver {
public static func asSignal(query: Query) -> Signal<[T], Error> {
return Signal { observer in
let queryObserver = QueryObserver<T>(query: query) { result in
switch result {
case .success(let value):
observer.receive(value)
case .failure(let error):
if let firestoreError = error as? FirestoreError, case .noSnapshot = firestoreError {
observer.receive([])
} else {
observer.receive(completion: .failure(error))
}
}
}
return BlockDisposable {
queryObserver.stopListening()
}
}
}
}
In a brand new project though, I am using Combine and am trying to rewrite this. So far as I have managed to write this, but it doesn't work. Which makes sense: the observer is not retained by anything so it's immediately released, and nothing happens.
extension QueryObserver {
public static func asSignal(query: Query) -> AnyPublisher<[T], Error> {
let signal = PassthroughSubject<[T], Error>()
let observer = QueryObserver<T>(query: query) { result in
switch result {
case .success(let value):
print("SUCCESS!")
signal.send(value)
case .failure(let error):
if let firestoreError = error as? FirestoreError, case .noSnapshot = firestoreError {
signal.send([])
} else {
signal.send(completion: .failure(error))
}
}
}
return signal.eraseToAnyPublisher()
}
}
How do I make the Combine version work? How can I wrap existing async code? The only examples I found used Future for one-off callbacks, but I am dealing with multiple values over time.
Basically I am looking for the ReactiveKit-to-Combine version of this.
Check out https://github.com/DeclarativeHub/ReactiveKit/issues/251#issuecomment-575907641 for a handy Combine version of a Signal, used like this:
let signal = Signal<Int, TestError> { subscriber in
subscriber.receive(1)
subscriber.receive(2)
subscriber.receive(completion: .finished)
return Combine.AnyCancellable {
print("Cancelled")
}
}

Not sure how to implement the SMB project into my app

I'm trying to implement SMB into my app and found this https://github.com/amosavian/AMSMB2. I'm not sure how to implement this code into the UI of my app. For example, do I connect the connect function to a button and if so how would I proceed to do so. What would I put into the parameters of connect() when I call it if I call it.
Here's the code from the repository:
import AMSMB2
class SMBClient {
/// connect to: `smb://guest#XXX.XXX.XX.XX/share`
let serverURL = URL(string: "smb://XXX.XXX.XX.XX")!
let credential = URLCredential(user: "guest", password: "", persistence: URLCredential.Persistence.forSession)
let share = "share"
lazy private var client = AMSMB2(url: self.serverURL, credential: self.credential)!
private func connect(handler: #escaping (Result<AMSMB2, Error>) -> Void) {
// AMSMB2 can handle queueing connection requests
client.connectShare(name: self.share) { error in
if let error = error {
handler(.failure(error))
} else {
handler(.success(self.client))
}
}
}
func listDirectory(path: String) {
connect { result in
switch result {
case .success(let client):
client.contentsOfDirectory(atPath: path) { result in
switch result {
case .success(let files):
for entry in files {
print("name:", entry[.nameKey] as! String,
", path:", entry[.pathKey] as! String,
", type:", entry[.fileResourceTypeKey] as! URLFileResourceType,
", size:", entry[.fileSizeKey] as! Int64,
", modified:", entry[.contentModificationDateKey] as! Date,
", created:", entry[.creationDateKey] as! Date)
}
case .failure(let error):
print(error)
}
}
case .failure(let error):
print(error)
}
}
}
func moveItem(path: String, to toPath: String) {
self.connect { result in
switch result {
case .success(let client):
client.moveItem(atPath: path, toPath: toPath) { error in
if let error = error {
print(error)
} else {
print("\(path) moved successfully.")
}
// Disconnecting is optional, it will be called eventually
// when `AMSMB2` object is freed.
// You may call it explicitly to detect errors.
client.disconnectShare(completionHandler: { (error) in
if let error = error {
print(error)
}
})
}
case .failure(let error):
print(error)
}
}
}
}
late but for reference as i couldnt find much when i tried
add the amsmb2 library and that template class file to your project
in the class file set serverurl, share, credential correctly for your smb server
then
declare a variable of class smb_client
then from your button or whatever
var myclient:smb_client=smb_client()
myclient.connect()
to connect to your smb server for example
you can extend that class templete to download/upload items etc
the operations are asynchronous

no response from server: unknown: transportError

I am getting the following error when querying my stitch remote collection in Swift: no response from server: unknown: transportError
This is my test query:
//var aircraftCollection: RemoteMongoCollection<Aircraft>!
var aircraftCollection: RemoteMongoCollection<Document>!
var mongoDb = MongoDB()
class MongoDB: ErrorListener {
init() {
do {
let client = try Stitch
.initializeDefaultAppClient(withClientAppID: "....")
let mongoClient = try client.serviceClient(
fromFactory: remoteMongoClientFactory, withName: "...."
)
client.auth.login(withCredential: UserPasswordCredential(withUsername: "....", withPassword: "....")) { result in
switch result {
case .success(let user):
// Get collections from database
aircraftCollection = mongoClient.db("database").collection("aircraft")
let aircraft = Document(dictionaryLiteral: ("_id", ObjectId()))
aircraftCollection.sync.insertOne(document: aircraft, { (result) in
switch result {
case .success(_):
print("inserted successfully")
case .failure(let e):
fatalError(e.localizedDescription)
}
})
aircraftCollection.find().first { result in
switch result {
case .success(let aircraft):
let aircraftList = aircraft.map { $0 }
print(aircraftList)
case .failure(let error):
// this is where error occurs
print(error.localizedDescription)
}
}
case .failure(let error):
print("Error in login: \(error)")
}
}
} catch let error {
print("do catch error")
print(error)
}
}
My connection seems to be fine, I have another stitch app built in JavaScript on localhost that performs the same query without any trouble.
I am using pod 'StitchSDK', '~> 5.0.0'
This shouldn't matter, but are you intentionally trying to utilize mobile sync, or would you rather just call aircraftCollection.insertOne()