Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the proper way to work with AWSTask objects in Swift?

Hello and thanks in advance for your time.

In my code I am making various requests to AWSSQS which all return AWSTask. I have found working with these AWSTask objects to be very difficult while also trying to keep all the logic specific to AWS in a single class so I can easily switch to a different cloud service if need be.

Ideally, what I would like to do is execute a series of AWS tasks asynchronously in a serial fashion. Normally I would just add tasks to a custom Serial Dispatch Queue but since The AWSTask objects are themselves asynchronous tasks, I can't do that.

Here is a simple example that illustrates the problem I am having. It doesn't have any real world purpose but it does a good job illustrating the problem. Below, I have code to create a SQS queue, send a message to a SQS queue, receive a message from an SQS queue, and delete a SQS queue. Let's say I want to do those four things in a serial, asynchronous fashion. In other words, I want to make sure the previous task succeeded before attempting the next task.

ViewController

DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async {
        awsClass.runTest()
        DispatchQueue.main.async {
            print("Test Finished")
        }
    }

AwsClass

public func createQueue(){
    guard let createQueueRequest = AWSSQSCreateQueueRequest() else{fatalError()}

    createQueueRequest.queueName = "TestQueue"

    sqs.createQueue(createQueueRequest).continueWith(block: {(task) -> AnyObject? in
        if task.error != nil {
            print(task.error!)
        }
        else if task.result != nil {
            self.queueUrl = task.result!.queueUrl!
            print("created queue at: \(self.queueUrl!)")
        }
        return nil
    })
}

public func deleteQueue(){
    if queueUrl != nil {
        guard let deleteQueueRequest = AWSSQSDeleteQueueRequest() else{fatalError()}

        deleteQueueRequest.queueUrl = queueUrl

        sqs.deleteQueue(deleteQueueRequest).continueWith(block: {(task) -> AnyObject? in
            if task.error != nil {
                print(task.error!)
            }
            else if task.result != nil {
                print("queue sucessfully deleted from \(self.queueUrl!)")
                self.queueUrl = nil
            }
            return nil
        })
    }
    else{
        print("Queue has already been deleted")
    }
}

public func sendMessage(messageData: String, toConnectId: String) {
    guard let sendMessageRequest = AWSSQSSendMessageRequest() else{fatalError()}
    sendMessageRequest.queueUrl = toConnectId
    sendMessageRequest.delaySeconds = 0
    sendMessageRequest.messageBody = messageData
    sqs.sendMessage(sendMessageRequest).continueWith(block: {(task) -> AnyObject? in
        if task.error != nil {
            print(task.error!)
        }
        else if task.result != nil {
            print("successfully sent message to \(toConnectId)")
        }
        return nil
    })
}

public func receiveMessage(){
    guard let receiveMessageRequest = AWSSQSReceiveMessageRequest() else{fatalError()}
    receiveMessageRequest.queueUrl = self.queueUrl
    receiveMessageRequest.maxNumberOfMessages = 1

    sqs.receiveMessage(receiveMessageRequest).continueWith(block: {(task) -> AnyObject? in
        if task.error != nil {
            print(task.error!)
        }
        else if task.result != nil {
            let message = (task.result?.messages?.first)!
            print("successfully received message with body:  \(message.body ?? "failed")")
        }
        return nil
    })
}

public func runTest(){
    let mySerialQueue = DispatchQueue(label: "mySerialQueue")
    mySerialQueue.sync {
        self.createQueue()
    }
    mySerialQueue.sync {
        self.sendMessage(messageData: "test", toConnectId: "https://someUrl")
    }
    mySerialQueue.sync {
        self.receiveMessage()
    }
    mySerialQueue.sync {
        self.deleteQueue()
    }
}

Since the AWSTasks are asynchronous with completion functions, the code quickly makes all four calls and then the completion functions are called whenever those tasks finish. Instead I want the completion function of the first task to finish before the next task begins.

like image 794
Alec Avatar asked Oct 18 '22 10:10

Alec


2 Answers

The AWSTask objects are meant to be "chained" together. Documentation can be found here: http://docs.aws.amazon.com/mobile/sdkforios/developerguide/awstask.html

A small example here:

