Статьи

Swift From Scratch: наследование и протоколы

Если вы читали предыдущие уроки этой серии, вы уже должны хорошо понимать основы языка программирования Swift. Мы говорили о переменных, константах и ​​функциях, а в предыдущем уроке мы рассмотрели основы объектно-ориентированного программирования в Swift.

В то время как игровые площадки являются отличным инструментом для игры со Swift и изучения языка, пришло время двигаться дальше и создать наш первый проект Swift в Xcode. На этом уроке мы собираемся создать основу для простого приложения, использующего Xcode и Swift. Попутно мы узнаем больше об объектно-ориентированном программировании, а также познакомимся с интеграцией Swift и Objective-C.

Если вы хотите следовать за мной, убедитесь, что на вашем компьютере установлен Xcode 8.3.2 или выше. Xcode 8.3.2 доступен в магазине приложений Apple .

Запустите Xcode 8.3.2 или выше и выберите New> Project … из меню File . В разделе iOS выберите шаблон приложения Single View и нажмите кнопку « Далее» .

Настройка проекта в Xcode

Назовите проект ToDo и установите Language в Swift . Убедитесь, что для устройства установлено iPhone, а флажки внизу сняты. Нажмите Далее, чтобы продолжить.

Настройка проекта в Xcode

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

Прежде чем мы продолжим наше путешествие в Swift, давайте немного посмотрим, что Xcode создал для нас. Если вы новичок в разработке Xcode и iOS, то большая часть этого будет для вас новой. Однако, работая с проектом Swift, вы получите лучшее представление о том, как выглядят классы и структуры и как они ведут себя в Swift.

Шаблон проекта не сильно отличается от проекта, созданного с Objective-C в качестве языка программирования. Наиболее важные различия связаны с AppDelegate и ViewController .

Если вы работали с Objective-C в прошлом, то вы заметите, что шаблон проекта содержит меньше файлов. В нашем проекте нет файлов заголовков ( .h ) или файлов реализации ( .m ). В Swift у классов нет отдельного заголовочного файла, в котором объявлен интерфейс. Вместо этого класс объявляется и реализуется в одном файле .swift .

В настоящее время наш проект содержит два файла Swift: один для класса AppDelegate , AppDelegate.swift , а другой для класса ViewController , ViewController.swift . Проект также включает две раскадровки: Main.storyboard и LaunchScreen.storyboard . Мы будем работать с основной раскадровкой чуть позже в этом уроке. В настоящее время он содержит только сцену для класса ViewController .

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

Первое, что мы затронем в этом уроке, — это наследование, общая парадигма объектно-ориентированного программирования. В Swift только классы могут наследовать от другого класса. Другими словами, структуры и перечисления не поддерживают наследование. Это одно из ключевых отличий между классами и структурами.

Откройте ViewController.swift, чтобы увидеть наследование в действии. Интерфейс и реализация класса ViewController довольно ViewController , что позволяет нам сосредоточиться на основных ViewController .

В верхней части ViewController.swift вы должны увидеть оператор импорта для инфраструктуры UIKit. Помните, что инфраструктура UIKit предоставляет инфраструктуру для создания функционального приложения iOS. Оператор импорта в верхней части делает эту инфраструктуру доступной для нас в ViewController.swift .

1
import UIKit

Ниже инструкции import мы определяем новый класс с именем ViewController . Двоеточие после имени класса не соответствует типу, как мы видели ранее в этой серии. Вместо этого класс после двоеточия является суперклассом класса ViewController . Другими словами, следующий фрагмент может быть прочитан, поскольку мы определяем класс с именем ViewController который наследуется от UIViewController .

1
2
3
class ViewController: UIViewController {
 
}

Это также объясняет наличие оператора import в верхней части ViewController.swift, поскольку класс UIViewController определен в платформе UIKit.

Класс ViewController настоящее время включает в себя два метода, но обратите внимание, что каждый метод имеет префикс с ключевым словом override . Это указывает на то, что методы определены в суперклассе класса — или выше по дереву наследования — и переопределяются классом ViewController .

01
02
03
04
05
06
07
08
09
10
11
class ViewController: UIViewController {
 
