Practical examples that will help you learn about what's new for us in Swift 4.
Swift 4.0 is a new version of many of your favorite programming language with new features that allow us to write simpler and safer code. You will be pleased to learn that this is not as dramatic as the epic changes in Swift 3.0 and most of the changes are backward compatible with your existing Swift code. Of course, you will need some time to make changes, but this should not take a lot of time.
From the translator:I apologize in advance for the design and maybe some minor flaws in the translation, because This is my first experience of a full-fledged translation of a technical article, not for myself, but for the benefit of others.
WARNING: Swift 4 at the time of writing and preparing the translation of the article is in active development and the author of the original selected only some of the most interesting and useful new features for discussion. Please keep in mind that more features will be available closer to the release.
')
Swift encoding and decoding
We know that the types of values are great, but we also know that they interact terribly with the Objective-C API, such as
NSCoding - you need to write a certain layer or use classes, and both options are unpleasant. Worse, even if you switch to classes, you need to write manual encoding and decoding methods, the writing of which is painful and error prone.
Swift 4 introduces the
Codable protocol, which allows you to serialize and deserialize your own data types without writing additional code and not worry about losing your data types. Moreover, you can choose how you want to serialize data: use the classic property list format or JSON.
Yes, you read everything correctly: Swift 4 allows you to serialize your own data types to JSON without writing any special code.Let's see how beautiful it is. First, here are the custom data type and some of its instances:
struct Language: Codable { var name: String var version: Int } let swift = Language(name: "Swift", version: 4) let php = Language(name: "PHP", version: 7) let perl = Language(name: "Perl", version: 6)
As you can see, I connected the
Codable protocol to the
Language structure. With this tiny addition, we can convert it to JSON, represented as
Data in the following way:
let encoder = JSONEncoder() if let encoded = try? encoder.encode(swift) {
Swift automatically encodes all the properties within your data type, you do not need to do additional actions.
Now you, if you, like me, have used
NSCoding for a long time, probably have some doubts: is this really what we need and how can we be sure that it works? Let's add some code to try to convert the
Data object so that we can display it in the console, then we need to decode it back into a new instance of the
Language structure:
if let encoded = try? encoder.encode(swift) { if let json = String(data: encoded, encoding: .utf8) { print(json) } let decoder = JSONDecoder() if let decoded = try? decoder.decode(Language.self, from: encoded) { print(decoded.name) } }
Both JSONEncoder and PropertyListEncoder have many customization options. For more information on other options, see
suggestions for developing this new functionality .
Multiline String Literals
Writing multi-line strings in Swift has always meant adding
\ n inside your lines to add line breaks where you want them. In the code, this does not look very good, but at least correctly displays information for users. Fortunately, Swift 4 adds a new syntax for multi-line string literals, which allows you to add line breaks and use quotation marks without escaping, while at the same time useful functionality such as string interpolation is available.
To start a string literal, you need to: write three double quotes
"" " and press
[Enter] . Then you can continue to write the string for as many times as you want including variables and line breaks, until you finish your line by pressing
[Enter] and writing three double quotes
" " " .
I would like to clarify about pushing a line break because string literals have two important rules:
1) when you open a line using
"" "the contents of your line should begin on a new line;
2) when you close a line using
"" " this designation should also be at the beginning of a new line.
Here it is in action:
let longString = """ When you write a string that spans multiple lines make sure you start its content on a line all of its own, and end it with three quotes also on a line of their own. Multi-line strings also let you write "quote marks" freely inside your strings, which is great! """
This creates a new line with several line breaks right in the definition - it is much easier to read and write.
For more information, see
suggestions for the development of this new functionality .
Improved keypaths in KVC (Key-Value Coding)
One of the most favorite features of Objective-C is the ability to refer to a property dynamically, rather than directly — that is, to be able to say
“this is an X object, here is its property that I would like to read” without reading it at all. These links, called
keypaths , are different from direct access to properties, because they don’t actually read or write the value, they just hide them to use later.
If you haven't used
keypaths before, let me show you an analogy of how they work using conventional Swift methods. We are going to define the Starship and Crew structures, then create one instance of each:
// struct Crew { var name: String var rank: String } // , struct Starship { var name: String var maxWarp: Double var captain: Crew func goToMaximumWarp() { print("\(name) is now travelling at warp \(maxWarp)") } } // let janeway = Crew(name: "Kathryn Janeway", rank: "Captain") let voyager = Starship(name: "Voyager", maxWarp: 9.975, captain: janeway) // `goToMaximumWarp()` let enterWarp = voyager.goToMaximumWarp // enterWarp()
Since the functions in Swift are first-class types, the last two lines can create a reference to the
goToMaximumWarp () method and call it later when we need it. The problem is that we cannot do the same for properties — we cannot say
“create a link to the property of the captain’s name, which I can check when the inevitable rebellion happens” , because Swift will just read the property directly and you will just get the original value.
This is fixed by
keypaths : they are property references like our
enterWarp () code. If you call the link now you get the current value, but if you call the link later, you will get the latest value. You can get through any number of properties and Swift uses its output type to ensure that you return the correct type.
The Swift development community spent a lot of time discussing the correct syntax for
keypaths because it must be something visually different from other Swift code and in the end this syntax uses backslashes:
\ Starship.name ,
\ Starship.maxWarp , and
\ Starship .captain.name . You can assign these values to variables and use them when you want them to any instance of the
Starship structure. For example:
let nameKeyPath = \Starship.name let maxWarpKeyPath = \Starship.maxWarp let captainName = \Starship.captain.name let starshipName = voyager[keyPath: nameKeyPath] let starshipMaxWarp = voyager[keyPath: maxWarpKeyPath] let starshipCaptain = voyager[keyPath: captainName]
This will make
starshipName as a
String , and
starshipMaxWarp will make
Double because Swift is capable of displaying data types correctly. The third example takes the property property and Swift also correctly defines it.
For more information, see
suggestions for the development of this new functionality .
Improved dictionary functionality
One of the most intriguing suggestions for Swift 4 was the addition of some new functionality to the dictionaries to make them more powerful, as well as make their behavior more predictable in certain situations.
Let's start with a simple example: filtering dictionaries in Swift 3 does not return a new dictionary. Instead, as a result of filtering, we get an array of tuples with key-value labels. For example:
let cities = ["Shanghai": 24_256_800, "Karachi": 23_500_000, "Beijing": 21_516_000, "Seoul": 9_995_000]; let massiveCities = cities.filter { $0.value > 10_000_000 }
After this code is executed, you cannot use the following entry to get the necessary data:
massiveCities["Shanghai"]
because it is no longer a dictionary. Instead, you need to use this code:
massiveCities[0].value
and that's not very cool.
Beginning with Swift 4, this code behaves as you would expect - filtering will return a new dictionary.
Yes, it is obvious that it will break any existing code that uses an array of tuples as the return value.
Similarly, the
map () method with dictionaries did not work as many people had hoped: you received a transmitted key-value tuple; moreover, it could be one value added to the array. For example:
let populations = cities.map { $0.value * 2 }
This is currently not modified in Swift 4, but a new method,
mapValues () , has
emerged , which should be much more useful because this method allows you to convert values and put them back into the dictionary using the original keys.
For example, this code will convert a numeric value and convert the data on the population of cities to a string and add it back to a new dictionary with the same keys: Shanghai, Karachi, and Seoul:
<let roundedCities = cities.mapValues { "\($0 / 1_000_000) million people" }
(If you're interested, mapping the dictionary keys is not safe, since you can accidentally create duplicates.)My favorite dictionary add-on is a grouping initializer, which converts a sequence into a sequence dictionary, which are grouped according to your wishes. Continuing our example with cities, we could use
cities.keys to return an array of city names, and then group them by the first letter:
let groupedCities = Dictionary(grouping: cities.keys) { $0.characters.first! } print(groupedCities)
This will output the following to the console:
["B": ["Beijing"], "S": ["Shanghai", "Seoul"], "K": ["Karachi"]]
Or we could group cities based on the length of their names:
let groupedCities = Dictionary(grouping: cities.keys) { $0.count } print(groupedCities)
This will output the following to the console:
[5: ["Seoul"], 7: ["Karachi", "Beijing"], 8: ["Shanghai"]]
Finally, it is now possible to access the dictionary key and provide a default value if the specified key is missing:
let person = ["name": "Taylor", "city": "Nashville"] let name = person["name", default: "Anonymous"]
Now, any experienced developer may argue that a similar result can be achieved easier and I agree with that. We could write it this way in the current version of Swift:
let name = person["name"] ?? "Anonymous"
However, this does not work if you change the meaning of the dictionary, and not just get it. You cannot immediately change the value of the dictionary, because access by this key returns an optional type - the key may not exist. Using the default dictionary values in Swift 4, you can write more concise code, for example:
var favoriteTVShows = ["Red Dwarf", "Blackadder", "Fawlty Towers", "Red Dwarf"] var favoriteCounts = [String: Int]() for show in favoriteTVShows { favoriteCounts[show, default: 0] += 1 }
This loop iterates through each line in
favoriteTVShows and uses the
favoriteCocation dictionary to track how often each element appears. We can change the dictionary in one line of code, because we know that it will always have a value: either 0 as the default or some larger number based on the previous count.
For more information, see
suggestions for the development of this new functionality .
Lines again collection!
This small change, but guaranteed will make many people happy: the strings are collections again. This means that you can now reverse them, iterate over the characters, use
map () and
flatMap () , etc. For example:
let quote = "It is a truth universally acknowledged that new Swift versions bring new features." let reversed = quote.reversed() for letter in quote { print(letter) }
This change was made as part of a set of amendments, called the
"Manifest String" .
One-way ranges
Last but not least, in Swift 4, there is a Python-like one-way cutting of collections, where the missing side of the range is automatically defined as the beginning or end of the collection. This does not affect the existing code, because it is a new approach to the existing operator, so you can not worry about potential breakage in the code. Examples:
let characters = ["Dr Horrible", "Captain Hammer", "Penny", "Bad Horse", "Moist"] let bigParts = characters[..<3] let smallParts = characters[3...] print(bigParts) print(smallParts)
This code first displays in the console:
["Dr Horrible", "Captain Hammer", "Penny"]
and then this:
["Bad Horse", "Moist"]
For more information, see
suggestions for the development of this new functionality .
More to come ...
At the time of the translation of the article, developers are already available Xcode 9 Beta 3 (from 10.07.2017) along with iOS 11, tvOS 11, watchOS 4, and the new version of macOS. What we have already seen is promising, because it is clear that the team is working hard to make Swift 4 as good as possible. This is primarily about adding new functions, and not about changing existing ones and this should make it easier to switch to a new stable version of the language.
Despite the fact that the development of Swift can sometimes be chaotic, Swift 4 again confirms the approach of the Apple community. I added a few sentences, each of the proposals was widely discussed by the community to reach agreement - these are not just Apple engineers who force changes simply because they can, instead they have a sensible and thoughtful approach to improve what is already a smart and elegant language. .
One of the functions that was postponed is ABI compatibility, which would allow developers to distribute compiled libraries - one of the few key missing features that remain in Swift today. I hope we get to this in Swift 5 ...