Модуль 1. Таблицы и Неизменность

Модуль 1. Таблицы и Неизменность

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

Во-первых, вы узнаете, как использовать таблицы для отображения списков информации и использовать шаблон проектирования master-detail, чтобы позволить пользователям взаимодействовать с информацией.

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

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

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


Уроки по Swift

  • Протоколы

Уроки по SDK

  • Жизненный цикл приложения
  • Модель-Представление-Контроллер (MVC)
  • Прокручиваемые представления
  • Табличные представления
  • Продвинутые табличные представления
  • Системные контроллеры представлений
  • Сохранение данных
  • Сложные экраны ввода

Что вы создадите

List — это приложение для отслеживания задач, которое позволяет пользователю добавлять, редактировать и удалять задачи в привычном интерфейсе master-detail. Вы можете настроить приложение для отслеживания любого типа информации, например, коллекции карточек или ваших любимых фильмов.

Урок 1.1 Протоколы

Протокол — это набор правил или процедур, определяющих, как что-то делается. Компьютеры общаются друг с другом, используя протоколы, такие как HTTP (Протокол передачи гипертекста) и TCP/IP (Протокол управления передачей/Интернет-протокол). HTTP — это стандарт, который определяет, как данные веб-сайта передаются между двумя компьютерами. TCP/IP — это стандарт связи, который определяет, как компьютеры находят и отправляют данные друг другу.

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

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


Что вы узнаете

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

Терминология

  • делегат (delegate)
  • применить (adopt)
  • протокол (protocol)
  • реализация (implementation)
  • только для чтения (read-only)
  • чтение/запись (read/write)
  • Codable (кодируемый)
  • Comparable (сравнимый)
  • conform (соответствовать)
  • CustomStringConvertible (настраиваемый строковый конвертируемый)
  • Equatable (сравнимый на равенство)

 


Связанные ресурсы

Протокол определяет шаблон методов, свойств и других требований, которые подходят для выполнения определенной задачи или функциональности. Затем протокол может быть принят другим типом, который предоставляет фактическую реализацию этих требований. Любой тип, который удовлетворяет требованиям протокола, соответствует этому протоколу.

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

  • CustomStringConvertible, который позволяет вам контролировать, как ваши пользовательские объекты выводятся в консоль;
  • Equatable, который позволяет вам определить, как экземпляры одного и того же типа равны друг другу;
  • Comparable, который позволяет вам определить, как экземпляры одного и того же типа сортируются;
  • Codable, который позволяет вам кодировать свойства вашего типа в виде пар ключ/значение, которые затем можно сохранить между запусками приложений.

Когда вы принимаете протокол в Swift, вы обещаете реализовать все методы, требуемые этим протоколом. Компилятор проверит, что все в порядке, и не соберет вашу программу, если что-то отсутствует.

Вывод информации с помощью CustomStringConvertible

Вы можете начать понимать роль протокола, рассматривая, как объекты выводятся в консоль с помощью функции print(). Как вы уже узнали, выполнение функции print() на переменной запишет текстовое представление объекта в консоль. Вы можете выводить значения типа String, Int и Bool с предсказуемыми результатами:

let string = "Hello, world!"
print(string)
 
let number = 42
print(number)
 
let boolean = false
print(boolean)
________________________
Console Output:
Hello, world!
42
false

Но что, если вы определите класс Shoe?

class Shoe {
  let color: String
  let size: Int
  let hasLaces: Bool
 
init(color: String, size: Int, hasLaces: Bool) {
  self.color = color
  self.size = size
  self.hasLaces = hasLaces
  }
}
 
let myShoe = Shoe(color: "Black", size: 12, hasLaces: true)
let yourShoe = Shoe(color: "Red", size: 8, hasLaces: false)
print(myShoe)
print(yourShoe)
____________________________________________________
Console Output:
Shoe
Shoe

(Ваше выражение print, вероятно, будет предварено чем-то вроде _lldb_expr_1, что можно игнорировать.)

Как видно, это выражение print совсем не информативно. Swift определяет протокол под названием CustomStringConvertible для решения подобных ситуаций. CustomStringConvertible имеет одно обязательное вычисляемое свойство, description, которое возвращает строковое представление экземпляра. Если вы принимаете этот протокол, вы контролируете, как именно ваши пользовательские типы представляются в виде печатных строковых значений.

Сначала примите протокол, добавив двоеточие и имя протокола, который вы хотите принять:

class Shoe: CustomStringConvertible {
  let color: String
  let size: Int
  let hasLaces: Bool
}

Затем соответствуйте протоколу, добавив требуемые функции или переменные. В этом примере вы добавите вычисляемое свойство description. Если вы не выполните этот шаг, компилятор отобразит ошибку, указывающую на несоответствие протоколу.

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

class Shoe: CustomStringConvertible {
  let color: String
  let size: Int
  let hasLaces: Bool
 
    init(color: String, size: Int, hasLaces: Bool) {
        self.color = color
        self.size = size
        self.hasLaces = hasLaces
    }
 
    var description: String {
        return "Shoe(color: \(color), size: \(size), hasLaces: \
        (hasLaces))"
    }
}
let myShoe = Shoe(color: "Black", size: 12, hasLaces: true)
let yourShoe = Shoe(color: "Red", size: 8, hasLaces: false)
print(myShoe)
print(yourShoe)
___________________________________________________
Console Output:
Shoe(color: Black, size: 12, hasLaces: true)
Shoe(color: Red, size: 8, hasLaces: false)

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

var description: String {

    let doesOrDoesNot = hasLaces ? "does" : "does not"
    return "This shoe is \(color), size \(size), and \
(doesOrDoesNot) have laces."
}
let myShoe = Shoe(color: "Black", size: 12, hasLaces: true)
let yourShoe = Shoe(color: "Red", size: 8, hasLaces: false)
print(myShoe)
print(yourShoe)
_______________________________________________________
Console Output:
This shoe is Black, size 12, and does have laces.
This shoe is Red, size 8, and does not have laces.

Сравнение информации с помощью Equatable

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

struct Employee {
  var firstName: String
  var lastName: String
  var jobTitle: String
  var phoneNumber: String
}
 
struct Company {
  var name: String
  var employees: [Employee]
}

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

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

Предположим, у вас есть доступ к текущему сотруднику через переменную Session.currentEmployee, которая возвращает экземпляр класса Employee, соответствующий текущему пользователю. Контроллер представления, отвечающий за отображение экрана деталей сотрудника, имеет свойство employee. Когда приложение загружает новый экран деталей сотрудника, вам нужно проверить, равны ли Session.currentEmployee и employee. Если это так, вы включите кнопку "Редактировать". Если нет, вы скроете кнопку.

Вы можете рассмотреть написание чего-то вроде следующего:

let currentEmployee = Session.currentEmployee 
 // Employee(firstName: "Daren", lastName: "Estrada", jobTitle:
 "Product Manager", phoneNumber: "415-555-0692")
let selectedEmployee = Employee(firstName: "James", lastName:
"Kittel", jobTitle: "Marketing Director", phoneNumber:
"415-555-9293")
 
if currentEmployee == selectedEmployee {
  // Enable "Edit" button
}

Код выше - хорошая идея. Оператор == проверяет, равны ли два значения. Но поскольку Employee - это пользовательский тип, вы должны указать Swift, как именно сравнивать два экземпляра на равенство. Вы сделаете это, приняв протокол Equatable.

Протокол Equatable требует, чтобы вы предоставили реализацию оператора == для вашего пользовательского типа с помощью статической функции ==, которая принимает параметры lhs (левая сторона) и rhs (правая сторона) и возвращает Bool, указывающий, равны ли два значения:

struct Employee: Equatable {
  var firstName: String
  var lastName: String
  var jobTitle: String
  var phoneNumber: String
 
  static func ==(lhs: Employee, rhs: Employee) -> Bool {
    // Logic that determines whether the value on the left-hand
       side and right-hand side are equal
  }
 
}

Рассмотрим приведенный выше пример с двумя сотрудниками. Как вы можете определить, равны ли два сотрудника? Следующий код возвращает true, если значения firstName и lastName одинаковы для обоих сотрудников:

struct Employee: Equatable {
  var firstName: String
  var lastName: String
  var jobTitle: String
  var phoneNumber: String
 
  static func ==(lhs: Employee, rhs: Employee) -> Bool {
    return lhs.firstName == rhs.firstName && lhs.lastName ==
    rhs.lastName
  }
}

Это был неплохой старт для реализации проверки на равенство. Но это быстро приводит к ошибкам. Рассмотрим двух разных сотрудников с одинаковым именем:

