Это история о баге, на исправление которого ушёл год.

Этой историей я начинаю серию рассказов о повседневной работе разработчика — о трудностях, с которыми мы сталкиваемся, и о том, как мы их преодолеваем. Сегодняшний рассказ о баге, который длился год.

Однажды я работал над проектом, в котором пользователи могли загружать видео на свои телефоны для просмотра в автономном режиме. Для этого мы использовали проприетарную стороннюю библиотеку, предоставленную контент-провайдерами. Как разработчик, у меня был доступ к примерам приложений и документации для этой библиотеки. Я реализовал все, что они просили, в нашем приложении.

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

В этом случае мне не пришлось слишком долго искать проблему. Быстро выяснилось, что библиотека запустила фоновый процесс загрузки видео. Когда все загрузки завершены, система пробуждает приложение и предоставляет обработчик завершения, который возвращается, когда приложение завершает обработку завершенных загрузок. В нашем случае система прислала нам этот обработчик, но мы его не вернули системе и не выполнили, поэтому система предупредила нас, что убьет приложение через 30 секунд и тут же убила его.

Подробнее об этом можно узнать здесь, в документации https://developer.apple.com/documentation/foundation/url_loading_system/downloading_files_in_the_background?language=objc

Короче говоря, мы должны сначала сохранить этот обработчик завершения:

func application(_ application: UIApplication,
                 handleEventsForBackgroundURLSession identifier: String,
                 completionHandler: @escaping () -> Void) {
        backgroundCompletionHandler = completionHandler
}

а затем выполнить его в основном потоке, когда произойдет другое событие

func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    DispatchQueue.main.async {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate,
            let backgroundCompletionHandler =
            appDelegate.backgroundCompletionHandler else {
                return
        }
        backgroundCompletionHandler()
    }
}

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

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

Но что я должен был делать? В конце концов, я должен был выпустить приложение. Так что мне ничего не оставалось, как подождать 2 секунды и вернуть этот обработчик в систему. Поскольку сторонняя библиотека часто представляет собой черный ящик, я не мог знать, сколько времени потребуется библиотеке для выполнения своих задач после завершения всех загрузок. Но видимых проблем не было, и мы выпустили приложение.

Девять месяцев спустя я получил электронное письмо — «ошибка, о которой вы сообщили, скоро будет исправлена»! И через три месяца я получил еще одно электронное письмо с объявлением о новой версии их SDK с исправленной ошибкой, о которой вы сообщали.

Вот выводы, которые я сделал из этого опыта:

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