Shorter ways to specify completion handler - ios

I've had this problem a couple of times now, so thought I'd reach out.
I have a number of network interfaces responsible for making Async network calls, there's about 5/6 functions in each interface which all use a completion handler with the same definition:
(success: Bool, resources: [String: AnyObject] -> Void)
I'm looking for an alternative to adding this to the end of every function as it forces every function declaration onto 2/3 lines. e.g.
func performSomeNetworkCall(withThisParam parm1: AnyObject, param2: AnyObject, completion: (success: Bool, resources: [String: AnyObject] -> Void)) {
}
I've had a couple of ideas about how to solve the problem of long declarations and rewriting the completion definition for every function.
Thought One
Using a typealias to define the closure like so:
typealias CompletionHandler = (success: Bool, resources: [String: AnyObject] -> Void)?
This, in theory, solves both issues as it's quick to write and solves the code length problems. However when calling function from an external source, the typealias doesn't autocomplete like regular closures, meaning you have to write it out every-time leading to mistakes.
Thought Two
Using a code snippet (when Xcode actually remembers you set them). This in theory works as well, as long as every other developer working on the code also has the same code snippet and it aware of it instead of writing the entire declaration.
Thought Three
I thought about defining the completion handler as a function and passing the function as the parameter, something like this:
func completionHandler() -> (success: Bool, resources: [String: AnyObject] -> Void) {
return completionHandler()
}
But haven't been able to achieve what I wanted with that.
Edit
What I was hoping to achieve with Thought Three
func completionHandler() -> ((success: Bool, resources: [String: AnyObject]) -> Void) {
return completionHandler()
}
func networkCall(completion: (success: Bool, resources: [String: AnyObject]) -> Void) {
let request = NSURLRequest(URL: NSURL(string: "http://www.google.co.uk")!)
let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
let handler = completionHandler()
handler(success: true, resources: [String: AnyObject]())
}
}
func foo() {
let handler = completionHandler()
networkCall(handler)
print(handler)
// hope to use handler.success || handler.resources here
}
Although currently just gets stuck on the completionHandler method call - (To point out the obvious...)

A typealias is the typical solution for this. It should autocomplete even outside of the original definition's file. I think the issue is in your definition of your typealias. If you define it like this (pay attention to the parentheses and no optional):
typealias CompletionHandler = (success: Bool, resources: [String: AnyObject]) -> Void
func performSomeNetworkCall(withThisParam parm1: AnyObject, param2: AnyObject, completion: CompletionHandler?) {
// Implementation
}
Then autocompleting it works like so:
After hitting the return key:

I'm looking for an alternative
IMHO, the better alternative to write much more concise code would utilise "Promises" or "Futures". These are not yet part of the Swift Standard Library, but other languages do have one or the other implementation - and there are already third party libraries for Swift.
For example, with "Scala-like" Futures your code may look as follows:
func performSomeNetworkCall(
withThisParam parm1: AnyObject,
param2: AnyObject)
-> Future<[String: AnyObject]>
So, instead of a completion handler, the asynchronous function returns a Future. The future is a generic class, whose type parameter equals the computed value. A future can also represent an error, if the underlying asynchronous function fails.
You obtain the eventual result though registering a continuation via a combinator function:
let future = performSomeNetworkCall(param1, param2: param2)
future.map { computedValue in
print("Result: \(computedValue)")
}
.onFailure { error in
print("Error: \(error)")
}
Here, map is a combinator function with which we can register a continuation. A combinator returns a new future based on the return value of its continuation, and thus we can combine futures which perform more complex tasks.
The continuation function will be called when the future has been successfully completed. It get's passed the value of the underlying asynchronous function as its parameter. The continuation returns a value or Void.
Another combinator is flatMap. As opposed to map, it's continuation just returns another future:
login().flatMap { token in
fetchUser(id: userId).map { user in
print("User: \(user)")
}
}.onFailure { error in
print("Error: \(error)")
}
The function login is an asynchronous function. When it completed successfully, it calls the asynchronous function fetchUser in its continuation. When fetchUser completes successfully, it prints the obtained user object.
If anything fails, the error will be propagated to the "failure handler" which has been registered with onFailure.

Related

Swift optional completion handler

