Swift 3 Conversion Steps. Or “The 9 steps to Swift bliss”

October 10, 2016


Estimated reading time 7 minutes

Bliss 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:

  1. Create new branch.
  2. Change Podfile to reflect the following platform :ios, '9.0', use_frameworks! inhibit_all_warnings!
  3. Update dependencies with a $ pod update
  4. Commit changes to git
  5. Convert to Swift 3 syntax
  6. Commit changes
  7. Fix errors (loads of them)
  8. Lint
  9. 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
  • ErrorTypeError
  • 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 comma
  • CGColorRefCGColor
  • when using multiple properties in single guard you must use let for each property. Also in optional binding.
  • UIControlState.NormalUIControlState()
  • UIView.animateWithDurationUIView.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 with NSFetchRequest(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 been DispatchQueue.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 of popViewController(animated:) without capturing the return value. — This answer comes directly from Stack Overflow

Warning of unused result

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.

Cocoapods fails when linting.

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.