- Update
This commit is contained in:
1 parent
d281d765ea
commit
5b074a5176
1261 files changed
+199158
-7303
No files matched your search
@@ -0,0 +1,167 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
/**
|
||||
* This class provides an optional base class that may be used to implement
|
||||
* a CoreDataStorage class for an xmpp extension (or perhaps any core data storage class).
|
||||
*
|
||||
* It operates on its own dispatch queue which allows it to easily provide storage for multiple extension instance.
|
||||
* More importantly, it smartly buffers its save operations to maximize performance!
|
||||
*
|
||||
* It does this using two techniques:
|
||||
*
|
||||
* First, it monitors the number of pending requests.
|
||||
* When a operation is requested of the class, it increments an atomic variable, and schedules the request.
|
||||
* After the request has been processed, it decrements the atomic variable.
|
||||
* At this point it knows if there are other pending requests,
|
||||
* and it uses the information to decide if it should save now,
|
||||
* or postpone the save operation until the pending requests have been executed.
|
||||
*
|
||||
* Second, it monitors the number of unsaved changes.
|
||||
* Since NSManagedObjectContext retains any changed objects until they are saved to disk
|
||||
* it is an important memory management concern to keep the number of changed objects within a healthy range.
|
||||
* This class uses a configurable saveThreshold to save at appropriate times.
|
||||
*
|
||||
* This class also offers several useful features such as
|
||||
* preventing multiple instances from using the same database file (conflict)
|
||||
* and caching of xmppStream.myJID to improve performance.
|
||||
*
|
||||
* For more information on how to extend this class,
|
||||
* please see the XMPPCoreDataStorageProtected.h header file.
|
||||
*
|
||||
* The framework comes with several classes that extend this base class such as:
|
||||
* - XMPPRosterCoreDataStorage (Extensions/Roster)
|
||||
* - XMPPCapabilitiesCoreDataStorage (Extensions/XEP-0115)
|
||||
* - XMPPvCardCoreDataStorage (Extensions/XEP-0054)
|
||||
*
|
||||
* Feel free to skim over these as reference implementations.
|
||||
**/
|
||||
|
||||
@interface XMPPCoreDataStorage : NSObject {
|
||||
@private
|
||||
|
||||
NSMutableDictionary *myJidCache;
|
||||
|
||||
int32_t pendingRequests;
|
||||
|
||||
NSManagedObjectModel *managedObjectModel;
|
||||
NSPersistentStoreCoordinator *persistentStoreCoordinator;
|
||||
NSManagedObjectContext *managedObjectContext;
|
||||
NSManagedObjectContext *mainThreadManagedObjectContext;
|
||||
|
||||
NSMutableArray *willSaveManagedObjectContextBlocks;
|
||||
NSMutableArray *didSaveManagedObjectContextBlocks;
|
||||
|
||||
@protected
|
||||
|
||||
NSString *databaseFileName;
|
||||
NSDictionary *storeOptions;
|
||||
NSUInteger saveThreshold;
|
||||
NSUInteger saveCount;
|
||||
|
||||
BOOL autoRemovePreviousDatabaseFile;
|
||||
BOOL autoRecreateDatabaseFile;
|
||||
BOOL autoAllowExternalBinaryDataStorage;
|
||||
|
||||
dispatch_queue_t storageQueue;
|
||||
void *storageQueueTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a core data storage instance, backed by SQLite, with the given database store filename.
|
||||
* It is recommended your database filname use the "sqlite" file extension (e.g. "XMPPRoster.sqlite").
|
||||
* If you pass nil, a default database filename is automatically used.
|
||||
* This default is derived from the classname,
|
||||
* meaning subclasses will get a default database filename derived from the subclass classname.
|
||||
*
|
||||
* If you attempt to create an instance of this class with the same databaseFileName as another existing instance,
|
||||
* this method will return nil.
|
||||
**/
|
||||
- (id)initWithDatabaseFilename:(NSString *)databaseFileName storeOptions:(NSDictionary *)storeOptions;
|
||||
|
||||
/**
|
||||
* Initializes a core data storage instance, backed by an in-memory store.
|
||||
**/
|
||||
- (id)initWithInMemoryStore;
|
||||
|
||||
/**
|
||||
* Readonly access to the databaseFileName used during initialization.
|
||||
* If nil was passed to the init method, returns the actual databaseFileName being used (the default filename).
|
||||
**/
|
||||
@property (readonly) NSString *databaseFileName;
|
||||
|
||||
/**
|
||||
* Readonly access to the databaseOptions used during initialization.
|
||||
* If nil was passed to the init method, returns the actual databaseOptions being used (the default databaseOptions).
|
||||
**/
|
||||
@property (readonly) NSDictionary *storeOptions;
|
||||
|
||||
/**
|
||||
* The saveThreshold specifies the maximum number of unsaved changes to NSManagedObjects before a save is triggered.
|
||||
*
|
||||
* Since NSManagedObjectContext retains any changed objects until they are saved to disk
|
||||
* it is an important memory management concern to keep the number of changed objects within a healthy range.
|
||||
*
|
||||
* Default 500
|
||||
**/
|
||||
@property (readwrite) NSUInteger saveThreshold;
|
||||
|
||||
/**
|
||||
* Provides access to the the thread-safe components of the CoreData stack.
|
||||
*
|
||||
* Please note:
|
||||
* The managedObjectContext is private to the storageQueue.
|
||||
* If you're on the main thread you can use the mainThreadManagedObjectContext.
|
||||
* Otherwise you must create and use your own managedObjectContext.
|
||||
*
|
||||
* If you think you can simply add a property for the private managedObjectContext,
|
||||
* then you need to go read the documentation for core data,
|
||||
* specifically the section entitled "Concurrency with Core Data".
|
||||
*
|
||||
* @see mainThreadManagedObjectContext
|
||||
**/
|
||||
@property (strong, readonly) NSManagedObjectModel *managedObjectModel;
|
||||
@property (strong, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
|
||||
|
||||
/**
|
||||
* Convenience method to get a managedObjectContext appropriate for use on the main thread.
|
||||
* This context should only be used from the main thread.
|
||||
*
|
||||
* NSManagedObjectContext is a light-weight thread-UNsafe component of the CoreData stack.
|
||||
* Thus a managedObjectContext should only be accessed from a single thread, or from a serialized queue.
|
||||
*
|
||||
* A managedObjectContext is associated with a persistent store.
|
||||
* In most cases the persistent store is an sqlite database file.
|
||||
* So think of a managedObjectContext as a thread-specific cache for the underlying database.
|
||||
*
|
||||
* This method lazily creates a proper managedObjectContext,
|
||||
* associated with the persistent store of this instance,
|
||||
* and configured to automatically merge changesets from other threads.
|
||||
**/
|
||||
@property (strong, readonly) NSManagedObjectContext *mainThreadManagedObjectContext;
|
||||
|
||||
/**
|
||||
* The Previous Database File is removed before creating a persistant store.
|
||||
*
|
||||
* Default NO
|
||||
**/
|
||||
|
||||
@property (readwrite) BOOL autoRemovePreviousDatabaseFile;
|
||||
|
||||
/**
|
||||
* The Database File is automatically recreated if the persistant store cannot read it e.g. the model changed or the file became corrupt.
|
||||
* For greater control overide didNotAddPersistentStoreWithPath:
|
||||
*
|
||||
* Default NO
|
||||
**/
|
||||
@property (readwrite) BOOL autoRecreateDatabaseFile;
|
||||
|
||||
/**
|
||||
* This method calls setAllowsExternalBinaryDataStorage:YES for all Binary Data Attributes in the Managed Object Model.
|
||||
* On OS Versions that do not support external binary data storage, this property does nothing.
|
||||
*
|
||||
* Default NO
|
||||
**/
|
||||
@property (readwrite) BOOL autoAllowExternalBinaryDataStorage;
|
||||
|
||||
@end
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,334 @@
|
||||
#import "XMPPCoreDataStorage.h"
|
||||
|
||||
@class XMPPJID;
|
||||
@class XMPPStream;
|
||||
|
||||
/**
|
||||
* The methods in this class are to be used ONLY by subclasses of XMPPCoreDataStorage.
|
||||
**/
|
||||
|
||||
@interface XMPPCoreDataStorage (Protected)
|
||||
|
||||
#pragma mark Override Me
|
||||
|
||||
/**
|
||||
* If your subclass needs to do anything for init, it can do so easily by overriding this method.
|
||||
* All public init methods will invoke this method at the end of their implementation.
|
||||
*
|
||||
* Important: If overriden you must invoke [super commonInit] at some point.
|
||||
**/
|
||||
- (void)commonInit;
|
||||
|
||||
/**
|
||||
* Override me, if needed, to provide customized behavior.
|
||||
*
|
||||
* This method is queried to get the name of the ManagedObjectModel within a bundle.
|
||||
* It should return the name of the appropriate file (*.xdatamodel / *.mom / *.momd) sans file extension.
|
||||
*
|
||||
* The default implementation returns the name of the subclass, stripping any suffix of "CoreDataStorage".
|
||||
* E.g., if your subclass was named "XMPPExtensionCoreDataStorage", then this method would return "XMPPExtension".
|
||||
*
|
||||
* Note that a file extension should NOT be included.
|
||||
**/
|
||||
- (NSString *)managedObjectModelName;
|
||||
|
||||
|
||||
/**
|
||||
* Override me, if needed, to provide customized behavior.
|
||||
*
|
||||
* This method is queried to get the bundle containing the ManagedObjectModel.
|
||||
**/
|
||||
- (NSBundle *)managedObjectModelBundle;
|
||||
|
||||
/**
|
||||
* Override me, if needed, to provide customized behavior.
|
||||
*
|
||||
* This method is queried if the initWithDatabaseFileName:storeOptions: method is invoked with a nil parameter for databaseFileName.
|
||||
* The default implementation returns:
|
||||
*
|
||||
* [NSString stringWithFormat:@"%@.sqlite", [self managedObjectModelName]];
|
||||
*
|
||||
* You are encouraged to use the sqlite file extension.
|
||||
**/
|
||||
- (NSString *)defaultDatabaseFileName;
|
||||
|
||||
|
||||
/**
|
||||
* Override me, if needed, to provide customized behavior.
|
||||
*
|
||||
* This method is queried if the initWithDatabaseFileName:storeOptions method is invoked with a nil parameter for storeOptions.
|
||||
* The default implementation returns the following:
|
||||
*
|
||||
* @{ NSMigratePersistentStoresAutomaticallyOption: @(YES),
|
||||
* NSInferMappingModelAutomaticallyOption : @(YES) };
|
||||
**/
|
||||
- (NSDictionary *)defaultStoreOptions;
|
||||
|
||||
/**
|
||||
* Override me, if needed, to provide customized behavior.
|
||||
*
|
||||
* If you are using a database file with pure non-persistent data (e.g. for memory optimization purposes on iOS),
|
||||
* you may want to delete the database file if it already exists on disk.
|
||||
*
|
||||
* If this instance was created via initWithDatabaseFilename, then the storePath parameter will be non-nil.
|
||||
* If this instance was created via initWithInMemoryStore, then the storePath parameter will be nil.
|
||||
*
|
||||
* The default implementation does nothing.
|
||||
**/
|
||||
- (void)willCreatePersistentStoreWithPath:(NSString *)storePath options:(NSDictionary *)storeOptions;
|
||||
|
||||
/**
|
||||
* Override me, if needed, to completely customize the persistent store.
|
||||
*
|
||||
* Adds the persistent store path to the persistent store coordinator.
|
||||
* Returns true if the persistent store is created.
|
||||
*
|
||||
* If this instance was created via initWithDatabaseFilename, then the storePath parameter will be non-nil.
|
||||
* If this instance was created via initWithInMemoryStore, then the storePath parameter will be nil.
|
||||
**/
|
||||
- (BOOL)addPersistentStoreWithPath:(NSString *)storePath options:(NSDictionary *)storeOptions error:(NSError **)errorPtr;
|
||||
|
||||
/**
|
||||
* Override me, if needed, to provide customized behavior.
|
||||
*
|
||||
* For example, if you are using the database for non-persistent data and the model changes, you may want
|
||||
* to delete the database file if it already exists on disk and a core data migration is not worthwhile.
|
||||
*
|
||||
* If this instance was created via initWithDatabaseFilename, then the storePath parameter will be non-nil.
|
||||
* If this instance was created via initWithInMemoryStore, then the storePath parameter will be nil.
|
||||
*
|
||||
* The default implementation simply writes to the XMPP error log.
|
||||
**/
|
||||
- (void)didNotAddPersistentStoreWithPath:(NSString *)storePath options:(NSDictionary *)storeOptions error:(NSError *)error;
|
||||
|
||||
/**
|
||||
* Override me, if needed, to provide customized behavior.
|
||||
*
|
||||
* For example, you may want to perform cleanup of any non-persistent data before you start using the database.
|
||||
*
|
||||
* The default implementation does nothing.
|
||||
**/
|
||||
- (void)didCreateManagedObjectContext;
|
||||
|
||||
/**
|
||||
* Override me if you need to do anything special just before changes are saved to disk.
|
||||
*
|
||||
* This method will be invoked on the storageQueue.
|
||||
* The default implementation does nothing.
|
||||
**/
|
||||
- (void)willSaveManagedObjectContext;
|
||||
|
||||
/**
|
||||
* Override me if you need to do anything special after changes have been saved to disk.
|
||||
*
|
||||
* This method will be invoked on the storageQueue.
|
||||
* The default implementation does nothing.
|
||||
**/
|
||||
- (void)didSaveManagedObjectContext;
|
||||
|
||||
/**
|
||||
* This method will be invoked on the main thread,
|
||||
* after the mainThreadManagedObjectContext has merged changes from another context.
|
||||
*
|
||||
* This method may be useful if you have code dependent upon when changes the datastore hit the user interface.
|
||||
* For example, you want to play a sound when a message is received.
|
||||
* You could play the sound right away, from the background queue, but the timing may be slightly off because
|
||||
* the user interface won't update til the changes have been saved to disk,
|
||||
* and then propogated to the managedObjectContext of the main thread.
|
||||
* Alternatively you could set a flag, and then hook into this method
|
||||
* to play the sound at the exact moment the propogation hits the main thread.
|
||||
*
|
||||
* The default implementation does nothing.
|
||||
**/
|
||||
- (void)mainThreadManagedObjectContextDidMergeChanges;
|
||||
|
||||
#pragma mark Setup
|
||||
|
||||
/**
|
||||
* This is the standard configure method used by xmpp extensions to configure a storage class.
|
||||
*
|
||||
* Feel free to override this method if needed,
|
||||
* and just invoke super at some point to make sure everything is kosher at this level as well.
|
||||
*
|
||||
* Note that the default implementation allows the storage class to be used by multiple xmpp streams.
|
||||
* If you design your storage class to be used by a single stream, then you should implement this method
|
||||
* to ensure that your class can only be configured by one parent.
|
||||
* If you do, again, don't forget to invoke super at some point.
|
||||
**/
|
||||
- (BOOL)configureWithParent:(id)aParent queue:(dispatch_queue_t)queue;
|
||||
|
||||
#pragma mark Stream JID caching
|
||||
|
||||
/**
|
||||
* This class provides a caching service for xmppStream.myJID to avoid constantly querying the xmppStream for it.
|
||||
*
|
||||
* The motivation behind this is the fact that to query the xmppStream for its myJID
|
||||
* requires going through the xmppStream's internal dispatch queue. (A dispatch_sync).
|
||||
* It's not necessarily that this is an expensive operation,
|
||||
* but storage classes sometimes require this information for just about every operation they perform.
|
||||
* For a variable that changes infrequently, caching the value can reduce some overhead.
|
||||
* In addition, if we can stay out of xmppStream's internal dispatch queue,
|
||||
* we free it to perform more xmpp processing tasks.
|
||||
*
|
||||
* If the xmppStream.myJID changes, the cache will automatically be updated.
|
||||
*
|
||||
* If you store any variant of xmppStream.myJID (bare, full, domain, etc) in your database
|
||||
* you are strongly encouraged to use the caching service.
|
||||
*
|
||||
* For example, say you're implementing a core data storage caching mechanism for Private XML Storage (XEP-0049).
|
||||
* The data you're caching is explictly tied to the stream's bare myJID. ([xmppStream.myJID bare])
|
||||
* You want your storage class to support multiple xmpp streams,
|
||||
* so you add a field to the database called streamBareJidStr (or whatever).
|
||||
* Given an xmppStream, you can use the built-in cache to quickly get the xmppStream.myJid property:
|
||||
*
|
||||
* [self myJidForXMPPStream:stream]
|
||||
*
|
||||
* This method will retrieve the myJID property of the given xmppStream the first time,
|
||||
* and then cache it for future lookups. The cache is automatically updated if the xmppStream.myJID ever changes.
|
||||
**/
|
||||
- (XMPPJID *)myJIDForXMPPStream:(XMPPStream *)stream;
|
||||
|
||||
/**
|
||||
* This method is invoked if the cached myJID changes for a particular xmpp stream.
|
||||
*
|
||||
* This method works in correlation with the myJIDForXMPPStream method.
|
||||
* In other words, calling myJIDForXMPPStream will cache the value.
|
||||
* If that value later changes, this method is invoked.
|
||||
*
|
||||
* So if the myJID of an xmpp stream changes, but there was no cached value for that xmpp stream,
|
||||
* then this method is never called. E.g. this method is only called for streams we're actually interested in.
|
||||
*
|
||||
* You may wish to override this method if your storage class prefetches data related to the current user.
|
||||
*
|
||||
* This method will be invoked on the storageQueue.
|
||||
* The default implementation does nothing.
|
||||
**/
|
||||
- (void)didChangeCachedMyJID:(XMPPJID *)cachedMyJID forXMPPStream:(XMPPStream *)stream;
|
||||
|
||||
#pragma mark Core Data
|
||||
|
||||
/**
|
||||
* The standard persistentStoreDirectory method.
|
||||
**/
|
||||
- (NSString *)persistentStoreDirectory;
|
||||
|
||||
/**
|
||||
* Provides access to the managedObjectContext.
|
||||
*
|
||||
* Keep in mind that NSManagedObjectContext is NOT thread-safe.
|
||||
* So you can ONLY access this property from within the context of the storageQueue.
|
||||
*
|
||||
* Important:
|
||||
* The primary purpose of this class is to optimize disk IO by buffering save operations to the managedObjectContext.
|
||||
* It does this using the methods outlined in the 'Performance Optimizations' section below.
|
||||
* If you manually save the managedObjectContext you are destroying these optimizations.
|
||||
* See the documentation for executeBlock & scheduleBlock below for proper usage surrounding the optimizations.
|
||||
**/
|
||||
@property (readonly) NSManagedObjectContext *managedObjectContext;
|
||||
|
||||
#pragma mark Performance Optimizations
|
||||
|
||||
/**
|
||||
* Queries the managedObjectContext to determine the number of unsaved managedObjects.
|
||||
**/
|
||||
- (NSUInteger)numberOfUnsavedChanges;
|
||||
|
||||
/**
|
||||
* You will not often need to manually call this method.
|
||||
* It is called automatically, at appropriate and optimized times, via the executeBlock and scheduleBlock methods.
|
||||
*
|
||||
* The one exception to this is when you are inserting/deleting/updating a large number of objects in a loop.
|
||||
* It is recommended that you invoke save from within the loop.
|
||||
* E.g.:
|
||||
*
|
||||
* NSUInteger unsavedCount = [self numberOfUnsavedChanges];
|
||||
* for (NSManagedObject *obj in fetchResults)
|
||||
* {
|
||||
* [[self managedObjectContext] deleteObject:obj];
|
||||
*
|
||||
* if (++unsavedCount >= saveThreshold)
|
||||
* {
|
||||
* [self save];
|
||||
* unsavedCount = 0;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* See also the documentation for executeBlock and scheduleBlock below.
|
||||
**/
|
||||
- (void)save; // Read the comments above !
|
||||
|
||||
/**
|
||||
* You will rarely need to manually call this method.
|
||||
* It is called automatically, at appropriate and optimized times, via the executeBlock and scheduleBlock methods.
|
||||
*
|
||||
* This method makes informed decisions as to whether it should save the managedObjectContext changes to disk.
|
||||
* Since this disk IO is a slow process, it is better to buffer writes during high demand.
|
||||
* This method takes into account the number of pending requests waiting on the storage instance,
|
||||
* as well as the number of unsaved changes (which reside in NSManagedObjectContext's internal memory).
|
||||
*
|
||||
* Please see the documentation for executeBlock and scheduleBlock below.
|
||||
**/
|
||||
- (void)maybeSave; // Read the comments above !
|
||||
|
||||
/**
|
||||
* This method synchronously invokes the given block (dispatch_sync) on the storageQueue.
|
||||
*
|
||||
* Prior to dispatching the block it increments (atomically) the number of pending requests.
|
||||
* After the block has been executed, it decrements (atomically) the number of pending requests,
|
||||
* and then invokes the maybeSave method which implements the logic behind the optimized disk IO.
|
||||
*
|
||||
* If you use the executeBlock and scheduleBlock methods for all your database operations,
|
||||
* you will automatically inherit optimized disk IO for free.
|
||||
*
|
||||
* If you manually invoke [managedObjectContext save:] you are destroying the optimizations provided by this class.
|
||||
*
|
||||
* The block handed to this method is automatically wrapped in a NSAutoreleasePool,
|
||||
* so there is no need to create these yourself as this method automatically handles it for you.
|
||||
*
|
||||
* The architecture of this class purposefully puts the CoreDataStorage instance on a separate dispatch_queue
|
||||
* from the parent XmppExtension. Not only does this allow a single storage instance to service multiple extension
|
||||
* instances, but it provides the mechanism for the disk IO optimizations. The theory behind the optimizations
|
||||
* is to delay a save of the data (a slow operation) until the storage class is no longer being used. With xmpp
|
||||
* it is often the case that a burst of data causes a flurry of queries and/or updates for a storage class.
|
||||
* Thus the theory is to delay the slow save operation until later when the flurry has ended and the storage
|
||||
* class no longer has any pending requests.
|
||||
*
|
||||
* This method is designed to be invoked from within the XmppExtension storage protocol methods.
|
||||
* In other words, it is expecting to be invoked from a dispatch_queue other than the storageQueue.
|
||||
* If you attempt to invoke this method from within the storageQueue, an exception is thrown.
|
||||
* Therefore care should be taken when designing your implementation.
|
||||
* The recommended procedure is as follows:
|
||||
*
|
||||
* All of the methods that implement the XmppExtension storage protocol invoke either executeBlock or scheduleBlock.
|
||||
* However, none of these methods invoke each other (they are only to be invoked from the XmppExtension instance).
|
||||
* Instead, create internal utility methods that may be invoked.
|
||||
*
|
||||
* For an example, see the XMPPRosterCoreDataStorage implementation's _userForJID:xmppStream: method.
|
||||
**/
|
||||
- (void)executeBlock:(dispatch_block_t)block;
|
||||
|
||||
/**
|
||||
* This method asynchronously invokes the given block (dispatch_async) on the storageQueue.
|
||||
*
|
||||
* It works very similarly to the executeBlock method.
|
||||
* See the executeBlock method above for a full discussion.
|
||||
**/
|
||||
- (void)scheduleBlock:(dispatch_block_t)block;
|
||||
|
||||
/**
|
||||
* Sometimes you want to call a method before calling save on a Managed Object Context e.g. willSaveObject:
|
||||
*
|
||||
* addWillSaveManagedObjectContextBlock allows you to add a block of code to be called before saving a Managed Object Context,
|
||||
* without the overhead of having to call save at that moment.
|
||||
**/
|
||||
- (void)addWillSaveManagedObjectContextBlock:(void (^)(void))willSaveBlock;
|
||||
|
||||
/**
|
||||
* Sometimes you want to call a method after calling save on a Managed Object Context e.g. didSaveObject:
|
||||
*
|
||||
* addDidSaveManagedObjectContextBlock allows you to add a block of code to be after saving a Managed Object Context,
|
||||
* without the overhead of having to call save at that moment.
|
||||
**/
|
||||
- (void)addDidSaveManagedObjectContextBlock:(void (^)(void))didSaveBlock;
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user