Completion handler in Swift requires odd underscore characters - swift

I just created a method with a completion handler. I am unable to compile my application, unless I add annoying underscore characters to my method like so:
func searchForLocation(name: String, completion: #escaping (_ results: Array<Any>, _ error: NSError) -> ()) { }
Why can I just define my method as:
func searchForLocation(name: String, completion: #escaping (results: Array<Any>, error: NSError) -> ()) { }
This certainly makes no sense, because Apple are able to create their own completion handlers without the annoying underscore characters, for example:
open func dataTask(with url: URL, completionHandler: #escaping (Data?, URLResponse?, Error?) -> Swift.Void) -> URLSessionDataTask

Just change your function declaration as:
func searchForLocation(name: String, completion: #escaping (Array<Any>, NSError) -> ()) { ... }
This is because, you can't use named parameters in the closure.

Related

Swift: Unable to make UIApplication conform to my protocol

I'm trying to make UIApplication conform to the following protocol:
protocol URLOpenerProtocol {
func open(_ url: URL, completionHandler: ((Bool) -> Void)?)
}
I've added the following extension:
extension UIApplication: URLOpenerProtocol {}
However, I am getting a conforming error.
Given the UIApplication already has the following method on it:
UIApplication.shared.open(_ url: URL, completionHandler: ((Bool) -> Void)?)
why won't this conform? The two seem to be indentical to me...
Error is as follows:
Type UIApplication does not conform to protocol URLOpenerProtocol
The actual open method in UIApplication is declared like this with 3 parameters, and the second and third ones happen to be optional (source):
func open(
_ url: URL,
options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:],
completionHandler completion: ((Bool) -> Void)? = nil
)
UIApplication has no two-parameter method with this signature:
func open(_ url: URL, completionHandler: ((Bool) -> Void)?)
It only has the three-parameter one. Therefore, it does not conform to your protocol.
You should add the options parameter into your protocol too, so that UIApplication can conform to it, and I would suggest that you add a protocol extension with the two-parameter version:
protocol URLOpenerProtocol {
func open(_ url: URL,
options: [UIApplication.OpenExternalURLOptionsKey : Any],
completionHandler: ((Bool) -> Void)?)
}
extension URLOpenerProtocol {
func open(_ url: URL,
completionHandler: ((Bool) -> Void)?) {
open(url, options: [:], completionHandler: completionHandler)
}
}

ParseSwift.initialize shows Cannot convert value of type error

I figure that it is time to start using Swift so I am working on a complete rewrite of an obj C app. This app uses Parse Server and CocoaPods. Following the ParseSwift documentation example in my AppDelegate.swift I added
import ParseSwift
class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
ParseSwift.initialize(applicationId: "AppID", clientKey: "", serverURL: URL(string: "https://server")!, liveQueryServerURL: URL(string: "https://server")!, authentication: ((URLAuthenticationChallenge,(URLSession.AuthChallengeDisposition,URLCredential?) -> Void) -> Void))
and so on ... But the initialize line is showing an error
Cannot convert value of type '((URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void).Type' to expected argument type '((URLAuthenticationChallenge, (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)?'
Authentication is an optional field so you don't need to pass a value.
If you are not doing anything with authentication (most cases), you shouldn't pass anything:
ParseSwift.initialize(applicationId: "AppID",
clientKey: "",
serverURL: URL(string: "https://server")!)
When you plan to use authentication for certificate pinning, you should use it as a trailing closure:
ParseSwift.initialize(applicationId: "AppID",
clientKey: "",
serverURL: URL(string: "https://server")!) { (challenge, credential) in
credential(.performDefaultHandling, nil)
}

Extend completionHandler of Document.close

I'd like to extend the Document.close function to first run some of my own code when the completionHandler triggers before calling the completionHandler that was passed to the function by the user. So something like this:
override func close(completionHandler: ((Bool) -> Void)? = nil) {
super.close(completionHandler: { (success) in
// TODO: do something AFTER writeContents has completed
// TODO: call completionHandler
})
}
So far so good, but I'm not able to call the completionHandler that was passed to the function. Using completionHandler() causes the compiler to complain about Cannot call value of non-function type '((Bool) -> Void)?'. A few StackOverflow searches later it sounds like we have to use the #escaping annotation:
override func close(completionHandler: #escaping ((Bool) -> Void)? = nil) {
super.close(completionHandler: { (success) in
// TODO: do something AFTER writeContents has completed
// TODO: call completionHandler
})
}
but the compiler complains again: #escaping attribute only applies to function types. This leads us to other questions like Swift 3 optional escaping closure parameter, but we are not able to change the function definition because it's overriding an (Objective-C) system library.
Am I misunderstanding something? If not, is there a way to workaround this limitation?
You can pass completion as below,
class Document: UIDocument {
override func close(completionHandler: ((Bool) -> Void)? = nil) {
let completion: ((Bool) -> Void)? = { success in
print("I got \(success)")
// TODO: do something AFTER writeContents has completed
//....
//....
//....
// TODO: call completionHandler
completionHandler?(success)
}
super.close(completionHandler: completion)
}
}

Optional #escaping Closure?

I want to use Optional #escaping closures to execute something when the closure is called in the function, but if i make it optional like that:
func checkForFavorites(_ completion: (#escaping (Bool) -> Void)?) {
}
i get this error
#escaping attribute may only be used in function parameter position
This may be an easy question so please have understanding

How to create a completion handler in Swift which I would call optionally?

Currently, I have a completion handler:
open func Start(completion: (() -> Void)) { ... }
but in this case I have to always call the completion.
How can I make an optional, so in some method I would use completion block but in others I would just skip them and do not add to my method calls?
For example, I want the same as in:
self.present(<#T##viewControllerToPresent: UIViewController##UIViewController#>, animated: <#T##Bool#>, completion: <#T##(() -> Void)?##(() -> Void)?##() -> Void#>)
I've tried
open func Start(completion: (() -> Void)? = nil) { ... }
adding question mark, but in this case I have to call an optional completion block
completion?()
and I cannot call simply
start()
where I do not need in completion block. It requires me to call it
You can make it an Optional Parameter with nil value by default:
open func Start(completion: (() -> Void)! = nil) {
guard completion != nil else {
return
}
completion()
}
Inside some other method:
func foo() {
Start()
Start(completion: nil)
Start(completion: {
// some code
})
Start {
// some code
}
}
You can use a default value that is not nil, e.g. a block that does nothing
open func start(completion: #escaping (() -> Void) = {}) {
}
However, I don't see what problem you have with calling completion?().