Практический в каждом приложении требуется манипулировать какими либо данными, сохранять, загружать, фильтровать или искать. Чаще всего для этого используются фреймворки, работающие по принципу ORM, т.к. в объектно-ориентированном окружении это делать проще и удобнее. В таких системах разработчик может работать с данными, как с привычными объектами, использовать их напрямую в качестве модели данных (в паттернах типа MVC). Для разработчиков iOS приложений уже доступен встроенный фреймворк Core Data, который можно использовать для хранения и манипулирования данными. Но среди сторонних альтернатив тоже есть популярные реализации, одна из них Realm. В этой статье мы постараемся сравнить эти два решения, увидим, что у них общего, в чем различия и посмотрим на быстродействие. Возможно этот обзор поможет вам определиться с выбором.
Синтаксис и базовые операции
Особенности реализации
Быстродействие
Выводы
Core Data
Для работы с CD в первую очередь необходимо создать специальный файл модели, где будут описаны типы хранимых данных. Для редактирования этого файла в Xcode есть специальный интерфейс.
После создания модели, нужно загрузить ее с помощью класса NSManagedObjectModel. Затем нужно создать объект класса NSPersistentStoreCoordinator, указав объект модели. После этого мы должны добавить хранилище, указав путь к файлу и способ хранения данных (в нашем случае мы будем использовать SQLite хранилище). И наконец мы создаем объект класса NSManagedObjectContext, с помощью которого и будем производить все манипуляции с данными. Сделаем для настройки Core Data специальный класс и будем использовать его:
Справедливости ради, можно сказать, что в iOS 10 появился класс NSPersistentContainer, который немного упрощает настройку и берет на себя создание объектов модели, координатора и контекстов. Мы могли бы использовать его, вместо нашего собственного класса CoreDataStack.
Realm
Аналогом NSManagedObjectContext здесь выступает класс RLMRealm, но для начала работы с ним не требуется никаких предварительных действий, экземпляр класса можно получить, вызвав метод [RLMRealm defaultRealm] в любом месте в коде приложения.
Далее для каждого entity нужно написать соответствующий класс, наследуемый от NSManagedObject, который будет использоваться в приложении в качестве элемента данных. Это можно сделать вручную, либо воспользоваться встроенным в Xcode генератором. В нашем примере мы сделаем это вручную, чтобы не нагромождать проект. У нас будет два entity, поэтому сделаем два класса.
Создание объекта:
Создание объекта:
Core Data
Для отслеживания изменений полей конкретного объекта можно использовать стандартный механизм KVO:
Realm
Для отслеживания изменений полей объектов и выборок используется одинаковый механизм. В обоих случаях с помощью метода addNotificationBlock: можно добавить блок, который будет срабатывать при изменениях. В ответ метод возвращает специальный объект-токен, который нужно хранить в памяти. Блок будет вызываться, пока токен существует.
Проведем несколько тестов на скорость выполнения основных операций, таких как сохранение новых объектов, обновление существующих и поиск. Мы не будем ставить своей целью проводить всесторонний тест с учетом всех возможных конфигураций, а просто взглянем, что мы получаем при использовании в самых обычных ситуациях. При желании, вы можете повторить или модифицировать тесты, ссылка на исходный код будет в конце статьи.
Все сравнительные замеры будут производиться на идентичных данных в равных условиях, на одном устройстве. На каждый тест будем делать по 5 запусков подряд. Перед каждым тестом
приложение перезапускается. Данных в тестах будет больше, чем обычно используют большинство реальных приложений, чтобы лучше видеть разницу измерений.
На всех диаграммах по оси Х указан порядковый номер измерения, по оси Y результаты измерений в секундах.
Создание 500000 объектов
В этом тесте мы создаем 50 тысяч объектов и для каждого создаем 10 объектов другого типа, связанных с ним.
Здесь стоит отметить, что Core Data выдержала 4 запуска теста, т.е. было создано всего 2000000 объектов, после чего приложение было завершено из-за нехватки памяти. Realm выдержал только 3 запуска, т.е. было создано 1500000 объектов.
Обновление 50000 объектов
В этом тесте мы запрашиваем все 50000 объектов, обновляем значение одного поля и сохраняем изменения
Выборка всех объектов
В этом тесте мы запрашиваем все 50000 объектов.
Выборка всех объектов и запрос значений всех полей
В этом тесте мы запрашиваем все 50000 объектов, и проходя по всем объектам из выборки, обращаемся ко всем полям с данными.
Выборка с использованием предиката (фильтр по числовому полю)
В этом тесте под выборку попадает половина имеющихся данных. Данные фильтруются с помощью предиката по полю числового типа.
Примечение: т.к. запросы в Realm "ленивые", в измерение входит обращение к полю .count выборки, чтобы заставить запрос выполниться. Кроме того, в целях оптимизации Realm не отдает сразу данные объектов, поэтому в подобных тестах добавлено еще одно измерение, включающее проход по выборке и обращение к полям объекта.
Выборка с использованием предиката (фильтр по строковому полю)
В этом тесте под выборку попадает половина имеющихся данных. Данные фильтруются с помощью предиката по полю строкового типа.
Примечение: т.к. запросы в Realm "ленивые", в измерение входит обращение к полю .count выборки, чтобы заставить запрос выполниться. Кроме того, в целях оптимизации Realm не отдает сразу данные объектов, поэтому в подобных тестах добавлено еще одно измерение, включающее проход по выборке и обращение к полям объекта.
Выборка с использованием предиката (используя данные из связанных объектов)
В этом тесте под выборку попадают почти все имеющиеся данные. Данные фильтруются с помощью предиката, используя значения из объектов другого типа, с которыми есть связь вида "один ко многим".
Примечение: т.к. запросы в Realm "ленивые", в измерение входит обращение к полю .count выборки, чтобы заставить запрос выполниться. Кроме того, в целях оптимизации Realm не отдает сразу данные объектов, поэтому в подобных тестах добавлено еще одно измерение, включающее проход по выборке и обращение к полям объекта.
Выборка с использованием текстового поиска (строковое поле)
В этом тесте под выборку попадают все имеющиеся данные. Данные фильтруются с помощью предиката CONTAINS[cd] по полю строкового типа. Значение поля - короткий строковый идентификатор.
Примечение: т.к. запросы в Realm "ленивые", в измерение входит обращение к полю .count выборки, чтобы заставить запрос выполниться. Кроме того, в целях оптимизации Realm не отдает сразу данные объектов, поэтому в подобных тестах добавлено еще одно измерение, включающее проход по выборке и обращение к полям объекта.
Выборка с использованием текстового поиска (строковое поле)
В этом тесте под выборку попадают все имеющиеся данные. Данные фильтруются с помощью предиката CONTAINS[cd] по полю строкового типа. Значение поля - фрагмент текста ~3Кб.
Примечение: т.к. запросы в Realm "ленивые", в измерение входит обращение к полю .count выборки, чтобы заставить запрос выполниться. Кроме того, в целях оптимизации Realm не отдает сразу данные объектов, поэтому в подобных тестах добавлено еще одно измерение, включающее проход по выборке и обращение к полям объекта.
Какой фреймворк для своего проекта выбрать, каждый решает сам, в зависимости от его особенностей и задач. С одной стороны Core Data доступен "из коробки", не увеличивает размер приложения, достаточно стабилен и умеет быстро выполнять текстовый поиск, а хранилище представляет собой один из стандартных форматов данных. С другой стороны, Realm более лаконичный, удобный и современный, в большинстве случаев работает быстрее и поддерживает синхронизацию данных и шифрование, к тому же, еще доступен на многих платформах. Несомненно, мы рекомендуем ознакомится и с Realm, и с Core Data, т.к. оба инструмента пригодятся для решения ваших задач.
Пообщаться на тему Core Data и Realm, выразить свое мнение или оспорить вышесказанное можно в нашей группе в Telegram.
Исходный код
Core Data Programming Guide
Realm Documentation
Наш Twitter https://twitter.com/ios_fathers
Наша группа в Telegram https://t.me/joinchat/EK6aXwxr0hj7rAc4Z1NiKw или @ios_fathers
Содержание
Особенности реализации
Быстродействие
Выводы
Синтаксис и базовые операции
1. Инициализация
Рассмотрим минимально необходимый набор действий и код для настройки и дальнейшего использования хранилища данных.Core Data
Для работы с CD в первую очередь необходимо создать специальный файл модели, где будут описаны типы хранимых данных. Для редактирования этого файла в Xcode есть специальный интерфейс.
После создания модели, нужно загрузить ее с помощью класса NSManagedObjectModel. Затем нужно создать объект класса NSPersistentStoreCoordinator, указав объект модели. После этого мы должны добавить хранилище, указав путь к файлу и способ хранения данных (в нашем случае мы будем использовать SQLite хранилище). И наконец мы создаем объект класса NSManagedObjectContext, с помощью которого и будем производить все манипуляции с данными. Сделаем для настройки Core Data специальный класс и будем использовать его:
#import "CoreDataStack.h"
@interface CoreDataStack()
@property (nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
@property (nonatomic) NSManagedObjectModel *managedObjectModel;
@property (nonatomic, readwrite) NSManagedObjectContext *managedObjectContext;
@end
@implementation CoreDataStack
-(NSPersistentStoreCoordinator *) persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil)
return _persistentStoreCoordinator;
NSURL *storeDirectoryURL = [[[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
storeDirectoryURL = [storeDirectoryURL URLByAppendingPathComponent:[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"]];
[[NSFileManager defaultManager] createDirectoryAtURL:storeDirectoryURL withIntermediateDirectories:YES attributes:nil error:nil];
NSURL *storeURL = [storeDirectoryURL URLByAppendingPathComponent:@"data.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:0 error:&error])
{
NSLog(@"NSPersistentStoreCoordinator persistent store initialization error %@", error);
_persistentStoreCoordinator = nil;
}
return _persistentStoreCoordinator;
}
-(NSManagedObjectModel *) managedObjectModel
{
if (_managedObjectModel != nil)
return _managedObjectModel;
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"data" withExtension:@"momd"];
_managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return _managedObjectModel;
}
-(NSManagedObjectContext *) managedObjectContext
{
if (_managedObjectContext != nil)
return _managedObjectContext;
NSPersistentStoreCoordinator *coordinator = self.persistentStoreCoordinator;
if (coordinator != nil)
{
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[_managedObjectContext setPersistentStoreCoordinator:coordinator];
}
return _managedObjectContext;
}
@end
Справедливости ради, можно сказать, что в iOS 10 появился класс NSPersistentContainer, который немного упрощает настройку и берет на себя создание объектов модели, координатора и контекстов. Мы могли бы использовать его, вместо нашего собственного класса CoreDataStack.
Realm
Аналогом NSManagedObjectContext здесь выступает класс RLMRealm, но для начала работы с ним не требуется никаких предварительных действий, экземпляр класса можно получить, вызвав метод [RLMRealm defaultRealm] в любом месте в коде приложения.
2. Создание модели
Рассмотрим процесс создания модели данных.Core Data
Во время инициализации стека Core Data мы рассмотрели создание файла модели и увидели интерфейс для ее редактирования. В этом интерфейсе мы создаем так называемые entity и задаем атрибуты, связи между entity. Кроме того, Xcode позволяет видеть нашу модель в виде диаграммы.Далее для каждого entity нужно написать соответствующий класс, наследуемый от NSManagedObject, который будет использоваться в приложении в качестве элемента данных. Это можно сделать вручную, либо воспользоваться встроенным в Xcode генератором. В нашем примере мы сделаем это вручную, чтобы не нагромождать проект. У нас будет два entity, поэтому сделаем два класса.
#import <CoreData/CoreData.h>
@class CDProject;
@interface CDSwifter : NSManagedObject
@property (nonatomic) int16_t age;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *level;
@property (nonatomic, copy) NSString *bio;
@property (nonatomic, retain) NSSet<CDProject*> *failedProjects;
@end
#import <CoreData/CoreData.h>
@class CDSwifter;
@interface CDProject : NSManagedObject
@property (nonatomic) int16_t bugs;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, retain) CDSwifter *swifter;
@end
Realm
Для описания модели достаточно создать класс, унаследованный от RLMObject и добавить в него необходимые property.#import <Realm/Realm.h>
#import "RMProject.h"
@interface RMSwifter : RLMObject
@property NSInteger age;
@property NSString *name;
@property NSString *level;
@property NSString *bio;
@property RLMArray<RMProject *><RMProject> *failedProjects;
@end
#import <Realm/Realm.h>
@interface RMProject : RLMObject
@property NSInteger bugs;
@property NSString *name;
@end
RLM_ARRAY_TYPE(RMProject)
3. Создание и сохранение объектов
Рассмотрим как создаются и сохраняются данные в обоих фреймворках.Core Data
Объекты создаются и изменяются в рамках NSManagedObjectContext, затем контекст сохраняется методом save: или изменения откатываются методом rollback.Создание объекта:
NSManagedObjectContext *context = ...;
CDSwifter *swifter = [NSEntityDescription insertNewObjectForEntityForName:@"CDSwifter" inManagedObjectContext:context];
swifter.name = @"Ivan Kholod";
swifter.level = kCDSwifterLevelBaby;
swifter.age = 12;
swifter.bio = @"NSBorrowKit owner";
Создание другого объекта и связи с первым объектом:CDProject *project = [NSEntityDescription insertNewObjectForEntityForName:@"CDProject" inManagedObjectContext:context];
project.name = @"NSBorrowKit";
project.bugs = 300;
project.swifter = swifter;
Сохранение данных контекста:NSError* error = nil;
if (![context save:nil])
{
NSLog(@"Failed to save NSManagedObjectContext: %@", error);
}
Realm
Пока объект не добавлен в RLMRealm, его можно использовать как любой другой объект в приложении. Добавление и последующее изменение делается только в рамках транзакции с помощью метода transactionWithBlock: либо методов beginWriteTransaction и commitWriteTransaction и их перегруженных аналогов.Создание объекта:
RMSwifter* swifter = [RMSwifter new];
swifter.name = @"Nerzh Woodcrust";
swifter.level = kRMSwifterLevelBaby;
swifter.age = 11;
swifter.bio = @"MacBook Pro 2017 lover";
Создание другого объекта и связи с первым объектом:RMProject* project = [RMProject new];
project.name = @"Unknown";
project.bugs = 1000;
[swifter.failedProjects addObject:project];
Сохранение данных:RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject:swifter];
}];
4. Запросы
Теперь рассмотрим как сохраненные данные можно получить и отфильтровать.Core Data
Получить все объекты CDSwifter:NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"CDSwifter"];
NSArray *result = [context executeFetchRequest:fetchRequest error:nil];
Получить подмножество объектов CDSwifter с age > 10:NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"CDSwifter"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"age > 10"];
NSArray *result = [context executeFetchRequest:fetchRequest error:nil];
Получить подмножество объектов CDSwifter, у которых среди failedProjects есть такие, у которых bugs > 100:NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"CDSwifter"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"ANY failedProjects.bugs > 100"];
NSArray *result = [context executeFetchRequest:fetchRequest error:nil];
То же самое, но с сортировкой по возрасту:NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"CDSwifter"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"ANY failedProjects.bugs > 100"];
fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"age" ascending:YES]];
NSArray *result = [context executeFetchRequest:fetchRequest error:nil];
Realm
Получить все объекты RMSwifter:RLMResults *result = [RMSwifter allObjects];
Получить подмножество объектов RMSwifter с age > 10:RLMResults *result = [RMSwifter objectsWhere:@"age > 10"];
Получить подмножество объектов RMSwifter, у которых среди проваленных проектов есть такие, у которых больше 100 багов:RLMResults *result = [RMSwifter objectsWhere:@"ANY failedProjects.bugs > 100"];
То же самое, но с сортировкой по возрасту:RLMResults *result = [[RMSwifter objectsWhere:@"ANY failedProjects.bugs > 100"] sortedResultsUsingKeyPath:@"age" ascending:YES];
Кроме того, Realm позволяет делать каскадные запросы, например:RLMResults *result = [[RMSwifter objectsWhere:@"level = %@", kRMSwifterLevelJunior] objectsWhere:@"age > 10"];
5. Отслеживание изменений
Важной частью работы с данными является отслеживание их изменений. Чаще всего это нужно для обновления интерфейса пользователя и уменьшения связности между различными частями кода приложения. Рассмотрим какие возможности дают нам оба инструмента.Core Data
Для отслеживания изменений полей конкретного объекта можно использовать стандартный механизм KVO:
-(void) subscribe
{
[self.swifter addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:NULL];
}
-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if (self.swifter == object)
{
if ([keyPath isEqualToString:@"age"])
{
NSLog(@".age value changed: %@", object);
}
}
}
Для отслеживания изменений в выборке можно использовать специальный класс NSFetchedResultsController. С помощью его делегата можно получать уведомления об изменении как набора объектов запроса (удаленные, добавленные, перемещенные объекты), так и о факте изменения конкретного объекта в выборке (но без информации о том, что изменилось).-(void) subscribe
{
NSManagedObjectContext *context = nil;
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"CDSwifter"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"age > 10"];
self.frc = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:nil cacheName:nil];
frc.delegate = self;
NSError* error = nil;
if (![self.frc performFetch:&error])
{
NSLog(@"Failed to fetch with FRC: %@", error);
}
}
#pragma mark - NSFetchedResultsControllerDelegate
-(void) controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
//...
}
-(void) controller:(NSFetchedResultsController *)controller didChangeSection:(id)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
//...
}
-(void) controllerWillChangeContent:(NSFetchedResultsController *)controller
{
//...
}
-(void) controllerDidChangeContent:(NSFetchedResultsController *)controller
{
//...
}
Realm
Для отслеживания изменений полей объектов и выборок используется одинаковый механизм. В обоих случаях с помощью метода addNotificationBlock: можно добавить блок, который будет срабатывать при изменениях. В ответ метод возвращает специальный объект-токен, который нужно хранить в памяти. Блок будет вызываться, пока токен существует.
-(void) subscribe
{
self.swifterUpdateToken = [self.swifter addNotificationBlock:^(BOOL deleted, NSArray<RLMPropertyChange *> *changes, NSError *error) {
for (RLMPropertyChange* change in changes)
{
NSLog(@"Value of property '%@' changed from '%@' to '%@'", change.name, change.previousValue, change.value);
}
}];
RLMResults *result = [RMProject objectsWhere:@"bugs > 100"];
self.projectsUpdateToken = [result addNotificationBlock:^(RLMResults *results, RLMCollectionChange *change, NSError *error) {
NSLog(@"Deletions: %@", change.deletions);
NSLog(@"Insertions: %@", change.insertions);
NSLog(@"Modifications: %@", change.modifications);
}];
}
Стандартный механизм KVO для отслеживания значений property у объектов тоже доступен и можно использовать его при желании.Особенности реализации
1. Управление объектами
Core Data: в рамках одного контекста один экземпляр entity представлен одним объектом в памяти, поэтому при соблюдении определенных условий объекты можно сравнивать на соответствие по указателю.
Realm: одной записи данных может соответствовать множество экземпляров класса RLMObject, поэтому объекты нет смысла сравнивать по указателю.
2. Запросы
Core Data: нет ленивых запросов; есть так называемые faults и relationship faults, т.е. частичная подгрузка данных entity и связанных entity в некоторых случаях, по мере необходимости; выборки статические и не меняются при изменении данных (не считая специального NSFetchedResultsController)
Realm: все запросы ленивые, реализованы собственные классы коллекций; данные подгружаются по мере необходимости; поддержка каскадных выборок; по умолчанию выборки и объекты автообновляемые и всегда соответствуют содержимому хранилища.
3. Сохранение и откат изменений
Core Data: все изменения объектов хранятся в рамках экземпляра NSManagedObjectContext; изменения передаются в родительский NSManagedObjectContext или NSPersistentStore во время операции save: или откатываются с помощью функций NSUndoManager у контекста.
Realm: все изменения объектов производятся в рамках транзакций и возможность отмены изменений существует только в рамках текущей транзакции.
3. Сохранение и откат изменений
Core Data: все изменения объектов хранятся в рамках экземпляра NSManagedObjectContext; изменения передаются в родительский NSManagedObjectContext или NSPersistentStore во время операции save: или откатываются с помощью функций NSUndoManager у контекста.
Realm: все изменения объектов производятся в рамках транзакций и возможность отмены изменений существует только в рамках текущей транзакции.
4. Индексы
Core Data: есть поддержка индексов, в iOS 11 появилось более продвинутое API для работы с ними, в том числе поддержка составных индексов.
Realm: есть поддержка индексов по атрибутам, primary key, нет составных индексов.
5. Наследование
Core Data: есть поддержка наследования классов в модели
Realm: нет поддержки наследования
5. Наследование
Core Data: есть поддержка наследования классов в модели
Realm: нет поддержки наследования
6. Шифрование
Core Data: нет встроенного шифрования
Realm: поддерживает встроенное прозрачное шифрование алгоритмом AES-256
7. Синхронизация
Core Data: есть синхронизация через iCloud между устройствами
Realm: есть синхронизация с сервером, авторизация
8. Многопоточность
Core Data: нельзя напрямую передавать данные между потоками; возможно создание дочерних контекстов, применение изменений дочернего контекста в родительский контекст; встроенная возможность выполнять операции с контекстом в private queue или main queue.
Realm: нельзя напрямую передавать данные между потоками; для каждого потока должен быть свой экземпляр RLMRealm; транзакции не блокируют операции чтения, но блокируют выполнение других транзакций; для корректной работы в многопоточной среде достаточно использовать метод [RLMRealm defaultRealm], который вернет собственный экземпляр RLMRealm для текущего потока.
Примечание: нужно быть осторожным при использовании Realm с private queue, т.к. код в рамках одной private queue может запускаться в контексте разных потоков, потому каждую операцию, запускаемую в private queue, нужно рассматривать, как отдельный поток, т.е. получать [RLMRealm defaultRealm] и запрашивать новые экземпляры данных.
9. Хранилище
Core Data: файл с данными можно просматривать стандартными инструментами.
Realm: для просмотра файла с данными есть свое приложение.
Быстродействие
Проведем несколько тестов на скорость выполнения основных операций, таких как сохранение новых объектов, обновление существующих и поиск. Мы не будем ставить своей целью проводить всесторонний тест с учетом всех возможных конфигураций, а просто взглянем, что мы получаем при использовании в самых обычных ситуациях. При желании, вы можете повторить или модифицировать тесты, ссылка на исходный код будет в конце статьи.
Все сравнительные замеры будут производиться на идентичных данных в равных условиях, на одном устройстве. На каждый тест будем делать по 5 запусков подряд. Перед каждым тестом
приложение перезапускается. Данных в тестах будет больше, чем обычно используют большинство реальных приложений, чтобы лучше видеть разницу измерений.
На всех диаграммах по оси Х указан порядковый номер измерения, по оси Y результаты измерений в секундах.
Создание 500000 объектов
В этом тесте мы создаем 50 тысяч объектов и для каждого создаем 10 объектов другого типа, связанных с ним.
Здесь стоит отметить, что Core Data выдержала 4 запуска теста, т.е. было создано всего 2000000 объектов, после чего приложение было завершено из-за нехватки памяти. Realm выдержал только 3 запуска, т.е. было создано 1500000 объектов.
Обновление 50000 объектов
В этом тесте мы запрашиваем все 50000 объектов, обновляем значение одного поля и сохраняем изменения
Выборка всех объектов
В этом тесте мы запрашиваем все 50000 объектов.
Выборка всех объектов и запрос значений всех полей
В этом тесте мы запрашиваем все 50000 объектов, и проходя по всем объектам из выборки, обращаемся ко всем полям с данными.
Выборка с использованием предиката (фильтр по числовому полю)
В этом тесте под выборку попадает половина имеющихся данных. Данные фильтруются с помощью предиката по полю числового типа.
Примечение: т.к. запросы в Realm "ленивые", в измерение входит обращение к полю .count выборки, чтобы заставить запрос выполниться. Кроме того, в целях оптимизации Realm не отдает сразу данные объектов, поэтому в подобных тестах добавлено еще одно измерение, включающее проход по выборке и обращение к полям объекта.
Выборка с использованием предиката (фильтр по строковому полю)
В этом тесте под выборку попадает половина имеющихся данных. Данные фильтруются с помощью предиката по полю строкового типа.
Примечение: т.к. запросы в Realm "ленивые", в измерение входит обращение к полю .count выборки, чтобы заставить запрос выполниться. Кроме того, в целях оптимизации Realm не отдает сразу данные объектов, поэтому в подобных тестах добавлено еще одно измерение, включающее проход по выборке и обращение к полям объекта.
Выборка с использованием предиката (используя данные из связанных объектов)
В этом тесте под выборку попадают почти все имеющиеся данные. Данные фильтруются с помощью предиката, используя значения из объектов другого типа, с которыми есть связь вида "один ко многим".
Примечение: т.к. запросы в Realm "ленивые", в измерение входит обращение к полю .count выборки, чтобы заставить запрос выполниться. Кроме того, в целях оптимизации Realm не отдает сразу данные объектов, поэтому в подобных тестах добавлено еще одно измерение, включающее проход по выборке и обращение к полям объекта.
Выборка с использованием текстового поиска (строковое поле)
В этом тесте под выборку попадают все имеющиеся данные. Данные фильтруются с помощью предиката CONTAINS[cd] по полю строкового типа. Значение поля - короткий строковый идентификатор.
Примечение: т.к. запросы в Realm "ленивые", в измерение входит обращение к полю .count выборки, чтобы заставить запрос выполниться. Кроме того, в целях оптимизации Realm не отдает сразу данные объектов, поэтому в подобных тестах добавлено еще одно измерение, включающее проход по выборке и обращение к полям объекта.
Выборка с использованием текстового поиска (строковое поле)
В этом тесте под выборку попадают все имеющиеся данные. Данные фильтруются с помощью предиката CONTAINS[cd] по полю строкового типа. Значение поля - фрагмент текста ~3Кб.
Примечение: т.к. запросы в Realm "ленивые", в измерение входит обращение к полю .count выборки, чтобы заставить запрос выполниться. Кроме того, в целях оптимизации Realm не отдает сразу данные объектов, поэтому в подобных тестах добавлено еще одно измерение, включающее проход по выборке и обращение к полям объекта.
Выводы
Пообщаться на тему Core Data и Realm, выразить свое мнение или оспорить вышесказанное можно в нашей группе в Telegram.
Ссылки
Исходный код
Core Data Programming Guide
Realm Documentation
Наш Twitter https://twitter.com/ios_fathers
Наша группа в Telegram https://t.me/joinchat/EK6aXwxr0hj7rAc4Z1NiKw или @ios_fathers
В Realm есть реализация наследования: https://realm.io/docs/swift/latest#model-inheritance
ОтветитьУдалитьНо там ведь написано обратное. Формально да, можно унаследовать один класс от другого, но ты не получишь ожидаемого результата, кроме шаринга кода методов, если таковые есть у классов модели. И далее написано, что функционал только в планах на реализацию.
Удалить