    override func viewDidLoad() {
        super.viewDidLoad()
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
 
}

Конструкция override не существует в Objective-C. В Objective-C вы реализуете переопределенный метод в подклассе без явного указания, что он переопределяет метод выше дерева наследования. Objective-C исполняет все остальное.

Хотя то же самое верно и в Swift, ключевые слова override повышают безопасность переопределения методов. Поскольку мы viewDidLoad() префикс viewDidLoad() к ключевому слову override , Swift ожидает этот метод в суперклассе класса или выше в дереве наследования. Проще говоря, если вы переопределите метод, который не существует в дереве наследования, компилятор выдаст ошибку. Вы можете проверить это, неправильно viewDidLoad() метод viewDidLoad() как показано ниже.

Ошибка компилятора

Давайте добавим табличное представление к контроллеру представления, чтобы отобразить список задач. Прежде чем мы это сделаем, нам нужно создать точку для представления таблицы. Розетка — это не что иное, как свойство, которое видно и может быть установлено в Интерфейсном Разработчике. Чтобы объявить выход в классе ViewController , мы ViewController свойство, переменную, к @IBOutlet .

01
02
03
04
05
06
07
08
09
10
11
12
13
class ViewController: UIViewController {
 
    @IBOutlet var tableView: UITableView!
 
    override func viewDidLoad() {
        super.viewDidLoad()
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
 
}

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

Подожди минуту. Выход tableView объявлен как неявно развернутый необязательный, а не необязательный. Нет проблем. Мы можем объявить выход tableView как необязательный, заменив восклицательный знак в приведенном выше фрагменте знаком вопроса. Это будет хорошо скомпилировано. Однако это также будет означать, что нам нужно будет явно развернуть свойство каждый раз, когда мы хотим получить доступ к значению, хранящемуся в необязательном элементе. Это быстро станет громоздким и многословным.

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

С объявленной розеткой пришло время подключить ее в Интерфейсном Разработчике. Откройте Main.storyboard и выберите контроллер представления. Выберите « Встроить»> «Контроллер навигации» в меню « Редактор» . Это устанавливает контроллер представления в качестве корневого контроллера представления контроллера навигации. Не волнуйтесь об этом сейчас.

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

Добавление представления таблицы

Пока инспектор подключений открыт, выберите контроллер представления и подключите выход tableView к только что добавленному представлению таблицы. Это соединяет выход ViewController класса ViewController с представлением таблицы.

Прежде чем мы сможем создать и запустить приложение, нам необходимо реализовать протоколы UITableViewDataSource и UITableViewDelegate в классе ViewController . Это включает в себя несколько вещей.

Нам нужно сообщить компилятору, что класс ViewController соответствует протоколам UITableViewDataSource и UITableViewDelegate . Синтаксис выглядит так же, как в Objective-C.

1
2
3
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    …
}

Протоколы, которым соответствует класс, перечислены после суперкласса, UIViewController в вышеприведенном примере. Если у класса нет суперкласса, что не редкость в Swift, то протоколы перечисляются сразу после имени класса и двоеточия.

Поскольку протокол UITableViewDelegate не определяет требуемые методы, сейчас мы только собираемся реализовать протокол UITableViewDataSource . Прежде чем мы это сделаем, давайте создадим свойство переменной для хранения элементов списка дел, которые мы собираемся перечислить в табличном представлении.

1
2
3
4
5
6
7
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
    @IBOutlet var tableView: UITableView!
     
    var items: [String] = []
     
    …
}

Мы объявляем items свойства переменной типа [String] и устанавливаем пустой массив [] в качестве начального значения. Это должно выглядеть знакомым к настоящему времени. Далее, давайте реализуем два обязательных метода протокола UITableViewDataSource .

Первый необходимый метод, numberOfRows(inSection:) , прост. Мы просто возвращаем количество элементов, хранящихся в свойстве items .

1
2
3
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return items.count
}

Второй обязательный метод, cellForRow(at:) , нуждается в объяснении. Используя синтаксис нижнего индекса, мы запрашиваем items для элемента, который соответствует текущей строке.

01
02
03
04
05
06
07
08
09
10
11
12
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // Fetch Item
    let item = items[indexPath.row]
 
    // Dequeue Cell
    let cell = tableView.dequeueReusableCell(withIdentifier: «TableViewCell», for: indexPath)
 
    // Configure Cell
    cell.textLabel?.text = item
     
    return cell
}

