Introduction
Swift is a powerful and intuitive programming language from Apple that is rapidly gaining popularity among iOS and macOS developers. However, even with its beautiful syntax and strong typing, beginners often make the same mistakes. In this article, we will break down the top 4 most common mistakes made by novice Swift developers and show you how to avoid them. You will learn how to properly work with optionals, avoid memory leaks, manage closures, and not confuse structs with classes.
1. Overusing Force Unwrapping
One of the most common mistakes is using the exclamation mark ! to force unwrap optionals. This can lead to a program crash if the value turns out to be nil.
Example of the mistake:
let optionalString: String? = nilprint(optionalString!) // Fatal error: Unexpectedly found nil while unwrapping an Optional valueHow to fix it:
Use safe unwrapping methods: if let, guard let, or the ?? operator (nil-coalescing).
// Safe unwrapping with if letif let unwrappedString = optionalString { print(unwrappedString)} else { print("String is nil")}
// Or using guard let in a functionfunc printString(_ str: String?) { guard let safeString = str else { return } print(safeString)}
// Using nil-coalescinglet result = optionalString ?? "Default value"print(result)Tip: Use ! only in cases where you are absolutely certain the value is not nil (for example, when working with IBOutlet in UIKit after the view has loaded).
2. Ignoring Memory Management and Leaks in Closures
Swift uses Automatic Reference Counting (ARC) for memory management. However, closures can create strong reference cycles if you do not use weak or unowned.
Example of a memory leak:
class ViewController: UIViewController { var closure: (() -> Void)? func setupClosure() { closure = { // Strong reference to self self.view.backgroundColor = .red } }}In this example, the closure captures self strongly, and self (ViewController) holds the closure. This creates a reference cycle.
How to fix it:
class ViewController: UIViewController { var closure: (() -> Void)? func setupClosure() { closure = { [weak self] in guard let self = self else { return } self.view.backgroundColor = .red } }}Tip: Always use [weak self] in closures that might outlive the object. If the object is guaranteed to live longer than the closure, you can use [unowned self], but be careful — accessing a deallocated object will cause a crash.
3. Incorrect Use of Structs and Classes
Beginners often do not understand the difference between structs (value types) and classes (reference types), which leads to unexpected bugs.
Typical mistake:
struct User { var name: String var age: Int}
class UserManager { var user = User(name: "Ivan", age: 25) func updateUser() { var newUser = user newUser.name = "Peter" // user.name is still "Ivan" — the struct is copied! print(user.name) // Ivan }}If you expected the change to affect the original object, you were mistaken. Structs are passed by value.
How to fix it:
Use a class if you need shared state. Use a struct