Swift 3 Conversion Steps. Or “The 9 steps to Swift bliss”
October 10, 2016
Estimated reading time 7 minutes
Every time Apple decided to bring out a new version of Swift, I’d dive headfirst and start the conversion — the sooner the better. None of the past versions were such a nightmare as from Swift 2.1 to 3.0.
These notes I wrote down while converting our project to a Swift 3.0 project. Hopefully someone finds them useful. If you have comments or like to see something added, please let me know.
Let me start with a big fat warning.
⚠️ Don’t ‘Update to recommended setting’. Do this later. ⚠️
So the steps to undertake and achieve Swift bliss are this:
- Create new branch.
- Change
Podfile
to reflect the followingplatform :ios, '9.0', use_frameworks! inhibit_all_warnings!
- Update dependencies with a
$ pod update
- Commit changes to git
- Convert to Swift 3 syntax
- Commit changes
- Fix errors (loads of them)
- Lint
- Commit changes
Breaking changes in Swift 3 Conversion
So first things first – you are not alone in this. Apple has created a good starting point with their migration guide. Read it before starting the conversion and keep it as a reference while converting.
- New keywords. It takes some getting used to. Current behavior changed too.
- String indexes and Ranges work different
ErrorType
⇒Error
- All enum cases are now lowercase
- Use of
_
in methods to supres/omit names parameters. Named parameters are now default. .[upper/lower]caseString
⇒.[upper/lower]cased()
because it is mutating.for
can now be used for named parameters.addTarget(self, action: ..., for: ...)
containsString()
⇒contains()
.objectForKey(...)
⇒object(forKey: ...)
- Colors are not a function call anymore but class var
where
keyword in optional binding not needed anymore, you can now just use a commaCGColorRef
⇒CGColor
- when using multiple properties in single
guard
you must uselet
for each property. Also in optional binding. UIControlState.Normal
⇒UIControlState()
UIView.animateWithDuration
⇒UIView.animate(withDuration
NSFetchedResultsController
is now a Generic and must be called with a concrete Type of the Entity you wish to fetch. Like so:NSFetchedResultsController
Same withNSFetchRequest(entityName: "Enitity")
String(self)
⇒String(describing: self)
.- Dispatch Queues tend to f-up the migrator wizard syntax. It mangles it like this:
DispatchQueue.main.sync(DispatchQueue.mainexecute: { () -> Void in
but should have beenDispatchQueue.main.sync {
Swiftable has more info on this
Errors:
Argument label does not match any available overloads
Solution: In the conversion to Swift 3 something went haywire, look in the original implenmentation wat the function signature is you want to implement.
Ambiguous reference
Solution: You need to provide more Type detail. Usually this is fixed by adding an : Type
or an as? Type
or with an optional binding if let ...
. When this method comes from an Objective-C external lib – what also helps is to annotate the Objective-C methods with NS_SWIFT_NAME(method(arg1:arg2:))
Method cannot be declared because its parameter uses an internal type
Solution: Take a close look at the access modifiers. The above states that there are no (and thus internal
), or wrong access modifiers used.
Instance method ‘method(param:)’ nearly matches optional requirement ‘method(in:)’ of protocol ‘SomeProtocol’
Solution: In some cases the Swift migrator fails but does find something that looks like that method signature. Usually fixable by searching and copying the orginal protocol for the method signature. In UIKit protocols is almost always the case that you need to supress the param name by adding an underscore to it. So func method(param:)
becomes func method(_ param:)
Expression of type “SomeType?” is unused.
Solution:
Before Swift 3, all methods had a “discardable result” by default. No warning would occur when you did not capture what the method returned.
In order to tell the compiler that the result should be captured, you had to add
@warn_unused_result
before the method declaration. It would be used for methods that have a mutable form (ex. sort and sortInPlace). You would add@warn_unused_result(mutable_variant="mutableMethodHere")
to tell the compiler of it.However, with Swift 3, the behavior is flipped. All methods now warn that the return value is not captured. If you want to tell the compiler that the warning isn’t necessary, you add
@discardableResult
before the method declaration.If you don’t want to use the return value, you have to explicitly tell the compiler by assigning it to an underscore:
_ = someMethodThatReturnsSomething()
Why did this change in Swift 3:
- Prevention of possible bugs (ex. using
sort
thinking it modifies the collection)- Explicit intent of not capturing or needing to capture the result for other collaborators
The UIKit API appears to be behind on this, not adding
@discardableResult
for the perfectly normal (if not more common) use ofpopViewController(animated:)
without capturing the return value. — This answer comes directly from Stack Overflow
SE-0047 Swift Evolution Proposal
API Changes:
Apple keeps a list of API’s that changed, the ones that have a Swift method signature. This way you can cross reference if you get any ‘Instance method nearly matches optional requirement of protocol’ errors.
When you use your own libraries with Cocoapods.
Errors with Generics
I’ve hit a couple of compiler errors which are already filled with Apple. Try to work around it or find a different solution to your problem. Unless Apple fixes this (which will probably be next version)
Once all your errors are gone and you linted your project with SwiftLint
Now you can update to the latest project settings.
Now - we’ve been through a lot of pain an frustration, go make yourself a cup-o-something and pat yourself on the back. “Well done you”. You deserved it.
Let’s hope Apple does not make breaking changes going forward with Swift.