Затем мы запрашиваем в табличном представлении ячейку с идентификатором "TableViewCell" , передавая путь индекса для текущей строки. Обратите внимание, что мы храним ячейку в константе, cell . Нет необходимости объявлять cell как переменную.

В следующей строке кода мы настраиваем ячейку табличного представления, устанавливая текст текстовой метки со значением item . Обратите внимание, что в Swift свойство textLabel объекта UITableViewCell объявлено как необязательный тип, отсюда и знак вопроса. Эта строка кода может быть прочитана, если установить свойство text текстовой метки в item если textLabel не равно nil . Другими словами, свойство text текстовой метки устанавливается, только если textLabel не равно nil . Это очень удобная конструкция безопасности в Swift, известная как дополнительная цепочка .

Перед созданием приложения нужно разобраться с двумя вещами. Во-первых, нам нужно сообщить табличному представлению, что ему нужно использовать класс UITableViewCell для создания новых ячеек табличного представления. Мы делаем это, вызывая registerClass(_:forCellReuseIdentifier:) , передавая класс UITableViewCell и идентификатор повторного использования, который мы использовали ранее, "TableViewCell" . Обновите метод viewDidLoad() как показано ниже.

1
2
3
4
5
6
override func viewDidLoad() {
    super.viewDidLoad()
 
    // Register Class for Cell Reuse
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: «TableViewCell»)
}

В настоящее время у нас нет элементов для отображения в виде таблицы. Это легко решить, items свойство items несколькими делами. Есть несколько способов сделать это. Я решил заполнить свойство items в viewDidLoad() как показано ниже.

1
2
3
4
5
6
7
8
9
override func viewDidLoad() {
    super.viewDidLoad()
 
    // Populate Items
    items = [«Buy Milk», «Finish Tutorial», «Play Minecraft»]
 
    // Register Class for Cell Reuse
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: «TableViewCell»)
}

Пришло время принять нашу заявку на спин. Выберите Run из меню продукта Xcode или нажмите Command-R . Если вы последовали, вы должны получить следующий результат.

Завершенное приложение

Обратите внимание, что я добавил заголовок To Do вверху панели навигации. Вы можете сделать то же самое, установив свойство ViewController экземпляра ViewController в viewDidLoad() .

01
02
03
04
05
06
07
08
09
10
11
12
override func viewDidLoad() {
    super.viewDidLoad()
 
    // Set Title
    title = «To Do»
 
    // Populate Items
    items = [«Buy Milk», «Finish Tutorial», «Play Minecraft»]
 
    // Register Class for Cell Reuse
    tableView.register(UITableViewCell.self, forCellReuseIdentifier: «TableViewCell»)
}

Хотя мы только что создали простое приложение, вы узнали немало новых вещей. Мы исследовали методы наследования и переопределения. Мы также рассмотрели протоколы и способы принятия протокола UITableViewDataSource в классе ViewController . Однако самое важное, что вы узнали, это то, как взаимодействовать с API Objective-C.

Важно понимать, что API iOS SDK написаны на Objective-C. Swift был разработан, чтобы быть совместимым с этими API. Основываясь на прошлых ошибках, Apple поняла, что Swift необходимо иметь возможность подключаться к iOS SDK без необходимости переписывать каждый API в Swift.

Комбинирование Objective-C и Swift возможно, но есть некоторые предостережения, которые мы рассмотрим более подробно по мере продвижения вперед. Из-за неустанного внимания Свифта к безопасности нам нужно время от времени преодолевать некоторые препятствия. Однако ни одно из этих препятствий не слишком велико, как мы узнаем на следующем уроке, в котором мы продолжим работу над нашим приложением.

А пока ознакомьтесь с некоторыми другими нашими курсами и учебными пособиями по разработке iOS на языке Swift!

  • стриж
    Создавайте приложения для iOS с помощью Swift
    Маркус Мюльбергер
  • IOS
    Идите дальше со Swift: анимация, работа в сети и пользовательские элементы управления
    Маркус Мюльбергер
  • стриж
    Запрограммируйте игру с боковой прокруткой с помощью Swift и SpriteKit
    Дерек Дженсен
  • iOS SDK
    Правильный способ поделиться состоянием между контроллерами Swift View
    Маттео Манфердини