Swift completion handler - escaping trailing closure - swift

I've been reading quite a few articles now about swift functions with closures, trailing closures and escaping functions. They all seem to give examples which is sufficiently different that I'm not understanding what Im doing wrong with my own function.
My main problem is with how to execute a trailing closure function.
I've created this function to upload an image to firebase. It takes two inputs and are supposed to return a string (imageURL). I do belive this function is ok.
func uploadImageToFirebaseAndReturnImageURL(directory: String, image: UIImage!, handler: #escaping(_ imageURL: (ImageURL)) -> ()) {
let imageName = NSUUID().uuidString // create unique image name
if let uploadData = UIImagePNGRepresentation(image) {
DB_STORE.child(directory).putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
print(error)
return
}
if let profileImageUrl = metadata?.downloadURL()?.absoluteString {
let d = ImageURL(imageURL: profileImageUrl)
handler (d)
}
return
})
}
}
My issue is how to execute this function correctly when it comes to the handler.
I want to first execute the function then when complete I want to get the imageURL and use this variable into another nested function that upload this variable(String) into a firebase database.
uploadImageToFirebaseAndReturnImageURL(directory: "profileImage", image: selectedImageFromPicker!, handler: { imageURL in
guard let uid = Auth.auth().currentUser.uid else { print("User is not logged in"); return }
DataService.instance.updateUserWithProfileImageURL(uid: uid, imageURL: imageURL)
print("")
}
What am I doing wrong?

To pass a trailing closure you need to end/close your function call and omit the closure argument label. For instance:
func foo(first: Int, second: Int, handler: (Int) -> Void) {
...
}
call syntax:
foo(first: 10, second: 20) { result in
/* trailing closure body */
}
By the way, you should simply your handler argument declaration from:
handler: #escaping (_ imageURL: (ImageURL)) -> ()
to this:
handler: #escaping (ImageURL) -> Void
Using Void or () is matter of style since they are logically the same. I prefer the former ;)

Related

Return Callback inside Callback Swift

I have a SDK integration that returns a response using a completion but I want to create another completion to return the callback response, but I don't know how.
This is my attempt to do that
func validatingProcces(completion: ((Any)?)->()) {
let conekta = Conekta()
conekta.delegate = self
conekta.publicKey = "key_KJysdbf6PotS2ut2"
conekta.collectDevice()
let card = conekta.card()
let token = conekta.token()
card!.setNumber(String(cardToSave!.cardNumber), name: cardToSave!.fullName, cvc: String(cardToSave!.cvc), expMonth: String(cardToSave!.month), expYear: String(cardToSave!.year))
token!.card = card
token!.create(success: { (data) -> Void in
completion(data as Any)
}, andError: { (error) -> Void in
print(error as Any)
completion(error as Any)
})
}
I have the following error:
Escaping closure captures non-escaping parameter 'completion'
and also:
Parameter 'completion' is implicitly non-escaping
Captured here
Ps. You'll find the SDK integration here:
https://github.com/conekta/conekta-ios
Thank you so much!
From the source code it looks like you could just make a callback like this:
completion: #escaping (Any?, Error?) -> ()
and pass in the result of the api callback so you can handle it elsewhere like this
token!.create(success: { data in
completion(data, nil)
}, andError: { error in
print(error as Any)
completion(nil, error)
})
Let me know if this works

Swift error while returning multiple values from function

