- PVSM.RU - https://www.pvsm.ru -
Если Вы уже разобрались, как программировать на Swift, тогда Вы наверняка уже знаете основы языка Swift и как писать классы и структуры. Но Swift — больше, чем это — намного больше. Задача этой статьи расскажать о очень сильной стороне языка Swift, которая уже успела стать популярной в нескольких других языках под названием дженерики.
С помощью языка программирования безопасного по отношению к типам, это обычная проблема как написание кода, который действует только на один тип, но вполне корректен и для другого типа. Представьте себе, например, что функция добавляет два целых числа. Функция, которая добавляет два числа с плавающей запятой, выглядела бы очень похожей — но фактически, она будет выглядеть идентично.
Единственным отличием будет тип значения переменных.
В языке со строгим контролем типов Вы должны были бы определить отдельные функции как addInts, addFloats, addDoubles, и т.д., где у каждой функции был правильный аргумент, и типы возвращаемых значений.
Много языков программирования осуществляют решения этой проблемы. C++, например, использует шаблоны. Swift, как Java и C # используют дженерики — отсюда и тема этого урока!
В этой статье об обобщённом программировании Swift, Вы нырнете в мир существующих дженериков на языке программирования, в том числе и тех которых Вы уже видели. Тогда, создавайте программу для поиска фотографий в Flickr с пользовательской универсальной структурой данных для отслеживания критерия поиска пользователя.
Примечание: Эта статья о функциональном программировании Swift предназначен для тех, кто уже знает основы Swift. Если Вы плохо знакомы с основами языка Swift, мы рекомендуем Вам сначала посмотреть некоторые наши другие уроки о языке Swift.
Введение в дженерики
Вы не могли бы знать это, но Вы, вероятно, уже видели Дженерики в работе в Swift. Массивы и словари — классические примеры безопасности дженериков в деле.
Разработчики Objective-C привыкли к массивам и словарям, содержащие объекты разных типов в той же самой коллекции. Это обеспечивает большую гибкость, но знаете ли вы, что массив возвращенный из API предназначен для хранения? Вы можете убедиться, посмотрев на документацию или на имена переменных, другая форма документации. Даже в случае с документацией, не существует никаких способов (кроме кода без ошибок!), чтобы предотвратить сбои в коллекции во время выполнения.
С другой стороны Swift, имеет массивы и словари. Массив Ints может содержать только Ints и никогда не может (например) содержат String. Это означает, что Вы можете зарегистрировать код путем написания кода, что позволяет компилятору произвести проверку типов для вас.
Например, в Objective-C UIKit, метод, который обрабатывает прикосновение с учетом пользовательского представления, выглядит следующим образом:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
Набор в этом методе, как известно, содержит только экземпляры UITouch, но только потому, что так сказано в документации. Но ничто не мешает объектам быть чем-то еще, и Вы обычно должны вычислить прикосновение в наборе как экземпляры UITouch, чтобы эффективно рассмотреть их как объекты UITouch.
В это время Swift не имеет набора, который определяется в стандартной библиотеке. Тем не менее, если вы использовали массив вместо набора, то вы могли бы написать вышеупомянутый метод следующим образом:
func touchesBegan(touches: [UITouch]!, withEvent event: UIEvent!)
Это свидетельствует о том, что сенсорная матричная клавиатура содержит только UITouch экземпляры, и компилятор выдаст ошибку, если код, обращаясь к этому методу, попытается передать что-либо еще. Мало того, что типы регулирования компилятора, размещенные в сенсорной матричной клавиатуре, то вам больше не нужно вычислять элементы экземпляров UITouch!
В целом, дженерики обеспечивают типы в качестве параметра класса. Все массивы действуют одинаково, храня значения в виде списка, но общие массивы параметризуют тип значения. Вы могли бы счесть это полезным, если это было так: Алгоритмы, которые вы будете использовать в массивах, не являются нетипоспецифическими, так что все массивы, со всеми типами значений, могут их разделить.
Теперь, когда у Вас есть основные сведения о дженериках и их пользе, Вы спокойно можете применить их к конкретному сценарию.
Как работают Дженерики
Чтобы проверить дженерики, Вы должны создать приложение, которое ищет изображения в Flickr.
Начните с загрузки [1] стартового проекта для этого урока. Откройте его и быстро ознакомиться с основными классами. Класс Flickr может обрабатывать к API Flickr. Обратите внимание, ключ для API который находится в этом классе – предоставляется сразу, но Вы можете использовать свой собственный в случае, если вы хотите расширить приложение. Вы можете подписаться на один из них здесь [2].
Скомпилируйте и запустите приложение, и Вы увидите это:
Не очень все же! Не бойтесь, картинки с кошечьками скоро появятся.
Упорядоченные словари
Ваше приложение будет загружать изображения для каждого пользовательского запроса, и самые последний поиск будут отображать в виде списка в верхней части экрана.
Но что, если Ваш пользователь ищет тот же самый элемент дважды? Было бы хорошо, если бы приложение перенесло старые результаты в верхнюю часть нового списка и заменило его новым результатом.
Вы можете использовать массив для структуры данных чтобы вернуть результат, но в целях изучения дженериков, Вам нужно создать новую коллекцию: упорядоченный словарь.
Во многих языках программирования и фреймворках (включая Swift) наборы и словари не гарантируют какой-нибудь порядок, в отличие от массивов. Упорядоченный словарь выглядит как обычный словарь, но содержит ключи в определенном порядке. Вы будете использовать эту функциональность для хранения результатов поиска, что позволяет быстро найти результаты, а также поддерживать в порядке таблицу.
Если Вы были неосмотрительными, можно создать структуру пользовательских данных, чтобы обработать упорядоченный словарь. Но Вы дальновидны! Вы хотите создать что-то, что Вы можете использовать в приложениях в течение многих последующих лет! Дженерики — это идеальный вариант.
Структура первичных данных
Добавьте новый файл, выбрав FileNewFile… и далее iOSSourceSwift File. Нажмите на Next и назовите файл OrderedDictionary. Наконец, нажмите на Create.
В результате чего у вас будет пустой файл Swift и нужно будет добавить следующий код:
struct OrderedDictionary {
}
Пока в этом нет ничего удивительного. Объект станет структурой, потому что он должен иметь семантику значений.
Примечание: Одним словом, семантика значений — это необычный способ сказать “скопировать/вставить”, а не “публичная ссылка”. Семантика значений дает много преимуществ, например, нет необходимости беспокоиться о другой части кода, который может неожиданно изменить ваши данные. Чтобы узнать больше, перейдите на Главу 3 как понять Swift с помощью уроков, “Классы и Структуры” [3].
Теперь Вы должны сделать его дженериком, таким образом, он сможет содержать любой тип значений, которые Вы хотите. Измените определение структуры на следующее:
struct OrderedDictionary<KeyType, ValueType>
Элементы в угловых скобках являются параметрами типа дженерика. KeyType и ValueType не являются типами сами по себе, а становятся параметрами, которые можно использовать вместо типов в пределах определения структуры. Если вам не понятно, то все станет ясно в ближайшее время.
Самый простой способ реализовать упорядоченный словарь является поддержание, как массивов, так и словарей. Словарь будет хранить преобразование данных, а массив ключи.
Добавьте следующий код в определение структуры:
typealias ArrayType = [KeyType]
typealias DictionaryType = [KeyType: ValueType]
var array = ArrayType()
var dictionary = DictionaryType()
Это свидетельствует о двух свойствах, как описано, а также о двух псевдонимах типа, которые дают новое имя существующему типу. Здесь Вы соответственно даете псевдонимы массивам и типам словарей для резервных массивов и словарей. Псевдонимы типа — это отличный способ взять сложный тип и дать ему намного более короткое имя.
Обратите внимание, Вы можете использовать параметры типов KeyType и ValueType из определения структуры вместо типов. Массив представляет собой массив KeyTypes. Конечно, нет такой типа, как KeyType; вместо этого Swift рассматривает его как любой тип пользователя Упорядоченного словаря во время инстанцирования обобщенного типа.
В этот момент, Вы заметите ошибку компилятора:
Type 'Keytype' oes not conform to protocol 'Hashable'
Это могло бы быть сюрпризом для вас. Взгляните на реализацию Dictionary:
struct Dictionary<KeyType: Hashable, ValueType>
Это очень похоже на определения OrderedDictionary, за исключением одной вещи — “: Hashable” после KeyType. Hashable после точки с запятой показывает, что тип, переданный для KeyType, должен соответствовать протоколу Hashable. Это вызвано тем, что Dictionary должен уметь хешировать ключи для своей реализации.
Ограничить обобщенные параметры типа таким образом стало очень распространенно. Например, Вы могли бы ограничить тип значения, чтобы соответствовать протоколам Equatable или Printable в зависимости от того, что Ваше приложение должно сделать с теми значениями.
Откройте OrderedDictionary.swift и замените ваше определение структуры следующим:
struct OrderedDictionary<KeyType: Hashable, ValueType>
Это показывает, что KeyType для OrderedDictionary должен соответствовать Hashable. Это означает, что независимо от того каким типом становится KeyType, он все же будет приемлемым в качестве ключа для основного словаря.
Теперь файл будет компилироваться без ошибок!
Ключи, значения и все такое прочее
Какая польза от словаря, если Вы не можете добавить значения к нему? Откройте OrderedDictionary.swift и добавьте следующую функцию в ваше определение структуры:
// 1
mutating func insert(value: ValueType, forKey key: KeyType, atIndex index: Int) -> ValueType?
{
var adjustedIndex = index
// 2
let existingValue = self.dictionary[key]
if existingValue != nil {
// 3
let existingIndex = find(self.array, key)!
// 4
if existingIndex < index {
adjustedIndex--
}
self.array.removeAtIndex(existingIndex)
}
// 5
self.array.insert(key, atIndex:adjustedIndex)
self.dictionary[key] = value
// 6
return existingValue
}
Все это ознакомит Вас с более новыми сведениями. Давайте рассмотрим их шаг за шагом:
Теперь, когда у Вас есть возможность добавлять значения в словарь, что относительно того, чтобы удалить значения?
Добавьте следующую функцию к определению структуры в OrderedDictionary:
// 1
mutating func removeAtIndex(index: Int) -> (KeyType, ValueType)
{
// 2
precondition(index < self.array.count, "Index out-of-bounds")
// 3
let key = self.array.removeAtIndex(index)
// 4
let value = self.dictionary.removeValueForKey(key)!
// 5
return (key, value)
}
Давайте рассмотрим еще раз код шаг за шагом:
Доступ к значениям
Вы можете теперь записывать в словарь, но Вы не можете читать из него — это бесполезно для структуры данных! Теперь Вам нужно добавить методы, которые позволят Вам получать значения из словаря.
Откройте OrderedDictionary.swift и добавьте следующий код к структуре определения, и укажите под array и объявлениями переменной dictionary:
var count: Int {
return self.array.count
}
Это — вычисляемое свойство для количества упорядоченного словаря, обычно необходимых данных для такой структуры данных. Количество в массиве будет всегда соответствовать количеству упорядоченного словаря, таким образом, все будет просто
Затем, Вам нужно получить доступ к элементам словаря. В Swift, Вы получите доступ к словарю, используя синтаксис индекса, следующим образом:
let dictionary = [1: "one", 2: "two"]
let one = dictionary[1] // Subscript
Теперь Вы уже знакомы с синтаксисом, но вероятно только видели, что он использовался для массивов и словарей. Как же Вы бы использовали Ваши собственные классы и структуры? Swift, к счастью, позволяет легко добавить поведение индекса к пользовательским классам.
Добавьте следующий код в нижнюю часть определения структуры:
// 1
subscript(key: KeyType) -> ValueType? {
// 2(a)
get {
// 3
return self.dictionary[key]
}
// 2(b)
set {
// 4
if let index = find(self.array, key) {
} else {
self.array.append(key)
}
// 5
self.dictionary[key] = newValue
}
Вот, что делает этот код:
Теперь Вы можете индексировать в упорядоченный словарь, как будто это был обычный словарь. Вы можете получить значение для определенного ключа, но что относительно того, чтобы получить доступ с помощью индекса, как с массивом? Видя как это – работает с упорядоченным словарем, было бы неплохо также получить доступ к элементу через индекс.
Классы и структуры могут иметь несколько индексных определений для различных типов аргументов. Добавьте следующую функцию в нижнюю часть определения структуры:
subscript(index: Int) -> (KeyType, ValueType) {
// 1
get {
// 2
precondition(index < self.array.count,
"Index out-of-bounds")
// 3
let key = self.array[index]
// 4
let value = self.dictionary[key]!
// 5
return (key, value)
}
}
Это подобно нижнему индексу, который Вы добавили ранее, за исключением того, что тип параметра — теперь Int, потому что, это то, что вы используете для ссылки на индекс массива. На сей раз, однако, тип результата — кортеж ключа и значения, потому что, именно Ваш OrderedDictionary хранит заданный индекс.
Как работает этот код:
Задание: Реализуйте сеттер для этого индекса. Добавьте набор с последующим завершением, как и в предыдущем определении индекса.
В этот момент, вы можете задаться вопросом, что произойдет, если KeyType является Int. Преимуществом дженериков является ввод любого хешового типа в качестве ключа, в том числе и Int. В этом случае, как же индекс знает, какой индекс код нужно использовать?
Вот где вам нужно будет дать больше информации о типе для компилятора, чтобы он знал, что ему делать. Обратите внимание, что каждый из индексов имеет другой тип возврата. Поэтому, если вы пытаетесь задать кортеж значения ключа, компилятор будет знать, что он должен использовать нижний индекс на подобии массива.
Тестирование системы
Давайте запустим программу так, чтобы Вы могли поэкспериментировать с тем, как выполнять компиляцию, какой метод индекса использовать, и как Ваш OrderedDictionary работает в целом.
Создайте новый Playground, нажав на FileNewFile…, выбрав iOSSourcePlayground и нажмите Next. Назовите его ODPlayground и затем нажмите Create.
Скопируйте и вставьте OrderedDictionary.swift в новый playground. Вы должны сделать это, потому что, к сожалению, на момент написания этого урока площадка не может «видеть» код в Вашем модуле приложения.
Примечание: Существует обходное решение для этого, кроме как метода скопировать/вставить, который здесь осуществляется. Если Вы переместили код своего приложения в фреймворк, то ваш Playground может получить доступ к вашему коду, как указывает Корин Крич.
Теперь добавьте следующий код в playground:
var dict = OrderedDictionary<Int, String>()
dict.insert("dog", forKey: 1, atIndex: 0)
dict.insert("cat", forKey: 2, atIndex: 1)
println(dict.array.description
+ " : "
+ dict.dictionary.description)
var byIndex: (Int, String) = dict[0]
println(byIndex)
var byKey: String? = dict[2]
println(byKey)
На боковой панели (или через ViewAssistant EditorShow Assistant Editor) можно увидеть выходную переменную println():
В этом примере, словарь имеет ключ Int, так как компилятор будет рассматривать тип переменной, которая определит, какой индекс нужно использовать. Так как byIndex является (Int, String) tuple, компилятор знает, что нужно использовать индексную версию стиля массива нижнего индекса, которая соответствует ожидаемому типу возвращаемого значения.
Попытайтесь удалить определение типа данных из одной переменной byIndex или byKey. Вы увидите ошибку компилятора, что свидетельствует о том, что компилятор не знает какой нижний индекс нужно использовать.
Подсказка: Чтобы запустить выведения типа, компилятор требует, чтобы тип выражения был однозначен. Когда несколько методов существует с теми же типами аргументов, но с разными типами возвращаемых, то вызывающая функция должна быть конкретной. Добавление метода в Swift может внести в сборку критические изменения, так что будьте внимательны!
Поэкспериментируйте с упорядоченным словарем в playground, чтобы понять, как это работает. Попробуйте добавиться к нему, удалиться из него и изменить ключ и значение типов, прежде чем вернуться в приложение.
Теперь Вы можете читать и писать в свой упорядоченный словарь! Это поможет заботиться о вашей структуре данных. Теперь Вы можете начинать работать с приложением!
Добавление поиска изображения
Пора вернуться назад к приложению.
Откройте MasterViewController.swift добавьте следующее переменное определение, чуть ниже двух @IBOutlets:
var searches = OrderedDictionary<String, [Flickr.Photo]>()
Это должен быть упорядоченный словарь, который содержит результат поиска, которые пользователь получил от Flickr. Как Вы можете видеть, он отображает String, критерий поиска, массив Flickr.Photo, или фотографии, возвращенные из API Flickr. Обратите внимание, Вы направляете ключ и значение в угловые скобки так же, как и для обычного словаря. Они становятся параметрами типа KeyType и ValueType в этой реализации.
Вы можете задаться вопросом, почему тип Flickr.Photo имеет период. Это потому, что фотография является классом, который определяется внутри класса Flickr. Эта иерархия является довольно полезной функцией Swiftа, помогая содержать пространство имен, сохраняя имена классов короткими. Внутри класса Flickr, вы можете использовать одну только Photo, которая относятся к классу фото, потому что контекст сообщает компилятору, что это такое.
Затем, найдите метод источника данных табличного представления под названием tableView(_:numberOfRowsInSection:) и измените его на следующий код:
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int
{
return self.searches.count
}
Этот метод теперь использует упорядоченный словарь, который указывает, сколько ячеек имеет наша таблица.
Затем, найдите метод источника данных табличного представления tableView (_:cellForRowAtIndexPath:) и измените его на:
func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath)
-> UITableViewCell
{
// 1
let cell =
tableView.dequeueReusableCellWithIdentifier("Cell",
forIndexPath: indexPath) as UITableViewCell
// 2
let (term, photos) = self.searches[indexPath.row]
// 3
if let textLabel = cell.textLabel {
textLabel.text = "(term) ((photos.count))"
}
return cell
}
Вот что вы делаете в этом методе:
А теперь поговорим о «начинке». Найдите расширение UISearchBarDelegate и измените единственный метод следующим образом:
func searchBarSearchButtonClicked(searchBar: UISearchBar!) {
// 1
searchBar.resignFirstResponder()
// 2
let searchTerm = searchBar.text
Flickr.search(searchTerm) {
switch ($0) {
case .Error:
// 3
break
case .Results(let results):
// 4
self.searches.insert(results,
forKey: searchTerm,
atIndex: 0)
// 5
self.tableView.reloadData()
}
}
}
Этот метод вызывается тогда, когда пользователь нажимает на кнопку Поиск. Вот то, что делается в этом методе:
Йе-еху-у! Теперь ваше приложение будет искать изображения!
Скомпилируйте и запустите приложение и сделайте несколько поисков. Вы увидите нечто вроде этого:
Теперь повторите один из поисков, которые находятся не вверху списка. И вы увидите, как он возвратиться в начало списка:
Нажали на один из поисков и заметите, что он не показывает фотографий. Пора это исправить!
Дайте мне фотографии!
Откройте MasterViewController.swift и найдите метод prepareForSegue. И замените его на:
override func prepareForSegue(segue: UIStoryboardSegue,
sender: AnyObject?)
{
if segue.identifier == "showDetail" {
if let indexPath = self.tableView.indexPathForSelectedRow()
{
let (_, photos) = self.searches[indexPath.row]
(segue.destinationViewController
as DetailViewController).photos = photos
}
}
}
При этом используется тот же метод searches упорядоченному словарю, как и при создании ячеек. Он не использует ключ (поиск по ключевому слову), тем не менее, таким образом, вы укажите тем самим, подчеркнувши, что эта часть tuple не должна быть связана с локальной переменной.
Скомпилируйте и запустите приложение, сделайте поиск, а затем нажмите на него. Вы увидите нечто вроде этого:
Кошкииии! Разве Вам не хочется помурлыкать вместо них от такого удовольствия?
Что дальше?
Примите мои поздравление, Вы узнали много нового о дженериках! Кроме того, Вы узнали о других интересных вещах, как индексирование, структуры, предусловие, и многое другое.
Если Вы хотите узнать больше о Дженериках, вам нужно посмотреть полную главу учебного пособия о Swift.
Я надеюсь, что Вы сможете использовать всю силу дженериков у Ваших будущих приложениях, чтобы избежать дублирования кода и оптимизировать код для многократного использования. Если у Вас есть какие-либо вопросы или комментарии, пожалуйста, присоединяйтесь к обсуждению на форуме!
Автор: yarmolchuk
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/ios/70925
Ссылки в тексте:
[1] загрузки: http://cdn1.raywenderlich.com/wp-content/uploads/2014/09/FlickrSearch-Starter1.zip
[2] здесь: https://www.flickr.com/services/apps/by/
[3] Классы и Структуры”: http://www.raywenderlich.com/store/swift-tutorials-bundle
[4] Источник: http://habrahabr.ru/post/239207/
Нажмите здесь для печати.