sqs.createQueue(/* parameters */).continueWithSuccess(block: {(task) -> Void in
    // Success
    return sqs.sendMessage(/* parameters */)
}).continueWithSuccess(block: {(task) -> Void in
    // Success
    return sqs.receiveMessage(/* parameters */)
}).continueWithSuccess(block: {(task) -> Void in
    // Success
    return sqs.deleteQueue(/* parameters */)
})
like image 197
donkon Avatar answered Oct 20 '22 22:10

donkon


Alright, so I found a solution to my question. It works exactly as desired but it does so in this nasty chain of completion functions. If anyone knows a more elegant solution, I am all ears!

ViewController

print("Starting Test")
    DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).async {
        atomConnector.runTest(completion: {
            print("test finshed")
        })
    }

AwsClass

public func createQueue(completion: @escaping () -> Void){
    guard let createQueueRequest = AWSSQSCreateQueueRequest() else{fatalError()}

    createQueueRequest.queueName = "TestQueue"

    sqs.createQueue(createQueueRequest).continueWith(block: {(task) -> Void in
        if task.error != nil {
            print(task.error!)
        }
        else if task.result != nil {
            self.queueUrl = task.result!.queueUrl!
            print("created queue at: \(self.queueUrl!)")
            completion()
        }
    })
}

public func deleteQueue(completion: @escaping () -> Void){
    if queueUrl != nil {
        guard let deleteQueueRequest = AWSSQSDeleteQueueRequest() else{fatalError()}

        deleteQueueRequest.queueUrl = queueUrl

        sqs.deleteQueue(deleteQueueRequest).continueWith(block: {(task) -> Void in
            if task.error != nil {
                print(task.error!)
            }
            else if task.result != nil {
                print("queue sucessfully deleted from \(self.queueUrl!)")
                self.queueUrl = nil
                completion()
            }
        })
    }
    else{
        print("Queue has already been deleted")
    }
}

public func sendMessage(messageData: String, toConnectId: String, completion: @escaping () -> Void) {
    guard let sendMessageRequest = AWSSQSSendMessageRequest() else{fatalError()}
    sendMessageRequest.queueUrl = toConnectId
    sendMessageRequest.delaySeconds = 0
    sendMessageRequest.messageBody = messageData
    sqs.sendMessage(sendMessageRequest).continueWith(block: {(task) -> Void in
        if task.error != nil {
            print(task.error!)
        }
        else if task.result != nil {
            print("successfully sent message to \(toConnectId)")
            completion()
        }
    })
}

public func receiveMessage(completion: @escaping () -> Void){
    guard let receiveMessageRequest = AWSSQSReceiveMessageRequest() else{fatalError()}
    receiveMessageRequest.queueUrl = self.queueUrl
    receiveMessageRequest.maxNumberOfMessages = 1

    sqs.receiveMessage(receiveMessageRequest).continueWith(block: {(task) -> Void in
        if task.error != nil {
            print(task.error!)
        }
        else if task.result != nil {
            let message = (task.result?.messages?.first)!
            print("successfully received message with body:  \(message.body ?? "failed")")
            self.deleteMessage(receiptHandle: message.receiptHandle, completion: completion)
        }
    })
}

public func deleteMessage(receiptHandle: String?, completion: @escaping () -> Void){
    guard let deleteMessageRequest = AWSSQSDeleteMessageRequest() else{fatalError()}
    deleteMessageRequest.queueUrl = self.queueUrl
    deleteMessageRequest.receiptHandle = receiptHandle

    sqs.deleteMessage(deleteMessageRequest).continueWith(block: {(task) -> Void in
        if task.error != nil {
            print(task.error!)
        }
        else if task.result != nil {
            print("successfully deleted message with receiptHandle:  \(receiptHandle)")
            completion()
        }
    })
}

public func runTest(completion: @escaping () -> Void){

    self.createQueue(completion: {
        self.sendMessage(messageData: "test", toConnectId: "https://someUrl", completion: {
            self.receiveMessage(completion: {
                self.deleteQueue(completion: {
                    completion()
                })
            })
        })
    })

}
like image 38
Alec Avatar answered Oct 21 '22 00:10

Alec