📜 ⬆️ ⬇️

“What's new in Swift 2?” With examples



Swift 2 focused on improving the language itself, interacting with Objective-C, and improving the performance of compiled applications. New features of Swift 2 are presented in 6 different areas:


I will consider the new features of Swift 2, accompanying them with examples whose code is on Github .

1. Fundamental language constructions


No more println ()


Usually we used the println () function to print a message on the console. In the Swift 2 version, we will use only print () . Apple combined the println () and print () functions into one. The print () function, by default, prints your message with a newline character "\ n". If you want, you can output a line without a newline:
')


map , filter and company


The definition of these convenient functions and methods through the collections in Swift 1.2 was not completely consistent. In Swift 1.2, there was no default implementation of the map method for the CollectionType protocol, since the default implementation of the protocol was not possible and extensions were made only for classes. Partly for this reason, map was defined as a method in the Array class (which implements the CollectionType protocol), and not defined in the Set class (which also implements the CollectionType protocol). In addition to this, the global map function was disclaimed, which took an instance of CollectionType as the first parameter. This created complete confusion.

// Swift 1.2 let a: [Int] = [1,2,3] //    map   Array let b = a.map{ $0 + 1 } //      map  map([1,2,3]) { $0 + 1 } let set = Set([1,2,3]) //  ,    map   Set set.map{ $0 + 1 } //   map   Set map(set) { $0 + 1 } 

It turned out that, depending on the type of collection, either the global map function is used, or the map method of this collection. It looks inconsistent and poorly readable if a chain of transformations is used using the methods and functions of map , filter and reduce .

Now, in Swift 2, protocol extensions are allowed, therefore map , filter & co are implemented at the protocol level for CollectionType , as protocol extensions. Therefore, the same methods will operate on Array , Set, or any other collection implementing the CollectionType protocol.

 // Swift 2 let a: [Int] = [1,2,3] let b = a.map{ $0 + 1 } let set = Set([1,2,3]) let anotherSet = set.map{ $0 + 1 } let sum = (1...100) .filter { $0 % 2 != 0 } .map { $0 * 2 } .reduce(0) { $0 + $1 } print(sum) // prints out 5000 

We see in the example above that filter now works on Range . This did not work in the previous version, because, although Range confirmed the CollectionType protocol, the filter method was not implemented. Now everywhere we have a much more understandable syntax of these methods for any collection.

Enum enums


In Swift 2, enum has sufficient reflection information to enable their printing.



The print (an) clause will now print Dragon correctly, although in the previous version of Swift, the output was completely non-informative (Enum Value) .

Another improvement regarding enum is that Swift now allows you to represent the associated values ​​of various types in enum . As an example, you can now legally present the Either type:



Now enum can be recursive, that is, we can build a tree using enum . Let's look at this example:



We must use the indirect keyword in front of case Node . And it allowed us to create a tree:



Here's what it looks like:



Now we can create a function that recursively traverses the entire tree and adds the numbers:



A result of 21 must be printed.

Diagnostics.


In addition to this, Swift 2 brought a huge number of improvements in diagnosing errors and assumptions to correct them, such as correctly defining a developer’s attempt to modify var using an immutable struct method, or when the var property never changes after initialization or when the result of a function call and etc.

One of the simplest changes makes the code more readable. As you know, Swift developers prefer to declare many things as constants, using let , rather than variables, using var . But what if you accidentally used the keyword var? Or did you think that you need to change it, but did not do it? Both Xcode 7 and Swift 2 will give you a warning that you don’t change this variable anywhere in your code - Xcode literally explores all uses of the variable and knows for sure whether you changed it or not.

Many options (Option Sets)