I can not understand what I did wrong
I have an app which loads posts and its comments. The view controller requests a function from an another file, which returns back (response?, comments?)
I get one error:
Initializer for conditional binding must have Optional type, not '(ActionResult?, [PostComment]?)'
For the line
if let (response, comments) = (response, comments )
What did I wrong?
commentsViewController
postComments.loadCommentForPost(id: postId) { (response, comments) in
// ERROR here: Initializer for conditional binding must have Optional type, not '(ActionResult?, [WorldMessageComment]?)'
if let (response, comments) = (response, comments ) {
if response!.success == 1 {
DispatchQueue.main.async(execute: {() -> Void in
self.comments = comments!
self.tableView.reloadData()
})
} else {
DispatchQueue.main.async(execute: {() -> Void in
self.handleResponses.displayError(title: response!.title, message: response!.message)
})
}
}
}
commentFunctions
func loadCommentsForPost(id: Int, completion: #escaping ((ActionResult?), [PostComment]?)->()){
// downloading the data and then
let comments : [PostComment] = ...
// Succesful
return completion((ActionResult(success: 1, title: responseTitle, message: responseMessage)), comments)
}
The issue is in the line:
if let (response, comments) = (response, comments ) {
What you are basically doing is creating a new non optional tuple on the right hand side of the assignment (with two optional components), so the compilator complains that you can't use if let with a non optional type.
You can in fact consider that the tuple return by loadCommentsForPost is already "split" in the arguments of the callback, so you can handle response and comments separately.
postComments.loadCommentForPost(id: postId) { response, comments in
if let response = response, let comments = comments {
if response.success == 1 {
...

How to make #escaping closure optional?

I am having difficulties with making this function pass through optional values, I am using an #escaping closure, but the issue arises where my code demands a certain parameter. So in this instance, I am trying to upload two images. However, the third parameter is giving me trouble. How can I make it so that a certain parameter(s) is optional/doesn't need to be called. And I can inject the data into a certain parameter if it does exist?
This is my code -
static func uploadImagesToFirebaseStorage(data: Data? = nil, secondData: Data? = nil, thirdData: Data? = nil, onSuccess: #escaping (_ imageURL: String, _ secondImageURL: String?, _ thirdImageURL: String?) -> Void) {
let firstPhotoIdString = NSUUID().uuidString
let secondPhotoIdString = NSUUID().uuidString
let thirdPhotoIdString = NSUUID().uuidString
let storageRef = Storage.storage().reference(forURL: Config.STORAGE_REF_ROOT ).child("posts").child(firstPhotoIdString)
storageRef.putData(data!, metadata: nil) { (metadata, error) in
if error != nil {
ProgressHUD.showError(error?.localizedDescription)
return
}
let secondStorageRef = Storage.storage().reference(forURL: Config.STORAGE_REF_ROOT ).child("posts").child(secondPhotoIdString)
secondStorageRef.putData(secondData!, metadata: nil) { (secondMetadata, error) in
if error != nil {
ProgressHUD.showError(error?.localizedDescription)
secondStorageRef.setValue(nil, forKey: secondPhotoIdString)
return
}
let thirdStorageRef = Storage.storage().reference(forURL: Config.STORAGE_REF_ROOT ).child("posts").child(thirdPhotoIdString)
thirdStorageRef.putData(thirdData!, metadata: nil) { (thirdMetadata, error) in
if error != nil {
ProgressHUD.showError(error?.localizedDescription)
return
}
if let firstPhotoURL = metadata?.downloadURL()?.absoluteString, let secondPhotoURL = secondMetadata?.downloadURL()?.absoluteString, let thirdPhotoURL = thirdMetadata?.downloadURL()?.absoluteString {
onSuccess(firstPhotoURL, secondPhotoURL, thirdPhotoURL)
}
}
}
}
}
I will do conditional checks to see if there is valid data to pass through but I want this type of outcome:
If value doesn't exist I do not need to include a certain parameter(s), after the conditional checks have been done
If it doesn't exist, forget about that parameter and carry on
I have found that if can make multiple instances of the same code and change the parameters, but I will need 20 instances, and as you can tell, that would not be efficient what so ever. So how can I have all parameters optional and pass data through without the code needing/demand every parameter?
I know a similar question has been asked, but I read it and didn't find it useful.
Thank you.
EDIT - More information
I am trying to upload images to Firebase. I have three parameters (for 3 images), however, I would like the function to be scalable in terms of it doesn't matter how many images I have, I can upload accordingly.
This is my call to the function:
static func uploadDataToServer(data: Data, secondData: Data? = nil, thirdData: Data? = nil, firstVideoURL: URL? = nil, secondVideoURL: URL? = nil, thirdVideoURL: URL? = nil, caption: String, onSuccess: #escaping () -> Void) {
if let secondImageData = secondData {
uploadImagesToFirebaseStorage(data: data, secondData: secondImageData) { (firstPhotoURL, secondPhotoURL, nil ) in
self.sendDataToDatabase(firstPhotoURL: firstPhotoURL, secondPhotoURL: secondPhotoURL, caption: caption, onSuccess: onSuccess)
}
}
}
}
In the above code, I am trying to upload two images, even thought I have three parameters hence why I set all but the first data AKA first image to nil. My issue is the code either expects a third image or it will crash because there is no third image. I inject the image from the ViewController class by passing the image (from the ImagePickerController) and using UIImageJPEGRepresentation().

swift OSX: serially generating files using GCD

I am trying to generate .aiff files using NSSpeechSynthesizer.startSpeakingString() and am using GCd using a serial queue as NSSpeechSynthesizer takes in a string and creates an aiff file at a specified NSURL address. I used the standard for loop method for a list of strings in a [String:[String]] but this creates some files which have 0 bytes.
Here is the function to generate the speech:
func createSpeech(type: String, name: String) {
if !NSFileManager.defaultManager().fileExistsAtPath("\(dataPath)\(type)/\(name)/\(name).aiff"){
do{
try NSFileManager().createDirectoryAtPath("\(dataPath)\(type)/\(name)/", withIntermediateDirectories: true, attributes: nil)
let URL = NSURL(fileURLWithPath: "\(dataPath)\(type)/\(name)/\(name).aiff")
print("Attempting to save speech \(name).aiff")
self.synth.startSpeakingString(name, toURL: URL)
}catch{
print("error occured")
}
}
}
And here is the function that traverses the dictionary to create the files:
for key in self.nodeLibrary.keys{
dispatch_sync(GlobalBackgroundQueue){
let type = self.nodeLibrary[key]?.0
let name = key.componentsSeparatedByString("_")[0]
if !speechCheck.contains(name){
mixer.createSpeech(type!, name: name)
}
}
}
The globalBackgroundQueue is an alias to the GCD queue call _T for readability.
The routine runs fine, creates folders and subfolders as required by another external function then synthesizes the speech but in my case I always get one or some which don't load properly, giving 0 bytes or a too small number of bytes which makes the file unuseable.
I read the following post and have been using these GCD methods for a while but I'm not sure where I'm wrong here:
http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1
Any help greatly appreciated as usual
edit: Updated with completion closure and found possibly a bug
I have created a closure function as below and use it in another helper method which checks for any errors such as sourceFile.length being 0 once loaded. However, all files exhibit a 0 length which is not possible as I checked each file's audio properties using finder's property command+i.
func synthesise(type: String, name: String, completion: (success: Bool)->()) {
if !NSFileManager.defaultManager().fileExistsAtPath("\(dataPath)\(type)/\(name)/\(name).aiff"){
do{
try NSFileManager().createDirectoryAtPath("\(dataPath)\(type)/\(name)/", withIntermediateDirectories: true, attributes: nil)
let URL = NSURL(fileURLWithPath: "\(dataPath)\(type)/\(name)/\(name).aiff")
let success = self.synth.startSpeakingString(name, toURL: URL)
completion(success: success)
}catch{
print("error occured")
}
}
}
func loadSpeech(type: String, name: String){
synthesise(type, name: name, completion: {(success: Bool)->Void in
if success{
print("File \(name) created successfully with return \(self.synthSuccess), checking file integrity")
let URL = NSURL(fileURLWithPath: "\(self.dataPath)\(type)/\(name)/\(name).aiff")
do{
let source = try AVAudioFile(forReading: URL)
print("File has length: \(source.)")
}catch{
print("error loading file")
}
}else{
print("creation unsuccessful, trying again")
self.loadSpeech(type, name: name)
}
})
}
The files are generated with their folders and both the method startSpeakingString->Bool and the delegate function I have in my class which updates the synthSuccess property show true. So I load an AVAudioFile to check its length. All file lengths are 0. Which they are not except for one.
When I say bug, this is from another part of the app where I load an AVAudioEngine and start loading buffers with the frameCount argument set to sourceAudioFile.length which gives a diagnostic error but this is out of context right now.
startSpeakingString(_:toURL:) will start an asynchronous task in the background. Effectively, your code starts a number of asynchronous tasks that run concurrently. This may be the cause of the problem that you experience.
A solution would need to ensure that only one task is active at a time.
The problem with startSpeakingString(_:toURL:) is, that it starts an asynchronous task - but the function itself provides no means to get notified when this task is finished.
However, there's a delegate which you need to setup in order to be notified.
So, your solution will require to define a NSSpeechSynthesizerDelegate.
You may want to create your own helper class that exposes an asynchronous function which has a completion handler:
func exportSpeakingString(string: String, url: NSURL,
completion: (NSURL?, ErrorType?) -> ())
Internally, the class creates an instance of NSSpeechSynthesizer and NSSpeechSynthesizerDelegate and implements the delegate methods accordingly.
To complete the challenge, you need to search for an approach to run several asynchronous functions sequentially. There are already solutions on SO.
Edit:
I setup my own project to either confirm or neglect a possible issue in the NSSpeechSynthesizer system framework. So far, may own tests confirm that NSSpeechSynthesizer works as expected.
However, there are few subtleties worth mentioning:
Ensure you create a valid file URL which you pass as an argument to parameter URL in method startSpeakingString(:toURL:).
Ensure you choose an extension for the output file which is known by NSSpeechSynthesizer and the system frameworks playing this file, for example .aiff. Unfortunately, the documentation is quite lacking here - so I had to trial and error. The list of supported audio file formats by QuickTime may help here. Still, I have no idea how NSSpeechSynthesizer selects the output format.
The following two classes compose a simple easy to use library:
import Foundation
import AppKit
enum SpeechSynthesizerError: ErrorType {
case ErrorActive
case ErrorURL(message: String)
case ErrorUnknown
}
internal class InternalSpeechSynthesizer: NSObject, NSSpeechSynthesizerDelegate {
typealias CompletionFunc = (NSURL?, ErrorType?) -> ()
private let synthesizer = NSSpeechSynthesizer(voice: nil)!
private var _completion: CompletionFunc?
private var _url: NSURL?
override init() {
super.init()
synthesizer.delegate = self
}
// CAUTION: This call is not thread-safe! Ensure that multiple method invocations
// will be called from the same thread!
// Only _one_ task can be active at a time.
internal func synthesize(input: String, output: NSURL, completion: CompletionFunc) {
guard _completion == nil else {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
completion(nil, SpeechSynthesizerError.ErrorActive)
}
return
}
guard output.path != nil else {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
completion(nil, SpeechSynthesizerError.ErrorURL(message: "The URL must be a valid file URL."))
}
return
}
_completion = completion
_url = output
if !synthesizer.startSpeakingString(input, toURL: output) {
fatalError("Could not start speeaking")
}
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
willSpeakWord characterRange: NSRange,
ofString string: String)
{
NSLog("willSpeakWord")
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
willSpeakPhoneme phonemeOpcode: Int16)
{
NSLog("willSpeakPhoneme")
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
didEncounterErrorAtIndex characterIndex: Int,
ofString string: String,
message: String)
{
NSLog("didEncounterErrorAtIndex")
}
internal func speechSynthesizer(sender: NSSpeechSynthesizer,
didFinishSpeaking finishedSpeaking: Bool)
{
assert(self._url != nil)
assert(self._url!.path != nil)
assert(self._completion != nil)
var error: ErrorType?
if !finishedSpeaking {
do {
error = try self.synthesizer.objectForProperty(NSSpeechErrorsProperty) as? NSError
} catch let err {
error = err
}
}
let url: NSURL? = NSFileManager.defaultManager().fileExistsAtPath(self._url!.path!) ? self._url : nil
let completion = self._completion!
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
if url == nil && error == nil {
error = SpeechSynthesizerError.ErrorUnknown
}
completion(url, error)
}
_completion = nil
_url = nil
}
}
public struct SpeechSynthesizer {
public init() {}
private let _synthesizer = InternalSpeechSynthesizer()
public func synthesize(input: String, output: NSURL, completion: (NSURL?, ErrorType?) -> ()) {
_synthesizer.synthesize(input, output: output) { (url, error) in
completion(url, error)
}
}
}
You can use it as shown below:
func testExample() {
let expect = self.expectationWithDescription("future should be fulfilled")
let synth = SpeechSynthesizer()
let url = NSURL(fileURLWithPath: "/Users/me/Documents/speech.aiff")
synth.synthesize("Hello World!", output: url) { (url, error) in
if let url = url {
print("URL: \(url)")
}
if let error = error {
print("Error: \(error)")
}
expect.fulfill()
}
self.waitForExpectationsWithTimeout(1000, handler: nil)
// Test: output file should exist.
}
In the code above, check the result of the call to synth.startSpeakingString(name, toURL: URL), which can return false if the synthesiser could not start speaking. If it fails, find out why, or just retry it.
Plus, add [NSSpeechSynthesiserDelegate][1], and look for the speechSynthesizer:didFinishSpeaking: callbacks there. When the synthesiser thinks it has finished speaking, check the file size. If it is zero, retry the operation.

