- PVSM.RU - https://www.pvsm.ru -

Вы когда-нибудь замечали, что какое-нибудь из ваших любимых приложений для iOS после очередного обновления перестало работать или же запуск его затягивается на полминуты? Обычно после этого их разработчики выпускают срочный багфикс. И это не всегда связано с багами в коде конечного программиста, иногда проблема лежит чуть глубже.
Мне кажется довольно странным, что эта ошибка возникает довольно часто (и должна возникать в «серьезных» проектах), но о ней почему-то умалчивают.
В этой статье речь пойдет о стандартной ошибке при инициализации CoreData-стека в iOS-приложениях.
Фреймворк CoreData — это мощное средство в руках Cocoa–разработчиков, почти бесплатная персистентность, легкость изменения данных, записи, поддержка версий, миграция с модели на модель — все то, что так часто необходимо в наших проектах. Кто-то использует его в readonly mode, кто-то сохраняет совсем чуть-чуть и работает с такой маленькой выборкой, ну а кто-то же использует его на полную катушку.
Скажу сразу, для того, чтобы встретиться с ошибкой, нужно собрать три причины: большой объем базы, инициализация стека CoreData в главной потоке, изменения схемы базы данных в новой версии вашей программы. Давайте рассмотрим, как легко все их можно собрать как разработчику.

При использовании «на полную» часто бывает так, что размер базы искусственно ничем не ограничен и может легко занимать и гигабайт.
За примерами последних долго ходить не надо. В интернете до сих пор люди задаются вопросом, можно ли хранить картинки в базе данных (кстати, в этой статье CoreData и база данных будут почти синонимами). Довольно популярные ответы, имеющие одобрение Stackoverflow-аудитории говорят о том, что до картинки до мегабайта хранить можно смело в базе данных. Например, тут stackoverflow.com/questions/2573072/coredata-store-images-to-db-or-not [1]
Ответ выглядит так:
< 100kb store in the same table as the relevant data
< 1mb store in a separate table attached via a relationship to avoid loading unnecessarily
> 1mb store on disk and reference it inside of Core Data
Или же ваше приложение может хранить вашу активную переписку пользователя за последние три года (мессенджер, почтовый клиент и пр).
С причинами больших объемов разобрались.
Ну тут разве могут быть сомнения? Думаю, процентов 100% новичков и точно процентов 70% разработчиков по-опытнее инициализируют весь стек CoreData в главном потоке выполнения программы.
Обычно при изменеии схемы базы данных (модели) СoreData переносит данные из старой базы в новую, если вы задали соответствующие правила. Самая простая т.н. легковесная миграция делается просто, вам нужно передать словарь опций при подключении хранилища к NSPersistentStoreCoordinator:
NSDictionary *optionsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
[_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:optionsDictionary error:&error];
Если перенос не укладывается в рамки легковесной миграции, то разработчики реализуют свою кастомную, но суть причины №3 не меняется.
Причины собраны. Довольно просто их собрать, не правда ли? Ну вот так же просто эти причины собирают многие разработчики, которые один раз вставили код инициализации на заре становления своего проекта и благополучно забыли про него до тех пор пока не увидели в Itunes что-то типа такого:
Вылетает на старте, перед этим долго тупит!
203 из 203 покупателей считают эту рецензию полезной
Думаю, все эти причины наталкивают вас на простой вывод. И он верен! Даже простое копирование данных про миграции на новую версию базы данных занимает время, и чем больше занимает файл базы данных, тем больше времени требуется. И мы говорим про простое копирование, а если нужно переиндексировать поля, например?
Узкое место было уже упомянуто — это подключение персистентного хранилища к объекту NSPersistentStoreCoordinator. Именно тут собранные воедино причины создают проблему. И если ваше приложение не отзывается 30 секунд, то система его закрывает.
На наше счастье создавать NSPersistentStoreCoordinator и подключать к нему хранилище можно в другом потоке. И на момент инициализации данных хороший тон — показать какое-нибудь окошко с надписью «Обновление данных», например.
Вот как это будет выглядеть в коде (сразу скажу, решение не претендует на звание идеального, если кто-то придумает лучше, то пишите в комментариях).
// Выносим весь код инициализации GUI из этого метода
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.launchOptions = launchOptions;
[self performSelectorInBackground:@selector(initCoreData) withObject:nil];
[NSThread sleepForTimeInterval:0.2]; // время, в течение которого может пройти подключение хранилище, если никаких изменений нет. Дешевое средство избегания ненужных миганий на экране
[self initialDisplayGUI];
return YES;
}
- (void)initialDisplayGUI {
if (self.dataIsReady) {
[self diplayAllGUIStuff];
} else {
self.dataPrepareController = [[MigrationProgressViewController alloc] init];
[dataPrepareController setDoneTarget:self withAction:@selector(diplayAllGUIStuff)];
dataPrepareController.view.frame = window.frame;
[window addSubview:dataPrepareController.view];
[window makeKeyAndVisible];
}
}
- (void)initCoreData {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if (self.persistentStoreCoordinator) {
NSLog(@"Storage was added");
}
self.dataIsReady = YES;
[pool release];
}
- (void)setDataIsReady:(BOOL)dataIsReady {
if (_dataIsReady != dataIsReady) {
_dataIsReady = dataIsReady;
[self performSelectorOnMainThread:@selector(diplayAllGUIStuff) withObject:nil waitUntilDone:NO];
}
}
-diplayAllGUIStuff метод, который содержит код, который был у вас в — (BOOL)application:didFinishLaunchingWithOptions:
Контроллер MigrationProgressViewController нужен для отображения, например, индикаторов оставшегося времени или хотя бы показывал, что процесс не повис. Единственная его задача — успокоить пользователя. Пользователям приятнее смотреть пусть даже на «голый» UIActivityIndicatorView, чем на повисший экран заставки и тем более иметь на руках вылетающее приложение.
Это, пожалуй, все. Избегайте подобных стечений обстоятельств и почаще пересматривайте код, особенно тот, что вставил за вас Xcode.
Автор: Silf
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/ios-development/12836
Ссылки в тексте:
[1] stackoverflow.com/questions/2573072/coredata-store-images-to-db-or-not: http://stackoverflow.com/questions/2573072/coredata-store-images-to-db-or-not
Нажмите здесь для печати.