Adventures in iOS Development
Unit Testing and Clean Code Exploration Toolbox Archive About Feed

Migrating to Swift 2.0 - It's The Little Things

Wtih Swift 2 now official, it’s good time to give migration from 1.2 a go. It can be longer-than-expected process - automatic updater can only get you so far. Instead of whining about it, I’ll go over some of the smaller features that don’t get as much spotlight as others.

forEach

Standard library has now forEach available for SequenceType. Own implementations and hacky use of map be gone! Combine this with information about types in collections added to the SDK and we can go from somewhat clunky deselectCells implementation:

func deselectCells() {
    for path in tableView.indexPathsForSelectedRows() as? [NSIndexPath] ?? [] {
        tableView.deselectRowAtIndexPath(path, animated: false)
    }
}

to an elegant one:

func deselectCells() {
    tableView.indexPathsForSelectedRows?.forEach {
        tableView.deselectRowAtIndexPath($0, animated: false)
    }
}

No longer there’s need to cover for nil case, when there are no selected cells. Closure’s body will be called only if return value is non-nil.

For such small piece of code we get three improvements:

  • seamless nil handling,
  • collection type information - no more casting,
  • less code to achieve same thing, one less (explicit) variable to worry about!

flatMap

In Swift 1.2 to filter out nil values from an array it was needed to combine filter and map:

let nilsAreHere: [Int?] = [1, nil, 3, 4, nil]
let noNilsAllowed = nilsAreHere.filter { $0 != nil }.map { $0! }

With 2.0 it’s been reduced to:

let nilsAreHere: [Int?] = [1, nil, 3, 4, nil]
let noNilsAllowed = nilsAreHere.flatMap { $0 }

Availibility checking

Say goodbye to more or less hacky ways of checking SDK API`s availibility:

  • respondsToSelector:,
  • UIDevice.currentDevice().systemVersion.compare,
  • NSProcessInfo().isOperatingSystemAtLeastVersion (that was actually no good for iOS 7 checks)
  • NSFoundationVersionNumber < NSFoundationVersionNumber_iOS_8_0.

Compiler now verifies whether code is accessing new APIs without proper availibility guards around it. Following would throw compilation error if deployment target was set to iOS 7.x:

NSProcessInfo.processInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 8, minorVersion: 0, patchVersion: 0))

Registering for push notifications with iOS 7 in mind can be implemented like this:

if #available(iOS 8.0, *) {
    let types: UIUserNotificationType = [.Alert, .Badge, .Sound]
    let settings = UIUserNotificationSettings(forTypes: types, categories: nil)
    app.registerUserNotificationSettings(settings)
    app.registerForRemoteNotifications()
} else {
    let types: UIRemoteNotificationType = [.Alert, .Badge, .Sound]
    app.registerForRemoteNotificationTypes(types)
}

Availibility attributes, besides preventing from one class of crashes, can be used to mark method as deprecated. Once again compiler helps to spot the issue - marking any usage of such method with a warning.

@available(*, deprecated, message="was deprecated because x, use shinyAction method instead")
func action() {
    // implementation
}

NS_OPTION updates

Foundation’s NS_OPTION representation in Swift changed from RawOptionSetType to OptionSetType. It impements ArrayLiteralConvertible protocol, so options can be nicely passed using array literal, instead of using | binary operator of C heritage.

// 1.2
let types: UIRemoteNotificationType = .Alert | .Badge | .Sound
// 2.0
let types: UIRemoteNotificationType = [.Alert, .Badge, .Sound]

While above change is purely stylistic, in the process the need for .allZeros option was eliminated. Just pass an empty array if there are no options. Nice small step towards a cleaner API.

NSLayoutConstraint.constraintsWithVisualFormat(format, options: [], metrics: nil, views: binding)

Honorable Mentions

There are a lot more changes worth mentioning. Won’t go over them, just attaching a table showing how nicer Swift’s Standard Library and working with Cocoa Touch got.

1.2 2.0
kCGBlendModeDestinationIn CGBlendMode.DestinationIn
view.setTranslatesAutoresizingMaskIntoConstraints(false) view.translatesAutoresizingMaskIntoConstraints = false
find(controllers, viewController) controllers.indexOf(viewController)
“2”.toInt Int(“2”)
var list = ["4", "3", "2", "1"]; list.sort(<) var list = ["4", "3", "2", "1"]; list.sortInPlace()
frame.rectByOffsetting(dx: dx, dy: dy) frame.offsetBy(dx: dx, dy: dy)