Is there anyway I can avoid the use of what appears to be some "boilerplate" code?

I am starting with a function that looks like this:
func getUser(command: APICommand, id: Int, handler: (apiResponse: APIResponse<User>) -> Void ) {
let url = apiPath + "users/\(id)"
Alamofire.request(.GET, url, parameters: requestParameters)
.responseObject { (a:NSURLRequest, b:NSHTTPURLResponse?, c:User?, d:AnyObject?, e:NSError? ) in
let apiResponse = APIResponse(command: command,request: a, response: b, swiftObject: c, rawObject: d, error: e)
if AlamofireAPIRequestRepository.resultIsRetryableError(e, command: command) {
println("retrying request")
command.retry()
} else {
handler(apiResponse: apiResponse)
}
}
}
I am going to have a number of functions that look very similar such as, getUserList() for example.
Looking at this I realized the entire Alamofire.request call is going to be pretty much boiler plate code. The only difference will be the Type of argument c passed into the closure that gets called by the responseObject() method. In this case it is User? , in the getUserList() method it will be UserList?
Is there any way I can make this more generic and avoid what appears to be just "boilerplate" code?
I Here is what I have tried.
func alamofireGetRequest<T>(url: URLStringConvertible, parameters: [String: AnyObject]?,
command: APICommand, handler: (apiResponse: APIResponse<T>) -> Void) -> Void {
Alamofire.request(.GET, url, parameters: parameters)
.responseObject { (a:NSURLRequest, b:NSHTTPURLResponse?, c:T?, d:AnyObject?, e:NSError? ) in
let apiResponse = APIResponse(command: command,request: a, response: b, swiftObject: c, rawObject: d, error: e)
if AlamofireAPIRequestRepository.resultIsRetryableError(e, command: command) {
println("retrying request")
command.retry()
} else {
handler(apiResponse: apiResponse)
}
}
}
but the compiler complains with:
Cannot invoke 'responseObject' with an argument list of type
'((NSURLRequest, NSHTTPURLResponse?, T?, AnyObject?, NSError?) -> _)'
and if I replace the c:T? above with c:User? it is happy.
A comment below referred to this question, which may explain why the solution I tried does not work, but does really answer my intended question as to how to avoid this duplicated code.
I think I found an acceptable solution.
Below is the relevant parts of the code, but basically I end up passing in an argument type of AnyObject in to the closure rather than a generic. Before you say that I am cheating you have to understand that my original call would have looked something like this.
func testGetUserRequestSuccess() {
let getUserCommand=commandFactory.createGetUserCommandUsing(apiRepo, id: 1) {
if let swiftObject = $0.swiftObject {
XCTAssertEqual(swiftObject.id!, 1, "id is correct")
}
self.expectation.fulfill()
}
commandProcessor.processCommand(getUserCommand)
wait(5)
}
now it looks like this:
func testGetUserRequestSuccess() {
let getUserCommand=commandFactory.createGetUserCommandUsing(apiRepo, id: 1) {
if let swiftObject = $0.swiftObject as? User {
XCTAssertEqual(swiftObject.id!, 1, "id is correct")
}
self.expectation.fulfill()
}
commandProcessor.processCommand(getUserCommand)
wait(5)
}
so I had to add the as? User cast to the if let statement, which seems reasonable.
relevant code for anyone interested.
The "specialty" code (what will be repeated for each operation)
func getUser(retriesUse retryProcessor: APICommandProcessor, command: APICommand, id: Int, handler: (apiResponse: APIResponse) -> Void ) {
let url = apiPath + "users/\(id)"
Alamofire.request(.GET, url, parameters: requestParameters)
.responseObject { (a:NSURLRequest, b:NSHTTPURLResponse?, c:User?, d:AnyObject?, e:NSError? ) in
let apiResponse = APIResponse(command: command,request: a, response: b, swiftObject: c, rawObject: d, error: e)
self.finishResponse(command, retryProcessor: retryProcessor, apiResponse: apiResponse, handler: handler)
}
}
The common part that I wanted to factor out (mainly a placeholder at this point)
func finishResponse(command: APICommand, retryProcessor: APICommandProcessor, apiResponse: APIResponse, handler: (apiResponse: APIResponse) -> Void) -> Void {
if AlamofireAPIRequestRepository.resultIsRetryableError(apiResponse.error, retryProcessor: retryProcessor, command: command) {
println("retrying \(command)")
retryProcessor.retryCommand(command)
} else {
println("request completed")
handler(apiResponse: apiResponse)
}
}
a supporting struct used above:
struct APIResponse {
let command: APICommand
let request: NSURLRequest
let response: NSHTTPURLResponse?
let swiftObject: AnyObject?
let rawObject: AnyObject?
let error: NSError?
}