Лекция 9. EmojiArt Drag / Drop. CS193P Spring 2021.

Это девятая Лекция курса Stanford CS193p, весна 2021 года. На Лекции 9 профессор рассматривает несколько разнообразных тем, прежде, чем погрузится в совершенно новый демонстрационный пример EmojiArt :

  • Коллекции Collection Identifiable элементов
  • Цвета: Color,  UIColor и  CGColor
  • Изображения: Image, UIImage
  • Drag & Drop в SwiftUI
  • Демонстрационный пример EmojiArt, MVVM, Drag & Drop эмодзи 
  • Многопоточное программирование в части НЕ блокировки UI

Коллекции Collection Identifiable элементов

Начинается Лекция 9 с очень интересной темы — коллекции Collection идентифицируемых Identifiable структур struct, которые являются Value ТИПами. Понятно, почему к таким коллекциям возник повышенный интерес в SwiftUI. Дело в том, что одна из основных синтаксических конструкций SwiftUI ForEach — превращает элементы именно такой коллекции в Views, которые в последующем объединяются в контейнеры вроде ZStack, VStack, HStack и т.д. Конечно, хочется комфортно общаться с такими коллекциями, а не использовать каждый раз довольно неуклюжий вариант firstIndex(where: { $0.id = card.id }). В этой ситуации профессор демонстрирует нам применение так называемого протокольно-ориентированного программирования в Swift, когда в расширениях extension соответствующих протоколов protocol вы реализуете нужные вам функции. Конечно, это продвинутое программирование в Swift, но выглядит оно просто, элегантно и мощно. В результате мы получаем для массивов Array, а также множеств Set и словарей Dictionary с Identifiable элементами такие удобные функции как remove, index (matching:) и индексацию (subscripting), которая позволяет изменять свойства элементов коллекции напрямую.

Цвета: Color,  UIColor и  CGColor

В Swift существуют три отдельных структуры данных для представления цвета: Color, UIColor и CGColor.

Color, как хамелеон, может играть разные роли в SwiftUI: это может быть спецификатор цвета, может быть стилем геометрической фигуры ShapeStyle и даже может быть View, например, Color.white может появляться повсюду, где появляется View. Из-за этой многогранной роли Color, у него несколько ограниченный API.

Но в SwiftUI сохранена ещё одна вещь, связанная с цветом, это класс class UIColor. Это то, где вы на самом деле можете манипулировать цветами, вы можете получить значение RGB (Red Blue Green) для любого цвета UIColor, там намного больше встроенных цветов, также есть системные цвета.  UIColor — это более мощный класс class, чем структура struct Color. В SwiftUI: оба они связаны очень простым кодом:Color (ui: UIColor).

Есть еще класс class CGColor — это фундаментальное представление цвета в системе рисования Core Graphic. Иногда CGColor всплывает в некоторых API для SwiftUI. Например, ColorPicker. Обычно вы используете Color в  ColorPicker, чтобы выбрать цвет,  но вы также можете использовать CGColor для выбора цвета, если хотите.

Все три варианта представления цвета — Color, UIColor, CGColor — можно преобразовывать один в другой и обратно.

Изображения: Image, UIImage

 Image — это в первую очередь View. Это не ТИП переменных var, которые содержат изображение. Это не способ хранения изображений. Это View для показа изображений на экране.

В свою очередь UIImage — это вещь, которая хранит настоящие изображения в формате JPEG или TIFF или еще каком-нибудь формате. Такое изображение можно масштабировать, изменять его размер и делать другие вещи с изображением. Как только вы получите нужное вам изображение UIImage, вы можете вызвать Image(uiImage:) для показа на экране. Это работает точно так же, как Color и UIColor.

Drag & Drop в SwiftUI

Drag & Drop — это передача данных от одного приложения другому приложению, например, можно перетащить изображение из Safari в наше приложение EmojiArt. В основе этого, то есть то, что действительно заставляет это работать, находится класс class NSItemProvider. Именно эта вещь выполняет передачу данных между приложениями. Он также решает проблемы с безопасностью и определенно там есть многопоточность, потому что мы не хотим блокировать UI двух приложений в случае передачи между ними больших по объему изображений.

Drag & Drop имеет в SwiftUI очень интересный API, потому что соединяет новый мир и старый мир. Мы реально будем запускать механизм Drag & Drop прямо в старом Мире, еще до-Swift Мире, Мире Objective-C, но это действительно классная “фишка”, и Apple интегрировала её в SwiftUI довольно красиво. Учитывая сложность NSItemProvider, профессор предоставил код, который облегчает взаимодействие между SwiftUI и этим классом NSItemProvider. Так что на удивление довольно легко выполнить Drag & Drop в SwiftUI.

Поражает невероятная лаконичность реализации технологии Drag & Drop в SwiftUI по сравнению c её реализацией в UIKit. Тем более, что у нас был точно такой же демонстрационный пример EmojiArt в предыдущем стэнфордском курсе CS193P Осень 2017 Разработка iOS 11 приложений в Swift” (запасная ссылка ), который выполнялся с применением UIKit, и нам есть с чем сравнить.

