How to call function with completion handler and parameters - swift

I have a func getData(completed: #escaping ()->()) that creates alamofire request with completion handler in one class. When alamofire ends it job, inside of this function i call completed() to notify that function ended its work. This func is called in other viewcontroller after button tap, but without completion handler, and then inside of this call i reload tableview with downloaded data as below.
Now i want to add to this func additional parameters to allow user modify URL of alamofire request, and get custom response. Parameters will be setted in other textfields. But now when i call downloadRepositories() i can't omit calling completion parameter.
How can i avoid calling completion handler in it or what other
completion handler should i implement?
Current alamofire request
class DataClass {
func getData(completed: #escaping () -> ()){
//alamofire request
Alamofire.request(url).responseJSON{
//reponse
completed()
}
}
And it's implementation
class OtherVC {
var dataClass = DataClass()
#objc func searchBtnTapped(sender: UIButton!){
dataclass.getData(){
self.TableView.reloadData()
}
}
}
What i would like to do
class DataClass {
func downloadRepositories(completed: #escaping () -> (), parameter1: String, parameter2: String) {
let parameters: Parameters = [ "parameterA": parameter, "parameterB": parameter2 ]
Alamofire.request(url, parameters: parameters).responseJSON{
//response
completed()
}
Implementation of modified func
class OtherVC {
var dataClass = DataClass()
#objc func searchBtnTapped(sender: UIButton!){
dataclass.getData(parameter1: someTextField.text, parameter2: someTextField2.text){
self.TableView.reloadData()
}
}
}
Of course I know that is not possible to pass parameters in func call like this, but how can do this other way?

Use like this:
func downloadRepositories(parameter1: String, parameter2: String, completed: #escaping () -> ()) {
let parameters: Parameters = [ "parameterA": parameter, "parameterB": parameter2 ]
Alamofire.request(url, parameters: parameters).responseJSON {
//response
completed()
}

You are on the right track)
If you want call func like this:
dataclass.getData(parameter1: someTextField.text, parameter2: someTextField2.text){
self.TableView.reloadData()
}
You just need change parameters order like this:
func downloadRepositories(parameter1: String, parameter2: String, completed: #escaping () -> ())
If i understand right and you want use Trailing Closures, then that's all

Related

Is there a way to nullify a escaping closure without calling it?

I'm looking for a way to nullify an escaping closure without calling it.
func should(completion: #escaping () -> Void) {
if something {
completion = nil
} else {
completion()
}
}
As long as I understand, the escaping closure can be called after the should function is finished, it will alive until it is get called.
Is there a way to nullify the closure(and captured values) without calling it?
In your example, the closure is in fact not escaping, since you're not assigning it to anything outside the function, so there's no need to nullify:
func should(completion: () -> Void) {
if !something {
completion()
}
}
But if it was escaping, say by assigning it to a property, then you could nullify the property to release it:
class Foo {
let fn: (() -> Void)?
func should(completion: #escaping () -> Void) {
fn = completion
}
func executeAndRelease() {
fn?()
fn = nil
}
}

Having trouble returning a user token from StoreKit as a string

import StoreKit
class AppleMusicAPI {
let developerToken = "ABC123"
func getUserToken() -> String {
var userToken = String()
SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken) { (receivedToken, error) in
userToken = receivedToken!
}
return userToken
}
}
I'm trying to basically return the userToken by doing AppleMusicAPI().getUserToken() however nothing gets returned (literally just blank/empty).
How can I output the token as a string?
If you take a look a the method signature that you're calling, it looks like this:
open func requestUserToken(forDeveloperToken developerToken: String, completionHandler: #escaping (String?, Error?) -> Void)
Notice the #escaping keyword which indicates that the completion block is not necessarily called right away (asynchronous). To fix your issue, I'd suggest using something like this:
class AppleMusicAPI {
let developerToken = "ABC123"
func getUserToken(completion: #escaping (String?, Error?) -> Void) {
SKCloudServiceController().requestUserToken(forDeveloperToken: developerToken, completionHandler: completion)
}
}

Passing closure in userInfo to NotificationCenter, getting cryptic messages from compiler at runtime

I'm trying to pass a closure in userInfo through NotificationCenter. The closures works as expected but I'm getting weird messages that I do not understand in runtime. What is the cause of this message?
The message is:
0x000000010292b350 [ProjectName]`partial apply forwarder for reabstraction thunk helper from #escaping #callee_guaranteed () -> (#out ()) to #escaping #callee_guaranteed () -> () at <compiler-generated>
This is how I post the notification.
let closure: (() -> Void) = {
print("TEST")
}
NotificationCenter.default.post(name: .test,
object: nil,
userInfo: ["closure" : closure ])
This is how I consume the notificaiton:
#objc private func test(_ notification: Notification) {
let closure = notification.userInfo?["closure"] as? (() -> Void)
closure?()
}
I'm using Swift 5.
Unfortunately I can't reproduce the error, but I would suggest wrapping the closure in a wrapper class. Things that need to round-trip into and out of Cocoa do better when they are NSObjects:
class Wrapper : NSObject {
let closure : () -> Void
init(closure: #escaping () -> Void) {
self.closure = closure
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let closure: (() -> Void) = {
print("TEST")
}
let wrapper = Wrapper(closure: closure)
let test = Notification.Name("Test")
NotificationCenter.default.addObserver(self,
selector: #selector(testing), name: test, object: nil)
NotificationCenter.default.post(name: test,
object: nil, userInfo: ["wrapper" : wrapper ])
}
#objc private func testing(_ notification: Notification) {
let wrapper = notification.userInfo?["wrapper"] as? Wrapper
wrapper?.closure()
}
}

Custom protocols

Im getting this error "Class declaration cannot close over value 'viewcontainer' defined in outer scope"
I created a procotol called NetworkResponse which have two methods on sucessResponse and onErrorResponse.
Then I have a class called Callback that extends from NetworkResponse and forced to implement that methods.
Here is my function :
public func login (callback : Callback, viewController : UIViewController) {
let callbackInstance: NetworkResponse = {
class callback : Callback {
override func onSucessResponse(response : NSDictionary){
viewController.dismiss(animated: true, completion: nil)
}
override func onErrorResponse(message : String, code : Int){
print("error")
}
}
return callback()
}()
postPath(callback: callbackInstance as? Callback)
}
I want to dismiss the controller from the anonymous class.
Any recomendation ?
No need to define a protocol and Callback class. Closure is just what you need.
import UIKit
public class TestInnerClass: UIViewController {
public func login(successCallback: ((response: NSDictionary) -> Void), errorCallback: ((message: String, code: Int) -> Void)) {
let success = false
let response = NSDictionary()
//
// Make your login request here, and change the `success` value depends on your response
// let response = ...
//
// If you are making a async request to login, then put the following codes inside your request callback closure.
//
if success {
successCallback(response: response)
} else {
errorCallback(message: "error occurred", code: -1)
}
}
override public func viewDidLoad() {
super.viewDidLoad()
login({
(response) in
// Get Called when success
self.dismissViewControllerAnimated(true, completion: nil)
}, errorCallback: ({
// Get called when failed
(message, code) in
print(message)
}))
}
}
I have written some sample codes for your case in my Github repo, and this example is using Alamofire to make network request, Just for your reference.
PS: Since I still using Xcode 7.3.1, so you may need to do some changes to the above code to adopt the swift 3 syntax requirement.

Completion blocks return nothing

import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
isSuccess(true, success: { (name) -> String in
return "My name is \(name)"
})
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func isSuccess(val:Bool, success: (name: String) -> String) {
if val {
success(name: "Jacky")
}
}
}
I expect it to return string "My name is Jacky",but it didn't .But if I change the isSuccess to :
func isSuccess(val:Bool, success: (name: String) -> String) {
if val {
print(success(name: "Jacky"))
}
}
}
Then it worked properly, why is that? Thanks in advance!
Your completion block returns a String.
When you invoke it by calling
success(name: "Jacky")
the completion block returns the String My name is Jacky. But you do nothing with that string. You just returned it and never used it.
In your second example, you actually used it - you took the string from the completion block, and printed it.
For example, instead the print, you could also write
let stringFromCompletionBlock = success(name: "Jacky")
That way you can see that it indeed returned a value.
And another thing is that the completion block should be call as the last thing in the function - this way you "notify" that the function has finished it's purpose, so it's not reasonable to use the value returned from a completion block inside the same function which called that completion block
First of all the closure in the function isSuccess should be declared this way. The closure should not return a String, it should just accept a String as param.
func isSuccess(val:Bool, success: (name: String) -> ()) {
if val {
success(name: "Jacky")
}
}
Next you could use that value to update the UI like follow
class ViewController: UIViewController {
weak var label:UILabel!
override func viewDidLoad() {
isSuccess(true) { (name) -> () in
dispatch_async(dispatch_get_main_queue()) {
self.label.text = "My name is \(name)"
}
}
super.viewDidLoad()
}
func isSuccess(val:Bool, success: (name: String) -> ()) {
if val {
success(name: "Jacky")
}
}
}