📜 ⬆️ ⬇️

Swift Features

In the framework of Mobile Camp Yandex, our colleague Denis Lebedev presented a report on the new programming language Swift. In his report, he touched upon the features of interaction with Objective-C, spoke about the features of the language that seemed to him the most interesting. And also about where to go on Github, and what repositories to look at in order to understand what can be done with Swift in the real world.

Swift development began in 2010. Engaged in her Chris Lattner. Until 2013, the process was not very active. Gradually more people were involved. In 2013, Apple focused on developing this language. Before the presentation at WWDC about Swift knew about 200 people. Information about him was kept in the strictest confidence.




Swift is a multi-paradigm language. It has OOP, you can try some functional things, although adherents of functional programming believe that Swift is not quite enough. But, it seems to me that such a goal was not set, after all, it is a language for people, not for mathematicians. You can write in procedural style, but I'm not sure that this is applicable for entire projects. Very interesting in Swift everything is arranged with typing. Unlike dynamic Objective-C, it is static. There is also a type inference. Those. most variable type declarations can be simply omitted. Well, the killer feature Swift can be considered a very deep interaction with Objective-C. Later I will talk about runtime, but for now we’ll restrict ourselves to the fact that the code from Swift can be used in Objective-C and vice versa. Pointers familiar to all developers on Objective-C and C ++ in Swift are absent.
')
Let's pass to features. There are a lot of them. I singled out for myself a few basic ones.

Consider the last feature in a little more detail. We are all used to that in Objective-C, when we do not know what to return, we return nil for objects and -1 or NSNotFound for scalars. Optional types solve this problem quite radically. An optional type can be represented as a box that either contains a value or contains nothing. And it works with any types. Suppose we have the following signature:

(NSInteger) indexOfObjec: (id)object; 

In Objective-C, it is unclear what the method returns. If there is no object, then it can be -1, NSNotFound or some other constant known only to the developer. If we consider the same method in Swift, we will see Int with a question mark:

 func indexOF(object: AnyObject) -> Int? 

This construct tells us that either the number or the void will return. Accordingly, when we received the packed Int, we need to unpack it. Unpacking can be of two types: safe (everything turns into if / else) and forced. We can use the latter only if we know for sure that there will be values ​​in our imaginary box. If it does not appear there, there will be a crash in rantayma.

Now let's talk briefly about the main features of classes, structures and enumerations. The main difference between classes and structures is that they are passed by reference. Structures are passed by value. As the documentation tells us, using structures spends far less resources. And all scalar types and Boolean variables are implemented through structures.

Enumerations I would like to highlight separately. They are completely different from their counterparts in C, Objective-C and other languages. It is a combination of class, structure and even a bit more. To show what I mean, consider an example. Suppose I want to implement a tree using enum . Let's start with a small listing with three elements (empty, node and sheet):

 enum Tree { case Empty case Leaf case Node } 

What to do with this is unclear. But in Swift, each enum element can carry some value. To do this, we add Int to the leaf, and the node will have two more trees:

 enum Tree { case Empty case Leaf(Int) case Node(Tree, Tree) } 

But since Swift supports generics, we will add support of any type to our tree:

 enum Tree<T> { case Empty case Leaf(T) case Node(Tree, Tree) } 

The tree declaration will look something like this:

 let tree: Tree<Int> = .Node(.Leaf(1), .Leaf(1)) 

Here we see another cool feature: we can not write the names of enumerations, because Swift displays these types at the compilation stage.

enum in Swift has another interesting feature: they can contain functions, just as in structures and classes. Suppose I want to write a function that returns the depth of our tree.

 enum Tree { case Empty case Leaf(Int) case Node(Tree, Tree) func depth<T>(t: Tree<T>) -> Int { return 0 } } 

What I don’t like about this function is that it takes a tree parameter. I want to make the function just return values ​​to me, and I would not need to transfer anything. Here we will use another interesting Swift feature: nested functions. Since there are no access modifiers yet - this is one of the ways to make the function private. Accordingly, we have _depth , which will now count the depth of our tree.

 enum Tree<T> { casefunc depth() -> Int { func _depth<T>(t: Tree<T>) -> Int { return 0 } return _depth(self) } } 

We see a standard switch, there is nothing sviftovogo here, just handle the option when the tree is empty. Further interesting things begin. We unpack the value that is stored in our list. But since we do not need it, and we just want to return the unit, we use an underscore, which means that we do not need a variable in the sheet. Next we unpack the knot from which we take the left and right parts. Then we call the depth function recursively and return the result. As a result, we get such a tree implemented on enum with some basic operation.

 enum Tree<T> { case Empty case Leaf(T) case Node(Tree, Tree) func depth() -> Int { func _depth<T>(t: Tree<T>) -> Int { switch t { case .Empty: return 0 case .Leaf(let_): return 1 case .Node(let lhs, let rhs): return max(_depth(lhs), _depth(rhs)) } } return _depth(self) } } 

The interesting thing about this enum is that this code written by him should work, but it does not work. In the current version, because of the enum bug, it does not support recursive types. In the future, it will work. For the time being, different hacks are used to bypass this bug. I will tell about one of them a bit later.

The next item in my story is collections, represented in the standard library by an array, dictionaries, and a string (enchantment collection). Collections, like scalars, are structures, they are also interchangeable with standard foundation types such as NSDictionary and NSArray. In addition, we see that, for some strange reason, there is no NSSet type. Probably used too rarely. Some operations (for example, filter and reverse ) have lazy calculations:

 func filter<S :Sequence>(…) -> Bool) -> FilterSequenceView<S> func reverce<S :Collection …>(source: C) -> ReverseView<C> 

Those. FilterSequenceView and ReverseView types are not a processed collection, but its representation. This tells us that these methods have high performance. In the same Objective-C, such tricky constructions cannot be found, since at the time of the creation of this language no one had yet thought about such concepts. Now lazy calculations penetrate programming languages. I like this trend, sometimes it is very effective.

The following feature was noticed, probably, by everyone who was somehow interested in the new language. But I still tell you about it. Swift has built-in variable immutability. We can declare a variable in two ways: through var and let . In the first case, the variables can be changed, in the second - not.

 var  = 3 b += 1 let a = 3 a += 1 // error 

Here begins an interesting thing. For example, if we look at a dictionary that is declared using the let directive, then when we try to change a key or add a new one, we will get an error.

 let d = ["key": 0] d = ["key"] = 3 //error d.updateValue(1, forKey: "key1") //error 

Arrays are different. We cannot increase the size of the array, but at the same time we can change any of its elements.

 let c = [1, 2, 3] c[0] = 3 // success c.append(5) // fail 

In fact, it is very strange, when trying to figure out what was happening, it turned out that this was a bug confirmed by the developer of the language. In the near future, it will be corrected, because This is really a very strange behavior.

Extensions in Swift are very similar to categories from Objective-C, but more penetrate the language. In Swift, you do not need to write imports: we can write an extension anywhere in the code, and it will be picked up by absolutely all the code. Accordingly, in the same way it is possible to expand structures and enams, which is also sometimes convenient. With the help of extensions, you can structure the code very well, it is implemented in the standard library.

 struct: Foo { let value : Int } extension Foo : Printable { var description : String { get {return "Foo"} } } extension Foo : Equatable { } func ==(lhs: Foo, rhs: Foo) -> Bool { return lhs.value == rhs.value } 

Next, let's talk about what in Swift is not. I can not say that something concrete is missing for me, because I haven't used it in production yet. But there are things that many complain about.

Now let's talk about how Objective-C and Swift can interfere. Everyone already knows that you can call Objective-C code from Swift. In the opposite direction, everything works exactly the same, but with some limitations. Enums, tuples, generic types do not work. Although there are no pointers, CoreFoundation types can be called directly. For many, the impossibility of calling C ++ code directly from Swift has become a frustration. However, you can write wrappers in Objective-C and call them already. Well, it is quite natural that it is impossible to subclass in Objective-C classes that are not implemented in it from Swift.

