Url session task failed with error

iOS. Работа с сетью, когда приложение не запущено Пользователи ожидают, что работа с сетью происходит «волшебно» и незаметно. Зависит эта волшебность от разработчиков системы и приложений. На систему повлиять сложно, поэтому ограничимся приложением. Тема эта сложная и проблем там несчётное количество. Обсудим те, с которыми пришлось столкнуться за последние несколько месяцев. Сразу прошу прощения […]

Содержание

  1. iOS. Работа с сетью, когда приложение не запущено
  2. Общая схема работы с сетью. URLSession
  3. Создание сессии для работы в background
  4. Delegate vs Callbacks
  5. Общая схема скачивания данных в фоне
  6. Общая схема фоновой отправки данных
  7. Что такое «приложение завершено»
  8. Как протоколировать работу приложения, если нельзя пользоваться отладочной консолью Xcode?
  9. Отслеживание прогресса скачивания или отправки данных
  10. «Передача» задач из обычной сессии в background сессию
  11. Грабли, на которые я наступил
  12. Приостановка бизнес-логики
  13. Тестирование на устройстве
  14. Ограничения
  15. Мелочи

iOS. Работа с сетью, когда приложение не запущено

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

Тема эта сложная и проблем там несчётное количество. Обсудим те, с которыми пришлось столкнуться за последние несколько месяцев. Сразу прошу прощения за объём. Покороче никак, слишком много мелочей, на которые стоит обратить внимание.

Для начала, разберёмся с терминологией.

Передача данных бывает в двух направлениях:

  • download (скачивание, загрузка данных с сервера),
  • upload (отправка данных на сервер).

Приложение может быть активно, а может работать в фоне. Формально, у него есть и другие состояния, но нас интересуют только эти:

  • background (когда приложение «свёрнуто»),
  • active (когда приложение активно, на экране).

Полезные паттерны: callback, delegate (Cocoa Design Patterns , про callback в Википедии). Также нужно знать, как работает URLSession (в статье по ссылке есть и упоминание фоновой работы с сетью, но вскользь).

Все примеры написаны на Swift 5, работают на iOS 11 и более новых (тестировалось на iOS 11 и 12) и предполагают использование обычных HTTP-запросов. По большей части всё это будет работать, начиная с iOS 9, но «есть нюансы».

Общая схема работы с сетью. URLSession

Работа с сетью не представляет особой сложности:

  • создаем конфигурацию URLSessionConfiguration ;
  • создаем по конфигурации экземпляр URLSession ;
  • создаем task (при помощи методов session.dataTask(…) и аналогичных);
  • подписываемся на обновления задачи. Обновления приходят асинхронно, могут приходить в delegate, который прописывается при создании сессии, а могут в callback, который создаётся при создании задачи;
  • когда увидели, что задача завершена, возвращаемся к логике приложения.

Простой пример выглядит так:

Эта схема сходна для различных задач, меняются только мелочи. И до тех пор, пока нам не требуется, чтобы работа с сетью продолжалась после того, как пользователь закрыл приложение, всё сравнительно просто.

Сразу отмечу, что даже в этом сценарии много интересного. Иногда требуется работать с хитрыми редиректами, иногда нужна авторизация, SSL-пиннинг или всё сразу. Про это можно прочитать много где. Работа с сетью в background состоянии почему-то описана сильно меньше.

Создание сессии для работы в background

Чем отличается background URLSession от обычной? Она работает вне процесса приложения, где-то внутри системы. Поэтому она не «умирает», когда завершается процесс приложения. Называется она background-сессией (также, как и состояние приложения, что немного путает) и требует специфической настройки. Например, такой:

У конфигурации множество других параметров, но эти относятся непосредственно к background сессиям:

  • identifier (передаётся в инициализаторе) — это строка, которая используется для сопоставления background сессий при перезапуске приложения. Если приложение перезапустилось, и вы создаёте background сессию с идентификатором, который уже используется в другой background сессии, то новая получит доступ к задачам предыдущей. Вывод из этого простой. Для корректной работы нужно, чтобы этот идентификатор был уникальный для вашего приложения и постоянный (можно использовать, например, производную от bundleId приложения);
  • sessionSendsLaunchEvents показывает, должна ли background сессия запускать приложение, когда завершается передача данных. Если этот параметр поставить false, то запуска не произойдёт, а все события приложение получит, когда само в следующий раз запустится. Если параметр true , то после завершения передачи данных, система запустит приложение и вызовет соответствующий метод AppDelegate: application(_:handleEventsForBackgroundURLSession:completionHandler:) ;
  • isDiscretionary даёт возможность системе планировать выполнение задачи более редко. Это, с одной стороны, улучшает длительность работы аккумулятора, с другой — может затормозить выполнение задачи. А может и ускорить. Например, если скачивается большой объем, система сможет поставить задачу на паузу до момента подключения к WiFi, а потом быстро всё скачать, не тратя медленный мобильный интернет (если он вообще разрешён, о чем дальше). Если таск создается тогда, когда приложение уже в background, то этот параметр автоматически проставляется в true ;
  • allowsCellularAccess — параметр, который показывает, что можно использовать сотовую связь для работы с сетью. Я с ним внимательно не игрался, но по отзывам, тут (вместе с аналогичным системным переключателем) разложено огромное количество граблей;
  • shouldUseExtendedBackgroundIdleMode. Полезный параметр, который показывает, что система дольше должна сохранять коннект с сервером, когда приложение уходит в фон. В противном случае коннект будет разорван.
  • waitsForConnectivity В условиях мобильного устройства связь может пропадать на короткие промежутки времени. Созданные в этот момент задачи могут либо быть приостановлены до появления связи, либо сразу вернуть ошибку «нет связи». Параметр позволяет управлять этим поведением. Если он false, то при отсутствии связи задача сразу же сломается с ошибкой. Если true — подождёт, пока не появится связь.
  • последняя строка (инициализатор сессии) содержит важный параметр, delegate. Про него — чуть подробнее.

Delegate vs Callbacks

Как я уже говорил выше, есть два способа получения событий от задачи/от сессии. Первый — callback:

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

Второй вариант работы с сессией — через delegate. В этом случае мы должны создать класс, который реализует протоколы URLSessionDataDelegate и (или) другие рядом стоящие (для разных типов задач протоколы немного отличаются). Ссылка на экземпляр этого класса живёт в сессии, а его методы вызываются при необходимости передачи в делегат событий. Прописать ссылку в сессии можно инициализатором. В примере прописывается self.

Для обычных сессий доступны оба метода. Background сессии умеют использовать только делегат.

Итак, настроили сессию, создали, давайте посмотрим на то, как что-нибудь скачать.

Общая схема скачивания данных в фоне

Чтобы скачать данные, обычно нужно сформировать запрос (URLRequest) , прописав в нём нужные параметры/заголовки/данные, создать URLSessionDownloadTask и запустить её на исполнение. Примерно так:

В этом месте ничего особо не отличается от обычной задачи на скачивание. Появились, правда, два параметра countOfBytesClientExpectsToSend/countOfBytesClientExpectsToReceive, они показывают объём данных, которые мы планируем отослать в запросе и получить обратно в ответе. Это нужно для того, чтобы система могла более грамотно распланировать работу с задачей, скачать быстрее, не перенапрягаясь. Эти величины не обязательно должны быть точные.

После resume() задача отправится на выполнение. Во время передачи данных будет передаваться прогресс (про него — читайте ниже, там тоже есть варианты), а после завершения выполнится несколько методов делегата. Среди них есть один очень важный:

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

После этого важного вызовется ещё метод, куда свалится ошибка, если она произошла. Если ошибки нет, error будет равен nil.

А что происходит при завершении, если приложение ушло в фон или было завершено? Как вызвать делегатные методы? Тут всё непросто.

Если закончилось скачивание чего-то, что было запущено приложением, и стоит флаг sessionSendsLaunchEvents в конфигурации сессии, то система запустит приложение (в фоне), и вызовет в AppDelegate, метод application(_:handleEventsForBackgroundURLSession:completionHandler:).

В этом методе приложение должно:

  • сохранить completionHandler (его потребуется вызвать через некоторое время, асинхронно и в главном потоке);
  • пересоздать фоновую сессию с тем же идентификатором, который был раньше (и который передаётся в этот метод на случай, если есть несколько фоновых сессий);
  • во вновь созданную сессию в делегат прилетят события (в частности, тот самый, важный, urlSession(_:downloadTask:didFinishDownloadingTo:) ), нужно их обработать, скопировать файлы, куда требуется;
  • после того, как все методы вызваны, вызовется ещё один метод делегата, который называется urlSessionDidFinishEvents(forBackgroundURLSession:) и в котором нужно будет вызвать сохранённый раньше completionHandler.

Важно. Вызывать completionHandler необходимо обязательно в главном потоке, используя DispatchQueue.main.async(. ) .

При этом нужно помнить, что всё это происходит в приложении, которое работает в фоне. А это значит, что ресурсы (время исполнения) ограничены. Быстро сохранить файлы куда нужно, поменять нужные состояния в приложении и завершить работу — вот примерно всё, что можно сделать. Если хочется сделать больше, то можно воспользоваться UIApplication.beginBackgroundTask() или новыми BackgroundTasks.

Общая схема фоновой отправки данных

Отправка файлов на сервер также работает с ограничениями. Начинается, впрочем, всё похожим образом: формируем запрос, создаем задачу (теперь это будет URLSessionUploadTask) , запускаем задачу. В чём проблема?

Проблема в том, как мы создаём запрос. Обычно мы формируем отправляемые данные, как Data . Background URLSession, не умеет с таким работать. И с потоковым запросом ( uploadTask(withStreamedRequest:) ) тоже не умеет. Необходимо записать всё, что нужно отправить, в файл, и создать задачу отправки из файла. Получается как-то так:

Зато нет необходимости прописывать размер, URLSession может его посмотреть сама. После отправки вызовется тот же метод делегата urlSession(_:task:didCompleteWithError:) , что и при скачивании. И точно также, если приложение было убито или ушло в фон в процессе отправки, прилетит application(_:handleEventsForBackgroundURLSession:completionHandler:), который нужно обработать ровно по тем же правилам, что и при скачивании данных.

Что такое «приложение завершено»

Чтобы тестировать фоновые скачивания и отправки, нужно имитировать завершение приложения (фоновая работа с сетью специально сконструирована, чтобы это переживать). Как это сделать? Изначально — никак. То есть нет никакого штатного (разрешённого, публичного) метода, который бы это позволял сделать. Посмотрим, где тут грабли.

  • во-первых, просто закрыть приложение (нажав кнопку Home или выполнив соответствующий жест) — не выйдет. Это не убьёт приложение, а лишь отправит его в фон. Смысл же работы с background сессией в том, что она работает даже, если приложение «совсем-совсем» убито;
  • во-вторых, нельзя, чтобы был подключён отладчик (AppCode, Xcode или просто LLDB), он не даст умереть приложению даже через некоторое время после его «закрытия»;
  • в-третьих, нельзя убивать приложение из панели задач (task manager, двойной Home или медленный свайп «наверх»). Таким образом убитое приложение считается убитым «насовсем», и система останавливает вместе с таким действием и фоновые сессии, привязанные к приложению;
  • в-четвертых, тестировать этот процесс нужно на настоящем устройстве. Там нет проблем с протоколированием (про это ниже) и оно более отлажено. Утверждается, что симулятор тоже должен работать, как нужно. Но я замечал необъяснимые странности, которые ничем, кроме глюков симулятора, не могу объяснить. В общем, тестируйте на устройстве;
  • единственный разумный способ сделать то, что хочется — функция exit(int) . Как все знают, выкладывать это в стор нельзя (это напрямую противоречит требованиям), но мы пока просто тестируем — не страшно. Мне известно два разумных варианта использовать эту функцию:
    • вызывать её автоматически в методе AppDelegate.applicationDidEnterBackground(_:) , чтобы приложение убивалось сразу после выхода в СпрингБорд;
    • сделать в интерфейсе компонент (например, кнопку, или на жест повесить действие), по нажатию на который, вызывается exit(. ).
      При этом приложение убьётся, а фоновая работа с сетью должна продолжиться. И, через некоторое время, мы должны получить вызов application(_:handleEventsForBackgroundURLSession:completionHandler:).

