Swift 4 includes many changes; 21 proposals have been implemented to be specific, but we will only cover a subset of those. Around 10 of these relate to the Swift package manager and of the remaining 11, some of the changes are minor improvements, so we will cover the ones that you will encounter in your day-to-day work.
What's new in Swift 4?
Setting up the environment
There are a couple of ways to run Swift 4.
It's a prerequisite that you have a developer account and then you can use either of the mentioned methods:
- Install Xcode 9, search for Xcode 9, log in with your developer account, and download the current beta available for downloads.
- In case you prefer to use Xcode 8, you can use the latest development snapshot for Swift 4.0 available at Swift.org. Once the download finishes, open the package .pkg file and install the snapshot. Open Xcode and go to Xcode | Toolchains | Manage Toolchains. Now pick the recently installed Swift 4.0 Snapshot and restart Xcode IDE:
Now your projects or playgrounds will use Swift 4 while compiling. We will use Xcode 9 for writing and executing all the code in this book. At the time of writing, the current Xcode 9 is in beta release version 6.
In the subsequent sections, you will read about the new features available in Swift 4, how you can transition to the latest Swift version, that is, Swift 4, and what should be the strategy for switching a massive code base written in Swift 3 to Swift 4; however, before that, a word of caution- the language is still in beta, and we should expect some changes and bug fixes along the lines until the official release is announced. With that being said, there is nothing to worry about; to keep an eye on the changes and stay up to date with the new implementations and bug fixes, follow the official release notes.
What’s changed?
Before we go ahead and discuss the new additions, let’s see what has changed or improved in the existing language.
Changes/improvements in Dictionary
Many proposals were made to enhance the Dictionaries and make them more powerful. In certain scenarios, Dictionaries might behave in an unexpected manner, and for this reason, many suggestions were made to change the way Dictionaries currently work in certain situations.
Let’s take a look at an example. Filtering returns the same data type; in Swift 3, if you used a filter operation on a Dictionary, the return type of the result would be a tuple (with key/value labels) and not a Dictionary. Consider this example:
let people = ["Tom": 24, "Alex": 23, "Rex": 21, "Ravi": 43]
let middleAgePeople = people.filter { $0.value > 40 }
After the execution, you cannot use middleAgePeople["Ravi"] since the returned result is not a Dictionary. Instead, you have to follow the tuple syntax to access the desired value because the return type is tuple, middleAgePeople[0].value, which is not implicitly expected.
Thanks to the new release, the current scenario has now changed as the new return type is a Dictionary. This will break any existing implementation in which you had written your code based on the return type, expecting it to be a tuple.
Similarly, while working with Dictionaries, the map() operation never worked the way most developers expected, since the return type could be a single value while you passed in a key-value tuple. Let's look at the following example:
let ages = people.map { $0.value * 2 }
This remains the same in Swift 4, but there is the addition of a new method mapValues(), which will prove to be of more use as it allows values passed to the method to be transformed and spit out as a Dictionary with original keys.
For example, the following code will round off and convert all the given ages to Strings, place them into a new Dictionary with the exact same keys, that is, Tom, Alex, Rex, and Ravi:
let ageBrackets = people.mapValues { "\($0 / 10) 's age group" }
Mapping Dictionary keys is not safe as we might end up creating duplicates.
Grouping initializer
Grouping initializer is the new addition to the Dictionary that converts a sequence into a Dictionary of sequences grouped as per your ambition. Continuing our people example, we can use people.keys to get back an array of people names and then group them by their first letter, like this:
let groupedPeople = Dictionary(grouping: people.keys) { $0.prefix(1) }
print(groupedPeople)
This will output the following:
["T": ["Tom"], "A": ["Alex"], "R": ["Rex", “Ravi”]]
Here, T, A, and R are initializers to the distinct names. For instance, consider that you had one more name in the Dictionary, say "Adam" aged 55:
["Tom": 24, "Alex": 23, "Rex": 21, "Ravi": 43, "Adam": 55]
In this case, the groupedPeople array might look something like this:
["T": ["Tom"], "A": ["Alex", "Adam"], "R": ["Rex", “Ravi”]]
Alternatively, we can group people based on the length of their names, as shown:
let groupedPeople = Dictionary(grouping: people.keys) { $0.count }
print(groupedPeople)
This will output the following:
[3: ["Tom","Rex"], 4: ["Alex", "Ravi","Adam"]]
Key-based subscript with default value
To understand this change, let’s first try to cite why it was required in the first place; let's take a look at the following code example:
let peopleDictionary : [String: AnyObject] = ...
var name = "Unknown"
if let apiName = peopleDictionary["name"] as? String {
name = apiName
}
Basically, our goal is to get the name of the user from some Dictionary (probably coming from some API) and in case it doesn't exist, we just want to keep the default name.
There are two problems with that approach. The first is the fact that we've probably got more than just a name field, and we end up with repetitive "if let" statements that are basically just making our code less readable.
The second problem is that just for the sake of unwrapping a value, we need to come up with some artificial name for the temporary assignment (and hey, we are not good at naming stuff anyway).
So the question now is, can we do better?
The previous solution would be to use generics or extensions to modify the behavior of the existing libraries used to write some generic method to retrieve the desired value, but with Swift 4, it's now possible to access a Dictionary key and provide a default value to use if the key is missing:
let name = peopleDictionary["name", default: "Anonymous"]
We can write the same thing using nil coalescing; you can alternatively use Swift 3 to write this line:
let name = peopleDictionary["name"] ?? "Anonymous"
However, that does not work if you try to modify the value in the Dictionary rather than just reading it. Accessing the key in the Dictionary returns an optional rather than an exact value and for this reason, we can't modify a Dictionary value in place, but with Swift 4, you can write much more maintainable and succinct code, as follows:
var friends = ["Deapak", "Alex", "Ravi", "Deapak"]
var closeFriends = [String: Int]()
for friend in friends {
closeFriends[friend, default: 0] += 1
}
The preceding loop in code loops over each entry in the friends array and populates the count of each entry in the closeFriends Dictionary. Since we know that the Dictionary will always have a value, we can modify it in one line of code.
Convert tuples to Dictionary
With Swift 4, you can now create a new unique Dictionary from an array of tuples consisting of duplicate keys. Let's take an example of an array of tuples with duplicate keys:
let tupleWithDuplicateKeys = [("one", 1), ("one", 2), ("two", 2), ("three", 3), ("four", 4), ("five", 5)]
Also, you want to convert this array into Dictionary, so you can do this:
let dictionaryWithNonDuplicateKeys = Dictionary(tupleWithDuplicateKeys, uniquingKeysWith: { (first, _) in first })
Now if you try to print dictionaryWithNonDuplicateKeys;
print(dictionaryWithNonDuplicateKeys), the output will be as illustrated:
["three": 3, "four": 4, "five": 5, "one": 1, "two": 2],
This is along with all the duplicate keys removed in the resulting Dictionary.
Convert arrays to Dictionary
You can create a new Dictionary by mapping two sequences one is to one or by mapping a sequence of keys and values according to a custom logic; let’s take a look at both the methods:
- Mapping two sequences (arrays) one is to one: Consider that you have two sequences personNames and ages as shown here:
let personNames = ["Alex", "Tom", "Ravi", "Raj", "Moin"]
let ages = [23, 44, 53, 14, 34]
You can create a Dictionary contacts by joining these two arrays, as follows:
let contacts = Dictionary(uniqueKeysWithValues: zip(personNames, ages))
The output will be this:
["Tom": 44, "Raj": 14, "Moin": 34, "Ravi": 53, "Alex": 23]
- Create a new Dictionary by mapping an array of keys and values according to a custom logic. Suppose you have two arrays- one with Strings representing all the odd numbers in words and other one with integers from 1 to 10:
let oddKeys = ["one", "three", "five", "seven", "nine"]
var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
- Now, consider that you want to create a Dictionary in which you want to map the String values to corresponding int values; you can do this as follows:
numbers = numbers.filter { $0 % 2 != 0 }
let oddDictionary = Dictionary(uniqueKeysWithValues: zip(oddKeys, numbers))
print(oddDictionary)
- The output will be this:
["six": 6, "four": 4, "eight": 8, "ten": 10, "two": 2]
Easy, isn’t it!
Resolving duplicates
Swift 4 allows us to initialize a Dictionary from a sequence with duple existence of entries and manage the duplicates easily. Suppose you have an array of friends as follows:
var friends = ["Deapak", "Alex", "Ravi", "Deapak"]
Also suppose that you want to create a Dictionary with all the friends, remove duplicates, and just maintain the count of the number of occurrences that occurred in the initial friends array; you can do this by initiating a new Dictionary, as follows:
let friendsWithMultipleEntries = Dictionary(zip(friends, repeatElement(1, count: friends.count)), uniquingKeysWith: +)
The output will be the following:
["Deapak": 2, "Ravi": 1, "Alex": 1],
This helps you avoid overwriting key-value pairs, without putting in a word. The preceding code besides the shorthand +, uses zip to fix duplicate keys by adding the two contrasting values.
Reserving capacity
Dictionary and sequence now have the capacity to explicitly, unambiguously reserve capacity.
Suppose you have a sequence of friends with an initial capacity of 4:
friends.capacity // 4
You can now reserve the capacity by doing this:
friends.reserveCapacity(20)
This reserves a minimum of 20 segments of capacity:
friends.capacity // 20
Reallocating memory to objects can be an expensive task, and if you have an idea about how much space it will require to store an object, then using reserveCapacity(_:) can be an easy and simple way to increase performance.
Swift 4 brings in a number of modifications to the Dictionary, 12 to be exact as per the official Apple developers guide, and a number of additions that we will discuss in subsequent sections:
Changes/improvements in Strings
Undoubtedly, the String is one of the majorly used data types in all the programming languages. Apparently, it is the data type that mankind understands better. Strings are important to the extent that they have the ability to significantly change our perception of how difficult or simple it is to learn a programming language. Hence, it becomes really important to follow any development to this data type. Strings received a major overhaul with Swift 4, making them collections of characters. In the earlier versions, several times, Swift, with its complicated way of handling subStrings and characters, went overboard in advocating accuracy over convenience.
Bid bye to string.characters
As part of the changes, this is one of the most welcomed changes. This change eliminates the necessity for a characters array on String, which means now you can reverse them, loop over them character-by-character, map() and flatMap() them, and more than anything, you can now iterate directly over a String object:
let vowels = "AEIOU"
for char in vowels {
print(char)
}
This prints the following:
A , E , I , O, U
Here, you not only get logical iteration through String, but also specific understanding for collection and sequence:
vowels.count , result is 5, no need of vowels.characters.count
vowels.isEmpty , result is false
vowels.dropFirst() , result is "EIOU"
String(vowels.reversed()) , result is "UOIEA"
There is a small improvement to the way characters behave—now you can obtain the UnicodeScalarView straight from the character, whereas earlier, instantiation of a new String was needed.
String protocol
Back in the days of Swift 1, Strings were a collection. In Swift 2, collection conformance was dropped because some of the behavior was thought to differ strongly enough from other collection types. Swift 4 reverses this change, so Strings are collection types again. One of the examples was described in the previous section where you were able to traverse over String vowels just like a normal array. In Swift 3, despite not being a collection, you could perform slicing operations on a String. Slicing a String in Swift 3 returned a String as well. This String was a String's own SubSequence, which led to some memory issues and one of the bigger changes in Swift 4 is the introduction of the subString types to remove these issues. For example, consider that we do this:
let secondIndex = vowels.index(after: vowels.startindex)
let subString = vowels[secondIndex...]
On doing this, if you inspect the type of subString, you will notice that it is String.SubSequence and not a String. With the existence of two types of String and SubSequence for adding functionality to Strings in your code base, you have to extend both types individually, which is quite cumbersome. Let's take a look at the following example:
We will add an extension to the Character type to determine whether a character is in uppercase:
extension Character {
var isUpperCase : Bool {
return String(self) == String(self).uppercased()
}
}
Using this, let's define a stripped uppercase method on String:
extension String {
func strippedUppercase() -> String {
return self.filter({ !$0.isUppercase})
}
}
So now that we have this method, we can use it on a String. So we can say that vowels.strippedUppercase() will return an empty String since all the characters in the vowels String are already uppercase.
If we grab a slice of the String though, that is, subString that we got earlier in the execution and use subString.strippedUppercase(), we get an error as subString is not a String anymore.
Does this mean that we need to extend the subString type and add this strippedUppercase() method as well? Thankfully NO!
Swift 4 also introduces String protocol. Both String and subString affirms to this new String protocol type. So anywhere we use extend String in our code base, we should now extend String protocol instead to ensure that subString gets the same behavior. So let's move the strippedUppercase() method into an extension of String protocol:
extension StringProtocol {
func strippedUppercase() -> String {
return self.filter({ !$0.isUppercase})
}
}
When we do this, we get an error because we need to be aware of what self means inside the method; self can now mean either a String or subString. To ensure that we always account for this, we will always convert self, make it a String instance, and then do the work we need. So if self is already a String, nothing happens and the function does not throw an error, but if it is a subString, we will make it a String and then call filter, and the preceding function works just fine:
extension StringProtocol {
func strippedUppercase() -> String {
return String(self).filter({ !$0.isUppercase })
}
}
There are more changes in String, but this should be the most used part that we might use from day to day.
Changed interpretation of grapheme clusters
An additional big advancement is the way String interprets grapheme clusters. Conformity of Unicode 9 gives resolution to this.
The use of extended grapheme clusters for character values in Swift 4 means that concatenation and modification of Strings may cause no affect on a resulting String's character count.
For example, if you append a COMBINING ACUTE ACCENT (U+0301) to the end of the String initialized to "cafe", the resulting String will have a character count of 4, and the fourth character will be "e", not e':
var word = "cafe"
print("total chars in \(word) is \(word.count)")
It prints "total chars in cafe is 4":
word += "\u{301}" // COMBINING ACUTE ACCENT, U+0301
print("totalchars in \(word) is \(word.count)")
It prints "total chars in café is 4", whereas the count would increase by 1 to reflect 5 as a result of print statement earlier.
Similar to Dictionaries, the total number of modifications made to String API can be summed up by the following image:
Access modifiers
The fileprivate access control modifier was used in Swift 3 to make important data visible outside the class in which it was declared but within the same file. This is how it all works in the case of extensions:
class Person {
fileprivate let name: String
fileprivate let age: Int
fileprivate let address: String
init(name: String, age: Int, address: String) {
self.name = name
self.age = age
self.address = address
}
}
extension Person {
func info() -> String {
return "\(self.name) \(self.age) \(self.address)"
}
}
let bestFriend = Person(name: "Robert", age: 31)
bestFriend.info()
In the preceding code, we created an extension for the Person class and accessed its private properties in the info() method using String interpolation. Swift encourages the use of extensions to break code into logical groups. In Swift 4, you can now use the private access level instead of fileprivate in order to access class properties declared earlier, that is, in the extension:
class Person {
private let name: String
private let age: Int
private let address: String
init(name: String, age: Int, address: String) {
self.name = name
self.age = age
self.address = address
}
}
extension Person {
func info() -> String {
return "\(self.name) \(self.age) \(self.address)"
}
}
let bestFriend = Person(name: "Robert", age: 31)
bestFriend.info()
These were all the changes introduced in Swift 4. Now we will take a look at the new introductions to the language.
What's new
In the next section, we will discuss new additions to the existing libraries and functionalities available in Swift.
JSON encoding and decoding
Everyone's favorite change to Swift 4 is of course the latest way to parse JSON. If you have been part of the Swift community for a while now, you'll know that every month we have a new hot way to parse JSON or so it feels, and now finally, we have an official way, so let's take a look. Let's talk about the Why first; Swift didn't ship with any native archival or serialization APIs and instead benefited from the Objective-C implementations available to it. While this allowed us to achieve certain end goals, it was both restrictive in that only NSObject subclasses can benefit, and we had to implement solutions for value types and as always, the existing solutions, NSJSONSerialization and so on, are far from Swift like.
The goal, therefore, for this proposal was to allow native archival and serialization of Swift Enums and Structs and to do that in a type safe way. Let's introduce some sample JSON to our playground to see how it works in practice and as an added benefit, we get to work with Swift 4's multiline String literal syntax:
import Foundation
let exampleJson = """
{
"name": "Photography as a Science",
"release_date": "2017-11-12T14:00:00Z",
"authors": [
{
"name": "Noor Jones"
},
{
"name": "Larry Page"
}
]
}
"""
We cannot convert this raw JSON in a datatype, so let's say as follows:
let json = exampleJson.data(using: .utf8)!
Now, let's define a type, and we will keep it simple so that we can see how the new API works:
struct Book {
let name: String
}
We want to decode the JSON into an instance of this struct Book and doing that in Swift 4 is super easy. First, we will make our model conform to our new Codable protocol, after which all we need to do is the following:
struct Book: Codable{
let name: String
}
let photographyBook = try! JSONDecoder().decode(Book.self, from: json)
If we match the values on the instance, we can see that they match the value in the JSON data and that's it, easy isn't it?
Multiline String literals
With earlier versions of Swift, you had to use \n in your Strings to add line breaks, which meant if the String was very long, then your code would start looking ugly with heaps of \n sprinkled across it. Proposal SEO163 introduces multiline literals to Swift with a very simple syntax. Long Strings or multiline Strings are Strings delimited by triple quotes, that is, """ so we can say as follows:
let paragraph = """
This is a paragraph to demonstrate an example of multi-line String literals and the use in the latest Swift 4 syntax!
"""
So you have to end the multiline String literals with triple quotes as well, as shown in the preceding code. The nice part about these multiline String literals is that they can contain newlines, single quotes, nested, and unescaped double quotes without the need to escape them. As an example, you can include some sample JSON to test against without escaping every single quote.
Smart key paths
Another important change introduced by Swift 4 is that of smarter key paths. Swift key paths are strongly typed and enforce a compile time check and remove a common runtime error.
You write a key path by starting with a backslash: `\Book.title`. Every type automatically gets a `[keyPath: …]` subscript to get or set the value at the specified key path:
struct Book {
var title = ""
let price : Float
}
let titleKeyPath = \Book.name
let mathsBook = Book(name: "Algebra", price: 10.50)
mathsBook[keyPath: titleKeyPath]
The value in the earlier mentioned keyPath is "Algebra".
The titleKeyPath object defines a citation to the name property. Then, it can be used as a subscript on that object. You can store and manipulate key paths. For example, you can append additional segments to a key path to drill down further. Key paths are composed of a root, and then you can drill down by following a combination of properties and subscripts.
If you change the variable of mathsBook from let to var, a specific property can also be modified through the keyPath subscript syntax:
mathsBook[keyPath: titleKeyPath] = "Trigonometry"
let newTitle = mathsBook[keyPath: titleKeyPath]
The value in the mentioned keyPath is "Trigonometry".
One sided ranges
Swift 4 makes it optional to provide a starting index or finishing index of ranges, as used with earlier versions of Swift.
With earlier versions of Swift, you had to do the following to use ranges:
let contactNames = [“Alex”, “Ravi”, “Moin”, “Vlad”, “Mark”]
let firstTwoContacts = contactNames[0..<2]
let lastThreeContacts = contactNames[2..<contactNames.count]
print(firstTwoContacts)
print(lastThreeContacts)
You will get the result as follows:
["Alex", "Ravi"] , for [0..<2]
["Moin", "Vlad", "Mark"], for [2..<contactNames.count]
However, with Swift 4, you no longer have to be constrained to lower bounds or upper bounds of the range mentioned in the for loop mentioned earlier in the code example. You can now use a one sided range where the missing side will automatically be treated as the start or end of a sequence:
let firstTwoContacts = contactNames[..<2]
let lastThreeContacts = contactNames[2...]
print(firstTwoContacts)
print(lastThreeContacts)
You will get the result as shown:
["Alex", "Ravi"] , for [..<2]
["Moin", "Vlad", "Mark"] for [2...]
Pattern matching with one sided ranges.
Pattern matching works really well with one sided ranges in switch statements, but you should be mindful of the one hitch that it has.
While writing a switch case, be careful to add a default case since you have to make your switch case exhaustive and since one sided ranges are infinite now, adding a default case becomes mandatory:
let selectedNumber = 7
switch selectedNumber {
case ..<0 :
print("You have selected a negative number.")
case 0... :
print("You have selected a positive number")
default :
break
}
Here, note that we have already covered all the scenarios in the first 2 cases:
- Case 1: All negative numbers up to -1
- Case 2: All positive numbers from 0 onward
Hence, we simply break out the switch statement in the default case.
swap versus swapAt
The swap(_:_: ) method in Swift 3 works on "pass by reference principle" and swaps two elements of a given array on the spot. In pass by reference, actual memory addresses are used rather than values:
var integerArray = [1, 2, 4, 3, 5]
swap(integerArray [2], integerArray [3])
As you can see, the parameters are passed as in out parameters, which means the actual references or placeholder addresses are accessible directly inside the function. On the other hand, Swift 4’s swapAt(_:_:) works on “pass by value” principle and only the corresponding indices are passed to the function to be swapped:
integerArray.swapAt(2, 3)
The swap(_:_:) function will not be seen in Swift 4, because it will be deprecated and removed, and you have a couple of approaches to replace it. The first approach uses a temporary constant as follows:
let temp = a
a = b
b = temp
The second takes advantage of Swift's built in tuples, as follows:
(b, a) = (a, b)
Improved NSNumber
With earlier versions of Swift, behavior of NSNumber might be unexpected and casting to Uint8 might result in absurd results:
let number1 = NSNumber(value: 1000)
let number1ConvertedToUInt = number1 as? UInt8
In this scenario, logically, the value inside number1ConvertedToUInt should be nil, but this was not the case and the value would be 1000% 255, that is, 232 instead. This is because the maximum value that a UInt can hold is 255. Fortunately, this behavior has been resolved in Swift 4 and now if you execute the same code in Swift 4, you should expect the value of number1ConvertedToUInt to be nil.
Directly access unicode scalars of characters
Swift 4 allows direct access to unicode scalars associated with characters:
let character: Character = “A”
let unicodeScalar = character.unicodeScalars
Done! Easy, isn’t it? Before you would have to convert the character to a String first and then try to access unicode scalar.
Migrating to Swift 4
As opposed to the previous releases of Swift, transitioning from Swift 3 to Swift 4 is less cumbersome when compared to the earlier migrations. The Swift migration tool is now bundled right into Xcode and is capable of handling most of the changes autonomously. Let's cover the steps required to update the version of a code base to the latest Swift release.
Preparation before migration
Ensure that the project that you are migrating builds successfully in Swift 3.2 mode, and all its tests pass. Keep in mind that Swift 3.2 does have significant changes from 3.1 as well as the SDKs against which you built, so you may need to resolve errors initially.
It’s highly recommended that you manage your project under source control. This enables you to easily review the applied changes via the migration assistant, get rid of them, and retry the migration if required.
Different from last year, the migrator is built directly into the compiler and not a separate tool, so it can work with both the versions of Swift, that is, 3.2, and 4.
Swift migration assistant
If you open your project with Xcode 9 for the first time, you will see a migration opportunity item in the Issue Navigator; click on it to activate a sheet asking you if you'd like to migrate. You can be reminded later or invoke the migrator manually from the menu—Edit | Convert | To Current Swift Syntax…
You will be given a list of targets to migrate. Only those targets that contain Swift code will be selected.
There is only one migration workflow this year, although there is a choice between two kinds of @objc inference:
- Minimize inference: Add an @objc attribute to your code only where it is needed based on static inference. After using this option, you need to follow the manual steps.
- Match Swift 3 behavior: Add an @objc attribute to your code anywhere it would be implicitly inferred by the compiler. This option does not change the size of your binary as it adds explicit @objc attributes everywhere.
Clicking on Next will bring up the Generate Preview sheet and a migration build will be initiated by the assistant to get source changes. Once this is completed, you will be shown all the changes that will be applied when you click on Save. This will also change the Swift language version build setting for the migrated targets to Swift 4.
You may find some issues while processing the targets during the migration process. You can check the log for these errors by switching to report navigator and converting the entry that was added.
Swift 4 migration changes overview
Extensive changes that the migrator suggests occur from data produced by a comparison of the previous SDK and the current SDK, which may drive renaming of identifiers and types, for example; and from normal compiler fix-its. There are some special arrangements where the migrator can safely perform simple mechanical changes.
SDK changes
Moving global constants into static type properties and converting String constants into Swift enumeration cases are the two most common SDK changes. Migrator handles these automatically. You’ll also see various type signature changes.
Notable special cases
The new release has many changes and additions, but there are some special cases that are worth mentioning. In the upcoming sections, we will cover some of these special cases.
New String
New APIs are added to String in Swift 4; the return types will be String or subString in certain cases. To ease this transition, the migrator will assist by adding explicit initializers in places where the API expects special cases.
We have already discussed some important changes in the String API earlier in the chapter.
Differentiating between single-tuple and multiple-argument function types
f: (Void) -> ()
When using f: (Void) -> () for the type of a function argument, it is generally meant to be f: () -> (), so the migrator will suggest that you use this type instead. Otherwise, with the new rules in SE-0110 for Swift 4, you will need to call the f function as f(()).
Adding tuple destructuring
Consider code such as the following:
swift func foo(_: ((Int, Int) -> ()) {} foo { (x, y) in print(x + y) }
The migrator must add explicit tuple destructuring to continue building in Swift 4, such as shown here:
swift func foo(_: ((Int, Int) -> ()) {} foo { let (x, y) = $0; print(x + y) }
Default parameter values must be public
The compiler now checks accessibility of referenced objects a bit more strictly; non-literal values that you want to use in public functions as arguments should also be public. Among other things, this exposes an opportunity for optimizing access to the value at the call site. As this may involve API design, the migrator does not suggest fixes, although there are some possibilities for you to consider as an API author:
- Making the referenced default values public
- Providing public functions that return a sensible default value
In both cases, consider the impact of exposing a new API, ensuring that you document your public symbols.
After migration
After applying the migrator changes, you might need to do some manual changes in order to build the project.
Some compiler errors might have some fixits; Xcode suggests these fixits and most of them are applied already by the migrator, but if the fixit does not fit almost 100% in the given scenario, then you might need to do it manually.
It is not necessary that the code that the migrator supplies is ideal, even if it compiles correctly. Use your best judgement and check that the changes are appropriate for your project.
Known migration issues
With any beta release of Xcode, numerous bugs get reported and with iterative releases of the beta versions for IDE, these bugs keep reducing. Similarly, there are some known issues with the current beta version of Xcode, and they will be fixed in the upcoming releases. At the time of writing this chapter, the Xcode IDE version is 9 beta 6, and mentioning all the known issues with the current version of IDE might change quite a lot in a few months, so it is better to follow the official release notes provided by Apple that are released with every IDE release in order to stay up to date with the latest known issues.
Using Carthage/CocoaPods projects
Here are some important points to consider when migrating a project with external dependencies using package managers such as Carthage, CocoaPods, or the Swift package manager:
- It is recommended to use source dependencies rather than binary Swift modules, because Swift 3.1 modules will not be compatible with Swift 3.2/4 modules unless you can get distributions that were built in Swift 3.2 or Swift 4 mode
- Ensure that your source dependencies work smoothly with Swift 3.2 as well as your own targets
- You need to remove Carthage file's search path or clean the build folder if you have used Carthage in your project
- It is not necessary to migrate your source dependencies as long as they can build in Swift 3.2 mode