SwiftLint для Swift: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

1. Что такое SwiftLint и зачем он нужен?

SwiftLint — это инструмент статического анализа кода (линтер) для языка программирования Swift. Он автоматически проверяет ваш код на соответствие набору правил, основанных на Swift Style Guide (рекомендациях по стилю кода от компании Apple) и лучших практиках сообщества. Представьте себе «умного ревьюера», который работает в реальном времени прямо в вашем редакторе кода, подсвечивая потенциальные проблемы, нарушения форматирования и логические ошибки.

Зачем это нужно? В больших проектах, где над одной кодовой базой работают десятки разработчиков, поддержание единого стиля кода становится критически важной задачей. SwiftLint решает эту проблему автоматически. Он не только помогает писать более чистый и читаемый код, но и предотвращает появление многих распространённых багов на ранних этапах. Например, он может предупредить вас об использовании устаревших API (deprecated), о слишком сложных функциях, которые трудно тестировать, или о неправильном форматировании отступов.

Интеграция SwiftLint в процесс разработки (CI/CD) позволяет автоматически отклонять Pull Request'ы, которые не соответствуют стандартам качества, экономя время на код-ревью и повышая общую стабильность проекта. Это обязательный инструмент для любой профессиональной команды iOS/macOS разработчиков.

2. Установка

SwiftLint можно установить несколькими способами. Самый популярный и рекомендуемый — через Homebrew (менеджер пакетов для macOS).

Способ 1: Homebrew (Рекомендуется)

Откройте терминал и выполните команду:

brew install swiftlint

Способ 2: CocoaPods

Если вы используете CocoaPods, добавьте в ваш Podfile:

pod 'SwiftLint'

Затем выполните:

pod install

Способ 3: Swift Package Manager (SPM)

Добавьте пакет в ваш Package.swift:

dependencies: [
    .package(url: "https://github.com/realm/SwiftLint.git", from: "0.54.0")
]

Способ 4: Mint

Если вы используете Mint:

mint install realm/SwiftLint

3. Быстрый старт: минимальный рабочий пример

После установки, самый простой способ проверить работу SwiftLint — запустить его из терминала в корневой папке вашего проекта.

  1. Создайте новый Swift файл example.swift с «плохим» кодом:
// example.swift
import Foundation

let x = 10
let y = 20
func sum(a:Int, b:Int)->Int {
return a + b
}
print(sum(a: x, b: y))
  1. Запустите линтер:
swiftlint lint --path example.swift

Вы увидите в терминале что-то вроде:

example.swift:3:1: warning: Colon Violation (colon) - Colons should be next to the identifier when specifying a type and next to the key in dictionary literals.
example.swift:5:1: warning: Vertical Whitespace Violation (vertical_whitespace) - Limit vertical whitespace to a single empty line.
example.swift:5:16: warning: Colon Violation (colon) - Colons should be next to the identifier when specifying a type.
example.swift:6:1: warning: Return Arrow Whitespace Violation (return_arrow_whitespace) - File should have a single trailing newline.
...

SwiftLint нашёл нарушения стиля. Теперь исправьте код, следуя подсказкам:

// example.swift (исправленный)
import Foundation

let x = 10
let y = 20

func sum(a: Int, b: Int) -> Int {
    return a + b
}

print(sum(a: x, b: y))

Запустите линтер снова — предупреждений не будет.

4. Основные методы/функции/классы

SwiftLint — это, по сути, CLI-инструмент, а не библиотека с классами, которые вы импортируете в свой код. Его «методы» — это команды терминала и правила конфигурации. Ниже представлены 8 ключевых концепций и команд.

4.1. Команда swiftlint lint

Сигнатура: swiftlint lint [--path <путь>] [--config <путь>] [--reporter <тип>]

Что делает: Основная команда для запуска анализа кода. Проверяет все Swift-файлы в указанной директории (или в текущей, если путь не указан) и выводит список нарушений.

Пример:

# Проверить только папку Sources
swiftlint lint --path Sources

# Использовать кастомный конфиг
swiftlint lint --config .swiftlint_custom.yml

# Вывести результат в формате JSON
swiftlint lint --reporter json

4.2. Команда swiftlint autocorrect

Сигнатура: swiftlint autocorrect [--path <путь>] [--config <путь>] [--format]

Что делает: Автоматически исправляет многие нарушения стиля (пробелы, отступы, точки с запятой и т.д.). Важно: Не все правила можно исправить автоматически. Используйте с осторожностью, предварительно сделав commit.

Пример:

# Автоматически исправить все файлы в проекте
swiftlint autocorrect

# Исправить и дополнительно отформатировать код (например, пробелы вокруг операторов)
swiftlint autocorrect --format

4.3. Правило force_cast (Force Casting)

Сигнатура: В конфиге: force_cast: error или force_cast: warning

Что делает: Запрещает использование принудительного приведения типов (as!). Это одна из самых частых причин крашей приложения. Правило рекомендует использовать безопасное приведение (as?).

Пример (нарушение):

