Обсуждение

Обсуждение

Многие приложения, ежедневно поступающие на рынок App Store, обладают возможностями соединения с теми или иными серверами. Некоторые выбирают с сервера данные для обновления, другие отсылают информацию на сервер и т. д. В течение долгого времени в iOS существовал лишь один способ обновлять контент в фоновом режиме. Требовалось «занять» у iOS некоторое количество времени (об этом мы говорили в разделе 14.2), и приложение могло потратить это время на завершение своей работы в фоновом режиме. Но такой способ работы является активным. Существует и пассивный способ решения аналогичных задач, когда приложение просто «сидит», а iOS сама выделяет приложению некоторое время на обработку данных в фоновом режиме. Итак, вам требуется просто подключить к приложению такую возможность и приказать системе iOS разбудить ваше приложение в относительно спокойный момент, когда будет удобно обработать данные в фоновом режиме. Обычно при этом происходит фоновое обновление информации.

Например, вам может потребоваться загрузить новый контент. Предположим, у нас есть приложение для работы с Twitter. Открывая это приложение, пользователь в первую очередь хочет просмотреть новые твиты. До недавнего времени это можно было реализовать лишь одним способом: открыть приложение, а потом потратить некоторое время на обновление списка твитов. Но теперь система iOS может активизировать твиттерное приложение в фоновом режиме и приказать ему обновить ленту сообщений. Таким образом, когда пользователь откроет это приложение, твиты на экране уже будут обновлены.

Чтобы задействовать в приложении возможность фонового обновления, нужно перейти на вкладку Capabilities (Возможности) в настройках вашего проекта, а в области Background Modes (Фоновые режимы) установить флажок Background fetch (Фоновое обновление) (рис. 14.1).

Рис. 14.1. Активизация фонового обновления в приложении

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

Для этого нужно вызвать метод экземпляра setMinimumBackgroundFetchInterval:, относящийся к классу UIApplication. В качестве параметра этому методу передаются временной интервал и частота, с которой iOS должна будить ваше приложение в фоновом режиме и приказывать ему получать новые данные для обновления. По умолчанию это свойство имеет значение UIApplicationBackgroundFetchIntervalNever. При таком значении iOS вообще не активизирует ваше приложение в фоновом режиме. Но значение этого свойства можно установить вручную, сообщив количество секунд, образующих интервал, либо просто передать значение UIApplicationBackgroundFetchIntervalMinimum, при котором iOS будет «прилагать минимальные усилия» — будить ваше приложение, но делать это очень редко.

— (BOOL) application:(UIApplication *)application

didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{

[application setMinimumBackgroundFetchInterval:

UIApplicationBackgroundFetchIntervalMinimum];

return YES;

}

Сделав это, реализуйте метод экземпляра application: performFetchWithCompletionHandler, относящийся к делегату вашего приложения. Параметр performFetchWithCompletionHandler: этого метода дает нам блоковый объект, который нужно будет вызвать, как только приложение закончит обновлять данные. Вообще этот метод вызывается в делегате приложения, когда iOS приказывает приложению получить в фоновом режиме новый контент для обновления. Поэтому вам придется отреагировать на это и вызвать обработчик завершения, как только все будет готово. Блоковый объект, который вам потребуется вызвать, будет принимать значение типа UIBackgroundFetchResult:

typedef NS_ENUM(NSUInteger, UIBackgroundFetchResult) {

UIBackgroundFetchResultNewData,

UIBackgroundFetchResultNoData,

UIBackgroundFetchResultFailed

} NS_ENUM_AVAILABLE_IOS(7_0);

