Как стать автором
Обновить

Комментарии 33

При обработки больших баз данных (более 10000 записей) не рекоммендуется использовать Core Data. Для таких целей можно использовать, например, FMDB
Еще при помощи FMDB можно выдирать данные из дискового кэша NSURLRequest.
Тут скорее не в количестве записей дело, а в сложности модели данных.

Как раз тупо показывать плоский список из > 10000 записей проще с помощью NSFetchedResultsController.
НЛО прилетело и опубликовало эту надпись здесь
Обычно его и использует. Но CoreData сразу позволяет работать с объектной моделью данных, а не записями. И в ней реализованы механизмы, позволяющие не считывать всё подряд, а брать только необходимые данные. При чём «на лету» и прозрачно — т.е. можно не грузить объект целиком, а только те его свойства, которые нужны. И загружать всё остальное без всяких усилий со стороны программиста.

Это обеспечивает выигрыш в скорости, по сравнению с чистым SQLite
как это может обеспечивать выигрыш, если на чистом sqlite можно сделать то же самое, пусть и не прозрачно для программиста?
Даёт выигрыш — в смысле при сопоставимых временных затратах. Разумеется, в принципе можно сделать и побыстрее, под свою задачу. Можно, но нужно ли на это тратить время? Core Data используют в достаточно сложных проектах, в простых нету смысла возиться с её заморочками. Реализация необходимого для такого проекта функционала может потребовать значительного времени и нет гарантии что не придётся многократно усложнять и оптимизировать. Чтобы в итоге получить свою Core Data, только дорого, долго и необкатаную.

В общем, встречал упоминания, что при переходе с SQLite на Core Data производительность возрасла. Обратных случаев не припомню.
У меня был случай когда ради определенной функциональности пришлось начать использовать отдельныое SQLite-хранилище, параллельно с основным Core Data, потому что тормозило (могу объяснить что и как тормозило, если интересно)
Интересно.
В модели была m2m-связь (далее просто «связь») «картинка — сущность». Картинки периодически надо было удалять, если они более не использовались сущностями с определенным флагом (ну например, картинка не используется в избранных постах).

Так вот, чтобы удалить одну картинку, Core Data вынуждена была грузить все связанные с ней сущности, чтобы удалять картинку из их коллекций «images». Это было медленно и печально.

Я видел два варианта решения:

  • Сделать связь односторонней: это приводило к ряду проблем в логике, также ответственность за целостность ложилась на меня
  • Хранить связь в отдельном SQLite-хранилище


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

В итоге все же реализовал вторым способом, обрабатывая уведомления о изменении NSManagedObjectContext и сохраняя нужную информацию в SQLite. Упоминаемая связь в Core Data стала атрибутом сущности, содержащим сериализованный NSArray (список идентификаторов картинок), SQLite же хранит полноценную двустороннюю связь.

Теперь когда надо удалить картинку, достаточно:

  • Удалить ни с чем не связанный объект картинки из Core Data (быстро)
  • Выполнить простой SQLite-запрос для удаления связи (очень быстро)
Ну, а во втором варианте решения ответственность за целостность также целиком и полностью на вас… Или я что-то не так понял? Был еще вариант нагло полезть в базу нативным sqlite, разобраться со структурой и руками написать пару соответствующих запросов.
Интересный случай, но поддержу вопрос: зачем вводить новую сущность (SQLite хранилище), если это не решило принципиального вопроса — необходимость заботиться о целостности данных.
Принципиальной проблемой все же была производительность.

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

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

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

Если программа «кэширует» данные для возможности работы в офлайне — это не кэш, это сохранённые для просмотра в офлайне данные.
Если игра после установки докачивает «кэш» необходимый для нормальной работы игры, это не кэш, это DLC.
Если браузер при загрузке Хабра сохраняет локально графический файл с логотипом, чтобы при повторном открытии Хабра не грузить его из интернета, это кэш.
Если программа офлайновых карт «кэширует» карты Франции или Новочебурашкина, это не кэш, это сохраняемые пользователем данные, потерей которых он вряд ли будет обрадован.
Если Хабраридер сохраняет локально скриншоты в статьях, это кэш.
Если Хабраридер сохраняет топики для прочтения в офлайне (вместе со скриншотами), это не кэш.

Если это однажды понять, то потом не будет мучительно больно, когда коллега после небольшого изменения в своём коде вдруг грохнет папку /cache на сервере, а вы там хранили сессионные данные (в лучшем случае) или загруженные пользователями фотографии, и тысячи пользователей получат непонятные ошибки или лишатся своих фоточек, или когда iOS при нехватке места вдруг удалит сохранённые для просмотра в офлайне записи или скачанные игрой текстуры, или закэшированные для офлайна карты, а пользователи наставят минусов приложению, потому что собирались им воспользоваться во время отпуска/перелёта/поездки на дачу и прилежно забили свой гаджет под завязку.
Кэш браузера хороший пример.

Если система, без моего уведомления почистит мне кэш бразуера (которым управлет замечу именно браузер, а не система — именно он позволяте мне выставить размер, и даже в ряде экзотических браузеров говорить что кэшировать, а что нет) — то это значит, что браузер сохраняет кэш куда-то нетуда. С таким же успехом он мог сохранять свой кэш в темп.

И как бы моей операционке бы не хватало места, оно никогда без моего ведома не почистит мне кэш браузера.

И это правильно. Ибо потом я приду туда где интернет 10 килобит и пинги по 4 секунды и очень растроюсь без кэша.

Поэтому хотите называйте это кэше, хотите DLC, хотите сохраняемыми пользователями данные — не важно. Важно, то что размешать такие данные в место которое может быть без уведомления программы которая данный создала (а уж тем более пользователя) в корне не верно.

Вот вы пишите

Если Хабраридер сохраняет локально скриншоты в статьях, это кэш.

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

И нафиг пользователю такое поведение? Только из-за того, что разрабочик считает это кэшем и положил в папку которая может быть внезапно смертна?

С играми, чтобы упростить пример, вот вам пример, у нас в игре есть встроенный бразуер на вебвью который позволяет ходить по порталу в игру (никакого отличия от хабраридера же?) — каждый раз когда там грузится странчика, загрузки всех картинок перехватываются и они кладуться пользователю на диск. В вашем понимании это кэш. А в моем понимании я хочу, чтобы у пользователя даже через полгода при открытии портала в игру все грузилось быстро — что гарантировать размещая данные в Library/Caches нельзя.

Т.е. может в Хабраридер можно позволить так поступать с пользователями, а нам нет.
Именно о таком непонимании я и написал.

Выставьте вы хоть гигабайт для кэша в браузере — когда-нибудь он переполнится, и новые файлы начнут вытеснять старые. А значит приехав «туда где интернет 10 килобит» вы внезапно окажетесь без дорогих вам файлов в кэше.

Кэш браузера — хороший пример. Может так случиться, что он займёт всё свободное пространство (айпад 16гб, например, постоянно не хватает места), а я вдруг решу записать фильм себе в дорогу. Умная система пойдёт и подчистит кэши в приложениях, и мой фильм поместится. Иначе мне придётся самостоятельно перебирать зажравшиеся приложения, копаться в их настройках подчищая кэши или вовсе их сносить.

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

Кэш — это то, без чего приложение будет работать, пусть и медленнее.

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

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

А вот любители сохранять в /Library/Caches как правило не заморачиваются не умным замещением файлов, ни политикой кэширования в зависимости от заполнености девайса и прочее, прочее…

А по поводу примера, вот у нас игра (онлайновая) может полностью работать через streaming — т.е. в целом даже если места на девайсе 0, то все равно все текстуры будут скачиватся, загружатся в память и игрок сможет играть хоть и медленее. Т.е. это нефига не DLC — т.е. это ресурсы которые необходимы для игры. Но при этом если, место на диске есть — то они будут кэшироваться. И заменятся на апдейте при сервере и т.п.

Т.е. никакой разницы между текстурами и данными в игре в нашем случае, и картинками на сайте я не вижу.

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

Видимо, у вас своя специфика — с играми через streaming я не сталкивался.
Зато сталкивался с использованием кэша для данных, которым в кэше не место — и как разработчик, и как пользователь.
Я вообще не могу придумать примеров когда пользователю будет лучше, что данные сохраняются в Library/Caches если честно.

Ну разве, что речь идет о данных которые живут в пределах одной сессии приложения, да и то на мой взгляд для пользователя _внезапная_ потеря таких данных без уведомления будет странноватой.
НЛО прилетело и опубликовало эту надпись здесь
Данные в /Library/Caches могут быть почищены в _любой_ момент. Обратного никто не гарантирует.

А ошибку нехватки места нужно обрабатывать вне зависимости от того что и куда сохраняется.

Или вы полагаете, что если сохраняете в /Library/Caches и кончилось место, система удалит что-то и вам удастся прозрачно продолжить сохранение? Это нет так.
НЛО прилетело и опубликовало эту надпись здесь
Когда поменяют, вы узнаете только тогда когда у ваших пользователей перестанет что-то работать, если вы расчитывали на это поведение. Например перестанет работать ваша игры и вы начнете терять десятки тысяч долларов ежедневно.

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

Когда поменяют, вы узнаете только тогда когда у ваших пользователей перестанет что-то работать

так подобное происходит после каждого мажорного релиза iOS. Не зря же разработчикам бета выдается за несколько месяцев до релиза.

На все потенциальные случаи изменения поведения ОС/SDK презервативы не придумаешь.
Вот с iOS7 менеджмент памяти (тригеры когда закрываются приложения в фоне и когда тебе memory warning кидают) — поменялось между GM и live-ом. Вот тебе на бетах все ок было, вышла лайвовая версия и настала жопа.
которые живут в пределах одной сессии приложения
Как раз это и не кэш, хотя, не спорю, удобно воспользоваться для хранения сессионных данных готовым фрэймворком кэширования, а отдельную обёртку для хранения временных файлов писать лень :)
>> Главное преимущество Core Data заключается в предоставление доступа к свойствам модели без необходимости разархивировать все данные. Однако сложность реализации Core Data в приложении, перекрывает это преимущество.

Вы не правы. Core Data очень просто готовить. Особенно это ощущается, когда надо модифить базу из нескольких разных тредов.
Просто, если использовать какой-нибудь фреймворк типа ObjectiveRecord .

Иначе надо самому писать код для создания NSPersistentStore, NSManagedObjectContext и прочего. Плюс надо создавать модель данных, рисовать связи, иногда реализовать NSValueTransformer, заботиться о работе с базой из разных потоков.

В то время, самая трудоемкая задача при использовании NSKeyedArchiver это реализация NSCoding, но при помощи AppCode ее можно решить нажатием трех кнопок.

Так что, я за разумный выбор инструментов в зависимости от задачи )
А если использовать Mantle, то реализацию NSCoding можно получить искаропки.

А код для настройки стека Core Data пишется один раз и делается компонентом.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий