📜 ⬆️ ⬇️

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

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


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


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

Protocol extensions


Protocol extensions were made possible in Swift 2, which made it possible to add new functions (complete with an implementation) to any classes, structures, and enumerations that implement the protocol.
')
Prior to Swift 2, both in Objective-C and Swift 1.x, the protocols contained only a declaration of methods. With protocol extensions in Swift 2, protocols can now include, along with the declaration, the implementation of methods. We have been waiting for this opportunity for Objective-C for years, so it's nice to see its implementation in a new language.

It often happens that some functionality needs to be added to all types that confirm a particular protocol (interface). For example, all collections can support the concept of creating a new collection based on the transformations of its elements. With old-style protocols, we could implement this possibility in two ways: 1) place the method in the protocol and require that each type confirming this protocol implement the method, or 2) write a global function that works on the values ​​of the types confirming the protocol.

Cocoa (Objective-C) in most cases prefers the first solution.

Swift 1.x used the second solution. Global functions such as map operated on any CollectionType . This provided an excellent separation of code in the implementation, but terrible syntax and the inability to override the implementation for a specific type.

let x = filter(map(numbers) { $0 * 3 }) { $0 >= 0 } //-- Swift 1 


With extensions of protocols, a third possibility has appeared, which significantly exceeds the other two. The map can be implemented in the protocol extension CollectionType . All types that support the CollectionType protocol will automatically receive map implementations for free.

 let x = numbers.map { $0 * 3 }.filter { $0 >= 0 } //-- Swift 2 

For example, consider in Swift 2 the implementation of the new myMap method as an extension of the CollectionType protocol.



As a result, we can immediately use myMap for Array <T> arrays


for dictionaries Dictionary <Key: Value>


for sets Set <T>


for strings.characters


for ArraySlice <T> slices


for StrideThrough <T> straights, but not directly, but through a map that converts a sequence ( SequenceType protocol) to a collection ( CollectionType protocol)


Protocol extensions underlie the new approach to software design, announced by Apple as a Protocol-Oriented Programming (EPP), existing in Swift along with traditional Object-Oriented Programming (OOP) and elements of Functional Programming (AF). It must overcome such OOP problems as “fragile base class” and the rigidity of inheritance (rigidity and fragility of inheritance), “diamond problem” , implicit separation of references to objects, the need for frequent use of “casting” down (downcasting ) in overridden methods. It is also difficult for many developers to describe polymorphism with words, and it is easier to show with an example, we will demonstrate the capabilities of Protocol-Oriented Programming with examples.

Example 1. Fischer-Jens shuffle algorithm


For a deeper consideration of these differences, let us consider the implementation of the Fisher – Jens Shuffle algorithm for “mixing” elements of a collection using the example of the shuffle function for Swift 1.2 (OOP) and Swift 2 (POP). This algorithm is often used when dealing cards in a card game.

In Swift 1.2, we would add the global shuffle function for working with collections according to method 2, that is, when using the global function with generics :



Let's look at this feature in more detail. The input of this global function takes as its argument the collection var list: C itself and returns a new collection of the same type C with “mixed” values ​​of the original list collection. This global function can be used for all types that support the MutableCollectionType protocol and have integer indices. There are two such collections: Array <T> and ArraySlice <T> slice



But look, what is the appeal to this global function?
 shuffle(strings1) shuffle(numbers1) 


This function does not allow using the “dot” notation; in the round brackets you need to specify the collection itself. This is very inconvenient if you have a whole chain of transformations, which leads to a lot of nested parentheses, and if this all alternates with the methods of the type, then in general - trouble. We have already seen a similar syntax above.

 let x = filter(map(numbers) { $0 * 3 }) { $0 >= 0 } //-- Swift 1 


If we want to work with the “dot” notation in Swift 1.2, then we add the shuffle function to the extension of each individual type, for example, the Array class (this is method 1). Moreover, we can add both the locally changing (mutating) method shuffleInPlace and the shuffle method, which returns a new array with “mixed” elements of the original array (non — mutating)):



The last two methods are extensions for the Array array and are available only for arrays.


Neither Set nor ArraySlice , no other CollectionType can use them.

In Swift 2, we add the shuffle and shuffleInPlace methods exclusively to extend the CollectionType and MutableCollectionType protocols (method 3):


And with the extension of the protocols, the shuffle and shuffleInPlace methods can now be applied to Set , and to Array , and to ArraySlice and any other CollectionType without any additional effort, and in the “dotted” notation we need:



When extending protocols in Swift 2, we can set restrictions on the type. As you can see from the code, we first perform the protocol extension only for the mutableCollectionType collection that is variable, which uses integers as indices, and then distribute it to the CollectionType .

At the end, you need to make a small remark about the algorithm of "mixing" the elements of the collection. In Swift 2, there is already an efficient and correct implementation of the Fisher-Jens shuffle algorithm in GameplayKit (which, despite its name, is not only suitable for games). True, this method works only with arrays.


Example 2. Farewell pipe (pipeline) operator |> with the advent of the possibility of extending protocols


With the advent of Swift and the creation of custom operators, including functional programming operators, attempts have been made and quite successful to solve some problems using functional programming techniques. In particular, for the Moon algorithm for calculating the check digit of a plastic card, it was proposed to use a pipe (pipeline) operator |> to avoid annoying throwing between functions and methods. But Swift 2 came out and the protocol extension technique made it even easier to solve this problem.

The original algorithm described by the developer


The algorithm consists of a sequence of steps, each of which will be described using an extension of either a protocol, or a type, or using known methods.

First, you need to be reminded that in Swift 2 the String is no longer a sequence, but String.characters is a sequence of characters, and we need to convert a character to an integer. We construct this transformation on an extension of type Int . That is, instead of String.toInteger we get Int.init (String) :



This conversion returns an Optional, since there may be spaces in the credit card number, and we need to continue to perform arithmetic operations on the obtained integers, so we use the flatMap method that appeared in Swift 2, which will remove all spaces in the card number



According to our algorithm, we have to consider numbers from right to left, and here they follow from left to right, therefore we will arrange our sequence of numbers in the opposite direction using the reverse method and the trivial map method



But we need not a simple map , but a map that performs transformations only on each Nth member of the sequence. Again we execute the extension, but now not of the type, but of the protocol SequenceType



We get the following result



Then we need a method to calculate the sum of any sequence containing integers:



and method of calculating the number modulo another number



As a result, we get the luhnchecksum () method, which we add to the String type and which calculates the checksum in one string



Now it's very easy to get the result:



Some features of the protocol extension


I think that protocol extensions can be Apple’s answer to the question of optional protocol methods. Pure Swift protocols cannot and should not have optional (optional) methods. But we are used to the optional (optional) methods in Objective-C protocols, for example, for such things as delegates:

 @protocol MyClassDelegate @optional - (BOOL)shouldDoThingOne; - (BOOL)shouldDoThingTwo @end 


Pure Swift has no equivalent:


Until now, everyone who confirmed this protocol had to implement all these methods. This conflicts with the idea of ​​delegating Cocoa as an option to optionally configure all delegate methods, preferring the default implementation of the methods. With the advent of protocol extensions in Swift 2, reasonable default behavior can be provided by the protocol itself:



Ultimately, this provides the same functionality as Objective-C’s @optional , but without mandatory checks at runtime.

API availability check


One professional problem that depresses iOS developers is the need to be very careful when using new APIs . For example, if you try to use UIStackView in iOS 8, then your application will crash. In ancient times, Objective-C developers would write similar code:

 NSClassFromString(@"UIAlertController") != nil 