As I said above, some types are interchangeable:


I will give an example of a class that is written in Swift, but can be used in Objective-C, you just need to add one directive:
 @objc class Foo { int (bar: String) { /*...*/} } 

If we want a class in Objective-C to have a different name (for example, not Foo , but objc_Foo ), as well as change the method signature, everything becomes a bit more complicated:

 @objc(objc_Foo) class Foo{ @objc(initWithBar:) init (bar: String) { /*...*/} } 

Accordingly, in Objective-C everything looks absolutely expected:

 Foo *foo = [[Foo alloc] initWithBar:@"Bar"]; 

Naturally, you can use all the standard frameworks. For all headers, their representation on Swift is automatically generated. Suppose we have a function convertPoint :

 - (CGPoint)convertPoint:(CGPoint)point toWindow:(UIWindow *)window 

It is fully converted to Swift with the only difference: there is an exclamation point near UIWindow . This indicates the very optional type I mentioned above. Those. if there is nil, and we do not check it, there will be crash in runtime. This is due to the fact that when the generator creates these headers, he does not know whether it can be nil or not, therefore he puts these exclamation marks everywhere. Perhaps soon it will be corrected somehow.

 finc convertPoint(point: CGPoint, toWindow window: UIWindow!) -> GCPoint 

In detail, it’s too early to talk about Swift's innards and performance, since it’s not known that it’s going to live from the current runtime to the first version. Therefore, for now let's touch this topic only superficially. To begin with, all Swift objects are Objective-C objects. There is a new root class SwiftObject. Methods are now stored not in classes, but in virtual tables. Another interesting feature is that the types of variables are stored separately. Therefore, it becomes a bit more difficult to decode classes on the fly. For coding method metadata, an approach called name mangling is used. For example, take a look at the Foo class with the bar method returning Bool :
 class Foo { func bar() -> Bool { return false } } 

If we look at the binary, for the bar method we will see a signature of the following form: _TFC9test3Foo3barfS0_FT_Sb . Here we have Foo with a length of 3 characters, the length of the method is also 3 characters, and Sb at the end means that the method returns Bool . A not very pleasant thing is connected with this: debug logs in XCode all fall into exactly this form, so it’s not very convenient to read them.
Probably everyone has already read about the fact that Swift is very slow. By and large this is true, but let's try to figure it out. If we compile with the -O0 flag, i.e. without any optimizations, Swift will be slower than C ++ 10 to 100 times. If we compile with the -O3 flag, we will get 10 times slower than C ++. The -Ofast flag -Ofast not very secure, since it disables inta checking, etc., in runtime. In production it is better not to use it. However, it allows you to improve performance up to the C ++ level.
You need to understand that the language is very young, it is still in beta. In the future, the main problems with speed will be fixed. In addition, the legacy of Objective-C stretches behind Swift, for example, in cycles there are a huge number of retainers and releases that are essentially not needed in Swift, but they slow down the speed very much.

Then I will talk about things that are not very related to each other that I encountered during the development process. As I said above, macros are not supported, so the only way to make a cross-platform view is as follows:

 #if os(iOS) typealias View = UView #else typealias View = NSView #endif class MyControl : View { } 

This if is not exactly a preprocessor, but simply a language construct that allows you to test the platform. Accordingly, we have a method that returns us to what platform we are on. Depending on this, we alias the View . So we create MyControl , which will work on both iOS and OS X.

The next feature - pattern matching - I really like. I'm a little fond of functional languages, where it is used very widely. Take for example the problem: we have a point on the plane, and we want to understand which of the four quadrants it is in. We all imagine what that code would be in Objective-C. For each quadrant, we will have such absolutely wild conditions, where we have to check whether x and y fall into these frames:

 let point = (0, 1) if point.0 >= 0 && point.0 <= 1 && point.1 >= 0 && point.1 <= 1 { println("I") } ... 

