Краткое руководство по грамотному оформлению интерфейсов.

Предисловие

Это руководство я публикую для своих учеников, которые только начинают свой пусть в изучении языка Swift. В этой небольшой заметке я хочу расписать основные аспекты правильного структурирования классов, что бы навигация по ним была интуитивно понятной, как для создателя, так и для стороннего наблюдателя.

Введение

Читаемость кода является одним из самых важным аспектом в программировании и поэтому существуют определенные стандарты, как в написании кода, так и в оформлении интерфейсов. Особенно это касается вью контроллеров, которые сами по себе являются довольно массивными объектами и поэтому навигация по ним должна быть предсказуемой, в не зависимости от того работаете ли вы со своим проектом или же со сторонним.

Структура класса

Класс всегда начинается со свойств, к которым относятся аутлеты, вычисляемые свойства, а также публичные и приватные переменные и константы:

final class ViewController: UIViewController {

  // MARK: - IB Outlets
  @IBOutlet private var mainLabel: UILabel!
  @IBOutlet private var secondaryLabel: UILabel!
  @IBOutlet private var someLabel: UILabel!

  @IBOutlet private var mainTextField: UITextField!
  @IBOutlet private var secondaryTextField: UITextField!

  // MARK: - Public Properties
  var dataModel: DataModel!
  var someString = ""

  // MARK: - Private Properties
  private let dataManager = DataManager.shared
  private let networkManager = NetworkManager.shared
  private let imageManager = ImageManager.shared
}

Обратите внимание в какой последовательности объявлены свойства: сначала объявляются аутлеты, затем публичные переменные и константы, после которых уже идут приватные свойства. Так же обратите внимание на то, что аутлеты для лейблов разделяет пустая строка от аутлетов для текстовых полей. Такие разделители в виде пустых строк визуально отделяют логические блоки кода друг от друга, что повышает его читаемость. Не пренебрегайте ими, но и не злоупотребляйте. Достаточно одной пустой строки, что бы отделить одно от другого.

При помощи комментария // MARK: класс можно поделить на разделы. Эти комментарии играют роль заголовков, по которым можно быстро перемещаться по разделам. У каждого файла в проекте есть содержание, открыть которое можно, кликнув на название класса в верхней строке, в которой прописан путь до класса:

Содержание файла
Содержание файла

В этом списке отображаются все свойства и методы класса, а заголовки позволяют легче ориентироваться по всему содержимому и перемещаться в нужное место в один клик.

После свойств класса объявляются инициализаторы, если в них есть необходимость:

// MARK: - Initializers
init(name: String) {
  self.name = name
}

После инициализаторов объявляются переопределенные методы родительского класса. Это те методы, которые помечены ключевым словом override:

// MARK: - View Life Cycles
override func viewDidLoad() {
  super.viewDidLoad()
}

override func viewWillAppear(_ animated: Bool) {
  super.viewWillAppear(animated)
}

// MARK: - Navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  
}

После переопределенных методов объявляются IB Actions, т.е. методы связанные с элементами пользовательского интерфейса:

// MARK: - IB Actions
@IBAction private func buttonDidTapped() {
  
}

@IBAction private func sliderValueOnChange(_ sender: UISlider) {
  
}

После IB Actions определяются методы экземпляра:

// MARK: - Public Methods
func fetchData(from url: URL) {
  
}

func doSomething() {
  
}

И в самом конце класса объявляются приватные методы. Это те методы, которые создаются для реализации внутренней логики самого класса:

// MARK: - Private Methods
private func setupUI() {
  
}

private func setupNavigationBar() {
  
}

private func showAler(witnTitle title: String, andMessage message: String) {
  
}

Так же классы могу иметь расширения или extensions. Расширения позволяют группировать методы со связанным функционалом в одном месте, что делает код более организованным и читаемым. Так если класс необходимо подписать под протокол, то для этого лучше определить отдельное расширение с соответствующим заголовком:

// MARK: - UITabvleViewDataSource
extension ViewController: UITableViewDataSource {
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
  }

  func tableView(_ tableView: UITabvleView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
  }
}

Кроме того расширения полезно использовать для распределения обязанностей между командой. В этом случае для каждого расширения создается отдельный файл, что позволяет работать над одним классом сразу нескольким участникам проекта.

В итоге структура класса должна выглядеть следующим образом:

// MARK: - IB Outlets

// MARK: - Public Properties

// MARK: - Private Properties

// MARK: - Initializers

// MARK: - Overrides Methods

// MARK: - IB Actions

// MARK: - Public Methods

// MARK: - Private Methods

Общие советы

  • Не храните в классе методы, которые ни как не используются. Т.е. если у вас есть метод viewDidLoad(), но при этом он пустой, то смело его удаляйте. Ни чего лишнего в коде быть не должно.

  • Удаляйте все шаблонные комментарии, которые достаются вам “из коробки” при добавлении новых классов.

  • Используйте в качестве разделителей логических блоков кода пустые строки, но не более одной.

  • Названия классов должны быть емкими и отражать их суть. В проекте не должно быть классов с именем ViewController. Не сокращайте имена классов. В проекте не должно быть классов с именем MainVC.

Комментарии (2)


  1. varton86
    28.06.2023 11:14
    +1

    Я бы еще предложил сделать переменные IB Outlets и функции IB Actions приватными, зачем им наружу торчать)


  1. Debash Автор
    28.06.2023 11:14

    Согласен, хорошее замечание, поправлю