Итак, если iOS приказывает вашему приложению обновить контент новыми данными, вы пытаетесь получить эти данные, но их в наличии не оказывается, то вам придется вызвать обработчик завершения и передать ему значение UIBackgroundFetchResultNoData. Так вы сообщите iOS, что для обновления информации вашего приложения не нашлось новых данных, и система сможет откорректировать свой алгоритм планирования и механизмы искусственного интеллекта. В результате система станет вызывать приложение не столь часто. iOS действительно очень толково справляется с такими задачами. Допустим, вы приказываете iOS вызвать приложение в фоновом режиме, чтобы оно могло получить новый контент. Но сервер не дает приложению каких-либо обновлений, и в течение целой недели приложение активизируется на пользовательском устройстве в фоновом режиме, но так и не может получить новых данных и неизменно передает блоку завершения вышеупомянутого метода значение UIBackgroundFetchResultNoData. В таком случае совершенно очевидно, что iOS стоит будить это приложение не так часто. Так можно будет более экономно расходовать вычислительную мощность и, соответственно, заряд батареи.

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

#import <Foundation/Foundation.h>

@interface NewsItem: NSObject

@property (nonatomic, strong) NSDate *date;

@property (nonatomic, copy) NSString *text;

@end

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

#import <UIKit/UIKit.h>

@interface AppDelegate: UIResponder <UIApplicationDelegate>

@property (nonatomic, strong) UIWindow *window;

@property (nonatomic, strong) NSMutableArray *allNewsItems;

@end

Теперь выполним отложенное выделение массива. Это означает, что массив не будет выделяться и инициализироваться до тех пор, пока приложение прямо к нему не обратится. Так экономится память. Как только массив будет выделен, мы добавим к нему один новый элемент:

#import «AppDelegate.h»

#import «NewsItem.h»

@implementation AppDelegate

— (NSMutableArray *) allNewsItems{

if (_allNewsItems == nil){

_allNewsItems = [[NSMutableArray alloc] init];

/* Заранее записываем в массив один элемент */

NewsItem *item = [[NewsItem alloc] init];

item.date = [NSDate date];

item.text = [NSString stringWithFormat:@"News text 1"];

[_allNewsItems addObject: item];

}

return _allNewsItems;

}

<# Остаток кода делегата вашего приложения находится здесь #>

Теперь реализуем в нашем приложении метод, который будет имитировать вызов сервера. Можно сказать, что здесь мы играем в орлянку. Точнее, метод случайным образом генерирует одно из двух чисел — 0 или 1. Получив 1, мы считаем, что на сервере есть новые новостные материалы для загрузки. Получив 0, считаем, что такая новая информация на сервере отсутствует. Если мы получили 1, то сразу после этого добавляем в список новый элемент:

— (void) fetchNewsItems:(BOOL *)paramFetchedNewItems{

if (arc4random_uniform(2)!= 1){

if (paramFetchedNewItems!= nil){

*paramFetchedNewItems = NO;

}

return;

}

[self willChangeValueForKey:@"allNewsItems"];

/* Генерируем новый элемент */

NewsItem *item = [[NewsItem alloc] init];

item.date = [NSDate date];

item.text = [NSString stringWithFormat:@"News text %lu",

(unsigned long)self.allNewsItems.count + 1];

[self.allNewsItems addObject: item];

if (paramFetchedNewItems!= nil){

*paramFetchedNewItems = YES;

}

[self didChangeValueForKey:@"allNewsItems"];

}

Логический параметр указателя этого метода сообщит нам, появилась ли новая информация, добавленная в массив.

Теперь реализуем механизм фонового обновления в делегате нашего приложения, так, как было объяснено ранее:

— (void) application:(UIApplication *)application

performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))

completionHandler{

BOOL haveNewContent = NO;

[self fetchNewsItems:&haveNewContent];

if (haveNewContent){

completionHandler(UIBackgroundFetchResultNewData);

} else {

completionHandler(UIBackgroundFetchResultNoData);

}

}

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

#import «TableViewController.h»

#import «AppDelegate.h»

#import «NewsItem.h»

@interface TableViewController ()

@property (nonatomic, weak) NSArray *allNewsItems;

@property (nonatomic, unsafe_unretained) BOOL mustReloadView;

@end

@implementation TableViewController

— (void)viewDidLoad{

[super viewDidLoad];

AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;

self.allNewsItems = appDelegate.allNewsItems;

[appDelegate addObserver: self

forKeyPath:@"allNewsItems"

options: NSKeyValueObservingOptionNew

context: NULL];

[[NSNotificationCenter defaultCenter]

addObserver: self

selector:@selector(handleAppIsBroughtToForeground:)

name: UIApplicationWillEnterForegroundNotification

object: nil];

}

