Rules for better Swift code

Rules for better Swift code

For nearly last 6 months I’ve been writing code only in Swift. I can say it was a “love at first use”. Now I cannot imagine that I can start a new project with Objective-C. During these 6 months, I’ve read a lot of stuff about good practices in Swift. I found few nice ideas and I also have my own thoughts.

Get rid of storyboards

The title is not a mistake. Swift doesn’t like to work with storyboards in spite of they are recommended by Apple. Why? Using of storyboards forces you to use init?(coder aDecoder: NSCoder) inside UIViewController classes. That leads us to a question: How would you like to initiate the ViewController dependencies? The only answer is “You have to make them optional”. Generally speaking, when you mark something as an optional it has to have a reason in a business logic.

Let’s assume we have a LoginViewController which uses a LoginPresenter. Is there any sense, which reflects why LoginPresenter is an optional? No! LoginViewController shouldn’t exist without the LoginPresenter. Another good reason why unnecessary optionals are bad is that every value which will be returned from an optional function will always return an optional! Consider this example:

LoginViewController calls loginPresenter?.isValidEmail(textField.text). What would be the type of the result of this function? It would be an Optional<Bool> (aka. Bool?). To use the returned value you would have to unwrap it first. You have to make things non-optional whenever you can.

Optionals are great because we can make things non-optional.
Ayaka Nonaka

Use your own initializer inside ViewControllers

You have 2 possibilities how to use dependencies as non-optional values in UIViewController. The first possibility is to set the default value into the property while declaring it

class LoginViewController: UIViewController {
    let loginPresenter = LoginPresenter()
    [...]
}

It is better than having an optional, but it has its drawbacks. You are not able to inject mock/stub LoginPresenter inside UnitTest specs. Even better would be passing loginPresenter inside the initializer

class LoginViewController: UIViewController {
    let loginPresenter: LoginPresenter
    init(loginPresenter: LoginPresenter) {
        self.loginPresenter = loginPresenter
    }
    [...]
}

Now the LoginPresenter is not optional and you are able to inject an mock/stub in UnitTests.

But the init?(coder aDecoder: NSCoder) is required …

Yes, and the compiler will not let you forget about it. I know 2 ways how to make the compiler happy. The first option would be implementing init?(coder aDecoder: NSCoder) inside every UIViewController subclass and calling fatalError(“init(coder:) is not implemented”), but it feels like repeating yourself. An easier way is to create MyViewController type and making the init unavailable. Then all you have to do is to extend LoginViewController from MyViewController instead UIViewController. It also has some drawbacks, but they are justified because of greater good with not having optionals anywhere.

class MyViewController: UIViewController {
    @available(*, unavailable)
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
}

Use R.swift

As we all know, Swift is a very static typed language. Unfortunately, when you have to load a resource to the application you have to depend on strings. Changing Nib identifier will crash your app if you forgot to change the identifier wherever it is used.

The solution is R.swift. R.swift is an open-source library which will generate R.swift.generated file where you will have all resources like, images, nibs identifiers, localized strings etc. stored as structures and variables. Whenever a resource will be lost, the corresponding structure will disappear from R.swift.generated so you will get a compile-time error.

How it looks like? Check the sample:

let icon = UIImage(named: "settings-icon")
let font = UIFont(name: "San Francisco", size: 42)
let viewController = CustomViewController(nibName: "CustomView", bundle: nil)
let string = String(format: NSLocalizedString("welcome.withName", comment: ""), locale: NSLocale.currentLocale(), "Arthur Dent”)
let icon = R.image.settingsIcon()
let font = R.font.sanFrancisco(size: 42)
let viewController = CustomViewController(nib: R.nib.customView)
let string = R.string.localizable.welcomeWithName("Arthur Dent")

On Github, you can also find an alternative which is SwiftGen. It offers the same features as R.swift, but I prefer syntax in R.swift.

Use SwiftLint

Take care of you code-style. If you prefer to open brackets in the same line as function declaration do so, but stick to it in the whole project. When a project grows it is simpler to read it. Sometimes it is difficult to maintain the same code style when you work alone and it is nearly impossible to maintain it when you work in a group.

That’s why you should use SwiftLint. Lint is a tool which enforces style and convention by marking things with error or warning highlights. It is highly customizable, what rules should be used. It even can autocorrect smaller things like missing spaces or removing unnecessary semicolons. To install it just use Homebrew formula:

brew install swiftlint

Structure your files inside directories

I don’t like how Xcode organise files since my iOS development beginning, not since my Swift time. Nonetheless, it is important to mention about it now.

Putting everything into one folder and using only Xcode’s groups inside project leads to the big mess. Trying to find files in finders can be a really hard task to do. However, on Github, you can find a command-line tool that reorganizes your Xcode project folder to match your Xcode groups. Its name is Synx. I’ve also created a git hook to run synx before every push.

Use self only when compiler demands it

In Objective-C world to use a class property we had to call it with self key-word before. Don’t do so in Swift. Use it only when compiler demands it i.e. inside closures. First of all, you don’t lose anything. Code highlight uses different colors for instance or local variables, so you can distinguish that. The benefit from the rule is when you have to search for a retain-cycle it is much simpler to check all self usage in the first order.



Use if-let only when you have to

How many possibilities do we have in swift to use unwrapped optionals? 3. The first, probably the most known and the most overused, is if let. The second is guard, which helps you to avoid “xmas tree structure”. The lasts are map and flatMap functions over an optional. If you want to read more about map or flatMap I highly recommend this article written by @mokagio.

Next time when you have to play with optional, think first about map and flatMap. If they do not fit your requirements, think about guard. If guard also doesn’t fit use if let

func avatarOfCurrentUser() -> NSURL? {
  return currentUser()?.pictureURL
    .flatMap(NSURL.init)
    .map(addSmallSuffix)
}

flatMap and map are really powerful in making the code more expressive, aren’t they?

Conclusion

The last 6 months has been an amazing time for me with Swift. I’m surprised that Storyboards do not fit into the Swift nature but still, have so many fans. Remember, “Optionals are great because we can make things non-optional.”

I need to say, I really like Swift’s functional nature. map and flatMap can make your code more declarative which is nice. Go on and try it 😉

If you enjoyed reading, share it with your friends by clicking here 😉

 

 


Recommend articles:

  1. Swift API Guidelines
  2. Swift Optionals, Functional Programming, and You
  3. Strong, weak, unowned