marcoarment / fcmodel Goto Github PK
View Code? Open in Web Editor NEWAn alternative to Core Data for people who like having direct SQL access.
License: MIT License
An alternative to Core Data for people who like having direct SQL access.
License: MIT License
I am not having any actual problems with this right now, but think of this scenario:
There is a sync engine, that syncs the local database with a webservice. After downloading a set of changes, it merges them into the local database. It wraps this in a notification batch to only notify the UI once after the sync completed.
Now, while the sync engine is going through a big set of changes, the user makes a change in the ui, which would normally cause a notification to fire that tells other parts of the ui to update. But since the sync engine is busy, this notification is delayed until it finishes syncing.
This potential problem could be addressed by making -beginNotificationBatch
and -endNotificationBatchAndNotify:
affect the thread they are called on only.
I think this change would make a lot of sense, but I am not quite sure wether there are any drawbacks. Just throwing it out there.
PS: I think it would also be nice to have another block-based method something like this:
[FCModel performBlockWithBatchedNotifications:^{
// do some changes
} andNotify:YES];
unsavedChanges
is an internal method, do we really need to copy the dictionary in order to make it immutable? Unless someone casts it back to mutable, what would be the harm to simply returning the NSMutableDictionary
? All callers of the method must assume that it is immutable anyway.
https://github.com/marcoarment/FCModel/blob/master/FCModel/FCModel.m#L793
We're building our iOS and Android apps in tandem and were looking to keep our schemas aligned. With FCModel, we had to force our class prefix on the table name.
It would be a nice-to-have for expandQuery to use a specific table name of our choosing.
Example: JBPerson.h corresponds to table Person.
Calling [FCModel saveAll] gives me an exception if I've got deleted instances still around, complaining about saving deleted instances. Currently I'm getting around this with an "if (deleted) return;" in saveByNotification.
Apologies if I missed something though, I only switched over from CoreData today.
When persisting an NSURL to the database it is not safe to simply convert it to an absolute string and store it. If the NSURL is storing a file URL then the resulting string will not work to reconstruct a valid file URL. Instead, it should persist to disk as a bookmark in a BLOB. Since the field type would differ based on whether the NSURL is a file URL or not I am unsure how to fix this.
Is there a way to get the changed properties of a FCModel instance?
Just pushed the first commit e0f9b3d of what might be called "FCModel 2" to the "non-unique-instances" branch. This is a big, likely-breaking change that applies a lot of lessons learned since the original design:
Instances are no longer tracked or kept unique in memory. All instances are "detached".
dataWasUpdatedExternally
, reloads, and conflict resolution are no longer necessary and have been removed.allLoadedInstances
is no longer possible and has been removed.All saves and deletes are expected to succeed. Saves shouldn't unexpectedly fail or be blocked.
should/didInsert/Update/Delete
, saveWasRefused
, saveDidFail
. To customize behavior, override save
and call [super save]
from the subclass.FCModelSaveResult
is removed. delete
now returns void and save
now returns a BOOL to indicate whether changes were made.lastSQLiteError
is removed.FCModelException
.saveAll
has been removed. Saves should happen intentionally, right after you change the data.NSURL
, NSDate
, etc. have been removed to avoid a lot of subtle bugs and inconsistencies between formats.Notifications have been simplified to just a single FCModelChangeNotification for any change to a table.
FCModelInstanceSetKey
has been replaced by FCModelInstanceKey
and will always be one instance if known, or unset for many/unknown.Take a look and see what you think so far. What other changes should we consider now that we're breaking things?
PLEASE don't ship any apps with this yet.
I have a model class that has a property named value
, of type id
.
Depending on some external information, my implementations of -serializedDatabaseRepresentation...
and -unserializedDatabaseRepresentation...
will convert the database value into one of some possible types (NSDate
, NSString
, NSNumber
, ...).
For this property, FCModel warns me about allowing NULL
and says the property is a primitive type, while it very much isn't.
I don't know if this has any further implications or if it is just the warning.
(fwiw, the database colum is declared without any type)
When I try to use [self executeUpdateQuery:@"DELETE FROM $T"] to delete all records from table, but If have some unsaved changes, this will raise "has unsaved changes during a write-consistency reload" exception.
I believe you forgot to add a semaphore wait/signal in this method. There might be other ones, I didn't look closely.
Our app has sensitive data so we have an inactivity timeout. When the timeout occurs they have to sign in again and we close the database. Upon signing in again, we open the database and 💥!
EXC_BAD_ACCESS
on FCModelDatabaseQueueOperation
.
0x2c542d4: jmp 0x2c5426a ; object_cxxDestructFromClass(objc_object*, objc_class*) + 22
What is the proper way of cleaning up/closing the db?
Creating db:
- (void)open:(NSString *)databasePath{
[self.logger debug:@"Opening database: %@", databasePath];
[FCModel openDatabaseAtPath:databasePath withSchemaBuilder:^(FMDatabase *db, int *schemaVersion) {
self.database = db;
self.database.logsErrors = YES;
self.database.crashOnErrors = NO;
self.database.traceExecution = NO;
[self.database beginTransaction];
self.schemaVersion = *schemaVersion;
[self runMigrations];
*schemaVersion = self.schemaVersion;
[self.database commit];
[self.logger debug:@"Database ready: %@", databasePath];
}];
}
...
- (void)close{
if (self.database) {
[self.database close];
}
}
On timeout:
[DBClass close];
Thoughts? What's the best way to address this?
From my understanding sqlite treats all primary integer key columns as "somewhat" AUTOINCREMENT:
http://www.sqlite.org/faq.html#q1
Is the legacy treatment in +primaryKeyValueForNewInstance really necessary? Would it be possible to introduce a macro to disable it?
While we have updated the model to remove the autoincrement, our alpha app tester have a good bit of valuable data in the old scheme (autoincrement is a pain to remove). Is there an issue I overlooked when running the new logic on old schemes (as a temporary measure)?
I'm currently investigating an exception that's difficult to reproduce, possibly related to concurrent usage of FCModel (version 4c00223)
The exception is [NSDate doubleValue] "Unrecognized selector sent to instance", and it happens in line 241 in FCModel.m:
} else if (propertyClass == NSDate.class) {
return [NSDate dateWithTimeIntervalSince1970:[databaseValue doubleValue]];
So, apparently, the databaseValue is already an instance of NSDate, how can this happen?
Possibly this has to do with unsaved changes from another thread, as I'm also getting the occasional SIGSEGV in [FCModel reload]:
__18-[FCModel reload:]_block_invoke in FCModel.m on Line 649
__35-[FCModelDatabaseQueue inDatabase:]_block_invoke in FCModelDatabaseQueue.m on Line 79
-[FCModelDatabaseQueueOperation main] in FCModelDatabaseQueue.m on Line 17
Any help would be greatly appreciated.
(And thanks for this great library!)
I ran into a problem where I got two object instances of the same database row.
It's contradicting the README.md section "Retention and caching":
Each FCModel instance is exclusive in memory by its table and primary-key value. If you load Person ID 1, then some other query loads Person ID 1, they'll be the same instance (unless the first one got deallocated in the meantime).
This is what I did:
MyFCModel *newlyCreated = [MyFCModel new];
foo.myField = @"123";
[newlyCreated save];
NSArray *array = [MyFCModel instancesWhere:@"myField == ?", @"123"];
NSAssert(array.count == 1, @"More than 1 element");
MyFCModel *first = [array firstObject];
if (newlyCreated != first) {
NSLog(@"%@ and %@ are different objects!", newlyCreated, first);
}
After some digging in FCModel.m I found the method registerUniqueInstance
. It is neither used anywhere nor publicly exposed in FCModel.h . After exposing the method and calling it on my newly created object everything worked fine and I got only one instance.
Any thoughts on this? Is there something bogus? I'm not that deep into the FCModel source to finally say what's the best solution or if I'm misunderstanding something.
This one seems to be fairly consistent in terms of crashing:
Exception Type: SIGABRT
Exception Codes: #0 at 0x3b9281f0
Crashed Thread: 0
Thread 0 Crashed:
0 libsystem_kernel.dylib 0x3b9281f0 __pthread_kill + 8
1 libsystem_pthread.dylib 0x3b992797 pthread_kill + 56
2 libsystem_c.dylib 0x3b8d8fdd abort + 74
3 libsystem_malloc.dylib 0x3b950d67 free + 380
4 libobjc.A.dylib 0x3b36c3ad object_dispose + 18
5 podcasts 0x0016d639 -[FCModel dealloc] (FCModel.m:778)
6 libobjc.A.dylib 0x3b374b6b objc_object::sidetable_release(bool) + 172
7 CoreFoundation 0x30b4b93d CFRelease + 554
8 CoreFoundation 0x30b666e3 -[__NSSetI dealloc] + 124
9 libobjc.A.dylib 0x3b374b6b objc_object::sidetable_release(bool) + 172
10 libobjc.A.dylib 0x3b3750d7 (anonymous namespace)::AutoreleasePoolPage::pop(void*) + 356
11 CoreFoundation 0x30b4ec69 _CFAutoreleasePoolPop + 14
12 CoreFoundation 0x30be41cb __CFRunLoopRun + 1304
13 CoreFoundation 0x30b4ef0f CFRunLoopRunSpecific + 520
14 CoreFoundation 0x30b4ecf3 CFRunLoopRunInMode + 104
15 GraphicsServices 0x35a70663 GSEventRunModal + 136
16 UIKit 0x3349a16d UIApplicationMain + 1134
17 podcasts 0x00049ee7 main (main.m:14)
18 libdyld.dylib 0x3b872ab7 start + 0
Any ideas what this could be? Seems like an FCModel object is in a set, the set gets released then it crashes. Latest version as at 2 July 2014.
It would be nice if it is was possible to customize the tables that are excluded from the FCModel in the scheme builder (e.g. when using a repopulated dataset that is also used elsewhere):
https://github.com/marcoarment/FCModel/blob/master/FCModel/FCModel.m#L874
Which FCModel function to use to insert/update multiple objects at once (for eg. passing an array of FCModel objects to save function)?
I have unit tests that are crashing after the test runs when it calls [FCModel closeDatabase]
. The crash happens on the following line:
dispatch_semaphore_wait(g_instancesReadLock, DISPATCH_TIME_FOREVER);
The crash occurs because the g_instancesReadLock
variable is nil. If in my test I do something that triggers the uniqueMapInit
class method which in turn initializes this variable then the closeDatabase call works fine. Is there a better place to initialize this variable so closeDatabase always works?
Queries happen from the main thread but are they executed on the main thread? I've looked through the code and saw the queues work but I'm not seasoned enough to grasp all of the queue bits in obj-c. :)
The way it works right now, FCModel is implemented as a bunch of class methods, relying on private global variables. It's kind of convenient, but not as flexible as I'd like. This has caused me a few issues when I wanted to open more than one database at once (for example).
Wouldn't it be more flexible to separate the library into FCModel
, the subclass of all database table/classes, and a FCDatabase
or FCManager
, that handles the connection to one database?
I'm using FCModel to get some records based on a join table. The query looks similar to:
SELECT Photos.* FROM Photos JOIN PhotosetPhotos ON PhotosetPhotos.photo_id = Photos.id WHERE PhotosetPhotos.photoset_id = ?
I cannot find a way however to execute this using FCModel. How should things like these be handled?
Regards,
pieter
I've found myself wanting to use ORDER in a few places and right now it's a little tricky, I'm happy to submit pull requests for any features but first I thought I'd bounce them off you.
If I want to get all the playlist items ordered by their position I have to do either:
+(NSArray *) instancesOrderedBy:(NSString *) order {
NSString *fullQuery = [NSString stringWithFormat:@"SELECT * FROM PlaylistEntry ORDER BY %@", order];
__block NSArray *results = nil;
[[self databaseQueue] inDatabase:^(FMDatabase *db) {
FMResultSet *resultSet = [db executeQuery:fullQuery];
results = [self instancesFromResultSet:resultSet];
[resultSet close];
}];
return results;
}
Or if I'm feeling like hacking something gross I could do
NSArray *items = [PlaylistEntry instancesWhere:@"1=1 ORDER BY position ASC"];
Would you take a patch to add instancesMatchingSQLQuery:
or instancesOrderedBy:
or both?
The initial comment in each source file says that it's copyrighted. Shouldn't they be updated to instead reflect the MIT license?
Hi,
If the database value column = NULL, an error occurs "Terminating app due to uncaught exception 'NSInvalidArgumentException', setNilValueForKey : could not set nil as the value".
- (void)decodeFieldValue:(id)value intoPropertyName:(NSString *)propertyName
{
if (value == [NSNull null]) value = nil;
if (class_getProperty(self.class, propertyName.UTF8String)) {
[self setValue:[self unserializedRepresentationOfDatabaseValue:value forPropertyNamed:propertyName] forKeyPath:propertyName];
}
}
This happens when the value = [NSNull null].
I think it would be nice to have a declarative way to give default values for not null
columns.
As far as I can see, the best way right now is to override -didInit
, and set default values if .existsInDatabase == NO
.
Imho, it would be nicer if classes could implement a method like +defaultValueForFieldName:
that takes precedence over the fieldInfos defaultValue
(basically another else-if here).
That would even allow classes to set not null
properties to nil
temporarily, circumventing the default values.
If you would be willing to merge something like this, let me know and i'll whip up a pull request.
While I think storing NSDates as doubles is the best in most cases, FMDB has the option to store dates as strings, and in some cases I think it can be useful.
Also, it seems that if you do nothing it already converts dates to doubles, I don't know if you needed to do it on the FCModel side for some other reason.
Here's the relevant portion of FMDatabase: https://github.com/ccgus/fmdb/blob/395f7b7a9800fbe51f3a31ffb275481b5939999b/src/FMDatabase.m#L451-L456
It allows using your own NSDateFormatter
using +[FMDatabase storeableDateFormat:]
and -setDateFormat:
I have it more or less working on my fork (maybe it's not the best approach though), if you think it's something you'd like to add I can put some more time into polishing and testing it.
Currently, subclasses may override this method to provide a custom table name instead of just using their class name:
+ (NSString *)tableName { return NSStringFromClass(self); }
I forgot that I even wrote that in, and I've always assumed in the rest of the code that the class name was always the table name. Humorously, custom table names don't actually work, and almost nobody noticed until now.
See #55.
Since FCModel scans the database schema after opening to get required property info, the only way to continue supporting custom table names is to use objc_getClassList
to iterate through every class that exists to scan for FCModel subclasses at launch, which I think sucks.
Since they don't work anyway and the only fix would suck, I propose removing custom table names.
I know that some people have maintained forks here with custom table names or prefixes. I've chosen not to integrate them into FCModel for simplicity, easy debugging, and reasons like this — to me, this is a clear win for convention over configuration. You have a model class named OCPost? It's in a table named OCPost.
Using FCModel, is there a way to create a detached copy of an object? I would like to be able to do something like this:
Person *joe = [Person new];
joe.name = @"Joe";
[joe save];
DetachedPerson *detachedJoe = [joe detachedCopy];
DetachedPerson *secondDetachedJoe = [joe detachedCopy];
detachedJoe == secondDetachedJoe // true
joe.age = 26;
[joe save];
DetachedPerson *thirdDetachedJoe = [joe detachedCopy];
detachedJoe == thirdDetachedJoe // false
Is it possible to do something like with FCModel? It -[FCModel copy]
was supported I think I could use that instead, but I'm not sure.
#import "FMDatabase.h"
#import "FMDatabaseQueue.h"
should become
#import <FMDB/FMDatabase.h>
#import <FMDB/FMDatabaseQueue.h>
as it's not part of the project when using cocoapods. Else you'll get 'FMDatabase.h' file not found
I am planning to update my app that uses FCModel to include Today widget that will also read data from the app's database (via app group's shared container). Should I take any measures to avoid problems when de-facto two apps are reading from the same SQLite database? (The Today widget will only read data, not write them).
If you open a database and close it before any operation is done to it. It'll crash due to g_instancesReadLock hasn't been initialised yet, as there hasn't been any chance to call uniqueMapInit
yet.
NSString *dbPath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"testDB.sqlite3"];
[FCModel openDatabaseAtPath:dbPath withSchemaBuilder:^(FMDatabase *db, int *schemaVersion) {
}];
[FCModel closeDatabase];
[FCModel allInstances] returns an NSArray with same value
When I perform [Toto allInstances]
, it returns an NSArray containing only the same first FCModel Object at each row. NSArray Count = 230
When I perform the query on OS X SQLite Client SELECT * FROM Toto
, it returns 230 distinct rows.
SQLite Toto table definition has two primary keys : GUID & Version
here is Toto.h :
#import "FCModel.h"
@interface Toto : FCModel
// properties from sql columns model
@property (nonatomic, copy) NSString* GUID;
@property (nonatomic) NSDate* WalkingDate;
@property (nonatomic) NSData* json_data;
@property (nonatomic) NSString* user_ID;
@property (nonatomic) NSString* SystemInfo;
@property (nonatomic) NSNumber* Version;
@property (nonatomic) NSString* State;
@property (nonatomic) float LastModificationDate;
@property (nonatomic) BOOL isDirty;
@property (nonatomic) NSString* ReasonForChange;
@end
Thanks for your help.
With the new KVO-less approach, I guess you don't plan to include a replacement for -didChangeValueForFieldName:fromValue:toValue:
? If you don't, feel free to close this right away, I just wanted to ask wether I need to roll my own KVO now.
(I found it to be rather useful though. It would be nice to have something similar.)
I have this column in a table:
creationDate INTEGER NOT NULL
The corresponding class has a property:
@property (nonatomic) NSDate *creationDate;
Now the problem is: The code in +openDatabaseAtPath:WithDatabaseInitializer:schemaBuilder:
decides that the default value for any INTEGER
column is @(0)
, which is obviously not good for a NSDate
property.
I think this should default to [NSDate dateWithTimeIntervalSince1970:0]
.
A simple fix would be to use -unserializedRepresentationOfDatabaseValue:forPropertyNamed:
for default values.
Any chance you can add support for transactions?
Sometimes, it's very handy to have control over these when adding/updating items.
Regards,
Pieter
We should probably update the README because of #33.
Do you agree?
What does you think of adding an instancesOrderedBy
method? Right now I am doing this with a where query.
[Person instancesWhere:@"id order by `createdTime` desc"];
It would be nice if I could do:
[Person instancesOrderedBy:@"`createdTime` desc"];
I don't want to override +allInstances
because I sort my models differently depending on the case. What do you think?
I have an app that has an entity with an app-supplied primary key. A possible sequence goes like this:
MyModel *m1 = [MyModel instanceWithPrimaryKey:@"xyzzy"];
// ...
[m1 save];
// ...
[m1 delete];
// ... time passes
MyModel *m2 = [MyModel instanceWithPrimaryKey:@"xyzzy"];
// ...
[m2 save];
The [m2 save]
will frequently fail because the m1 instance ends up being in the cache and returned from [MyModel instanceWithPrimaryKey:@"xyzzy"]
. The save then fails because the instance is marked as deleted. Calling dataWasUpdatedExternally
after the delete doesn't help because the m1 instance recaches itself.
I think the solution is twofold:
If you think this approach is correct, I'll try it out and put together a pull request.
Before I even access any models, I get this error as I create the database schema:
FCModelDatabaseQueue has an open FMResultSet after inDatabase:
Why would this happen before I use any models?
Thanks!
Hello, I am having problem with cached instances of my models,
I create the initial instance with
[MyModel instanceWithPrimaryKey:ID]
After building the model, we perform some queries with
[MyModel inDatabaseSync:^(FMDatabase *db) {
[db executeUpdate: QUERY]
}];
updating some fields in the database, and call
[MyModel dataWasUpdatedExternally];
After these updates in another thread I call instanceWithPrimaryKey, and the model is returning with outdated data before the updates.
Is there any way around this?
Thanks
Hi there!
I'm using HEAD, and I'm experiencing a deadlock. Care to have a look?
#0 0x030737ca in __psynch_cvwait ()
#1 0x03038d1d in _pthread_cond_wait ()
#2 0x0303abd9 in pthread_cond_wait$UNIX2003 ()
#3 0x0094821b in -[__NSOperationInternal _waitUntilFinished:] ()
#4 0x0086f1c8 in -[NSOperation waitUntilFinished] ()
#5 0x008775ea in -[NSOperationQueue addOperations:waitUntilFinished:] ()
#6 0x000ef7f5 in -[FCModelDatabaseQueue execOnSelfSync:] at /Users/david/Development/geranium.iosapp/Pods/FCModel/FCModel/FCModelDatabaseQueue.m:56
#7 0x000efb43 in -[FCModelDatabaseQueue inDatabase:] at /Users/david/Development/geranium.iosapp/Pods/FCModel/FCModel/FCModelDatabaseQueue.m:75
#8 0x000dee9b in +[FCModel instanceFromDatabaseWithPrimaryKey:] at /Users/david/Development/geranium.iosapp/Pods/FCModel/FCModel/FCModel.m:189
#9 0x000de639 in +[FCModel instanceWithPrimaryKey:databaseRowValues:createIfNonexistent:] at /Users/david/Development/geranium.iosapp/Pods/FCModel/FCModel/FCModel.m:140
#10 0x000de226 in +[FCModel instanceWithPrimaryKey:] at /Users/david/Development/geranium.iosapp/Pods/FCModel/FCModel/FCModel.m:121
#0 0x03073802 in __psynch_mutexwait ()
#1 0x03039945 in _pthread_mutex_lock ()
#2 0x030397ac in pthread_mutex_lock ()
#3 0x0087db0d in -[NSRecursiveLock lock] ()
#4 0x008b9b28 in -[NSObject(NSKeyValueObserverRegistration) addObserver:forKeyPath:options:context:] ()
#5 0x000e66ab in __55-[FCModel initWithFieldValues:existsInDatabaseAlready:]_block_invoke at /Users/david/Development/geranium.iosapp/Pods/FCModel/FCModel/FCModel.m:646
#6 0x02a7da7a in __65-[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:]_block_invoke ()
#7 0x02a7d97e in -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:] ()
#8 0x029e2125 in -[NSDictionary enumerateKeysAndObjectsUsingBlock:] ()
#9 0x000e5d80 in -[FCModel initWithFieldValues:existsInDatabaseAlready:] at /Users/david/Development/geranium.iosapp/Pods/FCModel/FCModel/FCModel.m:609
#10 0x000df19b in __46+[FCModel instanceFromDatabaseWithPrimaryKey:]_block_invoke at /Users/david/Development/geranium.iosapp/Pods/FCModel/FCModel/FCModel.m:192
#11 0x000efc3c in __35-[FCModelDatabaseQueue inDatabase:]_block_invoke at /Users/david/Development/geranium.iosapp/Pods/FCModel/FCModel/FCModelDatabaseQueue.m:78
#12 0x000ef062 in -[FCModelDatabaseQueueOperation main] at /Users/david/Development/geranium.iosapp/Pods/FCModel/FCModel/FCModelDatabaseQueue.m:17
#13 0x00948c79 in -[__NSOperationInternal _start:] ()
#14 0x008c59c8 in -[NSOperation start] ()
#15 0x0094af44 in __NSOQSchedule_f ()
#16 0x02d084d0 in _dispatch_client_callout ()
#17 0x02cf6047 in _dispatch_queue_drain ()
#18 0x02cf5e42 in _dispatch_queue_invoke ()
#19 0x02cf6de2 in _dispatch_root_queue_drain ()
#20 0x02cf7127 in _dispatch_worker_thread2 ()
#21 0x03037dab in _pthread_wqthread ()
Is there a way you could do something like this?
[Person defaultOrder:@"ORDER BY pos"];
Then on queries such as allInstances
it would automatically sort the array by that?
Maybe it could work by just appending that string?
Thanks!
P.S. I am totally loving this so far & am planning to use this in an upcoming app!
Here's a quick rundown of the crash:
+dataWasUpdatedExternally
is called after a custom importing operationpostChangeNotification:changedFields:instance:
is called with the instance
parameter set to nil
.postChangeNotification:changedFields:instance:
, a nil
value on instance
results in a lock
initialized with NULL
.dispatch_semaphore_wait
is called with lock
at NULL
.I would have done a pull request, but I'm not sure how this should be fixed. Should we avoid calling -postChangeNotification:changedFields:instance:
when no instances are loaded?
Is there any reason that the user_version PRAGMA was not used for storing the schema version instead of storing it inside the metadata table? If not, might it not be preferable?
The concept of a model instance with a not-yet-set primary key is weird, and requires a lot of hacky special handling. Right now, to handle AUTOINCREMENT
, models may have unset primary keys from new
until a successful save
. In your app's various functions, there's no guarantee that any model instance actually has a primary key at the time you're operating on it (unless you check every time, which is tedious and error-prone).
SQLite's AUTOINCREMENT
documentation shows that its automatic rowid
pseudo-column is already fulfilling many of the same duties for reads, which is available in any query's ORDER BY
clause in FCModel already (and I could make it a property without a ton of effort).
I've also found that I've never actually used AUTOINCREMENT
. It's not well-suited to a world with sync and concurrency. In every case so far, random 64-bit integers or GUID strings have been the better choice. It's better to remove a bad option than to let people shoot themselves in the foot with it.
I'd like to remove AUTOINCREMENT
support and replace it with optional randomly generated 64-bit signed-integer primary keys. (FCModel subclasses could do their own thing, like GUID strings, if they wanted different key-generation behavior.) This would clean up the FCModel code a bit and prevent some of those weird potential bugs in usage.
Models would no longer be permitted to instantiate without any primary key value — you'd still use instanceWithPrimaryKey:
or instanceWithPrimaryKey:createIfNonexistent:
normally, but if you simply called new
, a random key value would be assigned that's unique among all existing values in the table and all unsaved values currently in memory. Modifying any model's primary-key value after instantiation would raise an exception.
What do you think?
The issue is that the comparison in FCModel.m:750
falsely flags the unchanged NSDate as different:
(lldb) p [(NSDate*)oldValue timeIntervalSince1970]
(double) $0 = 1396611801.1373141
(lldb) p [(NSDate*)newValue timeIntervalSince1970]
(double) $1 = 1396611801.1373141
(lldb) p [(NSDate*)newValue isEqual:(NSDate*)oldValue]
(char) $2 = '\0'
(lldb) p [(NSDate*)newValue isEqualToDate:(NSDate*)oldValue]
(char) $3 = '\0'
(lldb) p [(NSDate*)newValue timeIntervalSinceDate:(NSDate*)oldValue]
(double) $4 = -0.000000059604644775390625
I have added a unit test on a branch that shows this issue. I am currently working to identify a work around to what appears to be a rounding error somewhere.
See edits below. My console gets flooded with these errors:
2014-08-20 12:00:29.939 app[29675:4407] Unknown error calling sqlite3_step (19: column postId is not unique) eu
2014-08-20 12:00:29.940 app[29675:4407] DB Query: INSERT INTO "Link" ("last_utc","author","kind","url","created_utc","name","lastUpdated","customImgURL","recommendedIds","gilded","hasCustomImg","selfText","thumbnail","edited_utc","score","elapsed","categoryClass","likes","commentListId","title","numComments","sortedCommentListId","postId") VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
I'm manually creating Links with alloc/init, then using EasyMapping to fill them with data from a JSON response.
NSMutableArray *models = [NSMutableArray arrayWithCapacity:JSONArray.count];
NSArray* ids = [JSONArray valueForKeyPath:@"data.id"];
NSDictionary* existingLinks = [Link keyedInstancesWithPrimaryKeyValues:ids];
for (NSDictionary *JSONDictionary in JSONArray){
Link *model = nil;
if ([existingLinks objectForKey:JSONDictionary[@"data"][@"id"]]) {
model = [existingLinks objectForKey:JSONDictionary[@"data"][@"id"]];
}
else {
model = [[Link alloc] init];
model.postId = JSONDictionary[@"data"][@"id"];
}
[EKMapper fillObject:model fromExternalRepresentation:JSONDictionary[@"data"] withMapping:[self linktMapping]];
[models addObject:model];
[model save];
}
It does not appear that I can alloc/init and then specify the primary key. The PK I'm using comes from the server, and is guaranteed to be unique. It is not supposed to be generated on the client, so I cannot reliably override primaryKeyValueForNewInstance
, which it looks like init
calls.
So: how should I create new objects and specify a primary key without querying for them?
Edit: After more digging, the DB error comes from the fact that primaryKeyValueForNewInstance
is somehow creating duplicate keys...
Edit 2: Switching to [Link instanceWithPrimaryKey:JSONDictionary[@"data"][@"id"]];
does not stop the flood of these error messages...
I'm opening the connection to the database like the sample code shows and while running it through Instruments, I'm getting around ~3.7s for [FMResultSet next] under Time Profiler.
In my DB, I have 20 items, with negligible BLOB data and several attributes — nothing too fancy nor complicated.
My speculation is that I'm iterating through an NSArray that is the returned value of calling my model's -allInstances but it seems that operation should be performant.
I'm not sure if there is a spinlock occurring in [FMResultSet next] but I'm curious to hear if anyone has encountered this before.
Can we get a podspec update for the latest code?
Are there some rules around retaining FCModel objects that aren't in the documentation? I see crashes like this one:
Exception Type: SIGSEGV
Exception Codes: SEGV_ACCERR at 0x2000000c
Crashed Thread: 0
Application Specific Information:
objc_msgSend() selector name: set_rowValuesInDatabase:
Thread 0 Crashed:
0 libobjc.A.dylib 0x3b36f626 objc_msgSend + 6
1 podcasts 0x00166cd7 -[FCModel reload:] (FCModel.m:710)
2 CoreFoundation 0x30bdd1f1 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 10
3 CoreFoundation 0x30b5153f _CFXNotificationPost + 1716
4 Foundation 0x3153ba3d -[NSNotificationCenter postNotificationName:object:userInfo:] + 74
5 podcasts 0x00163671 __35+[FCModel dataWasUpdatedExternally]_block_invoke (FCModel.m:203)
6 libdispatch.dylib 0x3b84d833 _dispatch_call_block_and_release + 8
7 libdispatch.dylib 0x3b84d81f _dispatch_client_callout + 20
8 libdispatch.dylib 0x3b85449f _dispatch_main_queue_callback_4CF$VARIANT$mp + 276
9 CoreFoundation 0x30be58f1 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 6
10 CoreFoundation 0x30be41c5 __CFRunLoopRun + 1298
11 CoreFoundation 0x30b4ef0f CFRunLoopRunSpecific + 520
12 CoreFoundation 0x30b4ecf3 CFRunLoopRunInMode + 104
13 GraphicsServices 0x35a70663 GSEventRunModal + 136
14 UIKit 0x3349a16d UIApplicationMain + 1134
15 podcasts 0x00043ee7 main (main.m:14)
16 libdyld.dylib 0x3b872ab7 start + 0
and it's cousin:
Exception Type: SIGSEGV
Exception Codes: SEGV_ACCERR at 0x7000000c
Crashed Thread: 0
Application Specific Information:
objc_msgSend() selector name: _rowValuesInDatabase
Thread 0 Crashed:
0 libobjc.A.dylib 0x3a958626 objc_msgSend + 6
1 podcasts 0x0013c849 __25-[FCModel unsavedChanges]_block_invoke (FCModel.m:790)
2 CoreFoundation 0x30012093 __65-[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:]_block_invoke + 96
3 CoreFoundation 0x30011fb7 -[__NSDictionaryM enumerateKeysAndObjectsWithOptions:usingBlock:] + 160
4 podcasts 0x0013c767 -[FCModel unsavedChanges] (FCModel.m:787)
5 podcasts 0x0013bdc5 -[FCModel reload:] (FCModel.m:714)
6 CoreFoundation 0x300981f1 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 10
7 CoreFoundation 0x3000c53f _CFXNotificationPost + 1716
8 Foundation 0x309f6a3d -[NSNotificationCenter postNotificationName:object:userInfo:] + 74
9 podcasts 0x00138671 __35+[FCModel dataWasUpdatedExternally]_block_invoke (FCModel.m:203)
10 libdispatch.dylib 0x3ae36833 _dispatch_call_block_and_release + 8
11 libdispatch.dylib 0x3ae3681f _dispatch_client_callout + 20
12 libdispatch.dylib 0x3ae3d49f _dispatch_main_queue_callback_4CF$VARIANT$mp + 276
13 CoreFoundation 0x300a08f1 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 6
14 CoreFoundation 0x3009f1c5 __CFRunLoopRun + 1298
15 CoreFoundation 0x30009f0f CFRunLoopRunSpecific + 520
16 CoreFoundation 0x30009cf3 CFRunLoopRunInMode + 104
17 GraphicsServices 0x34f62663 GSEventRunModal + 136
18 UIKit 0x3295516d UIApplicationMain + 1134
19 podcasts 0x00018ee7 main (main.m:14)
20 libdyld.dylib 0x3ae5bab7 start + 0
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.