Swift Features

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

    Swift development began in 2010. Chris Luttner was engaged in it. Until 2013, the process was not very active. Gradually, more and more people became involved. In 2013, Apple focused on developing this language. Before the presentation at WWDC, about 200 people knew about Swift. 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 a bit out of place. But, it seems to me that such a goal was not set, still it is a language for people, and not for mathematicians. You can write in a procedural style, but I'm not sure if this applies to entire projects. Very interesting in Swift everything is arranged with typification. Unlike dynamic Objective-C, it is static. There is also type inference. Those. most variable type declarations can simply be omitted. Well, the killer feature of Swift can be considered a very deep interaction with Objective-C. Later I will talk about runtime, but for now we will limit ourselves to the fact that the code from Swift can be used in Objective-C and vice versa. There are no pointers familiar to all Objective-C and C ++ developers in Swift.

    Let's move on to features. There are a lot of them. I have identified several key ones for myself.
    • Namespacing. Everyone understands the Objective-C problem - because of two-letter and three-letter classes, name conflicts often arise. Swift solves this problem by introducing obvious and understandable namespaces. While they do not work, but for the release, everyone should fix it.
    • Generic classes & functions. For people who wrote in C ++, this is a fairly obvious thing, but for those who have come across mostly Objective-C, this is a fairly new feature that will be interesting to work with.
    • Named / default parameters. You won’t surprise anyone with named parameters; they already were in Objective-C. But the default settings are a very useful thing. When our method accepts five arguments, three of which are set by default, the function call becomes much shorter.
    • Functions are first class citizens. Functions in Swift are first order objects. This means that they can be passed to other methods as parameters, and also returned from other methods.
    • Optional types. Optional types are an interesting concept that came to us in a slightly modified form from functional programming.

    Let's consider the last feature in a bit 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 thought of as a box that either contains a value or does not contain anything. And it works with any types. Suppose we have this 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 look at the same method in Swift, we see Int with a question mark:

    func indexOF(object: AnyObject) -> Int?
    

    This construction tells us that either a number or a void will return. Accordingly, when we received the packed Int, we need to unpack it. There are two types of unpacking: 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 he is not there, there will be a crash in runtime.

    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 consumes far less resources. And all scalar types and Boolean variables are implemented through structures.

    I would like to single out the transfers. They are completely different from their counterparts in C, Objective-C, and other languages. This is a combination of class, structure, and even a little more. To show what I mean, consider an example. Suppose I want to implement a tree with 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 element enumcan carry some value. To do this, we will add a sheet Int, 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 {
    	case Empty
    	case Leaf(T)
    	case Node(Tree, Tree)
    }
    

    The tree declaration will look something like this:

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

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

    We enumin the Swift, there is another interesting feature: they can contain functions exactly the same 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: Tree) -> Int {
    		return 0
    	}
    }
    

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

    enum Tree {
    	case …
    	func depth() -> Int {
    		func _depth(t: Tree) -> Int {
    			return 0
    		}
    		return _depth(self)
    	}
    }
    

    We see a standard switch, there is nothing swift, we simply process the option when the tree is empty. More interesting things begin. We unpack the value that is stored in our sheet. But since we don’t need it, and we just want to return the unit, we use the underscore, which means that we do not need the variable in the sheet. Further we we unpack the node from which we get the left and right parts. Then we call the depth function recursively and return the result. As a result, we get such a enumtree implemented with some basic operation.

    enum Tree {
    	case Empty
    	case Leaf(T)
    	case Node(Tree, Tree)
    	func depth() -> Int {
    		func _depth(t: Tree) -> 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 with this enumis that this code he wrote should work, but it doesn’t work. In the current version, due to a bug, it enumdoes not support recursive types. In the future it will work. So far, various hacks are used to circumvent this bug. I’ll talk about one of them a little later.

    The next paragraph of my story is the collections represented in the standard library by an array, dictionaries and a string (a collection of charms). 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. They are probably too rarely used. In some operations (for example, filterand reverse) there are lazy calculations:

    func filter(…) -> Bool) ->
    	FilterSequenceView
    func reverce(source: C) ->
    	ReverseView

    Those. types FilterSequenceViewand ReverseView- this is not a processed collection, but its representation. This tells us that these methods have high performance. In the same Objective-C you will not find such tricky constructions, since at the time of the creation of this language no one had thought about such concepts. Now lazy computing is penetrating programming languages. I like this trend, sometimes it is very effective.

    The following feature was already noticed, probably by everyone who was somehow interested in the new language. But I’ll tell you about her anyway. Swift has built-in immutability of variables. We can declare a variable in two ways: through varand let. In the first case, the variables can be changed, in the second - no.

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

    Here an interesting thing begins. For example, if we look at a dictionary that is declared using the directive let, then when we try to change the 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 a bit different. We cannot increase the size of the array, but 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 the matter, it turned out that it was a bug confirmed by the language developer. In the near future it will be fixed, because this is really very strange behavior.

    Extensions in Swift are very similar to the categories from Objective-C, but they penetrate the language more. You do not need to write import in Swift: 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 very well structure the code, this 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 is not in Swift. I can’t say that something specific is missing for me, because I haven’t used it in production yet. But there are things that many complain about.
    • Preprocessor. It is clear that if there is no preprocessor, then there are no cool macros that generate a lot of code for us. Cross-platform development is also difficult.
    • Exceptions. The mechanism of executions is completely absent, but you can throw an NSException, and Objective-C runtime will handle all this.
    • Access control. After reading a book about Swift, many were confused by the lack of access modifiers. In Objective-C, this was not, everyone understood that it was necessary, and waited in a new language. In fact, the developers simply did not have time to implement beta access modifiers. They will already be in the final release.
    • KVO, KVC. For obvious reasons, there is no Key Value Observing and Key Value Coding. Swift is a static language, and these are features of dynamic languages.
    • Compiler attributes. There are no compiler directives that report deprecated methods or whether there is a method on a particular platform.
    • performSelector.This method from Swift is completely mowed down. This is a rather unsafe thing and even in Objective-C it should be used with caution.

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

    As I said above, some types are interchangeable:
    • NSArray < - > Array;
    • NSDictionary < - > Dictionary;
    • NSNumber - > Int, Double, Float.


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

    If we want the class in Objective-C to have a different name (for example, not Foo, but objc_Foo), and also change the signature of the method, things get a little 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. Let's say we have a function convertPoint:

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

    It is fully converted to Swift with the only difference: UIWindowthere is an exclamation mark around . This indicates the very optional type that I mentioned above. Those. if there is nil, and we won’t check it, there will be a crash in runtime. This is due to the fact that when the generator creates these headers, it does not know if it can be nil or not, therefore it puts these exclamation points everywhere. Perhaps soon they will somehow correct it.

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

    In detail, it's too early to talk about the internals and performance of Swift, since it is not known what of the current runtime will survive to the first version. Therefore, so far we will touch on this topic only superficially. To begin with, all Swift objects are Objective-C objects. The new root class SwiftObject appears. Methods are now stored not with classes, but in virtual tables. Another interesting feature is that variable types are stored separately. Therefore, decoding classes on the fly becomes a little more difficult. To encode method metadata, an approach called name mangling is used. For example, look at a class Foowith a method barthat returns Bool:
    class Foo {
    	func bar() -> Bool {
    		return false
    	}
    }
    

    If we look at the binary, for the method barwe will see the signature of the following: _TFC9test3Foo3barfS0_FT_Sb. Here we have Foowith a length of 3 characters, the length of the method is also 3 characters, and Sbin the end means that the method returns Bool. A not very pleasant thing is connected with this: debug logs in Xcode all fall in exactly this form, so reading them is not very convenient.
    Probably everyone has already read that Swift is very slow. By and large, this is true, but let's try to figure it out. If we compile with a flag -O0, i.e. without any optimizations, Swift will be slower C ++ 10 to 100 times. If compiled with the flag -O3, we get 10 times slower than C ++. Flag-Ofastnot very safe, since it disables int overflow checking in runtime, etc. It is better not to use it in production. However, it can improve performance to the level of C ++.
    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 for Swift, for example, in cycles there are a huge number of retaines and releases that, in essence, are not needed in Swift, but they slow down performance very much.

    Further I will talk about things that are not very related to each other, which 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 ifone is not quite a preprocessor, but simply a language construct that allows you to test the platform. Accordingly, we do not have a method that returns us on which platform we are. Depending on this, we do an alias on View. In this way, we create MyControlone that will work on both iOS and OS X.

    The next feature - pattern matching - I really like. I am a little fond of functional languages, there it is used very widely. Take the problem as an example: we have a point on the plane, and we want to understand which of the four quadrants it is in. We all imagine what kind of code this is in Objective-C. For each quadrant, we will have such absolutely wild conditions where we must check whether x and y fall into this framework:

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

    In this case, Swift gives us some convenient pieces. First, we have a tricky range operator with three points. Accordingly, it casecan check whether a point falls in 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 ten times more readable than what Objective-C can provide us with.

    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 addwith such a tricky declaration: two pairs of brackets with parameters instead of one. This gives us the opportunity to either call this function almost like usual and get the result 8, or call it with one parameter. In the second case, magic happens: at the output we get a function that accepts Intand returns too Int, i.e. we partially applied our function addto the top five. Accordingly, we can then apply the function add5with a triple and get a figure eight.

    As I said, there is no preprocessor, so even implementing it assertis a non-trivial thing. Suppose we have a task to write someassert. We can test it for debug, but so that the code that does not execute in the assert, we must pass it as a closure. Those. we see that we have 5 % 2in braces. In terminology, Objective-C 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 nobody will use assertes like that. Therefore, Swift has automatic closures. In the method declaration we see @autoclosure, accordingly, the first argument turns into a closure, and curly braces can be omitted.

    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 explicit type conversion. Swift is a typed language, therefore, as in Objective-C, we cannot populate objects with an id type. Therefore, consider the following example. Suppose I have a structure Boxthat receives some value during initialization, which cannot be changed. And we have a packaged Intunit.

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

    We also have a function that takes input Int. Accordingly, boxedIntwe cannot transfer there, because the compiler will tell us that it Boxdoes not convert to Int. The craftsmen gutted the guts of the swift a bit and found a function that allows you to convert the type Boxinto 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 it 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. we cannot receive information on methods.

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

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

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

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

    Finally, I’ll talk about repositories that are worth a look:
    • BDD Testing framework: Quick . This is the first thing that everyone lacked. The framework is actively developing, new matchers are constantly being added.
    • Reactive programming: RXSwift . This is a reimagining of ReactiveCocoa using the constructs provided by Swift.
    • Model mapping: Crust . Analogue Mantle for Swift. Allows you to map JSON objects into swift objects. Many interesting hacks are used that can be useful in development.
    • Handy JSON processing: SwiftyJSON . This is a very small library, literally 200 lines. But it demonstrates the full power of enumerations.

    Also popular now: