How to make #escaping closure optional? - swift

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().

Related

AutoreleasingUnsafeMutablePointer crashes the app

I receive some data from internet and need to guess the encoding if it's not provided, so I use this function stringEncoding(for:encodingOptions:convertedString:usedLossyConversion:), and it requires passing AutoreleasingUnsafeMutablePointer for receiving the converted string, I wrote code like this:
var str = "Hello, playground"
func decode(data: Data) -> String? {
var covertedString = NSString()
let stringPointer = AutoreleasingUnsafeMutablePointer<NSString?>(&covertedString)
guard NSString.stringEncoding(for: data, encodingOptions: nil, convertedString: stringPointer, usedLossyConversion: nil) != 0 else {
return nil
}
return covertedString as String
}
let data = str.data(using: .utf8)!
decode(data: data)
While the covertedString I got out of the function call is correct, the app always crashes. Any idea why AutoreleasingUnsafeMutablePointer is make it crashes? I tried to not passing convertedString, then it's not crashing any more, so looks like it's the root case. Any idea why it's crashing?
I am using Xcode Version 10.1 (10B61), with Swift 4
In your particular case the problem is that you have created an NSString, but then taken a pointer to a NSString?, which is a different thing.
But that doesn't really matter here. You don't create AutoreleasingUnsafeMutablePointer directly (or generally any kind of UnsafePointer). They're not promised to be valid by the time you use them. Instead, you create them implicitly using &.
func decode(data: Data) -> String? {
var convertedString: NSString? = "" // <- Make sure to make this optional
guard NSString.stringEncoding(for: data,
encodingOptions: nil,
convertedString: &convertedString, // <- Use &
usedLossyConversion: nil) != 0
else {
return nil
}
return convertedString as String?
}

Can't get data returned from dataTask()

For one week I have been trying to get a string returned from dataTask().
I already read a lot here on StackOverFlow and also from serval sites where they tackle this topic. For example, this one. So I already understand that it's that the dataTask doesn't directly return values, cause it happens on different threads and so on. I also read about closures and completion handlers. I really got the feeling that I actually already got a little clue what this is about. But I can't get it to work.
So this is my code. I just post the whole code so no-one needs to worry that the problem sticks in a part which I don't show. Everything is working fine until I try to return a value and save it for example in a variable:
func requestOGD(code gtin: String, completion: #escaping (_ result: String) -> String) {
// MARK: Properties
var answerList: [String.SubSequence] = []
var answerDic: [String:String] = [:]
var product_name = String()
var producer = String()
// Set up the URL request
let ogdAPI = String("http://opengtindb.org/?ean=\(gtin)&cmd=query&queryid=400000000")
guard let url = URL(string: ogdAPI) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /todos/1")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result, which is String. It willbecome split and placed in a dictionary
do {
let answer = (String(decoding: responseData, as: UTF8.self))
answerList = answer.split(separator: "\n")
for entry in answerList {
let entry1 = entry.split(separator: "=")
if entry1.count > 1 {
let foo = String(entry1[0])
let bar = String(entry1[1])
answerDic[foo] = "\(bar)"
}
}
if answerDic["error"] == "0" {
product_name = answerDic["detailname"]!
producer = answerDic["vendor"]!
completion(product_name)
} else {
print("Error-Code der Seite lautet: \(String(describing: answerDic["error"]))")
return
}
}
}
task.resume()
Here I call my function, and no worries, I also tried to directly return it to the var foo, also doesn't work The value only exists within the closure:
// Configure the cell...
var foo:String = ""
requestOGD(code: listOfCodes[indexPath.row]) { (result: String) in
print(result)
foo = result
return result
}
print("Foo:", foo)
cell.textLabel?.text = self.listOfCodes[indexPath.row] + ""
return cell
}
So my problem is, I have the feeling, that I'm not able to get a value out of a http-request.
You used a completion handler in your call to requestOGD:
requestOGD(code: listOfCodes[indexPath.row]) {
(result: String) in
// result comes back here
}
But then you tried to capture and return that result:
foo = result
return result
So you're making the same mistake here that you tried to avoid making by having the completion handler in the first place. The call to that completion handler is itself asynchronous. So you face the same issue again. If you want to extract result at this point, you would need another completion handler.
To put it in simple terms, this is the order of operations:
requestOGD(code: listOfCodes[indexPath.row]) {
(result: String) in
foo = result // 2
}
print("Foo:", foo) // 1
You are printing foo before the asynchronous code runs and has a chance to set foo in the first place.
In the larger context: You cannot use any asynchronously gathered material in cellForRowAt. The cell is returned before the information is gathered. That's what asynchronous means. You can't work around that by piling on further levels of asynchronicity. You have to change your entire strategy.

