WWDC 2015

Swift in practices

Problem: Users on Different OS Releases?

  • change the app to required the latest OS? no
  • hold back on adopting new releases? no
  • Adopt new feature and support older OS Release? yes

always use latest SDK to build and set Minimum deployment target for minimum supported OS release

  • Absence of earlier OS
  • framework (set the framework as optional)
//not suggested way as the class might be private in previous versions.
//or typo to make this condition true but the action caused crashes. 
if([NSDataAsset class]) {
}
// not suggested as easy to typo and different syntax for from classes
if ([view responsToSelector:@selector(abc)] ) {
    [view abc];
}
// not suggested as easy to typo and different syntax
if (&functionAvailableInIOS9) {
}

New Swift way to check OS Version

//Assume our min. deploy target is iOS 7
let locationManager = CLLocationManager()
//ERROR as requestWhenInUseAuthorization() not available in iOS 7
locationManager.requestWhenInUseAuthorization()
//do new version check
//compiler knows that method runs when it is iOS8 or up
if #available(iOS 8.0, *) {
    locationManager.requestWhenInUseAuthorization()
}

using #available so that the compiler can help developer to check the system version safely without worrying

#available(iOS 9.0, OSX 10.11, *) to support multiple platform

Bailing out early

guard #available(iOS 9.0, *) else { return }
let asset = NSDataAsset(name: "Dragon")

Factoring the code

Method with system version requirement

class MyClass {
    @available(iOS 8.0, *)
    func functionThatWithiOS8() {
    }
    func otherMethod() { ... }
}
let myClass = MyClass()
myClass.otherMethod()
//must use #available to check in order to invoke
if #available(iOS 8.0 , *) {
    myClass.functionThatWithiOS8()
}

Class with system version requirement

@available(iOS 8.0, *)
class MyClass {
    func functionThatWithiOS8() {
    }
    func otherMethod() { ... }
}
//must use #available to check in order to invoke
//in iOS 7 the class is not available
if #available(iOS 8.0 , *) {
    let myClass = MyClass()
    myClass.otherMethod()
    myClass.functionThatWithiOS8()
}

@available and Subclassing

class CustomBlurView : UIView { ... } 
func makeBlurView() -> UIView {
    if #available(iOS 8.0, *) {
        //user newer api to make blur view
        return UIEffectView(...)
    }
    return CustomBlurView(...)
}
let blurView = makeBlurView()

Enforcing Application Constraints

Using Enum

We normally use this have a UIImage

let appleImage = UIImage(named: "Apple")!

This comes with three issues

  1. duplicated information as name is in the code and asset catalog
  2. Using String as name may caused typo and compiler can not check
  3. forced unwrapping

A specific enum that solves the issues with strictly typed and no force unwrapping with additional benefits:

  1. centrallized constants
  2. Does not pollute global namespace
  3. must use one of the enum cases
  4. not failable UIImage init
enum UIImage {
    enum AssetIndentifer: String {
        //in Xcode 7 beta 3, the case name will convert into String if no other value assigned
        case Apple
        case Orange
        case Banana 
        ...
    }
    //add unwrapped init 
    convenience init!(assetIdentifer: AssetIndentifer) {
        self.init(named: assetIdentifer.rawValue)
    }
}

Then we can use the enum outside

let appleImage = UIImage(assetIdentifer: .Apple)

We also have segue identifiers in each view controllers

someViewController.swift
override func prepareForSegue(segue: UIStoryBoardSegue, sender: AnyObject?) {
    switch segue.identifier {
        case "segue1"
        case "segue2"
        //error as this switch have not default case
    }
}

We can use the techique before to wrap the identifer into enum. When we add a new enum case, compiler find the switch did not handle the case

class someViewController : UIViewController {
    enum SegueIdentifer: String {
        case Segue1
        case Segue2
        case Segue3
        ...
    }
    override func prepareForSegue(segue: UIStoryBoardSegue, sender: AnyObject?) {
        guard let identifier = segue.identifer, 
               segueIdentifer = SegueIdentifer(rawValue: identifer)
        else { 
            //not the segue identifer we want.. 
       }
        switch segue.identifier {
            case Segue1
            case Segue2
            // error as not all cases handled
        }
    }
    // we add extra function that accept enum as segue
    func performSegueWithIdentifer( segueIdentifer : SegueIdentifer, sender : AnyObject? ) {
        preformSegueWithIdentifier(segueIdentifer.rawValue, sender)
    }
}

even so, if we have multiple view controllers, the enum and segue pattern will be repeated. Instead, we can use protocol to restrict the view controllers to have same enum and functions and we can have all logic in one place.

protocol SegueHandlerType {
    //leverage the enum and related swift functions
    typealias SegueIdentifier: RawPresentable
}

and use extension to limit what type can use this protocol

extension SegueType where Self: UIViewController, SegueIdentifer.RawValue == String {
    func preformSegueWithIdentifier( segueIdentifer: SegueIdentifer, sender: AnyObject?) {
        performSEgueWithIdentifier(segueIdentifer.rawValue, sender: sender)
    }
    func segueIdentiferForSegue(segue: UIStoryboardSegue) -> SegueIdentifer {
        guard let identifer - segue.identifer, segueIdentifer = SegueIdentifer(rawValue: identifer) 
        else {
            // invalid segue identifer.. 
        }
        return segueIdentifer
    }
}

Then we set the view controllers conform that protocol

class someViewController : UIViewController, SegueHandlerType {
    enum SegueIdentifer: String {
        ...
    }
    func handleAction(sender: AnyObject?) {
        performSegueWithIdentifier(.Segue1, sender: sender)
    }
    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        switch segueIdentiferForSegue(segue) {
            case .Segue1
            case .Segue2
            ...
            //no more error for needing default case as all enum case known and handled
        }
    }
}

if we add a new segue, the compiler will tell us missing handling in prepareForSegue and this promotes compiler time safety. This also helps reusability of the protocol and helps to reduce the error as constrainting the environment.