Как протоколировать работу приложения, если нельзя пользоваться отладочной консолью Xcode?

Ну, как нельзя. Можно, если очень хочется. Запускать из Xcode нельзя, а если приложение уже, например, перезапустилось по системному событию, можно присоединиться (attach to process) к приложению и подебажить. Но это решение так себе, нужно ведь и сам процесс перезапуска как-то тестировать.

Можно использовать протоколы (логи, logs). Есть несколько вариантов их реализации:

  • print. Используется часто, как «давайте быстро что-нибудь выведем». В нашем случае использовать нельзя, так как доступа к консоли на устройстве у нас нет, приложение убито.
  • NSLog. Будет работать, так как использует третий метод.
  • os_log. Самый правильный метод, позволяющий нормально настроить логи, проставить им нужный тип, отключить после отладки, не вырезая сам код, и так далее.

Внимание! С os_log встречаются проблемы (например, отсутствие debug-логов), которые воспроизводятся только в симуляторе, но не воспроизводятся на настоящем устройстве. Пользуйтесь устройством.

Как пользоваться os_log, читайте, как его правильно настроить, в документации Apple. В частности, стоит включить debug и info логи, по-умолчанию они скрываются.

Отслеживание прогресса скачивания или отправки данных

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

  • для отправки нужно использовать urlSession(_:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)
  • для скачивания есть похожий метод urlSession(_:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:)

Эти методы вызываются каждый раз, когда скачалась или отправилась очередная порция данных. Они не обязательно согласованы с методами завершения процесса, могут вызываться и после того, как данные полностью скачались или отправились, поэтому определять, что «всё завершилось» по ним нельзя.

Второй способ более интересный. Дело в том, что каждая задача предоставляет объект типа Progress (лежит он в поле task.progress ), который предоставляет возможность следить за произвольным процессом, в том числе за процессом передачи данных. Чем он интересен? Двумя вещами:

  • из объектов Progress можно создавать дерево выполнения задач, каждый узел которого будет показывать, насколько продвинулись все задачи, которые в нём содержатся. Например, если вам нужно отправить пять файлов, можно взять по прогрессу на каждый, сделать общий прогресс, добавить в него пять других, и следить за одним, родительским прогрессом, привязав его обновления к какому-нибудь элементу интерфейса;
  • можно добавлять свои прогрессы в это дерево, а также можно приостанавливать и отменять действия, связанные с добавленными прогрессами.

Как это относится к фоновому скачиванию или отправке данных? Никак. Делегатные методы не вызываются, и объекты прогресса умирают вместе с завершением работы приложения. Для background сессий этот способ не подходит.

«Передача» задач из обычной сессии в background сессию

Хорошо, с background сессией работать сложнее. Но это же удобно! Ни одна задача не потеряется, мы когда-нибудь получим все данные, которые запросили, почему не использовать background сессию всегда?

К сожалению, у неё есть недостатки, и серьёзные. Например, background сессия работает медленнее. В моих экспериментах скорость разнилась в несколько раз. Во-вторых, фоновое выполнение задачи может быть отложено (особенно если проставлен параметр isDiscretionary , который, как я упоминал, всегда true для задач, созданных, пока приложение работает в фоновом режиме.

Поэтому каждый раз, когда вы создаёте задачу, нужно точно понимать, какие критерии её работы, куда её добавлять, в обычную или в background сессию. Обычная работает быстрее, запускается сразу. Background — дольше, не сразу, но не убьётся, если пользователь закрыл приложение.

Если нет очевидного понимания, что задача должна быть выполнена в background сессии (как, например, некритичная передача очень большого количества данных, вроде синхронизации или бекапа), то стоит делать следующим образом:

  • начинаем задачу в обычной сессии. При этом запускаем backgroundTask, чтобы система понимала, что нам нужно время на завершение задачи. Это даёт какое-то время (до нескольких минут, но в iOS 13 что-то сломали и не понятно, что с этим происходит), чтобы задача успела выполниться.
  • если не успевает — то в завершении backgroundTask переносим задачу из обычной сессии в фоновую, где она продолжает работать, и завершается, когда может.

Как передать? Никак. Просто убиваем (cancel) обычную задачу, и создаём аналогичную (с таким же запросом) фоновую. Почему же это называется «передача»? И почему в кавычках?

Для отправки данных никакой передачи нет. Есть именно то, что описано. Убили одну задачу, запустили другую, все данные, которые отправили в первый раз, потерялись.

Для скачивания ситуация другая. Система знает, в какой файл какой запрос скачивается. Если вы запустите несколько задач на скачивание одинакового урла, например, она не будет выполнять запрос несколько раз. Данные скачаются один раз, после чего несколько раз выполнится завершающий делегатный метод (или колбэк). Здесь описан эксперимент, который это подтверждает. Скорее всего, внутри используется стандартное HTTP-кеширование, такое же, как в браузерах.

Вот примерный код, который это делает:

Если задача завершится раньше, чем сработает expirationHandler , то необходимо не забыть вызвать UIApplication.shared.endBackgroundTask(backgroundId) . Более подробно это описано в документации.

Чтобы помочь системе продолжить скачивание (например, отмена может привести к тому, что временный файл удалится перед тем, как возобновится фоновое скачивание), есть специальные методы:

  • чтобы получить данные, необходимые для возобновления, нужно отменить предыдущий таск методом cancel(byProducingResumeData:);
  • чтобы создать таск, учитывая уже скачанные данные: downloadTask(withResumeData:) -> URLSessionDownloadTask, то есть как-то так:

Грабли, на которые я наступил

Самая сложная часть во всём этом — точно понимать, что происходит. Отличное протоколирование — первая задача, которую нужно сразу решать. Поведение background сессий нельзя протестировать никак, кроме как нормальными логами.

Также, к сожалению, работа background сессий никак не тестируется юнит-тестами, так как для их работы нужно приложение, нужно уметь уводить приложение в фон, необходимо уметь убивать приложение и потом отслеживать его запуск (причём запуск без UI, система перезапускает приложение в фоновом состоянии). Поэтому, повторюсь, единственный способ тестирования — вручную. И единственный способ видеть то, что тестируется — логами, в консоли, используя os_log. (или NSLog)

Приостановка бизнес-логики

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

Тестирование на устройстве

Тестировать нужно только на устройстве. И ни в коем случае не пользоваться выгрузкой приложения из памяти (из переключателя задач), это действие всё ломает. Система считает, что пользователь не зря выгружает приложение, что ему это нужно, и не перезапускает фоновые процессы передачи данных.

Ограничения

Напомним ограничения фоновых сессий:

  • только делегатные методы, колбэки запрещены;
  • отправка — только файлов, ничего другое не работает;
  • получение прогресса передачи работает только пока приложение запущено, в фоне процесс недоступен (впрочем, зачем он убитому приложению…);

Мелочи

Если у вас в приложении есть расширения, там тоже можно работать с background сессиями, и даже использовать идентификаторы основного приложения. Но делать это нужно очень аккуратно, чтобы расширение и основное приложение что-нибудь не поломали друг другу. Вот немного документации по этому поводу: https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html#//apple_ref/doc/uid/TP40014214-CH21-SW1. Там много интересного, например:

If your app extension initiates a background NSURLSession task, you must also set up a shared container that both the extension and its containing app can access. Use the sharedContainerIdentifier property of the NSURLSessionConfiguration class to specify an identifier for the shared container so that you can access it later.

Источник

I am having trouble connecting to my server. Whenever I login with the correct info this URLSessionTask error pops up. I don’t think It has to do with my code but it might. I have a sign in page that works perfectly and I tested my login php file and it works perfectly. I don’t think its because of misplacement of files either. Does anyone have any suggestions?

 @IBAction func loginTapped(_ sender: Any) {
     
        // If no text entered
        if usernameTextfield.text!.isEmpty || passwordTextField.text!.isEmpty {
            
            //send message if fields are not filled
            print("User name (String(describing: usernameTextfield)) or password (String(describing: passwordTextField)) is empty")
            self.errorLabel.alpha = 1
            return
        } else {
            
            //Shortcuts
            let username = usernameTextfield.text
            let password = passwordTextField.text
            
            //send request to sql db
            let url = "http://346.0.0.3/myApp/login.php"
        
            let parameters: Parameters=[
             "username":usernameTextfield.text!,
             "password":passwordTextField.text!
            ]
            
            AF.request(url, method: .post, parameters: parameters as Parameters, encoding:
            URLEncoding.default).response { (response) in

                    switch response.result {
                         case .success:
                         //sign in
                         let tabBarController =
                         self.storyboard?.instantiateViewController(identifier: Constants.Storyboard.TabBarController) as? UITabBarController
                         self.view.window?.rootViewController = tabBarController
                         self.view.window?.makeKeyAndVisible()

                         case .failure(let error):
                         // Couldn't sign in
                            self.errorLabel.text = error.localizedDescription
                         self.errorLabel.alpha = 1
                    }
                  }

I’m trying to implement FCM to send a push notification (using the legacy api), but am unable to do so.

I have verified that my server key (taken from Firebase console) and device token are accurate, and I have successfully sent a push note to my device via Postman.

However, I am unable to do so via code. I am calling the below code from my simulator and «attempting» to send a push note to my physical device.

let urlString: String = "https://fcm/googleapis.com/fcm/send"
let key = "key=[my server key]"

let headers: HTTPHeaders = [
    "Content-Type": "application/json",
    "Authorization": key
]

let notificationParameters: Parameters = [
    "to": "[my push token of physical device]",
    "notification": [
        "title": "My title",
        "body": "My body"
    ]
]

print("1")
AF.request(urlString, method: .post, parameters: 
notificationParameters, encoding: JSONEncoding.default, headers: 
headers).responseJSON { response in
    print("3")
    switch response.result {
    case .success:
        printSuccess("Successfully sent notification")
    case .failure(let error):
        print("Failed to send notification: (error)")
        print(error.errorDescription)
        print(error.failureReason)
    }
    print("4")
}
print("2")

Here’s the log:

1
2
2022-03-31 15:23:24.725607-0500 Wurtle with Friends[22207:12775410] 
[connection] nw_socket_handle_socket_event [C3.1:3] Socket SO_ERROR 
[61: Connection refused]
2022-03-31 15:23:24.727572-0500 Wurtle with Friends[22207:12775410] 
Connection 3: received failure notification
2022-03-31 15:23:24.727789-0500 Wurtle with Friends[22207:12775410] 
Connection 3: failed to connect 1:61, reason -1
2022-03-31 15:23:24.728130-0500 Wurtle with Friends[22207:12775410] 
Connection 3: encountered error(1:61)
2022-03-31 15:23:24.729779-0500 Wurtle with Friends[22207:12775409] 
[boringssl] boringssl_metrics_log_metric_block_invoke(153) Failed 
to log metrics
2022-03-31 15:23:24.731134-0500 Wurtle with Friends[22207:12775410] 
Task <B52206D8-E22D-4D8F-B3F5-815692558860>.<1> HTTP load failed, 
0/0 bytes (error code: -1004 [1:61])
2022-03-31 15:23:24.736922-0500 Wurtle with Friends[22207:12775410] 
Task <B52206D8-E22D-4D8F-B3F5-815692558860>.<1> finished with error 
[-1004] Error Domain=NSURLErrorDomain Code=-1004 "Could not connect 
to the server." UserInfo={_kCFStreamErrorCodeKey=61, 
NSUnderlyingError=0x60000279c300 {Error 
Domain=kCFErrorDomainCFNetwork Code=-1004 "(null)" UserInfo=
{_NSURLErrorNWPathKey=satisfied (Path is satisfied), interface: 
en1, _kCFStreamErrorCodeKey=61, _kCFStreamErrorDomainKey=1}}, 
_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <B52206D8-
E22D-4D8F-B3F5-815692558860>.<1>, 
_NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <B52206D8-E22D-4D8F-B3F5-815692558860>.<1>"
), NSLocalizedDescription=Could not connect to the server., 
NSErrorFailingURLStringKey=https://fcm/googleapis.com/fcm/send, 
NSErrorFailingURLKey=https://fcm/googleapis.com/fcm/send, 
_kCFStreamErrorDomainKey=1}
3
Failed to send notification: sessionTaskFailed(error: Error 
Domain=NSURLErrorDomain Code=-1004 "Could not connect to the 
server." UserInfo={_kCFStreamErrorCodeKey=61, 
NSUnderlyingError=0x60000279c300 {Error 
Domain=kCFErrorDomainCFNetwork Code=-1004 "(null)" UserInfo=
{_NSURLErrorNWPathKey=satisfied (Path is satisfied), interface: 
en1, _kCFStreamErrorCodeKey=61, _kCFStreamErrorDomainKey=1}}, 
_NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <B52206D8-
E22D-4D8F-B3F5-815692558860>.<1>, 
_NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <B52206D8-E22D-4D8F-B3F5-815692558860>.<1>"
), NSLocalizedDescription=Could not connect to the server., 
NSErrorFailingURLStringKey=https://fcm/googleapis.com/fcm/send, 
NSErrorFailingURLKey=https://fcm/googleapis.com/fcm/send, 
_kCFStreamErrorDomainKey=1})
Optional("URLSessionTask failed with error: Could not connect to 
the server.")
4

This is my first time implementing FCM and I’ve been stuck on it for a while now, so any help is appreciated. Thank you

Push-уведомление FCM через AlamoFire — сбой URLSessionTask с ошибкой: не удалось подключиться к серверу

Я пытаюсь реализовать FCM для отправки push-уведомления (используя устаревший API), но не могу этого сделать.

Я проверил, что мой ключ сервера (взятый из консоли Firebase) и токен устройства точны, и я успешно отправил push-уведомление на свое устройство через Postman.

Однако я не могу сделать это с помощью кода. Я вызываю приведенный ниже код из своего симулятора и «пытаюсь» отправить push-заметку на мое физическое устройство.

Это мой первый раз, когда я внедряю FCM, и я застрял на нем некоторое время, поэтому любая помощь приветствуется. Спасибо

Почему вы хотите запустить FCM в приложении iOS?

вы должны получить токен FCM и токен обновления, как сказано в консоли firebase в файле Appdelegate:

вы не должны отправлять push из приложения iOS, это небезопасно из-за ключа сервера и т. д., а также вы должны добавить сертификат облачного сообщения и ключ сообщения по запросу firebase. а также у вас должен быть токен назначения fcm (на устройство). Отправитель push-уведомлений должен быть вашим бэкэндом, у которого есть все токены FCM устройства.

вы должны отправить токен FCM, который вы получаете в Appdelegate для [«user», «device»] на серверную часть, и ваш сервер должен сохранить его для пользовательского устройства.

Токен FCM — это ключ карты для каждого токена APNS устройства.

Спасибо за ответ Мазиар. Я уже получаю обновленный токен FCM в делегате приложения; в коде, который я разместил, токен введен вручную для простоты. Я попробовал оба способа и убедился, что токен обновлен и верен, но он по-прежнему регистрирует те же ошибки. Я также сделал все остальные вещи, которые вы упомянули (за исключением фактической отправки push-уведомления из бэкэнда). Push-уведомление корректно отправляется как тестовое сообщение из консоли Firebase, а также из Postman.

Как вы думаете, это потому, что я пытаюсь отправить push-уведомление с моего симулятора на физическое устройство? Я знаю, что симуляторы не могут получать push-уведомления, но могут ли они хотя бы отправить их на физическое устройство? Это то, что я делаю здесь. Мой симулятор отправляет push-уведомление на предоставленный токен устройства (который является токеном моего физического устройства, зарегистрированного для push-уведомлений).

Кроме того, вы упоминаете, что вам не следует отправлять push-уведомления из приложения iOS из-за того, что ключ сервера не является безопасным. Означает ли это, что я должен активировать свой бэкэнд, чтобы активировать FCM, чтобы активировать APN только для отправки push-уведомления? Не будет ли это слишком лишним.

@Eric [мой push-токен физического устройства] — это то же устройство, на котором вы находитесь, когда вы отправляете push на другое устройство, у вас должен быть токен назначения, как получить токен назначения? когда вы можете отправить push-уведомление от клиента, каждый может отправить его, поэтому я могу отправить push-уведомление вашим клиентам (даже с почтальоном), FCM — это промежуточное ПО для унификации отправки push-уведомления на обеих платформах (Android и ios, веб) и ваш бэкэнд не хочет реализовывать как push API, так и должен просто реализовывать FCM. это не лишнее.

@Eric, например, если я отправляю запрос на серверную часть, серверная часть знает, какой пользователь (пользователи) должен получить push-уведомление, а клиент не знает о других пользовательских токенах, даже у пользователя может быть несколько устройств, и все устройства должны получить толчок.

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

Источник

Почему может не работать приложение «Мой Билайн»

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

После установки программы у потребителя могут возникать проблемы с авторизацией. Существует перечень распространенных причин, почему не работает приложение «Мой Билайн», о них далее будет рассказано подробней.

Почему может не работать приложение

Утилита «Мой Билайн» – удобный и простой инструмент, позволяющий управлять сим-картой, всеми установленными на ней услугами и тарифным планом. Программа представляет собой упрощенную аналогию персонального кабинета пользователя на официальном сайте провайдера Билайн. Она имеет схожий набор функциональных возможностей. С ее помощью пользователь способен:

  1. Подключать, деактивировать контракт на симке. Устанавливать дополнительные опции для экономии, подписки, скидки в роуминге и на связь по России.
  2. Контролировать текущий баланс на телефоне. Своевременно пополнять лицевой счет, переводить денежные средства другому пользователю.
  3. Получать подтвержденную справочную информацию о новых предложениях компании Билайн.
  4. Запрашивать поддержку у сервисной службы при появлении вопросов, неполадок. Для этого в утилите реализован онлайн-чат с оператором, позволяющий быстро получить исчерпывающий ответ.

Все это превращает приложение в надежного помощника для абонента. Особенности работы:

  1. Сервис Билайн работает бесплатно. Денежные средства взимаются лишь за потраченный интернет-трафик во время нахождения в программе.
  2. Приложение доступно только для клиентов компании провайдера. Войти можно путем авторизации, она осуществляется своим номером телефона.
  3. Для корректной и качественной работы сервиса необходимо иметь постоянное соединение с интернетом, активировать функцию передачи данных на смартфоне.
  4. По системным требованиям подходит для всех видов мобильных устройств, операционных систем.

Программа находится в свободном доступе в официальном магазине программного обеспечения и на сайте оператора. После загрузки на мобильное устройство утилита автоматически установится. Для доступа необязательно заходить в браузер.

Несмотря на все достоинства, у абонента Билайн могут возникнуть проблемы с работой сервиса. Например, он не может авторизоваться под своей учетной записью или программа не запускается на телефоне. Существует несколько распространенных причин сбоев, причем не всегда виновником выступает приложение. Чаще всего потребитель не способен запустить «Мой Билайн» просто по невнимательности.

Технические проблемы

Наиболее вероятной причиной отказа в работе программы является использование устаревшего оборудования. Перед установкой проверьте, подходит ли сотовый телефон предъявленным системным требованиям издателя. Операционная система для корректного функционирования – версия Андроид 2.2. Рассмотрим популярные причины некорректной работы приложения:

  1. Пользователь использует устаревший девайс, характеристики которого не подходят под минимальные системные требования.
  2. Мобильное устройство не подключено к интернету.
  3. Операционная система на телефоне устарела. Рекомендуется своевременно обновлять программное обеспечение самого смартфона и приложения.
  4. Неисправность телефона. Выход из строя передающие антенны, повреждена сим-карта.

Неправильная авторизация

После успешной активации и установки утилиты на смартфоне потребителю необходимо авторизоваться в системе. Чтобы открыть доступ к персональным сведениям, учетной записи, нужно прописать логин (номер абонента Билайн), защитный пароль. Если программа правильно запускается на девайсе, но у пользователя не получается пройти процедуру инициализации, войти в личный кабинет с телефона, то причины могут быть следующие:

  1. При написании персональных сведений для инициализации допущена опечатка, неправильно указан логин, пароль.
  2. Абонент забыл ключ от входа в систему.

Проблемы со связью

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

  1. Потребитель находится и пытается авторизоваться в учетной записи за пределами действия сети, на значительном отдалении от действия сигнала сотовой связи.
  2. Мобильный не подключен к интернету.
  3. Сим-карта пользователя заблокирована в одностороннем порядке по причине неуплаты, наличия задолженности на лицевом счете.
  4. На тарифном плане, установленном на телефоне, закончился доступный пакет трафика.
  5. Абонент забыл своевременно внести абонентскую плату за использование услуг связи.

Все это может послужить серьезной проблемой при работе с приложением.

Другие проблемы

Не стоит вычеркивать из списка человеческий фактор, невнимательность абонента. Рассмотрим распространенные случаи:

  1. На сотовом телефоне активирован режим «В самолете». Он автоматически блокирует мобильную сеть.
  2. Выключена функция передачи данных.
  3. Внутренняя память устройства переполнена. Перед загрузкой утилиты проверьте наличие свободного места на девайсе. В случае недостатка программу не получится установить.

Пути решения проблем

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

Проблема Способ самостоятельного решения
Нехватка средств на сим-карте Постоянно проверяйте текущий баланс на лицевом счете, своевременно пополняйте его и вносите абонентскую плату.
Проблемы со входом в профиль Если пользователь забыл пароль от входа в учетную запись, нужно восстановить его стандартным способом, для этого введите *110*9# . Через несколько минут на номер поступит SMS от провайдера с новым кодом от системы. После успешной авторизации поменяйте пароль в настройках утилиты.
Опечатка Внимательно проверьте указанные сведения, логин и код на наличие опечаток.
Отсутствие интернета При отсутствии постоянного доступа в сеть или нахождении за пределами стабильного приема сигнала оператора, найдите место с оптимальным приемом, попробуйте заново.

Поменяйте способ подключения к интернету. Переключите телефон на беспроводной способ соединения, посредством Wi-Fi роутера.

Не открывается программа В случае некорректной работы самого устройства или его несовместимости с системными требованиями придется поменять смартфон.

Если ни один из описанных методов не принес положительного результата, обратитесь в сервис технической поддержки оператора по номеру 0611 . Дождитесь ответа консультанта, подробно объясните сложившуюся ситуацию. Менеджер Билайн попросит назвать персональную информацию, паспортные реквизиты для подтверждения личности. Доступ к личному кабинету есть только у владельца сим-карты. По необходимости запросите новый пароль авторизации.

Мобильное приложение «Мой Билайн» станет незаменимым помощником абоненту. При появлении проблем с работой, авторизацией не забудьте проверить подключение к интернету. Если самостоятельно не получается справиться с неполадкой, то горячая линия примет вашу заявку в любое время дня.

Источник

Errors

Here’s a list of all the error codes you may experience with further documentation about why this happens and what to do about it.

Setup and Connectivity​

«Invalid Client ID or Redirect URI» and «OS Error while looking up redirect_uri»​

Check your home-assistant.log file for any errors about indieauth . If it also mentions a OS Error, you most likely have a broken IPv6 implementation. You can confirm this by running curl -v6 https://home-assistant.io/iOS/beta-auth from the machine you run Home Assistant on. If you receive a error about not being able to connect to the server, your IPv6 stack is broken and you should disable it.

«Invalid Client ID or Redirect URI» and «Timeout while while looking up redirect_uri»​

Check your home-assistant.log file for any errors about indieauth . If it also mentions a Timeout, you may have a problem with your DNS not behaving as expected. You can confirm this by running dig home-assistant.io and nslookup home-assistant.io from the machine you run Home Assistant on. If you see any errors there could be a dns problem.

Fixing this varies depending on your setup — but it’s worth trying the google dns servers 8.8.8.8 and 1.1.1.1 . If you are running a hassOS setup you can do this with ha dns options —servers dns://8.8.8.8 —servers dns://1.1.1.1 .

SSL error while looking up redirect_uri https://home-assistant.io/iOS​

This error means that your Home Assistant can’t negotiate the encrypted connection to https://home-assistant.io. This issue has been seen on installations running on top of MacOS where the installer notice about certificates was skipped and ignored. From the Python 3.7.5 ReadMe:

Certificate verification and OpenSSL This package includes its own private copy of OpenSSL 1.1.1. The trust certificates in system and user keychains managed by the Keychain Access application and the security command line utility are not used as defaults by the Python ssl module. A sample command script is included in /Applications/Python 3.7 to install a curated bundle of default root certificates from the third-party certifi package (https://pypi.org/project/certifi/). Double-click on Install Certificates to run it. The bundled pip has its own default certificate store for verifying download connections.

«Setup failed for dependencies: zeroconf «​

This error is usually caused by one of the two following issues:

  • You are running two Home Assistant instances with identical names. The solution is to rename one of them.
  • You are missing default_config: from your configuration.yaml file. It is possible to only add zeroconf: to configuration.yaml but adding default_config: will add several useful integrations along with zeroconf: .

Response status code was unacceptable: 400​

This occurs when the data sent during set up does not meet Home Assistant’s expectations. This most commonly occurs in two circumstances:

  • When you are running a version of Home Assistant older than the minimum requirement (currently 0.104.0)
  • You have unexpected characters in your device’s name. While setting up the Mobile App integration, we attempt to remove non-standard characters and emoji (as of Home Assistant 0.112). However, it is worth simplifying your device name to remove such characters if you are getting this error.

URLSessionTask failed with error​

This error is usually caused by one of the two following issues:

Источник

answered on 29 Jun 2020, 08:13 PM

Hi Andrea,

Unfortunately, I am not entirely sure why this would still be happening in the environment. The following are two troubleshooting options that we can still try.

  1. Can you tether your PC to the iPhone’s network?
    • In the article How To: Capture iOS article, it states that Kamen had to tether his PC to his iPhone.
  2. Can you switch to a different Wi-Fi Network (non-corporate) and try?
    • We have to use our public Wi-Fi to test things like this as the corporate Wi-Fi has stricter requirements.

Please give the above options a try and let me know the results. Thank you and I look forward to your reply.

Regards,

Eric R | Senior Technical Support Engineer
Progress Telerik

Progress is here for your business, like always. Read more about the measures we are taking to ensure business continuity and help fight the COVID-19 pandemic.
Our thoughts here at Progress are with those affected by the outbreak.

answered on 28 Sep 2020, 09:25 AM

answered on 08 Oct 2020, 06:12 AM

answered on 16 Oct 2020, 04:55 AM

Hi Nick, ok so I solved this, embarrassing but I must admit, all I had to do was email the cert to my phone from the laptop. Not sure why I can’t access the the the ip4v.fiddler:8866 on the iphone. Hopefully this helps someone else.

There are plenty of third party libraries you can use to perform HTTP requests in Swift, but I always default to Foundation‘s URLSession API. It is a first party API and easy to use. The fewer dependencies a project has the better. In this post, you learn how easy it is to perform an HTTP request in Swift using the URLSession API. I show you how to fetch a remote image and JSON data.

Creating a Playground

Fire up Xcode and create a playground by selecting New > Playground… from Xcode’s File menu. Choose the Blank template from the iOS > Playground section.

How to Make an HTTP Request in Swift

Remove the contents of the playground with the exception of the import statement for the UIKit framework at the top. The URLSession class is defined in the Foundation framework. Importing UIKit automatically imports Foundation.

import UIKit

Working with URLSession

URLSession replaces NSURLConnection. The URLSession API is easier to use and modern. It is available on iOS, tvOS, macOS, and watchOS. URLSession is one class of the URLSession API. There are a handful of types you need to become familiar with to perform an HTTP request in Swift.

A URLSession instance is the manager or coordinator of the requests your application performs. A request is referred to as a task, an instance of the URLSessionTask. You never directly use the URLSessionTask class. Foundation defines a number of URLSessionTask subclasses. Each subclass has a specific objective, such as downloading or uploading data.

Creating a Data Task

Let’s use the URLSession API to perform an HTTP request. The objective is to fetch the data for an image. Before we start, we need to define the URL of the remote image.

import UIKit

let url = URL(string: "https://bit.ly/2LMtByx")!

The next step is creating a data task, an instance of the URLSessionDataTask class. A task is always tied to a URLSession instance. To keep things simple, we ask the URLSession class for the shared singleton session object through its shared class property.

URLSession.shared

We then ask the URLSession instance to create a data task by invoking the dataTask(with:completionHandler:) method. This method returns a URLSessionDataTask instance and accepts two arguments, a URL object and a completion handler. The completion handler, a closure, is executed when the data task completes, successfully or unsuccessfully. The completion handler accepts three arguments, an optional Data object, an optional URLResponse object, and an optional Error object.

import UIKit

let url = URL(string: "https://bit.ly/2LMtByx")!

let task = URLSession.shared.dataTask(with: url) { data, response, error in

}

A data task fails or succeeds. If the data task fails, then error has a value. If the data task succeeds, then data and response have a value. We are not interested in the URLResponse object for now. We safely unwrap the Data object and use it to create a UIImage instance. If data is equal to nil, then the HTTP request failed and we print the value of error to the console.

import UIKit

let url = URL(string: "https://bit.ly/2LMtByx")!

let task = URLSession.shared.dataTask(with: url) { data, response, error in
    if let data = data {
        let image = UIImage(data: data)
    } else if let error = error {
        print("HTTP Request Failed (error)")
    }
}

Even though we created a data task, the HTTP request isn’t executed. We need to call resume() on the URLSessionDataTask instance to execute it.

import UIKit

let url = URL(string: "https://bit.ly/2LMtByx")!

let task = URLSession.shared.dataTask(with: url) { data, response, error in
    if let data = data {
        let image = UIImage(data: data)
    } else if let error = error {
        print("HTTP Request Failed (error)")
    }
}

task.resume()

If you run the contents of the playground and wait a few seconds, you should see the result of the HTTP request appear in the results panel on the right. The dimensions of the UIImage instance are displayed in the results panel. No error is printed to the console, which means the HTTP request was successfully executed.

Fetching a Remote Image in Swift

Creating a Request

We created a data task by passing a URL object to the dataTask(with:completionHandler:) method. This works fine, but it doesn’t offer a lot of flexibility. The URLSession class defines another method that accepts a URLRequest object. As the name suggests, the URLRequest struct encapsulates the information the URL session needs to perform the HTTP request. Let me show you how this works.

We create a URLRequest object by passing the URL object to the initializer and store the result in a variable with name request. We modify the URLRequest object in a moment hence var instead of let.

var request = URLRequest(url: url)

We pass the URLRequest object to the dataTask(with:completionHandler:) method. The name of the method is similar to the one we used earlier. The difference is that it accepts a URLRequest object instead of a URL object.

import UIKit

let url = URL(string: "https://bit.ly/2LMtByx")!

var request = URLRequest(url: url)

let task = URLSession.shared.dataTask(with: url) { data, response, error in
    if let data = data {
        let image = UIImage(data: data)
    } else if let error = error {
        print("HTTP Request Failed (error)")
    }
}

task.resume()

Execute the contents of the playground. Notice that the result is identical. You may wonder what we gain by using URLRequest. The URLRequest object allows us to configure the HTTP request the URL session performs. We can set the HTTP method, through the httpMethod property. This isn’t necessary in this example since it defaults to GET.

request.httpMethod = "GET"

We can also define the request’s HTTP header fields. If you need to override the default HTTP header fields, then you set the allHTTPHeaderFields property, a dictionary of type [String:String]. This is also important if you need to deal with authentication. In this example, we add an HTTP header field to pass an API key to the server we are communicating with.

request.allHTTPHeaderFields = [
    "X-API-Key": "123456789"
]

You can also set the request’s HTTP header fields by invoking the setValue(_:forHTTPHeaderField:) method. This is a mutating method that sets the value for a given HTTP header field.

request.setValue("application/png", forHTTPHeaderField: "Content-Type")

Requesting JSON with URLSession

Fetching JSON is another common example. This isn’t any more difficult than fetching the data for a remote image. We update the URL object and set the value of the Content-Type HTTP header field to application/json.

import UIKit

let url = URL(string: "https://bit.ly/3sspdFO")!

var request = URLRequest(url: url)

request.setValue("application/json", forHTTPHeaderField: "Content-Type")

We have a few options to decode the response. I don’t recommend the JSONSerialization class unless you have a very good reason. The preferred and recommended approach is the Decodable protocol in combination with the JSONDecoder class.

We define a struct with name Book that conforms to the Decodable protocol. The Book struct defines two properties, title of type String and author of type String.

import UIKit

struct Book: Decodable {

    // MARK: - Properties

    let title: String
    let author: String

}

let url = URL(string: "https://bit.ly/3sspdFO")!

var request = URLRequest(url: url)

request.setValue("application/json", forHTTPHeaderField: "Content-Type")

In the completion handler, we create a JSONDecoder instance and invoke the decode(_:from:) method, passing in the type of the value to decode from the supplied JSON object and the JSON object to decode. We print the result to the console. Execute the contents of the playground to see the result.

let task = URLSession.shared.dataTask(with: url) { data, response, error in
    if let data = data {
        if let books = try? JSONDecoder().decode([Book].self, from: data) {
            print(books)
        } else {
            print("Invalid Response")
        }
    } else if let error = error {
        print("HTTP Request Failed (error)")
    }
}

Scratching the Surface

This post has taught you how to perform an HTTP request in Swift using the URLSession API. I hope you agree that this isn’t rocket science. The URLSession API is easy to use and, if you learn more about the API, flexible and powerful. We only scratched the surface in this tutorial.

Понравилась статья? Поделить с друзьями:
  • Url error 60 ssl certificate problem self signed certificate
  • Url error 60 ssl certificate problem certificate has expired
  • Url error 110
  • Urban vpn ошибка установки
  • Urban vpn setup ended prematurely because of an error