Swift completion handler - escaping trailing closure

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 ;)

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.

AlamoFire GET api request not working as expected

I am trying to get learn how to use AlamoFire and I am having trouble.
My method so far is as follows:
func siteInfo()->String?{
var info:NSDictionary!
var str:String!
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {(request, response, JSON, error) in
info = JSON as NSDictionary
str = info["access_key"] as String
//return str
}
return str
}
This returns nil which is a problem. From what I have read here, this is because the request can take a while so the closure doesn't execute till after the return. The suggested solution of moving the return into the closure does not work for me and the compiler just yells (adding ->String after (request,response,JSON,error) which gives "'String' is not a subtype of void"). Same goes for the other solution provided.
Any ideas? Even some source code that is not related to this problem, that uses AlamoFire, would be helpful.
Thanks!
One way to handle this is to pass a closure (I usually call it a completionHandler) to your siteInfo function and call that inside Alamofire.request's closure:
func siteInfo(completionHandler: (String?, NSError?) -> ()) -> () {
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {
(request, response, JSON, error) in
let info = JSON as? NSDictionary // info will be nil if it's not an NSDictionary
let str = info?["access_key"] as? String // str will be nil if info is nil or the value for "access_key" is not a String
completionHandler(str, error)
}
}
Then call it like this (don't forget error handling):
siteInfo { (str, error) in
if str != nil {
// Use str value
} else {
// Handle error / nil value
}
}
In the comments you asked:
So how would you save the info you collect from the get request if you
can only do stuff inside the closure and not effect objects outside of
the closure? Also, how to keep track to know when the request has
finished?
You can save the result of the get request to an instance variable in your class from inside the closure; there's nothing about the closure stopping you from doing that. What you do from there really depends on, well, what you want to do with that data.
How about an example?
Since it looks like you're getting an access key form that get request, maybe you need that for future requests made in other functions.
In that case, you can do something like this:
Note: Asynchronous programming is a huge topic; way too much to cover here. This is just one example of how you might handle the data you get back from your asynchronous request.
public class Site {
private var _accessKey: String?
private func getAccessKey(completionHandler: (String?, NSError?) -> ()) -> () {
// If we already have an access key, call the completion handler with it immediately
if let accessKey = self._accessKey {
completionHandler(accessKey, nil)
} else { // Otherwise request one
Alamofire.request(.GET, MY_API_END_POINT).responseJSON {
(request, response, JSON, error) in
let info = JSON as? NSDictionary // info will be nil if it's not an NSDictionary
let accessKey = info?["access_key"] as? String // accessKey will be nil if info is nil or the value for "access_key" is not a String
self._accessKey = accessKey
completionHandler(accessKey, error)
}
}
}
public func somethingNeedingAccessKey() {
getAccessKey { (accessKey, error) in
if accessKey != nil {
// Use accessKey however you'd like here
println(accessKey)
} else {
// Handle error / nil accessKey here
}
}
}
}
With that setup, calling somethingNeedingAccessKey() the first time will trigger a request to get the access key. Any calls to somethingNeedingAccessKey() after that will use the value already stored in self._accessKey. If you do the rest of somethingNeedingAccessKey's work inside the closure being passed to getAccessKey, you can be sure that your accessKey will always be valid. If you need another function that needs accessKey, just write it the same way somethingNeedingAccessKey is written.
public func somethingElse() {
getAccessKey { (accessKey, error) in
if accessKey != nil {
// Do something else with accessKey
} else {
// Handle nil accessKey / error here
}
}
}