Added just to make this article look better 😂.

Adding compile time safety to code

Vikash Anand
5 min readNov 2, 2019

--

Recently I watched a WWDC video “Swift in practice” which talked about many techniques among which one of the topic was “how to write compile time safe applications”. In this article I will be sharing couple of those techniques from the video.

Let’s start then

Case 1 — Asset Catalog Identifiers

So suppose an application has multiple images and to create an image from the asset catalogue, generally we write something similar to this

if let image = UIImage(named: iconName) {
self.iconImageView.image = image
}
OR
self.iconImageView.image = UIImage(named: iconName)!

Problem with this approach

  • We already have the images in the asset catalogue but still we have to unwrap it to use the images in the application as UIKit doesn’t know what all images are present in the asset catalogue.
  • Also it becomes very difficult to find out a typo in an image name if that particular image is being used in multiple locations in a project.

A classic solution for this typo problem would be to have a global constant for the image name. But still this will not save you the unwrapping effort also as you are allowed to pass any random string here, so it’s very much possible to pass a similar looking string constant here instead of the actual image name string constant.

So solution for this problem is to define application specific enums.

Step 1 - Create an enum to provide mapping for all the asset string identifiers.enum AssetIdentifier: String {
case IronMan = "ironMan"
case Hulk = "hulk"
case SpiderMan = "spiderMan"
case BlackWidow = "blackWidow"
case Thor = "thor"
}
Step 2 - Create a convenience initialiser which takes assetIdentifier as parameter and returns a non-optional image.extension UIImage {
convenience init!(assetIdentifier: AssetIdentifier{
self.init(named: assetIdentifier.rawValue)
}
}

And this is how you can use the new initialiser to get the image

self.iconImageView.image = UIImage(assetIdentifier: iconIdentifier)

Benefits of this approach

  • Typos and duplicate will be caught by the compiler
Duplicate case caught by compiler.
Typo caught by compiler.
  • Centrally located constant. So if I would want to add one more image to asset catalogue then I know exactly where I need to to go and add another image constant.
  • This doesn’t pollute the global namespace.
  • As you have defined an initialiser that returns a non-optional image so unwrapping is no more required.

Case 2 — Segue Identifiers

Consider the following scenario

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.identifier {
case "officalHomePage":
.
.
.
case "detail":
.
.
.
default:
fatalError("Unhandled segue...")
}
}

Here we have a ViewController with two segues and we are switching on those two segues.

Problem with this approach

  • We are using “stringly typed” segue identifiers here and due to which compiler will complain about “switch case not being exhaustive” and we have to add a default case to silence the compiler.
  • Also if I want to add a new segue to this view controller then it might become a bit of a task to locate the code where logic for this new segue needs to be added.

So the solution to this problem is to provide a mapping between segue identifiers and enums, similarly as the first case mentioned above.

class HeroesVC: UIViewController {
enum SegueIdentifier: String {
case OfficalHomePage = "officalHomePage"
case Detail = "detail"
}
.
.
.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {guard let identifier = segue.identifier, let segueIdentifier = SegueIdentifier(rawValue: identifier) else { fatalError("Invalid segue identifier") } switch segueIdentifier { case .OfficalHomePage:
.
.
.
case .Detail:
.
.
.
}
}

Benefits of this approach

  • Here instead of strings we are switching on enums and as the compiler knows that we defined only two cases so no need to provide a default case.
  • Also if we add new segue identifier enum , everywhere in the code compiler will automatically give an error that we don’t have exhaustive switch, so no need to look for the code to make changes for the new segue identifier.

Also sometime we have to invoke “performSegue(withIdentifier:sender:)” manually. Here again we are passing string segue identifier but as we have already defined enum for all the segues, so reusing the enum segueIdentifier instead of string will make more sense.

To do this we will define an overload on the UIKit provided method “performSegue(withIdentifier:sender:)” like following

func performSegue(withIdentifier identifier: SegueIdentifier, sender: AnyObject?) {
performSegue(withIdentifier: segueIdentifier.rawValue, sender: sender)
}

The above solution provided for segue identifier works for a single view controller and if you scale this solution to multiple view controllers then you will end up with duplicate code in all the view controllers.

So to make the above changes more reusable, we can extract the implementation of “prepareForSegue:sender:” and “performSegue(withIdentifier:sender:)” into a separate protocol like following

protocol SegueHandlerType {
associatedtype SegueIdentifier: RawRepresentable
}
extension SegueHandlerType where Self: UIViewController, SegueIdentifier.RawValue == String {func performSegue(withIdentifier identifier: SegueIdentifier, sender: AnyObject?) {
performSegue(withIdentifier: identifier.rawValue, sender: sender)
}
func segueIdentifierForSegue(segue: UIStoryboardSegue) -> SegueIdentifier {
guard let identifier = segue.identifier, let segueIdentifier = SegueIdentifier(rawValue: identifier) else { fatalError("Invalid segue identifier") }
return segueIdentifier
}
}

And all we have to do in the view controller is following

self.performSegue(withIdentifier: .OfficalHomePage, sender: sender)ANDoverride func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segueIdentifierForSegue(segue: segue) {
case .OfficalHomePage:
.
.
.
case .Detail:
.
.
.
}
}

Pretty straight forward, right!

Conclusion

Both the techniques mentioned above are taken straight from the WWDC video “Swift in practice” and i would highly recommend you to watch this.Also if in any part of this article you felt lost then again go and watch this video first and i am sure all you doubts will be answered.

I have created two sample projects for this. First / Starter project doesn’t have any of the above mentioned techniques, so you can download it and apply the above mentioned techniques yourself.Second /complete project has all the above mentioned techniques, just in case you want to have a look at the final implementation details.Links for both the projects is provided in the resource section of this article.

That’s it for now and i hope you find this helpful. Thanks for reading. 😀😀😀

Resource for this article:

Source code

--

--

Vikash Anand
Vikash Anand

No responses yet