2
votes

I am writing a Swift function using closure. A should-be-compilable code sample is like this,

import Foundation

typealias PKSynchronizeProgressBlock = (Double) -> Void
typealias PKSynchronizeCompletionBlock = (Bool, NSError?) -> Void

class X {

func synchronizeAppDataWithProgress(
    progress: PKSynchronizeProgressBlock?, 
    completion: PKSynchronizeCompletionBlock?) {
        dispatch_async(dispatch_get_main_queue(), {

                // Do a lot of downloading, and during the process
                // {
                // If progress is updated
                if (progress != nil) {
                    progress!(Double(0))
                }
                //
                // If something goes wrong
                if (completion != nil) {
                    completion!(false, nil)
                }
                // }
                dispatch_async(dispatch_get_main_queue(), {
                    if (completion != nil) {
                        completion!(true, nil)
                    }
                })
        })
}


func foo() {
    self.synchronizeAppDataWithProgress({ (progress: Double) -> Void in
        self.launchProgressBar.progress = progress
    }, completion: { (success: Bool, error: NSError?) -> Void in
        if success {
            self.launchProgressBar.progress = 1.0
        }
        else {
            print("Failed to synchronize app data with error %@", error!)
        }
    })
}

}

However, this code does not compile. Xcode says that

cannot invoke 'synchronizeAppDataWithProgress' with an argument list '(progress: (Double) -> Void, completion: (Bool, NSError?) -> Void)'

What should I do? Did I make any stupid mistake in my code?


Update:

Thanks to @Mario Zannone. I fixed the first two mistakes in my code above. That was: (1) I inserted a redundant progress: in the function call. I have removed that. (2) I updated UI in a thread other than main thread.

But the code still does not work if I don't comment out the following single line in the foo(),

self.launchProgressBar.progress = progress

Do you have any clue why?

1

1 Answers

0
votes

Xcode can be picky sometimes with the way arguments are listed inside closures. I've found it best to leave the type inferred. Also be sure to use capture lists to avoid strong reference cycles in your closures.

Using the Alamofire dependency, I've rewritten your code above and it compiles.

import Alamofire

typealias ProgressBlock = (Double) -> Void
typealias CompletionBlock = (Bool, ErrorType?) -> Void

class ExampleDataSource {
    func fetchData(progress: ProgressBlock?, completion: CompletionBlock?) {
        // Here we use the Alamofire Dependency for progress reporting simplicity.
        Alamofire.request(.GET, "https://www.myexampledomain.com")
            .progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
                // bytesRead, totalBytesRead, and totalBytesExpectedToRead are Int64
                // so we must perform unit conversion
                let progressPercentage = Double(totalBytesRead) / Double(totalBytesExpectedToRead)
                // here we optionally call the ProgressBlock 'progress' and pass it the progressPercentage
                progress?(progressPercentage)
            }
            .response { request, response, data, error in
                // here we usually parse the data, but for simplicity we'll
                // simply check to see if it exists.
                let completionFlag = (data != nil)
                // note that NSError? is interchangable with ErrorType?
                completion?(completionFlag, error)
        }
    }
    func performTasks() {
        // first we'll set up our closures...
        let progressBlock: ProgressBlock = { progress in
            // here we update the UI or whatever 
            // the nice thing about the Alamofire dependency is
            // that it's thread-safe :]
        }
        let completionBlock: CompletionBlock = { success, error in
            // here we do whatever we need to do when the
            // network operation finishes, or handle the 
            // errors appropriately
        }
        // then we'll pass them into our fetchData method
        fetchData(progressBlock, completion: completionBlock)
    }
}