let someValue: Any = "Hello"
let stringValue = someValue as! String // ❌ SwiftLint выдаст ошибку

Пример (исправление):

if let stringValue = someValue as? String {
    print(stringValue)
}

4.4. Правило force_unwrapping (Force Unwrapping)

Сигнатура: В конфиге: force_unwrapping: error

Что делает: Запрещает принудительное извлечение опционалов (!). Аналогично force_cast, это прямой путь к crash'у.

Пример (нарушение):

var optionalName: String? = nil
print(optionalName!) // ❌ Краш, если optionalName == nil

Пример (исправление):

guard let name = optionalName else { return }
print(name)

4.5. Правило line_length (Line Length)

Сигнатура: line_length: 120 (или warning: 120, error: 200)

Что делает: Ограничивает максимальную длину строки. Длинные строки тяжело читать, особенно на мобильных устройствах или при split-view. По умолчанию стоит 120 символов.

Пример (нарушение):

let veryLongString = "Это очень длинная строка, которая явно превышает лимит в 120 символов и её очень неудобно читать в редакторе кода, особенно если у вас открыт ещё один файл рядом." // ❌

Пример (исправление):

let veryLongString = """
Это очень длинная строка, которая явно превышает лимит
в 120 символов и её очень неудобно читать в редакторе кода,
особенно если у вас открыт ещё один файл рядом.
"""

4.6. Правило nesting (Nesting)

Сигнатура: nesting: type_level: 2, function_level: 3

Что делает: Контролирует уровень вложенности типов (классов, структур) и функций. Слишком глубокая вложенность делает код нечитаемым и сложным для тестирования.

Пример (нарушение):

class OuterClass {
    class InnerClass {
        class VeryInnerClass { // ❌ Уровень вложенности типов = 3, превышает лимит
            func doSomething() {}
        }
    }
}

4.7. Правило identifier_name (Identifier Name)

Сигнатура: identifier_name: min_length: 3, max_length: 40

Что делает: Проверяет имена переменных, функций, типов на соответствие правилам. По умолчанию запрещает слишком короткие (менее 3 символов, кроме i, j, k для циклов) и слишком длинные имена.

Пример (нарушение):

let a = 5 // ❌ Слишком короткое имя
let myVeryLongAndDescriptiveVariableNameForTheUserModel = User() // ❌ Слишком длинное

Пример (исправление):

let userCount = 5
let currentUser = User()

4.8. Правило cyclomatic_complexity (Cyclomatic Complexity)

Сигнатура: cyclomatic_complexity: 10

Что делает: Измеряет цикломатическую сложность функции (количество независимых путей выполнения). Высокая сложность означает, что функцию трудно понять, тестировать и поддерживать.

Пример (нарушение):

func processUser(input: String) -> String { // Сложность > 10
    if input == "a" { return "1" }
    else if input == "b" { return "2" }
    else if input == "c" { return "3" }
    // ... еще 10 условий
    else { return "0" }
}

Пример (исправление):

let mapping = ["a": "1", "b": "2", "c": "3"]
func processUser(input: String) -> String {
    return mapping[input] ?? "0"
}

5. Реальный практический пример (100-200 строк кода)

Рассмотрим пример типичного iOS-контроллера, который нарушает множество правил SwiftLint, и его исправленную версию.

Исходный код (с нарушениями)

import UIKit

class BadViewController: UIViewController {
    @IBOutlet var myButton: UIButton!
    @IBOutlet weak var nameLabel: UILabel!
    
    var items:[String] = []
    var selectedIndex:Int = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.title = "Bad"
        let x = 10; let y = 20; // Точка с запятой и несколько операторов в строке
        items = ["Apple", "Banana", "Cherry"]
        nameLabel.text = items[0]
        setupUI()
    }
    
    func setupUI() {
        myButton.layer.cornerRadius = 10
        myButton.backgroundColor = UIColor.blue
        myButton.setTitle("Press", for: .normal)
        myButton.addTarget(self, action: #selector(buttonPressed(sender:)), for: .touchUpInside)
    }
    
    @objc func buttonPressed(sender: UIButton!) { // Force unwrap
        let alert = UIAlertController(title: "Alert", message: "Button pressed!", preferredStyle: .alert)
        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
        self.present(alert, animated: true, completion: nil)
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return items.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = items[indexPath.row]
        return cell
    }
}

Запустив swiftlint lint, мы получим множество предупреждений: colon, semicolon, force_unwrapping, private_outlet, implicit_getter и другие.

Исправленный код (соответствует стандартам SwiftLint)

import UIKit

class GoodViewController: UIViewController {

    // MARK: - Outlets
    @IBOutlet private weak var myButton: UIButton!
    @IBOutlet private weak var nameLabel: UILabel!

    // MARK: - Properties
    private var items: [String] = []
    private var selectedIndex: Int = 0

    // MARK: - Lifecycle
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "Good"
        items = ["Apple", "Banana", "Cherry"]
        nameLabel.text = items[safe                    

Рекомендаций