Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Input parameter to closure in Swift with brackets

Tags:

swift

rx-swift

I am going through the following tutorial on RxSwift:

http://adamborek.com/thinking-rxswift/

and having trouble understanding the following pattern:

           searchBar.rx.text.orEmpty
------------> .flatMap { [spotifyClient] query in
                   return spotifyClient.rx.search(query: query)
             }.map { tracks in
                   return tracks.map(TrackRenderable.init)
             }

This square brackets input parameter: [spotifyClient] query seems very weird for me. I looked over official Apple documentation for closures and functions and I can not see any examples of such input parameters. In Objective C this would not bother me much, but it is Swift. Could anyone explain, what this parameter means here?

like image 446
Nikita Vlasenko Avatar asked Apr 05 '18 01:04

Nikita Vlasenko


People also ask

How do you declare a closure variable in Swift?

Syntax Of Closure: We're declaring the closure on the first line, then assign it to the constant birthday , and call the closure on the last line. The closure now has one parameter of type String . It's declared within the closure type (String) -> () . You can then use the parameter name within the closure.

How do you pass closure in Swift?

If we wanted to pass that closure into a function so it can be run inside that function, we would specify the parameter type as () -> Void . That means “accepts no parameters, and returns Void ” – Swift's way of saying “nothing”.

What is $0 and $1 in Swift?

$0 and $1 are closure's first and second Shorthand Argument Names (SAN for short) or implicit parameter names, if you like. The shorthand argument names are automatically provided by Swift. The first argument is referenced by $0 , the second argument is referenced by $1 , the third one by $2 , and so on.


1 Answers

You will need to understand the variable capturing of closure idea.

Consider this example:

struct Calculator {
    var a: Int
    var b: Int

    var sum: Int {
        return a + b
    }
}

Then you use this as:

let calculator = Calculator(a: 3, b: 5)

// You define a closure where you will use this calculator instance
let closure = {
    // closure captures the variables that are declared prior to the declaration of the closure.
    // your calculator instance is being captured here
    // it's default variable capture
    print("The result is \(calculator.sum)")
}

closure() // Prints "The result is 8"

Till now, everything is okay. You get what's expected.

Now consider you declare the calculator instance as var because in some point you need to mutate it's state. This is the case where complexity arises. Look:

var calculator = Calculator(a: 3, b: 5)

let closure = {
    print("The result is \(calculator.sum)")
}

// You change the state of your calculator instance anytime before the closure gets executed
calculator.b = 20
// When the closure actually executes, you will be affected by any changes outside the closure 
closure() // Prints "The result is 23"

So, the default variable capture isn't really helping you, instead it's creating problem in your case.


If you want to prevent this behaviour and print 8 even if the properties change after their capturing inside the closure, we can explicitly capture the variable with a capture list like this:

// [calculator] is your capture list
let closure = { [calculator] in
    print("The result is \(calculator.sum)")
}
// change anything with calculator instance
calculator.b = 20
// execute the closure
closure() // Prints "The result is 8"

Capture List keeps immutable copy of the variable(s). Thanks to this copy, further changes to calculator, outside the closure, will not affect the closure.

You can capture multiple variables at once, hence it's called Capture List. Example:

let closure = { [variable1, variable2, variable3] in
    print(variable1)
    print(variable2)
    print(variable3)
}

I recommend you read this article Capturing Values In Swift Closures.



Now, in your case spotifyClient is an instance of a class that may be responsible to make API calls. This instance may need some changes for calling different APIs. So, to prevent the affect of any changes to spotifyClient outside this closure you capture this instance in a Capture List.



Capture List vs. Parameter List:

You are confusing the parameter list with the capture list. The generic syntax is:

{ [capture list] (parameter list) in
    ...
    ...
}

Now take a look at the modified version of the above example:

let closure: (String)-> Void = { [calculator] stringParameter in // When using single parameter, you can always omit the () parentheses
    print("\(stringParameter). The result is \(calculator.sum)")
}

// change anything with calculator instance
calculator.b = 20
// execute the closure
closure("Hey") // Prints "Hey. The result is 8"
like image 104
nayem Avatar answered Oct 20 '22 05:10

nayem