0

I’m using the FileProvider library to look for files on my FTP server and Swift 5’s basic functionality to look for files in the “Documents“ folder of the device:

func lookForFiles() { //runs on a background thread
    var ftpExists = false
    var localExists = false

    let ftpFile:FileObject = lookForFTPFile()
    let localFile:FileObject = lookForLocalFile()

    //Compare,... files
}

func lookForFTPFile() -> FileObject? {
    var found:FileObject?

    ftpProvider?.contentsOfDirectory(path: mypath, completionHandler: { (contents, error) in
        //Look for ftp file
    }) //This is run in an async task according to documentation

    return found
}

This of course always returns "nil" because of the task in "contentsOfDirectory" (I also can't return the file from within).

Question: How do I wait for lookForFTPFile to finish before returning the result (which might be nil because it simply didn't find anything) - without just setting up a timer? I'd prefer to not mess with how the library sets up its asynchronous work.

Something like

var waitingbool = false
var found:FileObject?

func lookForFiles() { //runs on a background thread
    //Rest of code
    lookForFTPFile()
    while !waitingbool {}
    //Use "found"
}

func lookForFTPFile() {
    ftpProvider?.contentsOfDirectory(path: mypath, completionHandler: { (contents, error) in
        //Look for ftp file and save result in "found"
        self.waitingbool = true
    }) 
}

looks like might work but at the same time it seems to be breaking a lot of unwritten rules.

Neph
  • 1,823
  • 2
  • 31
  • 69
  • My question isn't a duplicate because I'm using a downloaded library, which I can't just edit to make `contentsOfDirectory` work better with my code, while the author of the other question created his own API (with his own task), which he can change however he likes. – Neph May 27 '19 at 12:13
  • The question **is** a duplicate. You cannot return something from a method which executes an asynchronous task and `while` loops to wait are horrible. The concrete API is irrelevant. The pattern of asynchronous data processing is always the same. – vadian May 27 '19 at 12:32
  • It's about the same problem but the solution could have been completely different: The author of the other question could have changed his API or the task he created himself to work with his other code, while I can't and also specifically asked for a solution that doesn't involve changing it. – Neph May 27 '19 at 12:52
  • The solution there and here is to add a completion handler which is (also) called asynchronously. Once again, the API is irrelevant. – vadian May 27 '19 at 12:54
  • If it's your own API a different solution could be to change it, so the API func is being called asynchronously from the outside instead from the inside. This would be a working solution for the other question but not for mine because I don't "know" the code of the library and won't mess with it either (which the author of the other question didn't specifically rule out). So yes, the "changeability" of the library makes it relevant. Adding an additional completion handler is an easier solution but the only proper one that'll work in my case. – Neph May 27 '19 at 13:08
  • *Adding an additional completion handler is **the*** (one and only reasonable) *solution*. – vadian May 27 '19 at 13:14

1 Answers1

2

Everyone who hasn't done async in Swift runs into the same problem. If you return a value from a method without a closure (as you are doing it), it must return sync. Since your completion handler runs async as you have noticed, we have a problem. You shouldreturn a value from a async method with a completion handler block.

I would have rewritten your method as follows:

func find(content: @escaping (FileObject?) -> ()) {
    var found: FileObject?

    // init the found variabel somewhere

    ftpProvider?.contentsOfDirectory(path: mypath, completionHandler: { (contents, error) in
    // You are in a closure completion block here also!

    // send a callback to our waiting function...
    content(contents)
    })
    // If ftpProvider is nil, please call the content completion handler block with nil also!
}

Calling side:

find { contents in // Capture self as unowned/weak maybe?
     // Use contents.
}
vadian
  • 274,689
  • 30
  • 353
  • 361
J. Doe
  • 12,159
  • 9
  • 60
  • 114
  • Caught me, I indeed haven't worked with threading in Swift yet. I have basic understanding of threads (from other programming languages) and I know that `DispatchQueue.main.async` returns work back to the main thread (e.g. for UI related stuff) but that's it. So your suggestion is basically creating a completion handler within a completion handler? How do I get the `FileObject` from "contents"? – Neph May 27 '19 at 12:12
  • Got it, thanks! I just checked: `contents` is a list of all files for "mypath" and I'm only using it to loop through the folder. So instead of returning that one I'm still searching through the folder in the extra `lookForFTPFile` func (but with the additional `@escaping`) and break if I find the right file, then call `content(found)` once I'm back outside the for loop. Btw, your code is missing 2 round brackets ;): 1. After `((FileObject?) -> ())`, 2. After the closing curly bracket of the calling side. – Neph May 27 '19 at 12:39
  • I edited the answer to fix the parentheses/brackets issues. – vadian May 27 '19 at 12:41
  • Yea sorry, I don't have a compiler atm :) thx for fixing – J. Doe May 27 '19 at 12:44
  • @J.Doe I looked at what `unowned`/`weak` do but sorry, I'm still a bit confused about why I would need to add either to `contents`. My `contents` is a `FileObject` and I'm either saving its name in a `var` (`self.filename = contents!.name` - I check if `contents == nil` beforehand and call a different func afterwards) or passing the whole object to a func that does stuff with it (`self.doStuffWithFileObject(contents!)`). Neither of the funcs called returns anything but they call other funcs and eventually load a different `ViewController` with a segue. Do I need `unowned` and/or `weak` here? – Neph Jul 09 '19 at 12:08