I am trying to create a service object in my Swift application to handle requests a bit easier. I've got most of it wired up, however I might be misunderstanding completion handlers.
I have this function that simply posts to a local API endpoint I have running.
func createList(name: String, completion: #escaping (Response) -> Void) {
let parameters = ["name": name, "user_id": session.auth.currentUser!.uid]
AF.request("\(url)/wishlist", method: .post, parameters: parameters, encoding: URLEncoding.default).responseDecodable(of: Response.self) { response in
switch response.result {
case .failure(let err):
print(err)
case .success(let res):
completion(res)
}
}
}
All that needs to happen is I need to pass that name to the function which I do here
barback.createList(name: name) -> after a button is tapped
However, I am now getting this error.
Missing argument for parameter 'completion' in call
My goal here is to just return the Response object so I can access attributes on that to do certain things in the UI. I was not able to return res here because, from my understanding, this is an async request and it's actually returning from that competition handler? (could be butchering that). The way I saw others doing this was by adding a competition handler to the params and adding an escape to that.
My end goal here is to do something like...
if barback.createList(name: name).status = 200
(trigger some UI component)
else
(display error toast)
end
Is my function flawed in it's design? I've tried changing my competition handler to be
completion: (#escaping (Response) -> Void) = nil
but run into some other errors there. Any guidance here?
Completion handlers are similar to function return values, but not the same. For example, compare the following functions:
/// 1. With return value
func createList(name: String) -> Response { }
/// 2. With completion handler
func createList(name: String, completion: #escaping (Response) -> Void) { }
In the first function, you'd get the return value instantly.
let response = barback.createList(name: name)
if response.status = 200 {
/// trigger some UI component
}
However, if you try the same for the second function, you'll get the Missing argument for parameter 'completion' in call error. That's because, well, you defined a completion: argument label. However, you didn't supply it, as matt commented.
Think of completion handlers as "passing in" a chunk of code into the function, as a parameter. You need to supply that chunk of code. And from within that chunk of code, you can access your Response.
/// pass in chunk of code here
barback.createList(name: name, completion: { response in
/// access `response` from within block of code
if response.status = 200 {
/// trigger some UI component
}
})
Note how you just say barback.createList, not let result = barback.createList. That's because in the second function, with the completion handler, doesn't have a return value (-> Response).
Swift also has a nice feature called trailing closure syntax, which lets you omit the argument label completion:.
barback.createList(name: name) { response in
/// access `response` from within block of code
if response.status = 200 {
/// trigger some UI component
}
}
You can also refer to response, the closure's first argument, by using $0 (which was what I did in my comment). But whether you use $0 or supply a custom name like response is up to you, sometimes $0 is just easier to type out.
barback.createList(name: name) {
/// access $0 (`response`) from within block of code
if $0.status = 200 {
/// trigger some UI component
}
}
Calling createList would look something more like this:
barback.createList(name: name) { response in
if response.status == 200 {
// OK
} else {
// Error
}
}
This fixes the issue because you are now running this completion closure - { response in ... } - where response is the value you pass in. In this case, you pass in res. See this post about using completion handlers.
If you did want an optional completion handler so you don't always need to include it, you could change the definition to the following (adding = { _ in }, meaning it defaults to an empty closure):
func createList(name: String, completion: #escaping (Response) -> Void = { _ in })
Another way is actually making the closure optional:
func createList(name: String, completion: ((Response) -> Void)? = nil)
And then inside the method you need ? when you call completion, since it's optional:
completion?(res)
Use the completion handler as mentioned in the comments and answers.
In addition you should include a completion when it fails, otherwise you
will never get out of that function.
I would restructure your code to cater for any errors that might happens, like this:
func createList(name: String, completion: #escaping (Response?, Error?) -> Void) {
let parameters = ["name": name, "user_id": session.auth.currentUser!.uid]
AF.request("\(url)/wishlist", method: .post, parameters: parameters, encoding: URLEncoding.default).responseDecodable(of: Response.self) { response in
switch response.result {
case .failure(let err):
print(err)
completion(nil, err)
case .success(let res):
completion(res, nil)
}
}
}
call it like this:
barback.createList(name: name) { (response, error) in
if error != nil {
} else {
}
}
If you do not put a completion(...) in your "case .failure" it will never get out of there.

CompletionHandler Behavior on Main vs Background thread

Does the thread from which a method is being used matter in terms of completionHandlers? When running on the Main thread, I have no problems; the (data) in completionHandler is called and thus the function's completionHandler within that.
The following is my code:
static func getInfo(user: User,completion: (([String:String]) -> Void)? = nil){
print(user.apiUid)
var enrolledCourses: [String:String] = [:]
NetworkingManager.staticGeneralRequest(withEndpoint: someURL, oauthToken: user.oauth_token, oauthTokenSecret: user.oauth_token_secret) { (data) in
let decodedData: [String:String] = data.getData()
completion?(decodedData)
}
}
//NOTE: According to the compiler,
//the use of an optional completionHandler means that it's `#escaping` by default.
Is this an issue pretaining to threading, and what are some best practices to ensure that code works well across threads?
Your closure was deallocated before you get the response. Adding #escaping like this: static func getInfo(user: User, completion: #escaping (([String:String]) -> Void)? = nil) to keep it in scope.

How to avoid Memory Leaks due to AFNetworking in swift

Im using AFNetworking in my project. When I try to find the memory leaks using instruments, its showing AFNetworking as reasons for memory leak. I'm creating a model class and within that class I'm calling the AFNetworking. Call to API is done from Viewcontroller to model class.Please help me how to fix this issue.
My coding pattern is adding below.
Class myViewController:UIViewcontroller{
override func viewDidLoad() {
super.viewDidLoad()
//apiCall
myModel.apiCallFuncT("value"){ (success) in
}
}
}
Class myModel{
var somevariable:String
class apiCallFuncT(parameters:string,completionHandler:#escaping(_ success:Bool)->Void){
//here Im calling the AFNetworking class. Im adding the call below.
ServiceManager.getDataFromNewServiceSolOne(serviceName: URL + "\(apiName)", parameters: param , success: { (result) in
completion(true)
})
}
//This is the serviceManger I'm calling from all models I have for api Calls.
class ServiceManager: NSObject {
static var sharedManager = AFHTTPSessionManager()
class func getDataFromNewServiceSolOne(serviceName: String, parameters : [String : String]?, success:#escaping (_ result: AnyObject) -> Void, failure :#escaping (_ error : NSError) -> Void) {
let manager = AFHTTPSessionManager(baseURL: NSURL(string: URL)! as URL)
manager.responseSerializer.acceptableContentTypes = ["text/html", "application/json"]
manager.post(serviceName, parameters: parameters, progress: { (progress) in
}, success: { (task, result) in
success(result as AnyObject)
}) { (task, error) in
failure(error as NSError)
}
}
}
To diagnose these issues, the “Debug Memory Graph” feature with “Malloc Stack” option is the first tool for which we reach. See How to debug memory leaks when Leaks instrument does not show them?
Anyway, when we run that, we can see that the problem is not in your use of the API. You don’t have any strong reference cycles in your code. In this example, we don’t see any of our app’s objects (other than app and scene delegates) in the panel on the left (because I dismissed the custom view controller that initiated the request). So that’s great.
So, what’s going on? The issue is the cycle between AFHTTPSessionManager and its NSURLSession. Unlike most delegates, NSURLSession keeps a strong reference to its delegate. As the documentation says:
The session object keeps a strong reference to this delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session, your app leaks memory until it exits.
So, you have two possible solutions:
Invalidate the session when the request is done. Call finishTasksAndInvalidate:
class func getDataFromNewServiceSolOne(serviceName: String, parameters: [String: String]?, success: #escaping (_ result: AnyObject) -> Void, failure: #escaping (_ error: NSError) -> Void) {
let manager = AFHTTPSessionManager(baseURL: URL(string: baseUrlString))
manager.post(serviceName, parameters: parameters, headers: nil, progress: { _ in
// this is intentionally blank
}, success: { task, result in
success(result as AnyObject)
}, failure: { task, error in
failure(error as NSError)
})
manager.session.finishTasksAndInvalidate() // add this line
}
The other approach is to not create a separate AFHTTPSessionManager for every request, but rather instantiate it once and then store it in a property. Then you don’t need to worry about invalidating it. Also, there is a certain amount of overhead in creating sessions, so we often instantiate a single session, and then perform many requests using that. And then you don’t have to worry about creating and invalidating a new session for every request.

How do I get WKWebView.evaluateJavaScript to return data in a function call

I'm working on some WKWebView parsing routines. I'm trying to validate that I've navigated to a page properly by checking it's document.title. I wrote a function to do this work, but I can't seem to figure out how to return the HTML data from the function or do the evaluation in the function and return a BOOL. I know I'm doing an async call here, but not sure how to wait for that call to end and feed the response back from my function call.
Here is my function:
func checkTitle (forWebView webView: WKWebView, title: String) -> String{
webView.evaluateJavaScript("document.title", completionHandler: { (innerHTML, error ) in
let titleString = innerHTML as? String
return (titleString)
})
This throws a compiler error. I've tried to declare the variable outside the call and then assign and return it after, but it tries to execute that before the async call is complete.
you should use a completion handler, something like this:
func checkTitle (forWebView webView: WKWebView, title: String, completion: #escaping (_ titleString: String?) -> Void) {
webView.evaluateJavaScript("document.title", completionHandler: { (innerHTML, error ) in
// Logic here
completion(innerHTML as? String)
})
}

Swift 3 :Closure use of non-escaping parameter may allow it to escape

I have the following function where I have completion handler but I'm getting this error:
Closure use of non-escaping parameter may allow it to escape
Here is my code:
func makeRequestcompletion(completion:(_ response:Data, _ error:NSError)->Void) {
let urlString = URL(string: "http://someUrl.com")
if let url = urlString {
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, urlRequestResponse, error) in
completion(data, error) // <-- here is I'm getting the error
})
task.resume()
}
}
Any of you knows why I'm getting this error?
I'll really appreciate you help
Looks like you need to explicitly define that the closure is allowed to escape.
From the Apple Developer docs,
A closure is said to escape a function when the closure is passed as an argument to the function, but is called after the function returns. When you declare a function that takes a closure as one of its parameters, you can write #escaping before the parameter’s type to indicate that the closure is allowed to escape.
TLDR; Add the #escaping keyword after the completion variable:
func makeRequestcompletion(completion: #escaping (_ response:Data, _ error:NSError)->Void) {
let urlString = URL(string: "http://someUrl.com")
if let url = urlString {
let task = URLSession.shared.dataTask(with: url, completionHandler: { (data, urlRequestResponse, error) in
completion(data, error) // <-- here is I'm getting the error
})
task.resume()
}
}
An "escaping" closure is a closure that can outlive the scope that it was created in. Escaping closures require special care around reference counting and memory management and can be harder to optimize.
Prior to Swift 3, the default for closures was to assume that they were escaping. This meant that developers had to specifically identify closures that are known not to escape to allow the compiler to make optimizations. The community found that in fact, the compiler could easily find out by itself if a closure is escaping or not, and decided that an aggressive approach to escaping could result in faster code. The result is that closures are now assumed to be non-escaping, and you need to flag closures that are escaping with the #escaping attribute.
In your case, the closure that URLSession.shared.dataTask accepts is itself an escaping closure, so if you use a closure inside of it, it also needs to be marked #escaping.
#escaping is infectious to all calling methods, and the compiler determines when you must include it.
Consider this example (which compiles):
dispatchSometime( { print("Oh yeah") })
func dispatchSometime(_ block: ()->()) {
dispatchNow(block)
}
func dispatchNow(_ block: ()->()) {
block()
}
This modified example, however, produces two errors of type non-escaping parameter may allow it to escape:
dispatchSometime( { print("Oh yeah") })
func dispatchSometime(_ block: ()->()) {
dispatchLater(block)
}
func dispatchLater(_ block: ()->()) {
DispatchQueue.main.async(execute: block)
}
The dispatch on main means the dispatchLater method needs #escaping, and once you've added that, the dispatchSometime method also requires #escaping for the example to compile.
dispatchSometime( { print("Oh yeah") })
func dispatchSometime(_ block: #escaping ()->()) {
dispatchLater(block)
}
func dispatchLater(_ block: #escaping ()->()) {
DispatchQueue.main.async(execute: block)
}
However, the take away is just:
Keep adding #escaping up the call chain until the compiler stops complaining.
The keyword doesn't change anything: it's a warning which says, essentially, "be careful to use weak with captured variables as they may be retained along with the block itself."
Implications
The really fun case with this is where you have to adjust several methods to include the #escaping keyword, which gets the compiler to stop complaining. However, if those methods are actually conforming to a protocol, that protocol's methods must also get the #escaping keyword, which also infects all other protocol conformants. Fun!

Resources