Swift is quickly growing as one of the top programming languages. Swift has overtaken Objective-C and has become the 14th most popular language in the TIOBE Index. Some of the reasons for this kind of popularity are: safe memory management, strong typing, generics, etc. Swift is cleaner and more readable than Objective-C, there are modules that eliminate class prefixes, and it also has half as many files in a project and understandable closure syntax — the list of benefits goes on. Overall, with Swift things have improved dramatically, and code has become simpler and stable.
When transitioning from Objective-C to Swift, it’s logical to map concepts you know in Objective-C onto Swift. You know how to create classes with Objective-C, and now you know the equivalent in Swift. However, every programming language has some specific features that are core to its design, that makes it different from others languages, and that the designers of that language have deliberately put into it to make things easier for the programmers. Thus, you should always think from the perspective of these language-specific features while designing the structure of your program in the respective language. Similarly, Swift does more than providing a better syntax for your app; here you have the opportunity to change the way in which you tackle problems and write code.
In this article, we will look at some of the design and coding guidelines that will help you apply the benefits of such Swift features to your programs, and that will make your code more Swift-like.
Swift is designed to be safe, so make use of Swift language features to make your code safe and robust.
For example, “array index out-of-bound” is a common exception condition with other programming languages. But in Swift, you can prevent it altogether because it’s pretty rare in Swift to need to use the index for array operations because the Swift array APIs are designed in such a way that you never need to use the index. There is an extensive set of collection access and iteration operations and syntax that you can use, which have internal bond checking that makes them reliable to use. For example, the first property of array is equivalent to “isEmpty? nil: self”. Another sign that Swift wants to discourage you from doing index math is the removal of traditional C-style for loops from the language in Swift 3.
Another example of a common exception condition is the null pointer exception. This can also be avoided in Swift with the help of Optional, which gives you a strict compile-time check on the nullable variables.
Default to structs unless you actually need a class-only feature or reference semantics.
Structs are preferable if the entity you want to create is relatively small and copiable because copying is much safer than having multiple references to the same instance, as happens with classes. This becomes more important when you are passing around a variable to many classes and/or in a multithreaded environment. If you can always send a copy of your variable to other places, you never have to worry about that other place changing the value of your variable. Moreover, with structs, there is much less to worry about in regards to memory leaks or multiple threads racing to access/modify a single instance of a variable.
Mark classes as final unless you have explicitly designed them to be inheritable.
Inheritance is a very useful tool, but it’s also very overused. Inheritance should be used when the classes form a strict hierarchy, where subclasses are their parent classes in every sense of the word. Many times, inheritance is used as a convenient means of code reuse, and this is a big part of why it gets a bad reputation. For such cases, use composition or extensions. Also, in case you want to use inheritance internally but not allow subclassing for external clients, mark a class public but not open. Always start by marking the class as final, unless you have a very good reason for not doing do.
Use guard to exit functions early.
The basic idea behind “guard” is to bail out as soon as possible. It is used to check a set of requirements that must be met before the rest of the method’s body is executed. This can also be done using conditionals, but conditionals are often the very cause of complexity. Nested conditionals and multiple conditions can make it difficult to find bugs, make the code hard to understand, make it easy to overlook edge cases. Nested conditionals and multiple conditions can make it difficult to find bugs, make the code hard to understand, and even make it easy to overlook edge cases.
The guard statement is ideal for getting rid of deeply nested conditionals whose sole purpose is validating the set of requirements. It makes the code more understandable because its syntax is more explicit about the requirement than a regular “if” statement. A guard statement is just as powerful as an if statement. You can use optional bindings, and even using “where” clauses are permitted.
Use extensions over inheritance for flexibility.
As we have seen in the above section, inheritance is a good tool, but it has been overused, and it has its own evils. Swift designers have come up with a good alternative called “extensions,” which helps you extend the functionality of a class, struct, enum, or protocol with ease. You can use extensions if you want to share common methods among related types, or you may want to add more functionality to some library classes. Swift is able to use extensions to improve the Swift standard library itself. It is a good point of reference for learning how to use extensions to improve your code design.
Default to immutable variables (let) unless you know you need mutation.
When you read a declaration like “let some = …”, you know that the value of some will never change; it’s enforced by the compiler. This helps greatly when reading through the code. Thus, always default to immutable variables (let) unless you know you need mutation. But don’t force it, in case a mutation makes code clearer or more efficient than using immutable variables. However, note that this is only true for types that have value semantics. A let variable to a class instance (i.e, a reference type) guarantees that the reference will never change (i.e, you can’t assign another object to that variable). However, the object to which the reference points can change.
Avoid using force-unwraps and implicitly unwrapped optional.
You define an optional as implicitly unwrapped when you define its type like “let x: String!”. This technique allows you to tell the compiler to automatically unwrap that value as if it wasn’t optional at all.
In force unwrapping, you add a “!” after an optional value to automatically unwrap it, without having to check whether it is nil or not. Unlike implicitly unwrapping, this technique is used on existing values (e.g, “let firstLenght:Int = strings.first!.lenght”).
Both of these approaches are dangerous. Unwrapping an optional value without taking into account its nullability is dangerous, and it can actually crash your app. There are some cases when implicitly or force unwrapping an optional can make sense, such as in the case of outlets. It is a good practice to never use “!” apart for outlets; rather, use “if let”s and “guard let”s in the code to avoid access of a nil value and crashing the app.
Using higher-order functions like “map,” “filter,” and “reduce” makes the code more readable. But don’t force it; if a simple “for loop” does the job, then use it.
Swift is influenced by functional programming. One of the key functional contributions is polished support for high-order functions. (A function is “higher-order” if it has one or more parameters that are functions and/or if it returns a function.) In Swift, passing a function really means passing a closure. Some of the higher-order functions available in Swift include:
- Map: Loops over a collection and applies the same operation to each element in the collection
- Filter: Loops over a collection and returns an array that contains elements that meet a condition
- Reduce: Combines all items in a collection to create a single value
- FlatMap: When implemented on sequences, flattens a collection of collections
Using higher-order functions makes code easier to understand and less cluttered.
Write extensions on existing types and protocols, instead of free functions.
Swift extensions are powerful, as they enable you to add behavior to any class, struct, or enumeration — even if you don’t have access to the original source code. This means you can add behavior to even primitive types like Int and Double using extensions.
Extensions encourage code reuse by encapsulating behavior that will be used more than once in your project in a single location. Additionally, they promote good code organization, leading to cleaner and more readable code when used to add behavior that’s closely related to the type they are extending. iOS Swift Cocoa touch libraries like Foundation are good references for how extensions can be used to organize code, as they use extensions to extend the behaviors of most of their classes, structs, and enumeration types.