📜 ⬆️ ⬇️

We use closures in Swift in full

Despite the fact that in Objective-C 2.0 there are closures (known as blocks ), previously Apple’s API reluctantly used them. Perhaps this is partly why many iOS programmers were happy to exploit third-party libraries, such as AFNetworking, where blocks are used everywhere. With the release of Swift, as well as the addition of new functionality to the API, working with closures has become extremely convenient. Let's look at what features their syntax has in Swift, and what tricks you can do with them.



We will move from simple to complex, from boring to fun. I apologize in advance for the abundant use of the mantras "function", "parameter" and "Double", but you cannot throw out the words from the song.

Part 1. Introduction


1.1. First Class Objects


To begin with, let us strengthen ourselves with the idea that in Swift functions are carriers of the proud status of first-class objects . This means that a function can be stored in a variable, passed as a parameter, returned as the result of another function. The concept of “type of function” is introduced. This type describes not only the type of the return value, but also the types of input arguments.
Suppose we have two similar functions that describe two mathematical operations of addition and subtraction:
func add(op1: Double, op2: Double) -> Double { return op1 + op2 } func subtract(op1: Double, op2: Double) -> Double { return op1 - op2 } 

Their type will be described as follows:
 (Double, Double) -> Double 

You can read it like this: “Before us is a type of function with two input parameters of type Double and a return value of type Double .”
We can create a variable of this type:
 //   var operation: (Double, Double) -> Double //      //   ,    - : for i in 0..<2 { if i == 0 { operation = add } else { operation = subtract } let result = operation(1.0, 2.0) // ""  println(result) } 

The code described above will be displayed in the console:
3.0
-1.0
')

1.2. Closures


We use another privilege of the first class object. Returning to the previous example, we could create such a new function that would accept one of our old functions like (Double, Double) -> Double as the last parameter. This is how it will look like:
 // (1) func performOperation(op1: Double, op2: Double, operation: (Double, Double) -> Double) -> Double { // (2) return operation(op1, op2) // (3) } 

Let's sort the tangled syntax into components. The function performOperation takes three parameters:

In its body, performOperation simply returns the result of executing the function stored in the operation parameter, passing the first two of its parameters to it.
So far, it looks confusing, and perhaps not even clear. A little patience, gentlemen.