Swift us in this case gives us a few convenient pieces. First, we have a tricky range-operator with three points. Accordingly, the case can check whether the point falls into the first quadrant. And the whole code will look something like this:

 let point = (0, 1) switch point { case (0, 0) println("Point is at the origin") case (0...1, 0...1): println("I") case (-1...0, 0...1): println("II") case (-1...0, -1...0): println("III") case (0...1, -1...0): println("IV") default: println("I don't know") } 

In my opinion, this is dozens of times more readable than what Objective-C can provide us.

In Swift, there is another absolutely niche thing that also came from functional programming languages ​​- function currying:

 func add(a: Int)(b: Int) -> Int { return a + b } let foo = add(5)(b: 3) // 8 let add5 = add(5) // (Int) -> Int let bar = add(b: 3) // 8 

We see that we have a function add with such a tricky declaration: two pairs of brackets with parameters instead of one. This gives us the opportunity to either call this function almost as usual and get the result of 8, or call it with one parameter. In the second case, magic happens: at the output, we get a function that takes an Int and returns an Int too, i.e. we partially applied our add function to the top five. Accordingly, we can then use the add5 function with a triple and get a figure eight.

As I said, the preprocessor is missing, so even implementing assert is not a trivial thing. Suppose we have a task to write some kind of assert . We can check it for debag, but in order for the code that is not executed in the assertion, we must pass it as a closure. Those. we see that we have 5 % 2 in curly braces. In Objective-C terminology, this is a block.

 func assert(condition:() -> Bool, message: String) { #if DEBUG if !condition() { println(message) } #endif } assert({5 % 2 == 0}, "5 isn't an even number.") 

It is clear that no one will use asses. Therefore, there are automatic closures in Swift. In the method declaration, we see @autoclosure , respectively, the first argument turns into a closure, and you can @autoclosure curly braces.

 func assert(condition: @auto_closure () -> Bool, message: String) { #if DEBUG if !condition() { println(message) } #endif } assert(5 % 2 == 0, "5 isn't an even number.") 

Another undocumented, but very useful thing is the explicit type conversion. Swift is a typed language, so as in Objective-C we cannot stick objects with id-type. Therefore, consider the following example. Suppose I have a Box structure, which in gets at initialization some value that cannot be changed. And we have a packed Int - unit.

 struct Box<T> { let _value : T init (_ value: T) { _value = value } } let boxedInt = Box(1) //Box<Int> 

We also have a function that takes an Int as input. Accordingly, we cannot transfer boxedInt there, since the compiler will tell us that Box not converted to an Int . The craftsmen slightly gutted the internal parts of the SWIFT and found a function that allows you to convert the Box type into a value that it hides in itself:

 extension Box { @conversion Func __conversion() -> T { return _value } } foo(boxedInt) //success 

Static typing of the language also does not allow us to run around the class and replace methods, as could be done in Objective-C. From what is now, we can only get a list of object properties and display their values ​​at the moment. Those. information about the methods we can not get.

 struct Foo { var str = "Apple" let int = 13 func foo() { } } reflect(Foo()).count // 2 reflect(Foo())[0].0 // "str" reflect(Foo())[0].1summary // "Apple" 

From the swift, you can directly call the C-code. This feature is not reflected in the documentation, but may be useful.

 @asmname("my_c_func") func my_c_func(UInt64, CMutablePointer<UInt64>) -> CInt; 

Swift, of course, is a compiled language, but this does not prevent it from supporting scripts. First, there is an interactive runtime environment that is run with the xcrun swift command. In addition, you can write scripts not in familiar scripting languages, but directly in Swift. They are xcrun -i 'file.swift' using the xcrun -i 'file.swift' .

Finally, I will talk about the repositories that are worth a look:

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


All Articles