The set of options is a way of representing a set of Boolean values, and in Swift 1.x it looked like this:

 viewAnimationOptions = nil viewAnimationOptions = .Repeat | .CurveEaseIn | .TransitionCurlUp if viewAnimationOptions & .TransitionCurlUp != nil { ... 

This type of syntax was widely used in Cocoa, but in reality, it is only a “relic” of the C language. So, in Swift 2, it is removed and its own type is presented for many options, it is the OptionSetType protocol:



So now the set of options can be any type of Set or struct , confirming the OptionSetType protocol. This leads to a clearer syntax when using multiple options:



The syntax does not rely on “bit” operations, as in previous versions, and does not use nil to represent an empty set of options.

It should be noted that the many OptionSetType options now rely on another feature in Swift 2, called the “default” implementation implementations for protocol extensions, so simply confirming the OptionSetType protocol, you get a default implementation, for example, for the method contains , subtractInPlace , unionInPlace and other set operations. We will look at protocol extensions later.

Functions and methods


The Swift 1.x syntax for declaring functions and methods was inherited from two different conventions originating from C, where the function arguments have no labels, and Objective-C, which labels the methods arguments. So you had such declarations:

 func save(name: String, encrypt: Bool) { ... } class Widget { func save(name: String, encrypt: Bool) { ... } save("thing", false) widget.save("thing", encrypt: false) 

In Swift 2, the above code would look like this:



So the functions get the same convention as the methods:


However, these changes do not apply to functions imported from the C and Objective-C APIs.

Additionally, the parameter label declaring model has become more convenient since the #option option, which was used in Swift 1.x to denote a parameter with the same internal ( internal ) and external ( external ) name, has been removed.

Scoping Operators


The new do clause allows developers to explicitly define the explicit scope of variables and constants. This can be useful for re-using already declared names or for early release of some resources. The do clause looks like this:



In order to avoid ambiguity with the do ... while clause, which is presented in earlier versions of Swift 1.x, the latter was renamed to repeat ... while in Swift 2.

UNIT testing


The problem with unit-testing Swift 1.x code is that Swift 1.x made you mark the public word with all that you want unit-testing to see. As a result, there are public notes where they should not be. All this is due to the fact that Test Target is different from Application Target , and files from your application that are internal are not available for Test Target .

In Swift 2, substantial unit relief has been achieved. Xcode 7 automatically compiles Swift 2 code in a special "test" mode



to access all internal definitions as if they are defined as public . This is done using the @testable attribute when importing our module.



That's all it takes, and you don't need to mark anything with the word public .

Moreover, these changes do not affect the main release of your application, maintaining the correct behavior both in terms of performance and in terms of access control.

2. Control the order of calculations


Swift 2 introduced new concepts for managing the order of computation, and improved existing structures.

Offer guard


The guard clause, as well as the if clause, executes the code depending on the Boolean value of the conditional expression. You use the guard clause to, if the Boolean value is true , continue the code following the guard clause.

A guard clause is essentially the inverse of an if clause. For if, we write:

 if condition { // true  } else { // false  } 

For guard , true the branch rises to a higher level compared to the false branch:

 guard condition else { // false  } // true  

Notice that the false branch should end execution in a closed context (scope), returning a value or “throwing” (throw) an error. You guarantee that the code in the true branch will be executed only if the condition is met.

This makes guard a natural way to check non-fatal preconditions without using the “Smetri pyramid” formed by nested if statements and without inversion of conditions.

Let's see what a typical code path looks like when using the traditional if statement .



The jsonDict dictionary is input to the createPersonFromJSON function, and a valid instance of the Person structure is created at the output if the corresponding information is presented in the dictionary, otherwise nil is returned. The function is written as it would appear in Swift 1.2 - using the if let construct. There are a couple of “pain points” in this code. First, the “turning off” of the direction of correct calculations from the main code, that is, the “successful” (in terms of condition) direction of calculations turned out to be “nested” in the if let clause. Secondly, the createPersonFromJSON function does not always return an instance of Person when we need it. The Person structure contains 3 properties, one of which is Optional , but the function returns the correct instance of Person only if we get values ​​from the dictionary that are different from nil for all 3 keys. Let's rewrite this function as follows: so that we can return an instance of Person if the address is missing, that is, if the address key returns nil .



We made a slight improvement in functionality. This version of the createPersonFromJSON2 function can now instantiate Person even if the address is nil . This reflects the Person structure better, but now we have a lot of if statements , as well as the need to expand the final values ​​assigned to name and age . Let's see how this can be improved with the new guard clause.



In the case of guard clauses, as well as with the if let clause, we can check for the presence of values, “expand” them and assign them to constants. However, with the guard let construct, code execution continues after curly braces {} , if the conditional expression is evaluated as true . This means that we can instantiate Person within the normal scope of the function without using additional code branching to expand the values. If any of the name or age values ​​is nil , then the code "jumps" to the else clause and an early return nil is performed.

Let's take a quick look at guard .

.

Offer defer



The defer clause resembles finally in other programming languages, except that it is not tied to a try clause, and you can use it anywhere. You write defer {...} and somewhere in the code and this block will be executed when the calculation control leaves this code scope (enclosing scope), and it does not matter whether the code gets to the end or receives the return clause or “throws An error. The operator defer is perfectly combined with guard and error handling (discussed later).

 guard let file1 = Open(...) else { //   file1 return } defer { file1.close() } guard let file2 = Open(...) else { //   file2 return } defer { file2.close() } //  file1  file2 . . . . . . . //      ,    

Note that defer works for file1 both in the normal course of the computational process and in the case of an error with the file file2 . This removes numerous repetitions from the code and helps you not to forget to “clear” something in any branch of the calculations. When handling errors, there is the same problem and the defer clause is best suited for this purpose.

Repeat - while


Swift 2.0 introduced syntactic changes to the do-while clause that was used before. Instead of a do-while , we now get a repeat-while .



There are two reasons for such changes:

When you use the do - while loop, it is immediately unclear that this is a construct to be repeated. This is especially true if the block of code inside the do clauses is large, and the while condition is outside the screen. To mitigate this circumstance, the do keyword has been replaced by repeat , which makes it clear to the user that this is a duplicate block of code.

The keyword do has a new assignment in Swift 2 in the new error handling model, which we will explore later.

Pattern matching


Swift has always had the power of pattern matching , but only in the switch construction. The switch construct considered the value value and compared it with several possible patterns. One of the drawbacks of the switch clause is that we have to present all possible options for the value , that is, the switch statement must be exhaustive and this causes inconvenience of use. Therefore, Swift 2 ported pattern matching capabilities, which previously were only for switch / case , to other sentences that control the flow of computations. if case is one of them, and it allows you to rewrite the code with the switch more briefly. Other sentences are for case and while case .

Pattern matching if case


New in Swift 2 is support for pattern matching inside if (and guard ) clauses. Let's first define the simplest enumeration Number , and then show how to use it.



1. Check a specific option (case)


Use case : we want to check if the value matches a specific case . This works regardless of whether this case has an associated value or not, but the value is not restored (if it exists).



The pattern starts with case .IntegerValue , and the value that must match this pattern, the myNumber variable, comes after the = equality sign. This may seem illogical, but we see the same thing when the Optional is expanded by the a1 value in the if let a = a1 construction: the a1 value that is being checked comes after the equal sign.

Here is the equivalent version of Swift 1.2 using switch :



2. Getting the associated value


We use case : we want to check whether the value corresponds to a specific case , and also to extract the associated value (or values).



The “pattern” has now become case let .IntegerValue (theInt) . The value that must match the “sample” is the same as in the previous example.

Below is an example reflecting the same concept, but applied to guard . The predicate semantics for guard and if are identical, so pattern matching works just as well.



3. Selection using the where clause


An optional where clause may be added to any case in the guard clause to provide additional restrictions. Let's modify the getObjectInArray: atIndex: function from the previous example:



4. Matching range




5. Use the tuple tuple




6. Complex if predicates


The if sentence in Swift 2 was surprisingly capable. It can have multiple predicates separated by a comma. Predicates fall into one of three categories:


Predicates are evaluated in the order of their definition and after not completing a predicate, the rest are not evaluated.

Pattern matching for case


Pattern matching can be used in conjunction with the for -in loop. In this case, our intention is to go through the elements of the sequence, but only on those that correspond to a given “pattern”. Here is an example:



Note that just like the “patterns” in the switch clause, you can retrieve a set of associated values ​​and use _ if you are not interested in this associated value. If necessary, you can also add additional constraints using the where clause.

Pattern matching while


Pattern matching can also be used with a while loop. In this case, we will repeat the body of the loop until a certain value in the predicate matches the “pattern”. Here is an example:



Note that the complex predicates described in section “6. Complex if ”predicates are also supported by a while loop , including the use of where .

Pattern for "unwrapping" numerous optional


In Swift 1.2, we had a nice compact syntax for “deploying” the Optionals set in one simple if let clause:

 var optional1: String? var optional2: String? if let optional1 = optional1, let optional2 = optional2 { print("Success") } else { print("Failure") } 

Great!

However, you still encounter a situation where you really need to manage various combinations of existing / missing Optional values. One such example is the form to fill in the username and password fields, and the user did not fill in one of them, and clicked the "Submit" button. In this case, you will want to show a special error in order to notify the user what exactly is missing. To do this, we can use the pattern makching in Swift 1.x!

 var username: String? var password: String? switch (username, password) { case let (.Some(username), .Some(password)): print("Success!") case let (.Some(username), .None): print("Password is missing") case let (.None, .Some(password)): print("Username is missing") case (.None, .None): print("Both username and password are missing") } 

It's a little awkward, but we enjoyed it right from the start.

In Swift 2, the syntax looks clearer:



At first glance, confused by the use of a question mark ? to show that the value is present (especially if it is associated with the idea of Optionals , when the value may or may not exist), but it must be recognized that this example becomes very understandable in contrast to the awkward syntax .Some (username) .

Error processing


In order to understand the new features of Swift related to error handling, it will be useful to remember that there are 3 ways where a function can end abnormally (hereafter, for brevity, let's switch to jargon and say “fall”):


Handling errors of the third type related to the situation is what Swift 2 is trying to improve.

If we consider a typical control scheme for such errors in Swift 1.x and Objective-C, then we find a scheme when the function takes the inout argument NSError? and returns Bool to represent the success or failure of the operation:

 //  error  ,    var error: NSError? // success  Bool: let success = someString.writeToURL(someURL, atomically: true, encoding: NSUTF8StringEncoding, error: &error) if !success { //     error: println("Error writing to URL: \(error!)") } 

This approach has many “dark” sides that make it less understandable, and what the method itself does, but more importantly, manual implementation of agreements regarding what is behind the returned Bool is required . If the method returns an object and received an error, then it returns nil ; if it is a boolean value, then false is returned, and so on. You need to know which method you are dealing with, what to check if the result is nil or false or something else when the method contains an NSError error object ? .Very confusing syntax. All these difficulties are related to the fact that Objective-C could not return a set of values ​​from a function or method, and if we need to notify the user about an error, such an ingrained way of handling it was suggested.

Swift 2 got new error management. It uses the do-try-catch syntax that replaces NSError . Let's see how you can use this new syntax. I will consider a very simple example of handling such errors, which the optional returns perfectly cope with.values, and for which the new syntax is not intended. But the simplicity of this example will allow me to focus your attention on the mechanism of “throwing out” and “catching” errors, and not on the complexity of their semantic content. In the end I will give a real example of processing data coming from the network.

Before an error can be thrown (throw) or caught (catch), it must be defined. You can define it in Swift 2 with the help of enum , which implements the ErrorType protocol :



In order for a function



to “throw out” (throw) an error, you need to announce the keyword throws in the function header :



Now this function can throw an error using the keyword throwand a link to a specific type of error:


If you try to call this function, the compiler will generate an error: “The function being called throws errors, and the reference to it is not marked with the try keyword and there is no error handling.”



Because the function declared that it is capable of throwing errors , and you have to “catch” potential mistakes. Let's try to use the keyword the try :



it was not enough, the compiler tells us that the required error handling, which is produced by using syntax do-try-catch :


Furthermore, in the block do-try-catch you have the opportunity to "catch" a few errors:



If the semantic part of errors does not interest you, then instead of using do-try-catch constructions , you can treat the values ​​you are interested in as with Optional :



aTry and aTrySuccess are Optional , so don't forget to “deploy” them before use!

Sometimes there is a method that can “fall” only in certain circumstances, and you know for sure that it will not “fall” in your mode of use. Then you can use try! .

If the function throws an error, it returns immediately. But sometimes you need to do some actions, such as freeing up resources or closing files, before the function returns. In this situation, the defer keyword already familiar to us works just fine . With the defer keyword, you can define a block of code that is always executed if the function returns, and it does not matter whether it returns normally or due to errors.

We can define a defer block anywhere in our function. Moreover, it is possible to define more than one defer block. In this case, they will be performed in the reverse order. Let's look at an example:



Let us consider the real example presented in the Natasha Murashev article . Swift 2.0: Let's try? .Consider the data that comes from an API (after deserialization):



This data needs to be converted to a Model for later use in an application:



The TodoItemParser parser deals with mixed data coming from an API, converts it into an understandable Model for later use in an application and “Throws” errors if it detects them:



Now we will perform the parsing of “good” data in the Model using the do-try-catch construction . We will perform the



parsing of “bad” data.



Instead of using the do-try-catch construct , you can treat the values ​​that interest us as Optional with the try? :



In the first part, we considered only a part of the new features of Swift 2:

- the fundamental constructs of the language, such as enum, scoping(scope), the syntax of the arguments, etc.
- pattern matching ( pattern matching )
- error handling ( error handling )

In the second part, we will look at the rest:

- availability check ( availability Available checking )
- extension ( the extensions ) protocol
- interaction with Objective-C

References used article:

the New features in Swift 2
What I Like in Swift 2
A Beginner's guide to Swift 2
the Error Handling in Swift 2.0 We do
Swift 2.0 We do: for Let's try?
Video Tutorial: What's New in Swift 2 Part 4: Pattern A Matching
the Throw for What the Do not the Throw
of The of the Best for What's in the New Swift

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


All Articles