SA1036 — Работа с URLSession
Содержание страницы
До сих пор вы использовали этот Data(contentsOf:)
метод для выполнения поиска в веб-службе iTunes. Это отлично подходит для простых приложений, но я хочу показать вам другой способ создания сетей, который является более мощным.
Сама iOS поставляется с рядом различных классов для работы в сети, от низкоуровневых сокетов, которые интересны только действительно хардкорным сетевым программистам, до удобных классов, таких как URLSession
.
В этой главе вы замените существующий сетевой код URLSession
API. Это API, который профессионалы используют для создания реальных приложений, но не волнуйтесь, это не сложнее, чем то, что вы делали раньше — просто более мощный.
В этой главе вы рассмотрите следующие вопросы:
- Ветвление it: создание ветвей Git для основных изменений кода.
- Приведение URLSession в действие: используйте
URLSession
класс для асинхронной сети вместо прямой загрузки содержимого URL-адреса. - Отмена операций: отмена запущенного сетевого запроса при инициировании второго сетевого запроса.
- Поиск по различным категориям: позволяет пользователю выбрать определенную категорию iTunes Store для поиска вместо возврата элементов из всех категорий.
- Загрузка иллюстраций: Загрузите изображения для элементов результатов поиска и отобразите их как часть списка результатов поиска.
- Объединить ветвь: объедините изменения из рабочей ветви Git обратно в основную ветвь.
Ветвь его
Всякий раз, когда вы вносите большие изменения в код — например, заменяете все сетевые вещи, URLSession
— есть вероятность, что вы все испортите. Я, конечно, делаю это достаточно часто! Вот почему разумно сначала создать ветку Git.
Репозиторий Git содержит историю всего кода приложения, но он также может содержать эту историю по разным путям.
Вы только что закончили первую версию сетевого кода, и он работает довольно хорошо. Теперь вы собираетесь полностью заменить это — надеюсь — лучшим решением. При этом вы можете зафиксировать свой прогресс в нескольких точках пути.
Что, если окажется, что переход на URLSession
него был не такой уж хорошей идеей? Затем вам нужно будет восстановить исходный код до предыдущей фиксации до того, как вы начали вносить эти изменения. Чтобы избежать этого потенциального беспорядка, вы можете сделать вместо него ветку.

Ветви в действии
Каждый раз, когда вы собираетесь добавить новую функцию в свой код или исправить ошибку, рекомендуется создать новую ветвь и работать над ней. Когда вы закончите и убедитесь, что все работает как надо, объедините свои изменения обратно в основную ветку. Разные люди используют разные стратегии ветвления, но это общий принцип.
До сих пор вы фиксировали свои изменения в “основной” ветви. Теперь вы создадите новую ветвь, назовем ее “urlsession”, и внесете в нее свои изменения. Когда вы закончите с этой новой функцией, вы объедините все обратно в основную ветвь.
Вы можете найти ветви для своего репозитория в навигаторе управления версиями:

Список ветвей управления версиями
BOS Щелкните правой кнопкой мыши на названии ветви (main), чтобы получить контекстное меню с возможными действиями, и выберите Новую ветвь из “main”…:

Контекстное меню ветви
BOS Вы получите диалоговое окно с запросом нового имени ветви. Введите urlsession в качестве нового имени и нажмите кнопку Создать.