Let us now pass as a third argument not a variable, but an anonymous function, enclosing it in curly {} brackets. The parameter passed in this way will be called the closure :
 let result = performOperation(1.0, 2.0, {(op1: Double, op2: Double) -> Double in return op1 + op2 // (5) }) // (4) println(result) //  3.0   

The code snippet (op1: Double, op2: Double) -> Double in is, so to speak, the “header” of the closure. It consists of:

Once again about what happened now, by points:
(1) The performOperation function is declared.
(2) This function takes three parameters. The first two are operands. The latter is the function that will be executed on these operands.
(3) performOperation returns the result of the operation.
(4) The function described by the closure was passed as the last parameter in performOperation .
(5) The body of the closure indicates which operation will be performed on the operands.

Part 2. Merry.
Syntactic sugar and unexpected "buns"


The authors of Swift have put a lot of effort into allowing users of the language to write as little code as possible and spend their precious time reading Habr as much as possible on the architecture of the project. Taking as a basis our example of arithmetic operations, let's see to what state we can “unleash” it.

2.1. We get rid of types by a call.

First, you can not specify the types of input parameters in the closure explicitly, since the compiler already knows about them. The function call now looks like this:
 performOperation(1.0, 2.0, {(op1, op2) -> Double in return op1 + op2 }) 

2.2. We use the tail closure syntax.

Secondly, if the closure is passed as the last parameter to the function, then the syntax allows you to shorten the record, and the closure code is simply attached to the call tail:
 performOperation(1.0, 2.0) {(op1, op2) -> Double in return op1 + op2 } 

2.3. Do not use the keyword "return".

A nice (in some cases) feature of the language is that if the closure code fits into one line, the result of the execution of this line will automatically be returned. Thus, the "return" keyword can be omitted:
 performOperation(1.0, 2.0) {(op1, op2) -> Double in op1 + op2 } 

2.4. We use shorthand names for parameters.

Go ahead. Interestingly, Swift allows the use of so-called shorthand (English shorthand) names for input parameters in the closure. Those. Each parameter is assigned an alias in the format $ n by default, where n is the sequence number of the parameter, starting from zero. Thus, it turns out that we don’t even need to invent names for the arguments. In this case, the entire "header" of the closure no longer carries any semantic load, and it can be omitted:
 performOperation(1.0, 2.0) { $0 + $1 } 

Agree, this record is not at all like the one that was at the very beginning.

2.5. Knight's move: operator functions.

These were all flowers. Now there will be a berry.
Let's look at the previous entry and ask ourselves what the compiler already knows about the closure? He knows the number of parameters ( 2 ) and their types ( Double and Double ). Knows the type of the return value ( Double ). Since only one line is executed in the closure code, he knows what he needs to return as the result of its execution. Is it possible to simplify this post somehow?
It turns out you can. If the closure only works with two input arguments, as a closure it is allowed to pass an operator function that will be executed on these arguments (operands). Now our call will look like this:
 performOperation(1.0, 2.0, +) 

Beauty!
Now you can perform elementary operations on our operands, depending on certain conditions, while writing a minimum of code.

By the way, Swift also allows you to use comparison operations as an operator function. It will look something like this:
 func performComparisonOperation(op1: Double, op2: Double, operation: (Double, Double) -> Bool) -> Bool { return operation(op1, op2) } println(performComparisonOperation(1.0, 1.0, >=)) //  "true" println(performComparisonOperation(1.0, 1.0, <)) //  "false" 

Or bit operations:
 func performBitwiseOperation(op1: Bool, op2: Bool, operation: (Bool, Bool) -> Bool) -> Bool { return operation(op1, op2) } println(performBitwiseOperation(true, true, ^)) //  "false" println(performBitwiseOperation(true, false, |)) //  "true" 


Swift is a fun programming language in some way. I hope the article will be useful for those who are starting to get acquainted with this language, as well as for those who are just wondering what is happening there with developers under iOS and Mac OS X.
___________________________________________________________________
UPD .: Real application

Some users complained about the lack of real-life examples. Just yesterday, I ran into a task that can be elegantly solved using short circuits.

If you need to create a priority queue, you can use the binary heap. As you know, it can be both MinHeap and MaxHeap, i.e. heaps, where in the root of the tree is the minimum or maximum element, respectively. The basic MinHeap implementation from MaxHeap will differ in essence only in check comparisons when restoring a binary heap invariant after adding / removing an element.

Thus, we could create the base class BinaryHeap , which will contain the property of comparison type (T, T) -> Bool . And the constructor of this class will accept the comparison method and then use it in the heapify methods. The base class prototype would look like this:
 class BinaryHeap<T: Comparable>: DebugPrintable { private var array: Array<T?> private var comparison: (T, T) -> Bool private var used: Int = 0 // -- // Internal Methods internal func removeTop() -> T? { //... } internal func getTop() -> T? { //... } // Public Methods: func addValue(value: T) { if used == self.array.count { self.grow() } self.array[used] = value heapifyToTop(used, comparison) //   ,     self.used++ } init(size newSize: Int, comparison newComparison: (T, T) -> Bool) { array = [T?](count: newSize, repeatedValue: nil) comparison = newComparison } } 

Now, in order to create the MinHeap and MaxHeap classes, we only need to inherit from BinaryHeap , and in their constructors it is easy to clearly indicate what comparison to use. This is how our classes will look like:
 class MaxHeap<T: Comparable>: BinaryHeap<T> { func getMax() -> T? { return self.getTop() } func removeMax() -> T? { return self.removeTop() } init(size newSize: Int) { super.init(size: newSize, {$0 > $1}) } } 

 class MinHeap<T: Comparable>: BinaryHeap<T> { func getMin() -> T? { return self.getTop() } func removeMin() -> T? { return self.removeTop() } init(size newSize: Int) { super.init(size: newSize, {$0 <= $1}) } } 

Source: https://habr.com/ru/post/241303/


All Articles