let currentEmployee = Employee(firstName: "James", lastName:
"Kittel", jobTitle: "Industrial Designer", phoneNumber:
"415-555-7766")
let selectedEmployee = Employee(firstName: "James", lastName:
"Kittel", jobTitle: "Marketing Director", phoneNumber:
415-555-9293")
 
if currentEmployee == selectedEmployee {
  // Enable "Edit" button
}

Этот код возвращает true, поэтому сотрудники смогут редактировать информацию друг друга в справочнике. Как можно создать более точную проверку, чтобы узнать, равны ли два экземпляра Employee? Вы можете обновить метод == для проверки каждого свойства:

struct Employee: Equatable {
  var firstName: String
  var lastName: String
  var jobTitle: String
  var phoneNumber: String
 
  static func ==(lhs: Employee, rhs: Employee) -> Bool {
      return lhs.firstName == rhs.firstName && lhs.lastName ==
      rhs.lastName && lhs.jobTitle == rhs.jobTitle &&
      lhs.phoneNumber == rhs.phoneNumber
  }
}

Если вы сравните имена, фамилии, должности и номера телефонов, вы получите более надежный результат. По мере того как вы будете расти как программист, вы начнете распознавать такие пограничные случаи и научитесь учитывать их в своем коде.

Иногда требования протокола могут быть сгенерированы автоматически. Например, если вы укажете, что один из ваших типов принимает Equatable и не напишете свой метод ==, компилятор Swift сгенерирует реализацию, которая сравнивает все свойства экземпляра, как в приведенном выше примере. Таким образом, если ваш метод == будет только сравнивать все свойства экземпляра, вам не нужно его писать. Компилятор сделает это за вас.

Чтобы использовать автогенерацию, ваш тип должен быть struct или enum — классы не поддерживают автогенерацию. Кроме того, каждый тип свойства также должен соответствовать Equatable.

struct Employee: Equatable {
  var firstName: String
  var lastName: String
  var jobTitle: String
  var phoneNumber: String
}

Сортировка информации с помощью Comparable

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

Вы начинаете со следующего:

let employee1 = Employee(firstName: "Ben", lastName: "Stott",
jobTitle: "Front Desk", phoneNumber: "415-555-7767")
let employee2 = Employee(firstName: "Vera", lastName: "Carr",
jobTitle: "CEO", phoneNumber: "415-555-7768")
let employee3 = Employee(firstName: "Glenn", lastName: "Parker",
jobTitle: "Senior Manager", phoneNumber: "415-555-7770")
let employee4 = Employee(firstName: "Stella", lastName: "Lee",
jobTitle: "Accountant", phoneNumber: "415-555-7771")
let employee5 = Employee(firstName: "Daren", lastName:
"Estrada", jobTitle: "Sales Lead", phoneNumber: "415-555-7772")
 
let employees = [employee1, employee2, employee3, employee4,
employee5]

Обратите внимание, что список выше не имеет определенного порядка.

Swift предоставляет протокол под названием Comparable, который позволяет определить, как сортировать объекты с использованием операторов <, <=, > или >=.

Comparable имеет два требования: тип должен также реализовать протокол Equatable и должен реализовать оператор <, который возвращает Bool, указывающий, является ли значение слева меньше значения справа.

В данном случае вам нужно отсортировать сотрудников в алфавитном порядке по фамилии. Тип String сам по себе является Comparable и использует оператор < для сортировки строк в алфавитном порядке, поэтому вы можете реализовать функцию < для Employee, чтобы возвращать true, если фамилия значения слева идет перед фамилией значения справа в алфавитном порядке:

struct Employee: Equatable, Comparable {
  var firstName: String
  var lastName: String
  var jobTitle: String
  var phoneNumber: String
 
  static func < (lhs: Employee, rhs: Employee) -> Bool {
      return lhs.lastName < rhs.lastName
  }
}

 

 


Отрывок из книги
Develop in Swift Data Collections
Apple Education
https://books.apple.com/ru/book/develop-in-swift-data-collections/id1581183203

Information

Apple, the Apple logo, Apple Books, Apple TV, Apple Watch, Cocoa, Cocoa Touch, Finder, Handoff, HealthKit, iPad, iPad Pro, iPhone, iPod touch, Keynote, Mac, macOS, Numbers, Objective-C, Pages, Photo Booth, Safari, Siri, Spotlight, Swift, tvOS, watchOS, and Xcode are trademarks of Apple Inc., registered in the U.S. and other countries. App Store and iBooks Store are service marks of Apple Inc., registered in the U.S. and other countries. ​
The Bluetooth® word mark and logos are registered trademarks owned by Bluetooth SIG, Inc. and any use of such marks by Apple is under license. ​
IOS is a trademark or registered trademark of Cisco in the U.S. and other countries and is used under license. ​
Other product and company names mentioned herein may be trademarks of their respective companies.