Создание новой ветки
Когда Xcode будет завершен, вы увидите, что добавлена новая ветвь “urlsession” и что теперь она является текущей.
Эта новая ветвь содержит точно такой же исходный код и историю, как и основная ветвь, или любая другая ветвь, которую вы использовали в качестве родительской для новой ветви. Но с этого момента два пути будут расходиться — любые изменения, которые вы вносите, происходят только в ветке “urlsession”.
Приведите URLSession в действие
Хорошо, теперь, когда вы находитесь в новой ветке, можно смело экспериментировать с этими новыми API.
BOS Во-первых, удалите performStoreRequest(with:)
из SearchViewController.swift. Да, все верно, этот метод вам больше не понадобится.
Не бойтесь удалять старый код. Некоторые разработчики только комментируют старый код, но оставляют его в проекте на случай, если он когда-нибудь им снова понадобится. Вам не нужно беспокоиться об этом, потому что вы используете систему управления версиями. Если вам это действительно нужно, вы всегда можете найти старый код в истории Git. Кроме того, если эксперимент провалится, вы можете просто выбросить эту ветвь и переключиться обратно на “исходную”.
Во всяком случае, дальше URLSession
. Это API, основанный на закрытии, что означает, что вместо создания делегата вы передаете ему закрытие, содержащее код, который должен быть выполнен после получения ответа от сервера. URLSession
вызывает это закрытие обработчиком завершения.
searchBarSearchButtonClicked(_:)
Изменить следующим образом:
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { if !searchBar.text!.isEmpty { . . . searchResults = [] // Replace all code after this with new code below // 1 let url = iTunesURL(searchText: searchBar.text!) // 2 let session = URLSession.shared // 3 let dataTask = session.dataTask(with: url) {data, response, error in // 4 if let error = error { print("Failure! \(error.localizedDescription)") } else { print("Success! \(response!)") } } // 5 dataTask.resume() } }
Это то, что делает код:
- Создайте
URL
объект с помощью текста поиска, как и раньше. - Получите общий
URLSession
экземпляр, который использует конфигурацию по умолчанию в отношении кэширования, файлов cookie и других веб-материалов. Если вы хотите использовать другую конфигурацию — например, ограничить сеть, когда доступен Wi-Fi, но не когда есть только сотовый доступ, — то вам нужно создать свои собственныеURLSessionConfiguration``URLSession
объекты and. Но для этого приложения по умолчанию все будет в порядке. - Создайте задачу данных. Задачи обработки данных предназначены для извлечения содержимого данного URL-адреса. Код из обработчика завершения будет вызван, когда задача данных получит ответ от сервера.
- Внутри замыкания вам задаются три параметра:
data
,response
, иerror
. Все это опции, поэтому их можноnil
и нужно развернуть, прежде чем вы сможете их использовать. Если возникла проблема, error содержитError
объект, описывающий, что пошло не так. Это происходит, когда невозможно связаться с сервером, или сеть не работает, или происходит какой-то другой аппаратный сбой. Еслиerror``nil
да , то связь с сервером прошла успешно;response
содержит код ответа сервера и заголовки, аdata
также фактические данные, полученные с сервера, в данном случае BLOB-объект JSON. На данный момент вы просто используетеprint()
его, чтобы показать успех или неудачу. - Наконец, после того как вы создали задачу данных, вам нужно позвонить
resume()
, чтобы запустить ее. Это отправляет запрос на сервер в фоновом потоке. Таким образом, приложение сразу же свободно продолжать —URLSession
так же асинхронно, как они приходят.
После внесения этих изменений вы можете запустить приложение и посмотреть, что URLSession
из него получится.
BOS Запустите приложение и найдите что-нибудь. Через секунду или две вы увидите консольное сообщение с надписью “Success!”, За которым последует дамп заголовков HTTP-ответа.
Отлично!

Краткий обзор закрытий
Вы уже несколько раз видели закрытия. Это действительно мощная функция Swift, и вы можете ожидать, что будете использовать их все время, когда будете работать с Swift-кодом. Поэтому хорошо иметь хотя бы базовое представление о том, как они работают.
Замыкание-это просто фрагмент исходного кода, который вы можете передавать, как и любой другой тип объекта. Разница между закрытием и обычным кодом заключается в том, что код из закрытия не выполняется сразу. Вместо этого он хранится в “объекте закрытия” и может быть выполнен позже, даже более одного раза.
Это именно то, что URLSession
делает: он удерживает закрытие “обработчика завершения” и выполняет его только при получении ответа от веб-сервера или при возникновении сетевой ошибки.
Хотя мы использовали трейлинг-закрытие выше, тот же код также может быть написан следующим образом:
let dataTask = session.dataTask(with: url, completionHandler: { data, response, error in . . . source code . . . })
То, что стоит completionHandler
за { }
скобками, — это закрытие. Форма закрытия всегда:
{ parameters in your source code }
или без параметров:
{ your source code }
Точно так же, как метод или функция, замыкание может принимать параметры. Они отделены от исходного кодаin
ключевым словом“”. В URLSession
обработчике завершения параметрами являютсяdata
,response
, и error
.
Благодаря Swift-выводу типов вам не нужно указывать типы данных параметров. Однако вы могли бы написать их полностью, если бы захотели:
let dataTask = session.dataTask(with: url, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) in . . . })
Совет: Для параметра без аннотации типа вы можете щелкнуть правой кнопкой мыши в Xcode, чтобы узнать, каков его тип. Этот трюк работает для любого символа в вашем коде.
Если вы не используете определенный параметр в коде закрытия , вы можете заменить его _
символом подстановочного знака:
let dataTask = session.dataTask(with: url, completionHandler: { data, _, error in . . . })
Если закрытие действительно простое, вы можете вообще исключить список параметров и использовать $0
, $1
, и так далее в качестве имен параметров.
let dataTask = session.dataTask(with: url, completionHandler: { print("My parameters are \($0), \($1), \($2)") })
Однако вы бы не сделали этого с URLSession
обработчиком завершения. Гораздо проще, если вы знаете, что параметры называютсяdata
,response
, и error
, чем помнить, что $0
означает,$1
, и.$2
Как вы видели весь код замыкания в этой книге, если замыкание является последним параметром метода, вы можете использовать завершающий синтаксис, чтобы немного упростить код:
let dataTask = session.dataTask(with: url) { data, response, error in . . . }
Теперь закрытие появляется после закрывающей скобки, а не внутри. Многие люди, включая меня, находят это более естественным для чтения.
Замыкания полезны и для других целей, таких как инициализация объектов и ленивая загрузка:
lazy var dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateStyle = .medium formatter.timeStyle = .short return formatter }()
Код для создания и инициализации DateFormatter
объекта находится внутри замыкания. В ()
конце концов, закрытие вычисляется, и возвращаемый объект помещается внутрь dataFormatter
переменной. Это распространенный трюк для размещения сложного кода инициализации прямо рядом с объявлением переменной.
Не случайно замыкания очень похожи на функции. В Swift замыкания, методы и функции — это действительно одно и то же. Например, вы можете указать имя метода или функции, когда ожидается закрытие, при условии, что параметры совпадают:
`let dataTask = session.dataTask(with: url, completionHandler: myHandler)
. . .
func myHandler(data: Data?, response: URLResponse?, error: Error?) {
. . .
}`
Вышесказанное несколько сводит на нет одно из главных преимуществ замыканий — сохранение всего кода в одном месте, — но бывают ситуации, когда это весьма полезно, когда метод действует как “мини” делегат.
И последнее, о чем следует помнить при закрытии, — это то, что они захватывают любые переменные, используемые внутри закрытия, в том числе self
. Это может создать циклы владения, что часто приводит к утечке памяти. Чтобы избежать этого, вы можете предоставить список захвата:
let dataTask = session.dataTask(with: url) { [weak self] data, response, error in . . . }
Всякий раз, когда вы обращаетесь к свойству или вызываете метод, вы неявно используете self
его . Однако внутри замыкания Swift требует, чтобы вы всегда писали self.
перед именем метода или свойства. Это дает понять, что self
захватывается закрытием:
let dataTask = session.dataTask(with: url) { data, response, error in self.callSomeMethod() // self is required }
SearchViewController
не нужно беспокоиться о URLSession
захватеself
, потому что задача передачи данных недолговечна, в то время как контроллер представления остается там так же долго, как и само приложение. Этот цикл владения совершенно безвреден. По мере того как вы добавляете больше функциональности в StoreSearch, вам придется использовать [weak self]``URLSession
его, иначе приложение может выйти из строя и сгореть!
Примечание: Swift также имеет концепцию закрытия “no escape”. Мы не будем вдаваться в это здесь, кроме как упомянуть , что закрытие без побега не захватывает
self
, так что вам не нужно писатьself.
“ везде”. Приятно, но вы можете использовать такие закрытия только при очень специфических обстоятельствах!

Коды состояния дескриптора
После успешного запроса приложение печатает HTTP-ответ с сервера. Объект response может выглядеть примерно так:
<NSHTTPURLResponse: 0x600002dab5e0> { URL: https://itunes.apple.com/search?term=Jack&limit=200 } { Status Code: 200, Headers { "Cache-Control" = ( "max-age=86371" ); "Content-Disposition" = ( "attachment; filename=1.txt" ); "Content-Encoding" = ( gzip ); "Content-Length" = ( 88447 ); "Content-Type" = ( "text/javascript; charset=utf-8" ); Date = ( "Fri, 13 Aug 2021 22:38:09 GMT" ); . . . } }
Если вы уже занимались какой-либо веб-разработкой, это должно выглядеть знакомо. Эти “HTTP-заголовки” всегда являются первой частью ответа от веб-сервера, который предшествует фактическим данным, которые вы получаете. Заголовки дают дополнительную информацию о только что произошедшем общении.
Что вас особенно интересует, так это код состояния. Протокол HTTP определил ряд кодов состояния, которые сообщают клиентам, был ли запрос успешным или нет. Без сомнения, вы знакомы с 404, веб-страница не найдена.
Код состояния, который вы хотите увидеть, равен 200 OK, что указывает на успех — в Википедии есть полный список кодов, wikipedia.org/wiki/List_of_HTTP_status_codes.
Чтобы сделать обработку ошибок приложения немного более надежной, давайте проверим, действительно ли код ответа HTTP равен 200. Если нет, то что-то пошло не так, и мы не можем предположить, что полученные данные содержат нужный нам JSON.
BOS Измените содержимое completionHandler
на:
if let error = error { print("Failure! \(error.localizedDescription)") } else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 { print("Success! \(data!)") } else { print("Failure! \(response!)") }
response
Параметр имеет тип данных URLResponse
, но у него нет свойства для кода состояния. Поскольку вы используете протокол HTTP, то на самом деле вы получили HTTPURLResponse
объект, подкласс URLResponse
. Итак, сначала вы приводите его к нужному типу, а затем смотрите на его statusCode
свойство — вы будете считать задание успешным только в том случае, если код состояния равен 200.
Обратите внимание на использование запятой внутри if let
оператора, чтобы объединить эти проверки в одну строку. Вы также могли бы написать его с помощью второго if
, но я нахожу, что это труднее читать:
} else if let httpResponse = response as? HTTPURLResponse { if httpResponse.statusCode == 200 { print("Success! \(data!)") }
Всякий раз, когда вам нужно развернуть необязательный параметр, а также проверить значение этого необязательного параметра, использованиеif let …, …
-самый хороший способ сделать это.
BOS Запустите приложение и найдите что-нибудь. Теперь вы должны увидеть что-то вроде:
Success! 295831 bytes
Поскольку полученные вами данные имеют форму Data
объекта, в отличие от текста, их содержимое не может быть распечатано. Таким образом, вы просто получаете длину данных.
Всегда полезно протестировать код обработки ошибок. Итак, давайте сначала подделаем ошибку и уберем ее с дороги.
iTunesURL(searchText:)
Введите , измените строку URL на:
"https://itunes.apple.com/searchLOL?term=%@&limit=200"
Здесь я изменил конечную точку с search
на searchLOL
. На самом деле не имеет значения, что вы там печатаете, если это то, что не может существовать на сервере iTunes.
BOS Запустите приложение еще раз. Теперь поиск должен ответить чем-то вроде этого:
<NSHTTPURLResponse: 0x60000217cf00> { URL: https://itunes.apple.com/searchLOL?term=Jack&limit=200 } { Status Code: 404, Headers { "Cache-Control" = ( "private, max-age=285" ); . . . } }
Как вы можете видеть, код состояния теперь 404 — страницы searchLOL нет — и приложение правильно считает это ошибкой. Это тоже хорошо, потому что если бы вы преобразовали значение data
в текст, data
то теперь оно содержало бы следующее::
`
404 Not Found
Not Found
The requested URL /searchLOL was not found on this server.
`
Это определенно не JSON, а HTML. Если бы вы попытались преобразовать это в объекты JSON, то потерпели бы ужасную неудачу.
Отлично, значит, обработка ошибок работает! Давайте проанализируем полученные данные JSON.
Разбор данных
BOS Во — первых, iTunesURL(searchText:)
верните все на круги своя-используйте ⌘+Z для отмены.
BOS В completionHandler
, замените print("Success! \(data)")
строку на:
if let data = data { self.searchResults = self.parse(data: data) self.searchResults.sort(by: <) DispatchQueue.main.async { self.isLoading = false self.tableView.reloadData() } return }
Это разворачивает необязательный объект из data
параметра, а затем вызывает parse(data:)
преобразование содержимого словаря в SearchResult
объекты, как вы делали это раньше. Наконец, вы сортируете результаты и помещаете все в табличное представление. Это должно выглядеть очень знакомо.
Важно понимать, что закрытие обработчика завершения не будет выполняться в основном потоке. Поскольку URLSession
все сети выполняются асинхронно, он также вызовет обработчик завершения в фоновом потоке.
Анализ JSON и сортировка списка результатов поиска потенциально могут занять некоторое время — не секунды, но, возможно, достаточно долго, чтобы быть заметными. Вы не хотите блокировать основной поток, пока это происходит, поэтому предпочтительнее, чтобы это происходило и в фоновом режиме.
Но когда приходит время обновлять пользовательский интерфейс, вам нужно переключиться обратно на основной поток — это правила. Вот почему вы оборачиваете перезагрузку табличного представления в DispatchQueue.main.async
замыкание.
Если вы забудете это сделать, ваше приложение может по-прежнему работать. Вот в чем коварство работы с несколькими потоками. Однако он также может потерпеть крах всевозможными таинственными способами. Поэтому помните, что пользовательский интерфейс всегда должен происходить в основном потоке. Напишите это на записке и приклейте к своему экрану!
BOS Запустите приложение. Поиск должен снова сработать. Вы успешно заменили старый сетевой код на URLSession
!
Совет: Если вы хотите определить с помощью кода, выполняется ли определенный фрагмент кода в основном потоке или нет, добавьте следующий фрагмент кода:
print("On main thread? " + (Thread.current.isMainThread ? "Yes" : "No"))
Продолжайте, вставьте это в верхнюю часть
completionHandler
закрытия и посмотрите, что там написано.Конечно, официальная рамочная документация должна быть вашей первой остановкой. Обычно, когда метод принимает закрытие, в документах упоминается, выполняется ли он в основном потоке или нет. Но если вы не уверены или просто не можете найти его в документах, добавьте вышесказанное
print()
и будьте просветлены.
Обрабатывать ошибки
BOS В самом конце закрытия обработчика завершения, под if
операторами, добавьте следующее:
DispatchQueue.main.async { self.hasSearched = false self.isLoading = false self.tableView.reloadData() self.showNetworkError() }
Выполнение кода доходит сюда только в том случае, если что-то пошло не так. Вы звонитеshowNetworkError()
, чтобы сообщить пользователю о проблеме.
Обратите внимание, что вы делаете tableView.reloadData()
это и здесь, потому что содержимое табличного представления необходимо обновить, чтобы избавиться от индикатора Loading…. И конечно же, все это происходит на главном потоке.
Упражнение: Почему предупреждение об ошибке не появляется при успешном выполнении? В конце концов, приведенный выше фрагмент кода находится в нижней части закрытия, так что разве он не всегда выполняется?
Ответ: После успешной загрузки данных return
оператор завершает закрытие после отображения результатов поиска в табличном представлении. Таким образом, в этом случае выполнение никогда не достигает нижней части закрытия.
BOS Подделать ситуацию с ошибкой, чтобы проверить, что код обработки ошибок действительно работает.
Тестирование ошибок-это не роскошь! Последнее, чего вы хотите, — это чтобы ваше приложение вышло из строя при возникновении сетевой ошибки из-за неправильного кода обработки ошибок. Я работал над кодовыми базами, где было очевидно, что предыдущий разработчик никогда не утруждал себя проверкой того, что приложение способно восстанавливаться после ошибок — вероятно, именно поэтому они были предыдущим разработчиком.
В дикой природе что-то пойдет не так, и вашему приложению лучше быть готовым справиться с этим. Как говорят разрушители мифов, “неудача-это всегда вариант”.
Работает ли код обработки ошибок? Отлично! Пришло время добавить в приложение новые сетевые функции.
Это хорошее время для фиксации ваших изменений. Помните, что эта фиксация происходит только в ветке “urlsession”, а не в основной ветке.
Отмена операций
Что происходит, когда поиск занимает много времени, и пользователь начинает второй поиск, в то время как первый все еще продолжается? Приложение не отключает панель поиска, поэтому пользователь может это сделать. Когда вы имеете дело с сетью — или с любым асинхронным процессом, на самом деле, — вы должны продумать такие ситуации.
Невозможно предсказать, что произойдет, но, скорее всего, это будет странный опыт для пользователя. Они могут увидеть результаты своего первого поиска, которых они больше не ожидают, только для того, чтобы через несколько секунд они были заменены результатами второго поиска. Сбивает с толку!
Но нет никакой гарантии, что первый поиск завершится раньше второго, поэтому результаты поиска № 2 могут прийти первыми, а затем быть перезаписаны результатами поиска № 1, что определенно не то, что пользователь хотел видеть.
Поскольку вы больше не блокируете основной поток, пользовательский интерфейс всегда принимает пользовательский ввод, и вы не можете предполагать, что пользователь будет сидеть неподвижно и ждать, пока запрос не будет выполнен.
Обычно это можно исправить одним из двух способов:
- Отключите все элементы управления. Пользователь не может касаться чего-либо во время выполнения операции. Это не значит, что вы блокируете основной поток; вы просто следите за тем, чтобы пользователь не мог испортить порядок вещей.
- Отмените текущий запрос,когда пользователь инициирует новый запрос.
Для этого приложения вы выберете второе решение, потому что оно обеспечивает более приятный пользовательский опыт. Каждый раз, когда пользователь выполняет новый поиск, вы отменяете предыдущий запрос. URLSession
это очень просто: задачи с данными имеют cancel()
метод.
Когда вы создали задачу данных, вам был дан URLSessionDataTask
объект, и вы поместили его в локальную константу с именем dataTask
. Однако отмена задачи должна произойти при следующем searchBarSearchButtonClicked(_:)
вызове.
Хранение URLSessionDataTask
объекта в локальной переменной уже недостаточно; вам нужно сохранить эту ссылку за пределами области действия метода. Другими словами, вы должны хранить его в переменной экземпляра.
BOS Добавьте следующую переменную экземпляра в SearchViewController.swift:
var dataTask: URLSessionDataTask?
Это необязательно, потому что у вас не будет задачи с данными до тех пор, пока пользователь не выполнит поиск.
BOS In searchBarSearchButtonClicked(_:)
, удалить let
из строки, создающей новый объект задачи data:
dataTask = session.dataTask(with: url) {data, response, error in
Вы удалили let
ключевое слово, потому dataTask
что оно больше не должно быть локальным; теперь оно ссылается на переменную экземпляра.
BOS В конце метода добавьте знак вопроса в строку, с которой начинается задача.:
dataTask?.resume()
Поскольку dataTask
это необязательный параметр, вы должны каким-то образом развернуть его, прежде чем сможете его использовать. Здесь вы используете необязательную цепочку.
BOS Наконец, в верхней части метода, прежде чем вы установите isLoading
значениеtrue
, добавьте:
dataTask?.cancel()
Если есть активная задача с данными, это отменяет ее, гарантируя, что никакие старые поиски никогда не смогут помешать новому поиску.
Благодаря необязательной цепочке, если поиск еще не был выполнен и dataTask
продолжается nil
, это просто игнорирует вызовcancel()
. Вы также можете развернуть необязательный if let
символ, но использование вопросительного знака короче и так же безопасно.
Упражнение: Почему вы не можете написать
dataTask!.cancel()
, чтобы развернуть необязательный файл?
Ответ: Если есть опция nil
, то использование !
приведет к сбою приложения. Предполагается!
, что вы можете развернуть необязательный файл только тогда, когда будете уверены, что его не будет nil
. Но в самый первый раз, когда пользователь вводит что-то в строку поиска, dataTask
все равно будет nil
и использовать !
не очень хорошая идея.
BOS Протестируйте приложение с этим вызовом и без dataTask.cancel()
него, чтобы почувствовать разницу.
Используйте панель настроек Network Link Conditioner для задержки каждого запроса на несколько секунд, чтобы было легче выполнить два запроса одновременно.
Хм… вы можете заметить что-то странное. Когда задача передачи данных отменяется, вы получаете всплывающее окно network error.
Как оказалось, когда задача данных отменяется, ее обработчик завершения все еще вызывается, но с Error
объектом с кодом ошибки -999. Вот что вызвало появление предупреждения об ошибке.
Вам придется сделать обработчик ошибок немного умнее, чтобы игнорировать код -999. В конце концов, пользователь, отменяющий предыдущий поиск, не является поводом для паники.
BOS В разделе completionHandler
измените if let error
раздел на:
if let error = error as NSError?, error.code == -999 { return // Search was cancelled } else if let httpResponse = . . .
Это просто завершает закрытие, когда возникает ошибка с кодом -999. Остальная часть закрытия пропускается.
BOS Если вы удовлетворены тем, что он работает, зафиксируйте изменения в репозитории.
Примечание: Возможно, вы не думаете, что стоит делать коммит, когда вы изменили только несколько строк, но много маленьких коммитов часто лучше, чем несколько больших. Каждый раз, когда вы исправляете ошибку или добавляете новую функцию, это хорошее время для фиксации.
Поиск по разным категориям
В iTunes Store есть обширная коллекция продуктов, и каждый поиск возвращает не более 200 наименований. Может быть трудно найти то, что вы ищете, только по имени. Таким образом, вы добавите на экран элемент управления, который позволит пользователям выбрать категорию, в которой они хотят искать. Это будет выглядеть так:

Поиск в категории Программное обеспечение
Этот тип элемента управления называется сегментированным элементом управления и используется для выбора одного варианта из набора вариантов.
Добавьте сегментированный элемент управления
Откройте раскадровку. Перетащите новую панель инструментов в представление и поместите ее под строку поиска. Вы будете использовать панель инструментов исключительно как контейнер для сегментированного элемента управления.
Убедитесь, что панель инструментов не добавляется в табличное представление. Возможно, проще всего перетащить его из библиотеки объектов непосредственно в контур документа и поместить под строку поиска. Затем измените его положение Y на 51.
Панель инструментов поставляется с элементом в ней. Выберите элемент и удалите его. Если вам трудно сделать это на холсте, выделите элемент через контур документа и затем удалите его.
BOS Выбрав панель инструментов, откройте меню Добавить новые ограничения и закрепите его сверху, слеваи справа.
BOS Перетащите новый сегментированный элемент управления из библиотеки объектов на панель инструментов.
Теперь дизайн должен выглядеть следующим образом:

Сегментированный элемент управления находится на панели инструментов под строкой поиска
BOS Выберите сегментированный элемент управления — возможно, вам придется снова использовать контур документа, так как сегментированный элемент управления внедряется в элемент кнопки панели, когда вы помещаете его на панель инструментов, — и в инспекторе атрибутовустановите количество сегментов равным 4.
BOS Измените название первого сегмента на «Все«. Затем выберите второй сегмент через выпадающий список Сегментов и установите его название на Музыку. Третий сегмент должен называться Программным обеспечением, а четвертый-электронными книгами.
Примечание: Вы также можете изменить название сегмента, дважды щелкнув его внутри.
Теперь сцена должна выглядеть так:

Готовый сегментированный элемент управления
Затем вы добавите новый выход и метод действия для сегментированного элемента управления. Это хорошая возможность попрактиковаться в использовании помощника редактора.
Используйте помощник редактора
BOS Нажмите Control+Option+⌘+Enter, чтобы открыть редактор помощника, а затем Control-перетащите сегментированный элемент управления в исходный код контроллера представления, чтобы добавить новый выход.:
@IBOutlet weak var segmentedControl: UISegmentedControl!
Чтобы добавить метод действия, вы также можете использовать Помощник редактора. Элемент управления-снова перетащите сегментированный элемент управления в исходный код, но на этот раз выберите следующее:

Добавление метода действия для сегментированного элемента управления
- Соединение: Действие
- Имя: segmentChanged
- Тип: UISegmentedControl
- Событие: Значение изменено
- Аргументы: Отправитель
BOS Нажмите кнопку Connect, чтобы добавить метод действия. Затем добавьте print()
оператор к новому методу:
@IBAction func segmentChanged(_ sender: UISegmentedControl) { print("Segment changed: \(sender.selectedSegmentIndex)") }
Введите ⌘+Enter, чтобы закрыть помощник редактора. Это очень удобные сочетания клавиш для запоминания.
BOS Запустите приложение, чтобы убедиться, что все по-прежнему работает. Нажатие на сегмент должно ввести в консоль номер — индекс этого сегмента.

Сегментированный контроль в действии
Используйте сегментированный элемент управления
Если вы выполните поиск сейчас, то заметите, что первая строка табличного представления снова частично скрыта. Поскольку вы разместили панель инструментов под строкой поиска, вам необходимо добавить еще 44 точки во вставку содержимого табличного представления.
BOS Измените эту строку наviewDidLoad()
:
tableView.contentInset = UIEdgeInsets(top: 91, left: 0, . . .
Вы будете использовать сегментированный элемент управления двумя способами. Прежде всего, он определяет, какие продукты будет искать приложение. Во-вторых, если вы уже выполнили поиск и нажали на одну из кнопок другого сегмента, приложение снова выполнит поиск новой категории продуктов.
Это означает, что теперь поиск может быть вызван двумя различными событиями: нажатием кнопки поиска на клавиатуре и выбором элемента в сегментированном элементе управления.
BOS Переименуйте searchBarSearchButtonClicked(_:)
метод в performSearch()``searchBar
параметр и удалите его.
Вы делаете это, чтобы поместить логику поиска в отдельный метод, который может быть вызван из нескольких мест. Удаление searchBar
в качестве параметра этого метода не является проблемой, потому что существует также @IBOutlet
свойство с таким именем, и любые ссылки на searchBar
in performSearch()
будут просто использовать это свойство.
BOS Теперь добавьте новую версию searchBarSearchButtonClicked(_:)
в исходный код:
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { performSearch() }
BOS Также замените метод segmentChanged(_:)
действия на:
@IBAction func segmentChanged(_ sender: UISegmentedControl) { performSearch() }
BOS Запустите приложение и убедитесь, что поиск все еще работает. Когда вы нажимаете на различные сегменты, поиск также должен быть выполнен снова.
Примечание: Во второй раз, когда вы будете искать то же самое, приложение может вернуть результаты очень быстро. Сетевой уровень теперь возвращает кэшированный ответ, поэтому ему не нужно загружать все это снова, что обычно является повышением производительности на мобильных устройствах. Тем не менее, существует API, чтобы отключить это поведение кэширования, если это имеет смысл для вашего приложения.
Осталось сделать одну вещь — вы должны сказать приложению, чтобы оно использовало категорию на основе выбранного сегмента для поиска. Вы уже видели, что с помощью свойства можно получить индекс выбранного сегментаselectedSegmentIndex
. Это возвращает Int
значение (0, 1, 2 или 3).
BOS Измените iTunesURL(searchText:)
метод так, чтобы он принимал это Int
в качестве параметра, а затем соответствующим образом создавал URL-адрес запроса.:
`func iTunesURL(searchText: String, category: Int) -> URL {
let kind: String
switch category {
case 1: kind = «musicTrack»
case 2: kind = «software»
case 3: kind = «ebook»
default: kind = «»
}
let encodedText = searchText.addingPercentEncoding(
withAllowedCharacters: CharacterSet.urlQueryAllowed)!
let urlString = «https://itunes.apple.com/search?» +
«term=(encodedText)&limit=200&entity=(kind)»
let url = URL(string: urlString)
return url!
}`
Это сначала превращает индекс категории из числа в строку kind
. Обратите внимание, что индекс категории передается методу в качестве нового параметра.
Затем он помещает эту строку за &entity=
параметром в URL-адресе. Для категории “Все” значение сущности пусто, но для других категорий это “MusicTrack”, “программное обеспечение” и “электронная книга” соответственно. Также обратите внимание , что вместо вызова String(format:)
вы теперь строите строку URL с помощью интерполяции строк.
BOS Войдя в performSearch()
систему , измените строку, которая получает URL-адрес, на следующую::
let url = iTunesURL( searchText: searchBar.text!, category: segmentedControl.selectedSegmentIndex)
И это должно сделать его!
Примечание: Вы могли бы использовать
segmentedControl.selectedSegmentIndex
непосредственно внутриiTunesURL
вместо передачи индекса категории в качестве параметра. Однако использование этого параметра-лучший дизайн. Это позволяет повторно использовать один и тот же метод с другим типом элемента управления, если вы решите, что сегментированный элемент управления на самом деле не является подходящим компонентом для этого приложения. Всегда полезно делать методы как можно более независимыми друг от друга.
BOS Запустите приложение и найдите “Стивен Кинг”. В категории «Все», которая дает результаты для чего угодно-от песен до фильмов, от подкастов до аудиокниг. Но если все, чего вы хотели,-это добраться до его книг, то теперь вы можете использовать категорию электронных книг, чтобы наконец найти некоторые из его романов.

Теперь вы можете ограничить поиск только электронными книгами
Это завершает дизайн пользовательского интерфейса главного экрана. Это такой же хороший момент, как и любой другой, чтобы заменить пустой экран запуска из шаблона.
Установите стартовый экран
BOS Удалите файл LaunchScreen.storyboard из проекта.
BOS На экране настроек проекта в разделе Значки приложений и изображения запускаизмените файл экрана запуска на Основной.
Теперь, когда приложение запускается, оно использует начальный контроллер представления из раскадровки в качестве стартового изображения.
BOS Зафиксируйте изменения и приготовьтесь к еще большему нетворкингу!
Скачать обложку
Результаты поиска JSON содержат несколько URL — адресов для изображений, и вы помещаете два из них — imageSmall
иimageLarge
-в SearchResult
объект. Теперь вы собираетесь загрузить эти изображения через Интернет и отобразить их в ячейках табличного представления. Загрузка изображений, как и использование веб-сервиса, — это просто выполнение HTTP-запроса к серверу, подключенному к Интернету. Примером такого URL-адреса является:
Нажмите на эту ссылку, и она откроет изображение в новом окне веб-браузера. Сервер, на котором хранится эта картинка, не itunes.apple.com но is2-ssl.mzstatic.com, но это не имеет никакого значения для приложения. Пока у него есть действительный URL-адрес, приложение будет просто извлекать файл в этом месте, независимо от того, где он находится и что это за файл.
Существуют различные способы загрузки файлов из Интернета. Вы собираетесь использовать URLSession
и написать удобное UIImageView
расширение, чтобы сделать это действительно удобным. Конечно, вы будете загружать эти изображения асинхронно!
Рефакторинг SearchResultCell
Во-первых, вы переместите логику настройки содержимого ячеек табличного представления в SearchResultCell
класс. Это лучшее место для этого. Логика, связанная с объектом, должна как можно больше жить внутри этого объекта, а не где-то еще.
Многие разработчики склонны запихивать все в свои контроллеры представлений, но если вы можете переместить часть логики в другие объекты, это сделает программу намного чище.
BOS Добавьте следующий метод в SearchResultCell.swift:
`// MARK: — Helper Methods
func configure(for result: SearchResult) {
nameLabel.text = result.name
if result.artist.isEmpty {
artistNameLabel.text = «Unknown»
} else {
artistNameLabel.text = String(format: «%@ (%@)», result.artist, result.type)
}
}`
Это в основном тот же код, что и в tableView(_:cellForRowAt:)
.
Теперь измените следующим образом:tableView(_:cellForRowAt:)
:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if isLoading { . . . } else if searchResults.count == 0 { . . . } else { . . . let searchResult = searchResults[indexPath.row] // Replace all code after this with new code below cell.configure(for: searchResult) return cell } }
Этот небольшой рефакторинг переноса некоторого кода из одного класса SearchViewController
в другой SearchResultCell
был необходим для того, чтобы следующий бит работал правильно.
Оглядываясь назад, можно сказать, что в любом случае имеет смысл делать подобные вещиSearchResultCell
, но до сих пор это не имело особого значения. Не бойтесь рефакторинговать свой код! Помните, что если вы облажаетесь, вы всегда можете вернуться к своему последнему коммиту Git.
BOS Запустите приложение, чтобы убедиться, что все по-прежнему работает нормально.
Расширение UIImageView для загрузки изображений
О’кей, а вот и самое интересное. Теперь вы добавите расширениеUIImageView
, которое загрузит изображение и автоматически отобразит его через представление изображения в ячейке представления таблицы всего одной строкой кода!
Как вы знаете, расширение можно использовать для расширения функциональности существующего класса без необходимости его подкласса. Это работает даже для классов из системных фреймворков.
UIImageView
не имеет встроенной поддержки загрузки изображений, но это очень распространенная вещь в приложениях. Здорово, что вы можете просто подключить свое собственное расширение, и с тех пор каждое UIImageView
ваше приложение имеет эту новую возможность.
BOS Добавьте новый файл в проект, используя шаблон файла Swift, и назовите его UIImageView+DownloadImage.swift.
BOS Замените содержимое нового файла следующим:
`import UIKit
extension UIImageView {
func loadImage(url: URL) -> URLSessionDownloadTask {
let session = URLSession.shared
// 1
let downloadTask = session.downloadTask(with: url) {
[weak self] url, _, error in
// 2
if error == nil, let url = url,
let data = try? Data(contentsOf: url), // 3
let image = UIImage(data: data) {
// 4
DispatchQueue.main.async {
if let weakSelf = self {
weakSelf.image = image
}
}
}
}
// 5
downloadTask.resume()
return downloadTask
}
}`
Это должно выглядеть очень похоже на то , что вы делали раньшеURLSession
, но есть некоторые отличия:
- После получения ссылки на общий
URLSession
доступ вы создаете задачу загрузки. Это похоже на задачу передачи данных, но она сохраняет загруженный файл во временное место на диске, а не хранит его в памяти. - Внутри обработчика завершения задачи загрузки вам дается URL — адрес, по которому вы можете найти загруженный файл-этот URL-адрес указывает на локальный файл, а не на интернет-адрес. Конечно, вы также должны проверить это
error``nil
, прежде чем продолжить. - С помощью этого локального URL-адреса вы можете загрузить файл в
Data
объект, а затем создать из него изображение. Вполне возможно, что построение завершитсяUIImage
неудачей, например, когда то, что вы загрузили, было не действительным изображением, а страницей 404 или чем-то еще неожиданным. Как вы можете сказать, имея дело с сетевым кодом, вы должны проверять наличие ошибок на каждом шагу! - Как только у вас есть изображение, вы можете поместить его в свойство
UIImageView
’simage
. Поскольку это код пользовательского интерфейса, вам нужно сделать это в основном потоке. Вот в чем хитрость: теоретически возможно, чтоUIImageView
к моменту поступления изображения с сервера оно уже не существует. В конце концов, это может занять несколько секунд, и к тому времени пользователь может перейти в другую часть приложения. В этой части приложения этого не произойдет, потому что представление изображения является частью ячейки табличного представления, и они перерабатываются, но не выбрасываются. Но позже вы будете использовать этот же код для загрузки изображения на экран, который может быть закрыт во время загрузки файла изображения. В этом случае вы не хотите устанавливать изображение, еслиUIImageView
оно больше не видно. Вот почему список захвата для этого закрытия включает[weak self]
в себя , гдеself
теперь ссылается наUIImageView
. ВнутриDispatchQueue.main.async
вам нужно проверить, существует ли еще “я”; если нет, то большеUIImageView
нечего устанавливать на изображение. - После создания задачи загрузки вы вызываете
resume()
ее для запуска, а затем возвращаетеURLSessionDownloadTask
объект вызывающему. Зачем его возвращать? Это дает приложению возможность при необходимости вызватьcancel()
задачу загрузки. Через минуту вы увидите, как это работает.
И это все, что вам нужно сделать. Отныне вы можете обращаться loadImage(url:)
к любому UIImageView
объекту вашего проекта. Круто, да?
Примечание: Swift позволяет объединить несколько
if let
операторов в одну строку, как вы сделали выше:
if error == nil, let url = …, let data = …, let image = … {
Здесь разворачиваются три варианта: 1)
url
, 2) результат отData(contentsOf:)
и 3) результат отUIImage(data:)
.Вы можете написать это как три отдельных
if let
оператора и один дляif error == nil
, но я нахожу , что иметь все внутри одногоif
оператора легче читать, чем множество вложенныхif
операторов, разбросанных по нескольким строкам.
Используйте расширение image downloader
BOS Переключитесь на SearchResultCell.swift и добавьте новую переменную экземпляра, downloadTask
, чтобы содержать ссылку на загрузчик изображений.:
var downloadTask: URLSessionDownloadTask?
Теперь добавьте следующие строки в конецconfigure(for:)
:
artworkImageView.image = UIImage(systemName: "square") if let smallURL = URL(string: result.imageSmall) { downloadTask = artworkImageView.loadImage(url: smallURL) }
Это говорит UIImageView
о том, чтобы загрузить изображение imageSmall
и поместить его в представление изображения ячейки. Пока загружается реальное художественное произведение, в представлении изображения отображается изображение — заполнитель-то же изображение символа, что и изображение из наконечника для этой ячейки.
Примечание: Вы можете загрузить изображение из каталога активов с помощью
UIImage(named:)``UIImage(systemName:)
– обратите внимание, что имя параметра отличается для каждого типа использования.
BOS Запустите приложение и наслаждайтесь красочными изображениями!

Теперь приложение загружает обложку альбома
Транспортная безопасность приложений
В то время как ваш опыт загрузки изображений отлично работал здесь, иногда, когда вы имеете дело с загрузкой изображений или обращаетесь к любому веб-URL-адресу, вы можете увидеть что-то вроде следующего в консоли Xcode, а также массу сообщений об ошибках, таких как следующие для неудачных задач загрузки:
The resource could not be loaded because the App Transport Security policy requires the use of a secure connection.
Хотя раньше вы могли загружать изображения по протоколу HTTP, теперь вы не можете этого делать. Вместо этого вам всегда нужно использовать HTTPS.
Если по какой-то причине вам нужно получить доступ к файлам через HTTP в вашем приложении, вы можете добавить ключ в Info.plist приложения, чтобы обойти эту функцию транспортной безопасности приложения, позволяя использовать простые http://
URL-адреса.
BOS Откройте вкладку Информация и добавьте новую строку. Выберите Настройки транспортной безопасности приложения из списка ключей.
Убедитесь, что Тип является Словарем.
BOS Добавьте новую строку в этот словарь и выберите в списке Разрешить произвольные нагрузки. Установите его в положение «ДА».

Переопределение транспортной безопасности приложений
Это все, что вам нужно сделать, чтобы получить доступ к HTTP-ссылкам. Однако вы должны обойти транспортную безопасность приложения только в том случае, если нет абсолютно никакого способа заставить приложение работать по протоколу HTTPS. Если вы создаете приложение, которое взаимодействует с сервером, которым вы управляете, то лучше всего включить HTTPS на сервере, а не отключать HTTPS в приложении.
Параметр Info предназначен только для тех случаев, когда вам нужно общаться с чужими серверами, которые не поддерживают HTTPS. Очевидно, что в этом случае приложение не должно отправлять конфиденциальные данные на эти серверы! Незащищенный HTTP следует использовать только для загрузки общедоступных данных, таких как изображения.
Если вы установите для ключа Разрешить произвольную загрузку значение YES, приложение может использовать любой URL-адрес, начинающийся с http://, независимо от домена. Чтобы разрешить HTTP только для определенных доменов, установите для произвольной загрузки значение NO и добавьте новую строку в словарь параметров транспортной безопасности приложения, а затем выберите домены исключений из списка ключей.
Значение для нового ключа домена исключений — это словарь. В этом словаре вы можете добавить новый словарь для каждого домена.
Например, веб-служба iTunes, по-видимому, размещает все свои предварительные изображения на веб-сайте mzstatic.com. Вы можете настроить параметр Info следующим образом:

Теперь приложение разрешает только http:// запросы от mzstatic.com и любой из его поддоменов, но требует https:// URL для любых других доменов.
Обратите внимание, что Apple указала, что эта возможность обхода App Transport Security (ATS) будет удалена в какое-то время в будущем. Поэтому не полагайтесь на то, что ATS-байпас-это то, что всегда будет доступно.
Отмена предыдущих загрузок изображений
Эти изображения уже выглядят довольно мило, но вы еще не совсем закончили. Помните, что ячейки табличного представления могут быть использованы повторно, поэтому теоретически возможно, что вы прокручиваете таблицу и какая-то ячейка будет использована повторно, пока ее предыдущее изображение все еще загружается.
Вам больше не нужен этот образ, поэтому вам действительно следует отменить ожидающую загрузку. Ячейки табличного представления имеют специальный методprepareForReuse()
, который идеально подходит для этого.
BOS Добавьте следующий метод в SearchResultCell.swift:
override func prepareForReuse() { super.prepareForReuse() downloadTask?.cancel() downloadTask = nil }
Вы просто отменяете любую загрузку изображения, которая все еще продолжается.
Упражнение: Поместите а
print()
вprepareForReuse()
метод и посмотрите, сможете ли вы его запустить.
При приличном Wi-Fi-соединении загрузка изображений происходит очень быстро. Вы почти не видите, как это происходит, даже если быстро прокручиваете страницу. Также помогает то, что файлы изображений небольшие — всего 60 на 60 пикселей — и что серверы iTunes работают быстро.
Это ключ к созданию быстрого приложения: не загружайте больше данных, чем вам нужно.

Кэширование
В зависимости от того, что вы искали, вы могли заметить, что многие изображения были одинаковыми. Например, вы можете получить много одинаковых обложек альбомов в результатах поиска. URLSession
достаточно умен, чтобы не загружать одинаковые изображения — или, по крайней мере, изображения с одинаковыми URL — адресами- дважды. Этот принцип называется кэшированием, и он очень важен на мобильных устройствах.
Мобильные разработчики всегда стараются оптимизировать свои приложения, чтобы делать как можно меньше. Если вы можете загрузить что-то один раз, а затем использовать его снова и снова, это намного эффективнее, чем повторная загрузка все время.
Изображения-это не единственное, что вы можете кэшировать. Например, вы также можете кэшировать результаты больших вычислений. Или представления, как вы делали в предыдущих приложениях, вероятно, даже не осознавая этого. Когда вы используете принцип ленивой загрузки, вы откладываете создание объекта до тех пор, пока он вам не понадобится, а затем кэшируете его для следующего раза.
Кэшированные данные не сохраняются вечно. Когда ваше приложение получает предупреждение о памяти, рекомендуется сразу удалить все кэшированные данные, которые вам не нужны. Это означает, что вам придется перезагрузить эти данные, когда они вам понадобятся позже, но это цена, которую вы должны заплатить. Ибо URLSession
это происходит совершенно автоматически, так что это снимает с ваших плеч еще одно бремя.
Некоторые кэши находятся в памяти — кэшированные данные остаются только в рабочей памяти компьютера. Но также можно кэшировать данные на диске. У вашего приложения даже есть специальный каталог для него-Library/Caches.
Политика кэширования, используемая StoreSearch, очень проста — она использует настройки по умолчанию. Но вы можете настроить URLSession
его на гораздо более продвинутый уровень. Загляните в документацию для URLCache
того, URLSessionConfiguration
чтобы узнать больше.

Объединить ветку
На этом заканчивается раздел, посвященный общению с веб-сервисом и загрузке изображений. Позже вы немного подправите запросы веб-службы, чтобы включить язык и страну пользователя, но на данный момент с этой функцией покончено. Надеюсь, вы получили хорошее представление о том, что возможно с помощью веб-сервисов и как легко встроить эту функциональность в ваши приложения URLSession
.
BOS Зафиксируйте эти последние изменения в репозитории.
Объедините ветку с помощью Xcode
Теперь, когда вы завершили функцию, вы можете объединить эту временную ветвь обратно в основную ветвь.
BOS Переключитесь на навигатор управленияверсиями, выберите основную ветвь в разделе ветви и щелкните правой кнопкой мыши, чтобы получить контекстное меню действий.
BOS Выберите Оформить заказ…, чтобы переключить активную ветвь обратно на основную ветвь.
Затем щелкните правой кнопкой мыши на ветке urlsession, чтобы снова получить контекстное меню, и выберите Объединить “urlsession” в “main”…:

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

Диалоговое окно подтверждения перед слиянием изменений
Теперь, когда основная ветвь обновлена с учетом сетевых изменений, если вы хотите, вы можете удалить ветвь “urlsession”. Или вы можете оставить его себе и поработать над ним позже.
Объединить ветвь из командной строки
Функции управления версиями в Xcode раньше были немного грубыми по краям. Таким образом, вполне возможно, что некоторые команды, особенно слияние изменений, могут работать некорректно. Если Xcode не хочет сотрудничать, когда вы пытаетесь объединить изменения, вот как вы это сделаете из командной строки.
Сначала закройте Xcode. Вы не хотите делать ничего из этого, пока Xcode все еще имеет открытый проект. Это просто напрашивается на неприятности.
BOS Откройте терминал cd
_в_ папке StoreSearch и введите следующие команды::
git stash
Это убирает все несохраненные файлы с пути — нет, это не имеет ничего общего с волосами на лице :] Это сохраняет все незафиксированные изменения, чтобы вы могли позже восстановить их, если потребуется.
git checkout main
Это переключает текущую ветвь обратно на основную ветвь.
git merge urlsession
Это объединяет изменения из ветви “urlsession” обратно в основную ветвь. Если в этот момент вы получите сообщение об ошибке, просто сделайте git stash
это снова и повторите git merge
команду.
Кстати, на самом деле вам не нужно хранить эти спрятанные файлы, так что если вы хотите удалить их из своего репозитория, вы можете это сделать git stash drop
. Если вы спрятали дважды, вам также нужно дважды сбросить.
BOS Откройте проект снова в Xcode. Теперь вы вернулись в главное отделение, и в нем также есть последние изменения в сети.
BOS Соберите и запустите, чтобы увидеть, все ли еще работает.
Git — довольно удивительный инструмент, но для ознакомления с ним требуется некоторое время. Поддержка Git в Xcode значительно улучшилась за эти годы, но для более сложных вещей вам все равно может понадобиться использовать командную строку — этому стоит научиться!
Примечание: Несмотря
URLSession
на то, что он довольно прост в использовании и вполне работоспособен, многие разработчики предпочитают использовать сторонние сетевые библиотеки, которые часто даже более удобны и мощны.Одной из самых популярных собственных библиотек Swift на данный момент является Alamofire (github.com/Alamofire/Alamofire).
Я предлагаю вам проверить некоторые из этих библиотек и посмотреть, как они вам понравятся. Сеть-настолько важная особенность мобильных приложений, что стоит ознакомиться с различными возможными подходами к передаче данных вверх и вниз по сети.
Вы можете найти файлы проекта для этой главы в разделе 36-URLSession в папке исходного кода.