This means “if the UIAlertControllerl class exists,” and is a way to check whether it runs on iOS 8 or later. But due to the fact that Xcode did not guess the true purpose of this code, it did not guarantee us the correctness of its execution. Everything changed in Swift 2, because you can write such code explicitly:

 if #available(iOS 9, *) { let stackView = UIStackView() //  ... } 


The magic happens with the appearance of the #available clause : it automatically checks whether you are running on iOS version 9 or later, and if “yes”, then the code from UIStackView will be launched. The presence of a “*” after “iOS 9” means that this offer will be executed for any future platform that Apple will introduce.

The #available clause is also remarkable because it gives you the opportunity to write code in the else block, because Xcode now knows that this block will be executed if iOS 8 or less is on the device and can warn you if you will use new APIs here . For example, if you wrote something like this:

 if #available(iOS 9, *) { // do cool iOS 9 stuff } else { let stackView = UIStackView() } 

... then you get an error:



Xcode sees that you are trying to use UIStackView where it is not available and it simply will not allow this to happen. So, switching from “whether this class is“ available ”to telling Xcode about our real intentions, we received tremendous support for our security.

Compatibility


When you write code in Swift, that is, a number of pre- written rules that tell the compiler whether you need and how, expose methods, properties, etc. on Objective-C. Moreover, at your disposal there are a small number of attributes with the help of which you can control this process. These attributes are:





In Swift 2, a new such attribute @nonobjc has appeared , which clearly prevents the use of properties or methods from being exposed in Objective-C. This attribute is very useful, for example, in the following case. In the Calculator application (see below), in the ViewController class, which inherits from the UIViewController Cocoa class, you define 3 methods with the same name performOperation , but with different arguments. This is normal, Swift is able to distinguish between these methods, simply based on the different types of argument. But Objective-C doesn't work that way. In Objective-C, methods differ only by name, not by type. If these methods are exposed for use in Objective-C, then you will get an error saying that you cannot use them in this form in Objecrive-C. The thing is that our ViewController class, which we created for the calculator interface, inherits the UIViewCiontroller class from Cocoa, and the compiler automatically implicitly marks all properties and methods with the @objc attribute. If you do not intend to use methods in Objective-C, then you must supply them with the @noobjc attribute and the error disappears:


.........................

Another area in which Swift 2 is trying to improve compatibility is compatibility with C function pointers. The purpose of this improvement is to fix the Swift annoying constraint, which does not allow you to fully work with such an important C - framework as Core Audio , which intensively uses callback functions. In Swift 1.x, it was not possible to directly replace the pointer with the Swift function with a function. You needed to write a small “wrapper” in C or Objective-C that encapsulates the callback function. In Swift 2, it became possible to do this in a completely natural way for Swift 2. Pointers to C functions are imported into Swift as closures. You can pass any Swift 2 closure or function with suitable parameters to a code that expects a pointer to a C function — with one significant limitation: in contrast to closures, pointers to C functions do not have the concept of a “captured” state (they are just pointers). As a result, for compatibility with pointers to C functions, the compiler will allow using only those Swift 2 closures that do not “capture” any external context. Swift 2 uses the new @ convention © notation to indicate this agreement when calling:



For example, for the standard C sorting function qsort, it would look like this:


A very good example is presented in C Callbacks in Swift , which shows how to access CGPath or UIBezierPath elements by calling the CGPathApply function and passing a pointer to the callback function. CGPathApply then calls this callback for each path element.





Now go through the entire path and print a description of its elements:



Or you can count how many closepath commands in this path are :



In conclusion, we can say that Swift 2 automatically provides compatibility (bridges) of pointers to C functions and closures. This makes it possible (and very convenient) to work with a large number of C APIs that use function pointers as callbacks. Because conventions on calls to C functions do not allow these closures to “capture” an external state, you often have to pass external variables that your callback needs to access, via a void pointer, which many C APIs

New Objective-C Features


Apple introduced three new features in Objective-C in Xcode 7 with an eye to using them for smoother Swift compatibility:



Nullability


This feature was introduced already in Xcode 6.3, but it is worth mentioning that Objective-C now allows you to accurately characterize the behavior of any methods and properties to determine whether they can be nil or not. This is addressed directly to Swift's requirements for Optional or non- Optional types and makes the Objective-C interface more expressive. There are three qualifiers for nullability :



The qualifiers listed above can be used to annotate Objective-C classes, as in the following example:



In this example, the whole area marked with the brackets NS_ASSUME_NONNULL_BEGIN and NS_ASSUME_NONNULL_END is selected so that nonnull has a value that is used by default. This allows the developer to annotate only those elements that do not correspond to the default value.

Lightweight generics


Lightweight generics in Objective-C may be the most desirable in Objective-C over the past decade, especially for Apple engineers. They are necessary for use with collections like NSArray , NSDictionary , etc ... One of the disadvantages of collections in Objective-C is the loss of almost all type information when porting them to Swift, by default in Swift we get the AnyObject collection and have to apply down "casting "In the overwhelming number of cases. But now you can declare the type of array elements in Xcode 7 as follows:



In our case, we declare a variable array of strings. If you try to write a number into it, the compiler will issue a warning about the type mismatch.
Lightweight generics have proven very useful for interoperability between Objective-C and Swift in terms of representing the NSArray , NSDictionary , etc. classes, because now you don’t need to do a lot of "castings" in your Swift code because all Apple frameworks are written in Objective-C.



Do you see? Now the subviews are not an [AnyObject] array, they are passed to Swift as [UIView] .
Now in Objective-C you can declare your own generic class:



And use it


In case of a type mismatch, a warning is issued. Unfortunately, using your own generic types takes precedence only within Objective-C code and is ignored by Swift. They operate only at the compiler level, they are not in runtime.

__kindof types


__kindof types belong to generics , and their appearance is motivated by the following case. As is known, the UIView class has a subviews property, which is an array of UIView objects:

 @interface UIView @property(nonatomic,readonly,copy) NSArray< UIView *> *subviews; @end 


If you add UIButton to the parent UIView as the most remote subview in the background, and try to send him a message that matters only for UIButton , the compiler will issue a warning. This is good, but we know for sure that the subview in the background is a UIButton and we want to send a message to it:

 [view insertSubview:button atIndex:0]; //-- warning: UIView may not respond to setTitle:forState: [view.subviews[0] setTitle:@"Cancel" forState:UIControlStateNormal]; 


Using the __kindof type, we can provide some flexibility to the typing system in Objective-C so that the implicit "casting" of both the superclass and any subclass works:

 @interface UIView @property(nonatomic,readonly,copy) NSArray< _kindof UIView *> *subviews; @end //-- no warnings here: [view.subviews[0] setTitle:@"Cancel" forState:UIControlStateNormal]; UIButton *button = view.subviews[0]; 


The lightweight generics and __kindof types allow the developer to remove id / AnyObject almost everywhere from most of their APIs. The id may still be required in cases where there really is no information on what type you are dealing with:

 @property (nullable, copy) NSDictionary<NSString *, id> *userInfo; 


Links to articles used

New features in Swift 2
What I Like in Swift 2
A Beginner's guide to Swift 2
Error Handling in Swift 2.0
Swift 2.0: Let's try?
Video Tutorial: What's New in Swift 2 Part 4: Pattern Matching
Throw What Don't Throw
The Best of What's New in Swift
What's new in Swift 2
Swift 2.0: Availability Checking API
How do I shuffle an array in Swift?
Swift 2.0 shuffle / shuffleInPlace
C Callbacks in Swift,
Protocol extensions and the death of the pipe-forward operator
Swift protocol extension method dispatch
API Availability Checking in Swift 2
Interacting with C APIs
What's new in iOS 9: Swift and Objective-C
Xcode 7 Release Notes

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


All Articles