Очередная реализация ActiveRecord на Objective-C

    Хочу поделиться очередной реализацией паттерна ActiveRecord на Objective-C, а конкретно для iOS.

    Когда только начинал использовать CoreData в iOS разработке, то уже тогда появлялись мысли о том, что это взаимодействие можно как-то упростить. Спустя некоторое время я познакомился с ActiveRecord из RubyOnRails, и тогда я понял чего мне не хватает.
    Немного поискав на гитхабе нашел массу реализаций, но по разным причинам они мне не понравились. Одни написаны для CoreData, а мне она не нравится, в других нужно создавать таблицы руками, или писать raw sql-запросы. А в каких-то код был до неприличия ужасен, я и сам порой пишу не очень чисто, но огромный забор из вложенных if/switch/if/switch это чересчур.
    В конце концов решил написать свой велосипед, без CoreData и без SQL для пользователя.
    Главной причиной этой разработки был, есть и, надеюсь, будет — интерес к разработке.

    Вот что из этого всего вышло.
    А под катом небольшое описание возможностей и реализации (на самом деле много текста и кусков кода, резюме в самом конце статьи).

    Создание таблиц


    Первой проблемой было создание таблиц.
    В случае с CoreData ничего создавать не надо, нужно только описать сущности, а все остальное сделает CD.
    Я долго думал над тем как бы это лучше оформить, и через время меня осенило.
    Objective-C позволяет получить список всех подклассов для любого класса, и кроме того получить список всех его свойств. Таким образом описанием сущности будет являться простое описание класса, а все что мне останется сделать, так это собрать эту информацию и скомпоновать SQL-запрос на ее основе.
    Описание сущности

    @­interface User : ActiveRecord
    
    @­property (nonatomic, retain) NSString *name;
    
    @­end

    Получение всех подклассов

    static NSArray *class_getSubclasses(Class parentClass) {
        int numClasses = objc_getClassList(NULL, 0);
        Class *classes = NULL;
        classes = malloc(sizeof(Class) * numClasses);
        numClasses = objc_getClassList(classes, numClasses);
        NSMutableArray *result = [NSMutableArray array];
        for (NSInteger i = 0; i < numClasses; i++) {
            Class superClass = classes[i];
            do{
                superClass = class_getSuperclass(superClass);
            } while(superClass && superClass != parentClass);
            
            if (superClass == nil) {
                continue;
            }
            [result addObject:classes[i]];
        }
        return result;
    }

    Получение всех свойств вплоть до базового класса

    Class BaseClass = NSClassFromString(@"NSObject");
    id CurrentClass = aRecordClass;
    while(nil != CurrentClass && CurrentClass != BaseClass){
        unsigned int outCount, i;
        objc_property_t *properties = class_copyPropertyList(CurrentClass, &outCount);
        for (i = 0; i < outCount; i++) {
            //  do something with concrete property => properties[i]
        }
        CurrentClass = class_getSuperclass(CurrentClass);
    }


    Типы данных


    Для большей гибкости пришлось отказаться от базовых типов данных (int, double etc.) и работать только с классами в качестве полей таблицы.
    Таким образом в качестве поля таблицы можно использовать любой класс, единственное требование: он должен сам уметь сохранять и загружать себя.
    Для этого он должен реализовывать ARRepresentationProtocol

    @­protocol ARRepresentationProtocol
    
    @­required
    + (const char *)sqlType;
    - (NSString *)toSql;
    + (id)fromSql:(NSString *)sqlData;
    
    @­end

    Я реализовал эти методы для базовых типов Foundation фрэймворка при помощи категорий
    — NSDecimalNumber — real
    — NSNumber — integer
    — NSString — text
    — NSData — blob
    — NSDate — date (real)
    но набор этих классов может быть расширен в любой момент, без особого труда.

    Того же позволяет добиться и CoreData с типом данных Transformable, но я до сих пор так и не осилил как с ним работать.

    CRUD для записей


    Create

    Процесс создания новой записи очень прост и прозрачен

    User *user = [User newRecord];
    user.name = @"Alex";
    [user save];

    Read

    Получение всех записей

    NSArray *users = [User allRecords];

    Зачастую все записи не нужны, по-этому я добавил реализацию фильтров, но о них позже.

    Update

    User *user = [User newRecord];
    user.name = @"Alex";
    [user save];
    
    NSArray *users = [User allRecords];
    User *userForUpdate = [users first];
    userForUpdate.name = @"John";
    [userForUpdate update];    //    или [userForUpdate save]; 

    ActiveRecord следит за изменением всех свойств, и при обновлении создает запрос только на обновление измененных полей.

    Delete

    NSArray *users = [User allRecords];
    User *userForRemove = [users first];
    [userForRemove dropRecord];

    Все записи имеют свойство id(NSNumber), по которому производится удаление.

    Ненужные поля


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

    @­implementation User
    ...
    @­synthesize ignoredProperty;
    ...
    ignore_fields_do(
        ignore_field(ignoredProperty)
    )
    ...
    @­end


    Валидация


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

    // User.h
    @­interface User : ActiveRecord
        <ARValidatableProtocol>
    ...
    @­property (nonatomic, copy) NSString *name;
    ...
    @­end
    // User.m
    @­implementation User
    ...
    validation_do(
        validate_uniqueness_of(name)
        validate_presence_of(name)
    )
    ...
    @­end

    Кроме того я реализовал поддержку кастомных валидаторов, которые может добавлять сам пользователь.
    Для этого необходимо создать класс-валидатор, который должен реализовывать ARValidatorProtocol и описать его в валидируемом классе.
    ARValidatorProtocol

    @­protocol ARValidatorProtocol <NSObject>
    @­optional
    - (NSString *)errorMessage;
    @­required
    - (BOOL)validateField:(NSString *)aField ofRecord:(id)aRecord;
    @­end

    Custom validator

    //  PrefixValidator.h
    @­interface PrefixValidator : NSObject
        <ARValidatorProtocol>
    @­end
    //  PrefixValidator.m
    @­implementation PrefixValidator
    - (NSString *)errorMessage {
        return @"Invalid prefix";
    }
    - (BOOL)validateField:(NSString *)aField ofRecord:(id)aRecord {
        NSString *aValue = [aRecord valueForKey:aField];
        BOOL valid = [aValue hasPrefix:@"LOL"];
        return valid;
    }
    @­end

    Обработка ошибок


    Методы save, update и isValid возвращают булевые значения, в случае возврата false/NO можно получить список ошибок

    [user errors];

    после чего будет возвращен массив объектов класса ARError

    @­interface ARError : NSObject
    
    @­property (nonatomic, copy) NSString *modelName;
    @­property (nonatomic, copy) NSString *propertyName;
    @­property (nonatomic, copy) NSString *errorName;
    
    - (id)initWithModel:(NSString *)aModel property:(NSString *)aProperty error:(NSString *)anError;
    
    @­end

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

    Миграции


    Миграции реализованы на примитивном уровне: реагирует только на добавление новых полей к сущностям или на добавление новых сущностей.
    Для использования миграций не нужно ничего нигде прописывать.
    При первом запуске приложения создаются все таблицы, а при последующих запусках выполняется проверка на наличие новых полей ли таблиц, и если такие имеются, то производятся запросы alter table.
    Для того чтобы не инстанциировать проверку на наличие изменения в структурах таблиц нужно перед всякими обращениями к ActiveRecord отправить следующее сообщение

    [ActiveRecord disableMigrations];

    Транзакции


    Я также реализовал возможность использовать транзакции, для этого используются блоки

    [ActiveRecord transaction:^{
        User *alex = [User newRecord];
        alex.name = @"Alex";
       [alex save];
        rollback
    }];

    rollback — обыкновенный макрос который бросает исключение типа ARException.
    Тарнзакции можно использовать не только для отката в случае неудачи, но и для увеличения скорости выполнения запросов при добавлении записей.
    В одном из проектов был жуткий тормоз при попытке создать over9000 записей. Время выполнения дампа составляло около 180 секунд, после того как я обернул это в транзакцию BEGIN;… COMMIT; время снизилось до ~4-5 секунд. Так что советую всем, кто не в курсе.

    Связи


    Когда я познакомился с реализацией ActiveRecord в RoR, то я был восхищен простотой создания связанности между сущностями. По большому счету эта простота и послужила первой предпосылкой к созданию этого фрэймворка. И сейчас я считаю самой главной фичей в моем велосипеде как раз связи между сущностями, и их относительная простота.
    HasMany <-> BelongsTo

    // User.h
    @­interface User : ActiveRecord
    ...
    @­property (nonatomic, retain) NSNumber *groupId;
    ...
    belongs_to_dec(Group, group, ARDependencyNullify)
    ...
    @­end
    // User.m
    @­implementation User
    ...
    @­synthesize groupId;
    ...
    belonsg_to_imp(Group, group, ARDependencyNullify)
    ...
    @­end


    Макросы belongs_to_dec belonsg_to_imp принимают три параметра: имя класса с которым мы «связываемся», имя getter'а и тип зависимости.
    Типов зависимости реализовано два: ARDependencyNullify и ARDependencyDestroy, первый при удалении модели зануляет ее связи, а вторая удаляет все связанные сущности.
    Поле для этой связи должно совпадать с именем модели и начинаться с буквы в нижнем регистре
    Group <-> groupId
    User <-> userId
    ContentManager <-> contentManagerId
    EMCategory <-> eMCategory // немного коряво, но так уж исторически сложилось

    Обратная связь (HasMany)

    // Group.h
    @­interface Group : ActiveRecord
    ...
    has_many_dec(User, users, ARDependencyDestroy)
    ...
    @­end
    // Group.m
    @­implementation Group
    ...
    has_many_imp(User, users, ARDependencyDestroy)
    ...
    @­end

    Тоже самое что и в случае со связью типа BelongsTo.
    Главное что нужно запомнить: перед созданием связи обе записи доолжны быть сохранены, иначе они не имеют id, а связи завязаны именно на нем.

    HasManyThrough


    Для создания этой связи нужно оздать еще одну модель, промежуточную.

    // User.h
    @­interface User : ActiveRecord
    ...
    has_many_through_dec(Project, UserProjectRelationship, projects, ARDependencyNullify)
    ...
    @­end
    // User.m
    @­implementation User
    ...
    has_many_through_imp(Project, UserProjectRelationship, projects, ARDependencyNullify)
    ...
    @­end
    
    // Project.h
    @­interface Project : ActiveRecord
    ...
    has_many_through_dec(User, UserProjectRelationship, users, ARDependencyDestroy)
    ...
    @­end
    // Project.m
    @­implementation Project
    ...
    has_many_through_imp(User, UserProjectRelationship, users, ARDependencyDestroy)
    ...
    @­end

    Промежуточная, связующая модель

    // UserProjectRelationship.h
    @­interface UserProjectRelationship : ActiveRecord
    @­property (nonatomic, retain) NSNumber *userId;
    @­property (nonatomic, retain) NSNumber *projectId;
    @­end
    // UserProjectRelationship.m
    @­implementation UserProjectRelationship
    @­synthesize userId;
    @­synthesize projectId;
    @­end

    Эта связь имеет те же недостатки что и HasMany

    Макросы *_dec/*_imp добавляют вспомогательные методы для добавления связей
    set#ModelName:(ActiveRecord *)aRecord;    //    BelongsTo
    add##ModelName:(ActiveRecord *)aRecord;    //    HasMany, HasManyThrough
    remove##ModelName:(ActiveRecord *)aRecord;    //    HasMany, HasManyThrough

    Фильтры для запросов


    Очень часто требуется как-то отфильтровать выборку из базы:
    — поиск соответствующих какому-то шаблону записей (UISearchBar)
    — вывод в таблицу только 5 записей из тысячи
    — получение только текстовых полей записей, без доставания из базы кучи «тяжелых» картинок
    — еще масса вариантов :)

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

    Limit/Offset

    NSArray *users = [[[User lazyFetcher] limit:5] fetchRecords];
    NSArray *users = [[[User lazyFetcher] offset:5] fetchRecords];
    NSArray *users = [[[[User lazyFetcher] offset:5] limit:2] fetchRecords];

    Only/Except

    ARLazyFetcher *fetcher = [[User lazyFetcher] only:@"name", @"id", nil];
    ARLazyFetcher *fetcher = [[User lazyFetcher] except:@"veryBigImage", nil];

    Where

    iActiveRecord подерживает базовые условия WHERE

    - (ARLazyFetcher *)whereField:(NSString *)aField equalToValue:(id)aValue;
    - (ARLazyFetcher *)whereField:(NSString *)aField notEqualToValue:(id)aValue;
    - (ARLazyFetcher *)whereField:(NSString *)aField in:(NSArray *)aValues;
    - (ARLazyFetcher *)whereField:(NSString *)aField notIn:(NSArray *)aValues;
    - (ARLazyFetcher *)whereField:(NSString *)aField like:(NSString *)aPattern;
    - (ARLazyFetcher *)whereField:(NSString *)aField notLike:(NSString *)aPattern;
    - (ARLazyFetcher *)whereField:(NSString *)aField between:(id)startValue and:(id)endValue;
    - (ARLazyFetcher *)where:(NSString *)aFormat, ...;

    Тоже самое можно описать в стиле привычных и удобных NSPredicate'ов

    NSArray *ids = [NSArray arrayWithObjects:
                   [NSNumber numberWithInt:1],
                   [NSNumber numberWithInt:15], 
                   nil];
    NSString *username = @"john";
    ARLazyFetcher *fetcher = [User lazyFetcher];
    [fetcher where:@"'user'.'name' = %@ or 'user'.'id' in %@", 
                   username, ids, nil];
    NSArray *records = [fetcher fetchRecords];

    Объединения (join)


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

    - (ARLazyFetcher *)join:(Class)aJoinRecord
                    useJoin:(ARJoinType)aJoinType
                    onField:(NSString *)aFirstField
                   andField:(NSString *)aSecondField;

    Подерживаются разные типы join'ов
    — ARJoinLeft
    — ARJoinRight
    — ARJoinInner
    — ARJoinOuter
    думаю названия говорят сами за себя.
    С этой возможностю связан один небольшой костыль, потому для получения объединенных записей нужно вызывать

    - (NSArray *)fetchJoinedRecords;

    вместо

    - (NSArray *)fetchRecords;

    Этот метод возвращает массив из словарей, где ключами являются имена сущностей, а значениями — данные из базы.

    Сортировка


    - (ARLazyFetcher *)orderBy:(NSString *)aField ascending:(BOOL)isAscending;
    - (ARLazyFetcher *)orderBy:(NSString *)aField;// ASC по умолчанию
    
    ARLazyFetcher *fetcher = [[[User lazyFetcher] offset:2] limit:10];
    [[fetcher whereField:@"name"
           equalToValue:@"Alex"] orderBy:@"name"];
    NSArray *users = [fetcher fetchRecords];


    Хранилище


    Базу можно хранить как в Caches, так и в Documents, при этом в случае хранения в Documents к файлу добавляется атрибут отключающий бэкап

    u_int8_t b = 1;
    setxattr([[url path] fileSystemRepresentation], "com.apple.MobileBackup", &b, 1, 0, 0);

    иначе приложение получит reject от Apple.

    Резюме


    Проект на github — iActiveRecord.
    Возможности
    • поддержка ARC
    • поддержка unicode
    • миграции
    • валидации, с подержкой кастомных валидаторов
    • транзакции
    • кастомные тип данных
    • связи (BelongsTo, HasMany, HasManyThrough)
    • сортировка
    • фильтры (where =, !=, IN, NOT IN и другие)
    • объединения
    • поддержка CocoaPods


    Заключение



    В качестве оправдания заключения хочу сказать, что проект начинался just for fun, и он продолжает развиваться, в планах в конце концов вычистить кучу «грязного» кода и добавить другие полезные возможности.

    С удовольствием выслушаю адекватную критику.

    P.S. сообщения об ошибках пишите в ЛС, пожалуйста.

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 37

      0
      А теперь под ios можно же писать на настоящем руби. Проект можно закрывать, я считаю :)
        +4
        Ок, закрывайте, Ctrl+W.
        • UFO just landed and posted this here
          0
          не могли бы вы более подробно рассказать об этом?
        0
        Здорово, буду пробовать. По идее можно пробрасывать не только данные, но и части кода куда угодно. Этакие фабрики с паттернами.
          +1
          classes = malloc(sizeof(Class) * numClasses);
          

          Здесь утечка (нет вызова free) — лучше так:

          Class classes[sizeof(Class) * numClasses];
          
            +1
            да, согласен, когда -то удалил строку с free и забыл добавить на место. Спасибо
            +2
            User *user = [User newRecord];
            user.name = @"Alex";
            [user save];
            


            По коду мне кажеться что объект создан в autorelease pool, он не освобождается. Согласно правил именования сообщений:
            You own any object you create
            You create an object using a method whose name begins with “alloc”, “new”, “copy”, or “mutableCopy” (for example, alloc, newObject, or mutableCopy).
            


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

            А по проекту, мне не ясно в чем именно преимущество этого подхода, почему нужно использовать это вместо CoreData?
            У CoreData есть неоспоримое преимущество — с ним знакомы все Cocoa разработчики, поэтому код автоматически будет намного более понятен для других разработчиков (а это очень большое преимущество).
            Поэтому чтоб использовать какие-нибудь альтернативы — нужно для этого иметь весомые причины.
              +1
              ну и CoreData очень хорошо оптимизирован как по производительности, так и по использованию ресурсов: у вас все объектв грузяться в память, а в CoreData есть механизм кэширования и прозрачной подгрузки объектов по требованию (faulting), подержка уникальности объектов в пределаз одного контекста.
              Поэтому я бы воспользовался лучше CoreData.
                +1
                Да, объект нужно освобождать, просто не привел этого в примере.
                Я не утверждаю что мой велосипед лучше чем CoreData, или, не дай бог, он идеален =)
                Просто от скуки написал такую вещь, судя по отзывам с гитхаба кому-то это пригодилось, у кого-то была задача использовать существующую БД на sqlite, и CoreData'у туда было тяжело прикрутить.
              0
              А чем именно не нравится CoreData? Спрашиваю не ради споров, возможно что-то новое в копилку подводных камней добавите.
                +1
                Ну к примеру я создал две сущности, одна из них невалидна, и я не смогу сохранить другую, т.к. получу ошибку о невалидности какой-то сущности в данном контексте. Прийдется одну удалять, а затем создавать снова. Столкнулся с такой проблемой в одном из проектов, где было несколько связанных сущностей, и все это лихо валилось, при чем с exception'ом и совершенно непонятной ошибкой из серии Error -912.
                Создание объектов там не прозрачное, при создании нужно обязательно запихивать в контекст, как-то это неудобно, на мой взгляд.
                Но скорее дело в том что я плохо знаю CD.
                И как бы то ни было, я получил новые знания во время разработки, так что для меня польза как минимум в этом :)
                0
                Добавьте ещё индексы, а то в больших SQLite базах без них очень грустно.

                И всё же, если писать поверх чистой sqlite, я бы писал на FmDb — фактически ваша либа — тот же SQL, только называется как методы в Objective-C. Вы хорошо это прописали, написали и расписали, но вопрос в другом — нужна ли такая прослойка?
                  0
                  механизм миграций несколько смущает… в статье упоминается, что идёт проверка не добавились ли новые сущности или свойства. А как насчет переименовывания, изменения типа, удаления?

                  Если ты работал с rails, то имеешь представление о механизме миграций там. Наверное и тут было бы удобно иметь что-то подобное. Схему базы можно например хранить в plist. При запуске приложения смотришь есть ли файлик с базой, если нету, то создаёшь базу из plist. Если файлик базы есть, то смотришь какая версия, а затем применяешь миграции от текущей версии до последней если таковые имеются. При примерно таком раскладе мне кажется будет более гибкий механизм. В точ числе можно будет добавлять индексы на поля, чем сейчас сделать как я понял нельзя. Ну и прочие фишки)

                  Механизм с пропертями, которые автоматически являются и полями в базе мне не очень нравится. А ещё в добавок и надо указывать какая проперти не лежит в базе. Не удобно это и лишний раз люди могут допускать ошибки. Может аналогично как в CoreData стоит сделать? Т.е. да, для того, чтобы они были видны и всё такое придётся в h файле объявлять эти проперти. Но синтесайзить их где-нибудь в твоих классах на основе той же схемы. А в m файле люди будут для них писать @dynamic. Плюс это позволит ещё сделать удобную фичу, которой лично мне в core data не очень хватает: работа с обычными типами данных, особенно BOOL часто бывает нужен. Ну оборачивать BOOL в NSNumber ну совсем не прикольно. Иногда специально дополнительный геттер и сеттер, чтобы работать с ним как с BOOL, а не NSNumber. Так вот если синтесайз пропертей будет проиходить где-то в твоих классах либы, то наверное и получится как раз дать пользователю работать с обычными BOOL если надо! Имхо было бы удобненько)

                  Отдельное спасибо за поддержку CocoaPods ;)
                    +1
                    Индексы будут =)
                    Переименование и удаление не реализовано в самой sqlite, а делать миграции через создание временной таблицы может быть очень затратно, если таблица большая.
                    Если сразу пытаться создать продукт с поддержкой всего на свете, всех ньюансов и требований, то в итоге может выйти какой-то монстр. Все что сейчас реализовано было нужно мне в тот или иной момент, постепенно функционал расширяется, что-то добавляется, что-то улучшается. Всему свое время :)
                    И главное что я хотел видеть в своей поделке — это простота. Мне надоело делать массу телодвижений с CoreData'ой для создания простой сущности =)
                      0
                      CoreData может работать с обычными типами

                      image
                      0
                      Выглядит удобно! Ставлю +1 за индексы. И теперь главный вопрос: что с основной болью core data — многопоточностью? Если хочу обновлять базу в фоне — как это обрабатывается? Можно ли передавать сущности между потоками?
                        +1
                        Честно говоря над всем этим не думал, т.к. пока не было таких, но я открыт для предложений и готов внедрять новый функционал.
                          +1
                          Можете предоставить мне подобный use case, а я это реализую. Проблем возникнуть не должно.
                            0
                            Самый простой use case: приложение с новостями, синхронизация происходит в фоне — из интернета скачиваем новости в sqlite и уже из sqlite их отображаем.

                            Sqlite вообще штука однопоточная, нельзя с одним соединением работать из нескольких потоков, если у нас ActiveRecord, значит в сущности хранится ссылка на соединение с БД, так? Получается если мы создали сущность в одном потоке — передать ее в главный и сделать update уже нельзя. Я долго мучился, чтобы это исключить с CoreData, но до сих пор иногда приложение крешится в непонятных местах.

                            В Яндексе рассказывали, что они в контроллерах сохраняют только id-шники, сущности не хранят, и у них есть специальный менеджер контекстов, который смотрит в каком мы потоке и выдает соответствующий контекст. Еще встает вопрос с изоляцией соединений (если сделали update в одном, когда это увидим во втором).

                            А вообще у вас как — каждой сущности в БД соответствует только один объект или может быть два объекта, ссылающихся на одну и ту же строку в БД? И у них может быть два разных состояния.

                            Еще проблема — если мы сохранили сущность в каком-то контроллере, а в фоне (или даже в том же потоке) у нас вообще все сущности из базы удалили. Что будет в вашем ORM? В CoreData креш, и это часто напрягает.
                              +1
                              У меня менеджер для работы с базой — синглтон, обращения к которому лочатся через @­synchronized, так что по идее пока кто-то пишет в базу, другие не смогут читать, и наоборот. Это если я ничего не ошибаюсь в механизме @­synchronized.
                              Как будет больше свободного времени постараюсь проработать ваш use case, и вообще подумать над доступом из разных потоков.
                              Спасибо
                                0
                                Не, у вас же лочится только для получения статической переменной, а дальнейшая работа идет без лока.
                                Ок, подписался на твиттер проекта, пишите, если додумаете. Попробую один из следующих проектов запустить на iActiveRecord :)
                          0
                          Выглядит здорово. Намучался года два назад с sqlite3 — обновления записей встроенной в приложение базы данных стоили недели бессоных проклятий.

                          Обещаете, что теперь все будет летать?

                          Посоветуйте, стоит ли переходить на Вашу библиотеку для следующей задачи

                          1) есть приложение в которую встроена база данных sqlite3, содержащая 1000 записей о банкоматах (адреса и прочее)
                          2) в оффлайн режиме приложение показывает в разных ипостасях эти банкоматы
                          3) в онлайн режиме приложение в редких случаях меняет записи, если банкомат поменял адрес или уничтожен, или добавляет новые записи

                          Спасибо
                            +1
                            Думаю можно, но нужно пробовать. Сейчас использую это решение в одном из проектов, заодно правлю баги и улучшаю существующий функционал, делая его удобнее для использования.
                            Опять таки, я открыт для предложений, и готов вносить новый фичи по требованию.
                              –1
                              можно делать это на любом движке — на 1000 записях о производительности думать не имеет смысла. На реализации DAO на чистом sqlite / fmdb потратите 1 день, Core Data — 1.5 дня, но последующие правки будут проходить мягче, фрэймворке от автора — тоже, скорее всего, день
                                0
                                Поделитесь, пожалуйста, ссылкой на пример, соответствующий моей задаче на чистом sqlite / fmdb.
                              0
                              отличная библиотека, спасибо. давно такой ждал. обязательно попробую.
                                –2
                                Очередная попытка надрать задницу CoreData. Очередная неудачная попытка.
                                  0
                                  Если честно от макросов выпали глаза, какая то смесь руби и обж-с.
                                    +1
                                    Макросы можно заменить на обж-це код, но он еще страшнее.
                                    0
                                    А как насчет NSFetchedResultsController? Это очень удобная штука, даже не знаю как без нее жить, какой-то аналог будет у Вас реализован?
                                      0
                                      Никогда им не пользовался, потому и не думал о подобном функционале.
                                      Можете расказать чем он хорош и удобен? Как там с кастомизацией ячеек?
                                        0
                                        Хотя даже если этот котнроллер и очень крутой и очень удобный, то все равно не буду его реализовывать в рамках AR.
                                        Не должны UI и логика находится в одном месте.
                                          0
                                          Это не UI вовсе, этот класс позволяет следить за результатами запросов при изменении самих записей. Например если создать запрос с каким то предикатом и параметрами сортировки, то при изменении набора записей, соответствующих предикату, или их порядка согласно критериям сортировки, этот класс уведомляет и говорит что куда переместилось, вставилось и удалилось. Конечно он очень удобно сочетается с использованием UITableView, но ничто не мешает использовать его где угодно.
                                            0
                                            Это как раз та логика, которая позволяет легко и удобно изменять отображение, когда меняется модель, хотя этим не ограничивается.
                                              0
                                              Понял, подумаю, спасибо.

                                        Only users with full accounts can post comments. Log in, please.