Многие мобильные приложения показывают пользователю контент с сервера, и этот контент можно показывать в онлайне и оффлайне. Работа в онлайне тривиальна — при определенном UI событии, приложение читает данные с сети и показывает их пользователю. Работа в оффлайне может быть гораздо интереснее — возможность работы с документами в метро и тп. Но работа вне сети приносит и проблемы: теперь необходимо проводить синхронизацию данных и этот процесс не должен блокировать интерфейс пользователя.

Элементы в исходном коде


— доступ к сетевым ресурсам
— обработка xml
— доступ к файловой системе
— работа с потоками

Требования к приложению


Приложение должно показывать текстовые файлы с сервера в режиме оффлаин. При каждом старте происходит синхронизация. При этом процесс обновления не должен блокировать работу пользователя с интерфейсом. Сам процесс обновления состоит из двух шагов:
1. Чтение списка файлов с сервера
2. Загрузка отсутствующих файлов

Дизайн кода


Для управления всем процессом мы создадим класс UpdateManager, который будет управлять объектами «Updaters». На данный момент нам надо два «Updater'а»: один для чтения списка файлов и второй для работы с файлами. Для них определим единый фасад, что позволит расширять систему в будущем. Этот фасад будет иметь как минимум один метод — start — который будет вызваться UpdateManager'ом для каждого Updater'а по-очереди.

Мы заранее знаем, что будем использовать асинхронное соединение для доступа к сети. Это вынуждает нас явно продолжать работу UpdateManager'а после завершения работы каждого Updater'а.
Объявим два протокола:

@protocol UpdateManagerProtocol -(void)next;
end

@protocol UpdaterProtocol -(void)startUpdate:(id ) manager;
end

UpdateManagerProtocol объявляет один метод, который вызывается каждым Updater'ом по завершению работы.

Наши классы выглядит так:
Class Diagram

Все Updater'ы работают одинаково:
Seq diagram

XMLListUpdater выполняет шаги:
1. Читает xml файл с сервера в буфер
2. Разбирает xml
3. Добавляет каждый файл в очередь

FileUpdater выполняет шаги:
1. Получает следующий файл из очереди
2. Проверяет, если файл уже существует на диске
3. Скачивает файл
4. Повторяе�� процесс, если очередь не пуста

Исходный код


Для начала напишем код, без упоминания потоков.

UpdateManager.h объявляет один статический метод для старта всего процесса. В конструкторе (init) инстанса происходит создание всех Updater'ов, добавление их в очередь и вызов одного за другим.

Так как каждый Updater читает данные с сети, то общий код можно вынести в отдельный класс — NetworkClient. Он имплементирует UpdaterProtocol вместе с методом для запуска асинхронного соединения (startNetworkCall).

Первый Updater — XMLFileUpdater. При старте, он читает xml в память с заранее известного адреса. По завершению, XMLListUpdater создает xml парсер для обработки данных. Каждый файл из списка добавляется в очередь для обработки следующим Updater'ом.

Второй шаг обновления контента FilesUpdater — он должен прочитать очередь и скачать каждый отсутствующий файл.

Теперь мы можем стартовать процесс UpdateManager, при загрузке главного view — и приложение синхронизирует контент.

View содержит только одну кнопку, без каких либо действий. При обновлении контента интерфейс будет блокироваться и нажатие на кнопку это выявит. Позже мы избавимся от блокировки добавив новый поток.

Добавление отдельного потока


Так как у нас уже есть весь код работы с данными, то нам остается запустить отдельный поток и в нем выполнить обновление.
Добавим новый метод в UpdateManager — startInThread. С простыми шагами:
1. Создать NSAutoReleasePool
2. Запустить процесс обновления
3. Запустить RunLoop
4. Освободить pool

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

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

Теперь инициализацию UpdateManager можно передвинуть в main.m.

Замечания по исходному коду


UpdateManager.h содержит директивы компиляции — WORK_IN_SEPARATE_THREAD. Если она установлена в ноль, то новый поток не будет создаваться и UI будет блокироваться. При единице, обновление будет происходить в отдельном потоке

Исходный текст проекта: SF.net

Andrew Romanenco
andrew@romanenco.com
April, 2010