— (void) observeValueForKeyPath:(NSString *)keyPath

ofObject:(id)object

change:(NSDictionary *)change

context:(void *)context{

if ([keyPath isEqualToString:@"allNewsItems"]){

if ([self isBeingPresented]){

[self.tableView reloadData];

} else {

self.mustReloadView = YES;

}

}

}

— (void) handleAppIsBroughtToForeground:(NSNotification *)paramNotification{

if (self.mustReloadView){

self.mustReloadView = NO;

[self.tableView reloadData];

}

}

Наконец, потребуется написать необходимые методы источника данных нашего табличного вида, позволяющие записывать новые элементы в табличный вид:

— (NSInteger)tableView:(UITableView *)tableView

numberOfRowsInSection:(NSInteger)section{

return self.allNewsItems.count;

}

— (UITableViewCell *)tableView:(UITableView *)tableView

cellForRowAtIndexPath:(NSIndexPath *)indexPath{

static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView

dequeueReusableCellWithIdentifier: CellIdentifier

forIndexPath: indexPath];

NewsItem *newsItem = self.allNewsItems[indexPath.row];

cell.textLabel.text = newsItem.text;

return cell;

}

— (void) dealloc{

AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;

[appDelegate removeObserver: self forKeyPath:@"allNewsItems"];

[[NSNotificationCenter defaultCenter] removeObserver: self];

}

В данном примере кода мы извлекаем из очереди те ячейки табличного вида, которые имеют идентификатор Cell. Метод dequeueReusableCellWithIdentifier: forIndexPath: нашего табличного вида возвращает валидные ячейки, а не nil, потому что в файле раскадровки мы уже задали этот идентификатор для прототипа ячейки в табличном виде. Во время исполнения раскадровка регистрирует для iOS эту ячейку-прототип с заданным идентификатором. Поэтому мы можем извлекать ячейки из очереди, просто опираясь на данный идентификатор, и не регистрируем ячейки заранее.

Табличные виды подробно рассмотрены в главе 4.

Теперь запустите ваше приложение и нажмите кнопку Home (Главная), чтобы перевести ваше приложение в фоновый режим. Вернитесь в Xcode и в меню Debug (Отладка) выберите Simulate Background Fetch (Имитировать обновление в фоновом режиме) (рис. 14.2). Теперь вновь откройте приложение, не завершая его, и посмотрите, появится ли в табличном виде новый контент. Если не появится — то именно по той причине, что запрограммированная нами логика напоминает игру в орлянку. Приложение случайным образом «определяет», есть ли на «сервере» новый контент. Так имитируются вызовы сервера. Если не получите никакого «нового контента», просто повторите имитацию фонового обновления в меню Debug (Отладка), пока «информация» не будет «получена».

Рис. 14.2. Имитация фонового обновления в Xcode

До сих пор мы обрабатывали в системе iOS запросы на фоновое обновление, пока приложение находилось в фоновом режиме. Но что, если работа приложения полностью завершена и фонового режима в данный момент не существует? Как нам сымитировать описанную ситуацию и определить, сработает ли наша программа? Оказывается, Apple уже и об этом позаботилась. Выберите пункт Manage Schemes (Управление схемами) в меню Product (Продукт) в Xcode. Здесь скопируйте основную схему вашего приложения, нажав кнопку с плюсиком, а затем установив флажок Duplicate Scheme (Дублировать схему) (рис. 14.3).

Рис. 14.3. Дублирование схемы для обеспечения имитации фонового обновления в период, когда приложение не работает даже в фоновом режиме

Теперь перед вами откроется новое диалоговое окно, примерно такое, как на рис. 14.4. Здесь будет предложено установить различные свойства новой схемы. В этом диалоговом окне установите флажок Launch due to a background fetch event (Запуск, обусловленный событием фонового обновления данных), а потом нажмите ОК.

Рис. 14.4. Активизация схемы для запуска приложения с целью фонового обновления

Итак, теперь в Xcode записаны две схемы для приложения (рис. 14.5). Чтобы активизировать приложение с целью фонового обновления, вам просто понадобится выбрать только что созданную вторую схему и запустить приложение в симуляторе или на устройстве. В таком случае ваше приложение не перейдет в приоритетный режим. Вместо этого будет выдан сигнал для обновления данных в фоновом режиме, который, в свою очередь, вызовет метод application: performFetchWithCompletionHandler: делегата нашего приложения. Если вы правильно выполнили все шаги, описанные в этом разделе, то в обоих сценариях у вас будет полностью работоспособное приложение: и когда iOS вызывает программу из фонового режима, и когда приложение запускается с нуля, только для фонового обновления.

Рис. 14.5. Использование новой схемы для запуска вашего приложения и имитации фонового обновления данных

Данный текст является ознакомительным фрагментом.



Поделитесь на страничке

Следующая глава >

Похожие главы из других книг:

Обсуждение

Из книги автора

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


Обсуждение

Из книги автора

Обсуждение Фреймворк Assets Library — удобный посредник между разработчиком и библиотекой фотографий. Как будет указано в разделе 13.6, в iOS SDK вам предоставляются встроенные компоненты графического пользовательского интерфейса, которыми можно пользоваться для доступа к


Обсуждение

Из книги автора

Обсуждение Чтобы пользователь мог выбирать фотоснимки или видеоролики из своей библиотеки фотографий, необходимо установить свойство sourceType экземпляра UIImagePickerController в значение UIImagePickerControllerSourceTypePhotoLibrary и только потом открывать перед пользователем инструмент для выбора


Обсуждение

Из книги автора

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


Обсуждение

Из книги автора

Обсуждение Класс UIVideoEditorController, содержащийся в iOS SDK, позволяет программисту вывести на экран перед пользователем специальный интерфейс для редактирования. Все, что требуется сделать, — предоставить URL видеоролика, который предполагается отредактировать, а потом


Обсуждение

Из книги автора

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


Обсуждение

Из книги автора

Обсуждение Когда приложение переходит в фоновый режим, работа его основного потока приостанавливается. Потоки, которые вы создаете в своем приложении с помощью метода класса detachNewThreadSelector: toTarget: withObject:, относящегося к классу NSThread, также приостанавливаются. Если вы


Обсуждение

Из книги автора

Обсуждение Многие приложения, ежедневно поступающие на рынок App Store, обладают возможностями соединения с теми или иными серверами. Некоторые выбирают с сервера данные для обновления, другие отсылают информацию на сервер и т. д. В течение долгого времени в iOS существовал


Обсуждение

Из книги автора

Обсуждение В iOS приложение может запросить продолжить воспроизведение своих аудиофайлов, даже если оно само переходит в фоновый режим. В этом разделе мы воспользуемся плеером AVAudioPlayer, который прост и удобен в обращении. Наша задача — запустить аудиоплеер и воспроизвести


Обсуждение

Из книги автора

Обсуждение Когда приложение работает в приоритетном режиме, можно получать от экземпляра CLLocationManager делегатные сообщения, информирующие вас о том, что iOS обнаружила перемещение устройства на новое место. Однако если приложение переходит в фоновый режим и становится


Обсуждение

Из книги автора

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


Обсуждение

Из книги автора

Обсуждение При работе с приложениями, которые используют класс NSURLConnection, но, уходя в фоновый режим, не запрашивают у iOS дополнительного времени, обращаться с соединениями не составляет никакого труда. Рассмотрим на примере, как будет действовать асинхронное соединение,


Обсуждение

Из книги автора

Обсуждение Пока ваше приложение работает в фоновом режиме, может произойти многое! Например, пользователь может вдруг изменить локализацию устройства с iOS на странице Settings (Настройки) и задать, к примеру, испанский язык вместо английского. Приложения могут


Обсуждение

Из книги автора

Обсуждение В приложениях, написанных для iOS, файл пакета настроек может быть предоставлен пользователю для внесения собственных настроек. Эти настройки будут доступны пользователю в приложении (Settings) на устройстве. Чтобы лучше понять, как работает этот механизм,


Обсуждение

Из книги автора

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