Демонстрационный пример EmojiArt, MVVM, Drag & Drop эмодзи 

Первую часть этой демонстрации посвящена тому, чтобы просто настроить MVVM нашего приложения EmojiArt. Получается хорошее повторение того, что такое MVVM. По ходу дела показывается некоторый новый материал. Например, использование перечислений enum с ассоциированными значениями (associated values). Мы говорили об этом, мы знаем, что Optional является перечислением enum с ассоциированным значением, но мы собираемся сделать это для нашего собственного перечисления enum. В приложение EmojiArt добавляется Drag & Drop эмодзи, используя NSItemProvider.

Многопоточное программирование в части НЕ блокировки UI

Затем профессор возвращается к слайдам и объясняет некоторые примитивы многопоточного программирования, которые существуют в iOS для того, чтобы избежать блокировали нашего UI для таких затратных по времени вещей. Когда мы создаем UI мобильного приложения, одна из самых важных вещей заключается в том, что UI был отзывчивым. Каждый раз, когда мы касаемся пальцем нашего Ui, мы хотим, чтобы приложение отвечало нам.

Никогда нельзя делать UI мобильного приложения не реагирующим на действия пользователя. Но иногда вам необходимо делать вещи, которые могут потребовать длительного времени. Например, зайти в интернет и выбрать некоторое изображение, которое вы захотите загрузить как фоновое изображение background вашего документа EmojiArtDocument. Или вы занимаетесь машинным обучением, и вам нужно обработать большой кусок данных, чтобы что-то проанализировать для пользователя.

Так как же сохранить отзывчивость пользовательского интерфейса, когда такие “долгоживущие” задачи должны выполняться в нашем приложении?

Ответ состоит в том, что мы выполняем эти вещи в другом “потоке выполнения” (thread), отличным от главного потока (main thread), на котором выполняется UI. Сложность программирования с множеством таких “потоков выполнения” (thread) заключается в том, что вам необходимо отслеживать, какой код на каких потоках выполняется, но при этом сделать код читабельным и понятным, чтобы четко понимать, что где происходит и когда. Таким образом, когда речь идет об одновременно работающих вещах, в наш код как бы добавляется 4-ое измерение — время.

Swift управляет этой сложностью, используя так называемые очереди queues. Очередь queue — это не более, чем куча блоков кода, которые выстраиваются в очередь и просто терпеливо ждут “потока выполнения”, который возьмет их из очереди и запустит. самой важной очередью во всей iOS, конечно, является main queue. Это очередь, в которой все блоки кода, которые ожидают запуска, могут изменять UI. Каждый раз, когда мы хотим что-то делать с нашим UI, мы должны использовать main queue.

Конечно, на ряду с тем, что у нас есть main queue для UI, есть ещё и куча фоновых очередей background queues для выполнения “долгоживущих” задач, не связанных с UI, наподобие выборки чего-то из интернета или машинного обучения.

API для всего этого, я имею ввиду программный интерфейс (Application Programming Interface), все эти переменные var и функции func, вовлеченные в эту работу, называется GCD, Grand Central Dispatch. В GCD API существует ряд различных функций, но на самом деле все сводится к двум фундаментальным задачам:

  • Получение доступа к очереди queue
  • Постановка блока кода в очередь queue

Вот основные способы получения очереди queue: — это DispatchQueue.main для main queue и DispatchQueue.global ( qos: QoS) для фоновых очередей background queue с качеством обслуживания QoS, всё очень просто.

У фоновых очередей background queues есть приоритет друг относительно друга. Некоторые из них важнее при запуске, чем другие, и вы указываете это при создании с помощью качества обслуживания (Quality of Service) QoS. Но независимо от того, какое качество обслуживания QoS вы указываете, main queue всегда будет работать с более высоким приоритетом.

У нас 4 качества обслуживания (Quality of Service) QoS и вы можете выбирать из них:  .userInteractive, .userInitiated, .utility и .background.

Допустим у нас есть очередь queue, которую мы получили одним из этих двух способов. Для того, чтобы разместить блок кода, а точнее замыкание (closure), мы вызываем либо функцию .async, либо функцию .sync. Обе эти функции берут в качестве аргумента обычное замыкание и бросают его на эту очередь queue.

На следующей Лекции 10 мы научимся выполнять Drag & Drop для нашего фонового изображения background многопоточным способом, чтобы не блокировать UI.

P.S.  GCD почти полностью заменена встроенным в Swift асинхронным API, изложенным на WWDC 2021.

Код демонстрационного примера для Лекции 9 находится на Github для iOS 14 в папке EmojiArtL9.

Русскоязычный неавторизованный конспект Лекции 9, иллюстрированный, хронометрированный и представленный в виде PDF-файла, который можно скачать и использовать offline, а также в формате Google Doc доступны на платной основе.

Лекция 9. EmojiArt Drag / Drop. CS193P Spring 2021.: 2 комментария

Обсуждение закрыто.