NanoStore 是什么?
NanoStore 是一个开源、轻量级的无模式本地键值文档存储系统,用 Objective-C 编写,适用于 Mac OS X 和 iOS。
关系型数据库通常对您的数据结构有丰富的理解,但需要事先进行一些规划,并需要一些维护工作。NanoStore 提供了键值文档存储系统的灵活性,但同时也理解您的数据。因为数据基于键值,所以可以快速访问,并且可以根据需要无限扩展...这一切都无需担心模式。
主要优势
v2.5 - 2013年1月1日
从 v2.5 开始,plist 机制已被 NSKeyedArchiver 替换。原因有几个:它更紧凑,更快,使用内存更少。最重要的原因可能是它开启了存储其他数据类型的可能性。
现在支持 NSNull。感谢 Wanny (https://github.com/mrwanny) 抽出时间改进 NanoStore 的这一部分。
构建 NanoStore 非常简单。只需按照以下步骤操作
1) Download NanoStore
2) Open the NanoStore.xcodeproj file
3) Select Universal > My Mac 64-bit or 32-bit from the Scheme popup
4) Build (Command-B)
现在您应该在 NanoStore 项目目录中有一个新的 发布 目录,其中包含通用的静态库(armv6/armv7/i386)以及头文件。要将其添加到您的项目中,请执行以下操作
1) Drag the Distribution directory to the Project Navigator panel
2) Include #import "NanoStore.h" in your code
您还必须激活 LLVM 的 "程序流程测量" 设置
用法示例
#import "NanoStore.h"
@implementation MyDemoAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Instantiate a NanoStore and open it
NSFNanoStore *nanoStore = [NSFNanoStore createAndOpenStoreWithType:NSFMemoryStoreType path:nil error:nil];
...
If you want to add a dependency between your project and NanoStore so that it gets automatically rebuilt when
you update NanoStore, do the following (we'll assume your app is called "MyDemoApp"):
1) Select the MyDemoApp project in the Project Navigator
2) Select the MyDemoApp target
3) Expand the Target Dependencies box
4) Click "+" and add NanoStore
NanoStore 数据的基本单元称为 NanoObject。NanoObject 是符合 NSFNanoObjectProtocol
协议的任何对象。
在核心上,一个 NanoObject 仅仅是围绕两个属性的包装
该字典必须是可序列化的,这意味着只允许以下数据类型
(*) The data type NSData is allowed, but it will be excluded from the indexing process.
要保存和检索文档存储中的对象,NanoStore通过将其封装在NanoObjects中来移动数据。为了存储对象在NanoStore中,开发人员有三个选项
NSFNanoObject
类NSFNanoObject
扩展您的自定义类NSFNanoObjectProtocol
协议扩展您的自定义类无论您选择哪条路径,NanoStore都可以无缝地存储和检索文档存储中的对象。这个系统的美妙之处在于,NanoStore以存储对象的方式返回对象,即实例化原始存储的类的对象。
If the document store is opened by another application that doesn't implement the object that was stored, NanoStore
will instantiate a NSFNanoObject instead, thus allowing the app to retrieve the data seamlessly. If the object is then
updated by this application, the original class name will be honored.
App A stores an object of class Car.
App B retrieves the object, but since it doesn't know anything about the class Car, NanoStore returns a NSFNanoObject.
App B updates the object, with additional information. NanoStore saves it as a Car, not as a NSFNanoObject.
App A retrieves the updated object as a Car object, in exactly the same format as it was originally stored.
NanoStore中有三种可用的文档存储类型:内存中、临时和基于文件的。这些文档存储类型由NSFNanoStoreType
类型定义
NSFMemoryStoreType
Create the transient backing store in RAM. Its contents are lost when the process exits. Fastest, uses more RAM (*).
NSFTemporaryStoreType
Create a transient temporary backing store on disk. Its contents are lost when the process exits. Slower, uses less
RAM than NSFMemoryStoreType.
NSFPersistentStoreType
Create a persistent backing store on disk. Slower, uses less RAM than NSFMemoryStoreType (*).
Until the limit set by NSFNanoEngine's - (NSUInteger)cacheSize has been reached, memory usage would be the same for
in-memory and on-disk stores. When the size of the store grows beyond - (NSUInteger)cacheSize in-memory stores start to
consume more memory than on-disk ones, because it has nowhere to push pages out of the cache.
Typically, most developers may want to create and open the document store. To do that, use the following method:
+ (NSFNanoStore *)createAndOpenStoreWithType:(NSFNanoStoreType)aType path:(NSString *)aPath error:(out NSError **)outError
// Instantiate an in-memory document store and open it. The path parameter is unused.
NSFNanoStore *nanoStore = [NSFNanoStore createAndOpenStoreWithType:NSFMemoryStoreType path:nil error:nil];
// Instantiate a temporary document store and open it. The path parameter is unused.
NSFNanoStore *nanoStore = [NSFNanoStore createAndOpenStoreWithType:NSFTemporaryStoreType path:nil error:nil];
// Instantiate a file-based document store and open it. The path parameter must be specified.
NSString *thePath = @"~/Desktop/myDatabase.database";
NSFNanoStore *nanoStore = [NSFNanoStore createAndOpenStoreWithType:NSFPersistentStoreType path:thePath error:nil];
In the case of file-based document stores, the file gets created automatically if it doesn't exist and then opened. If
it already exists, it gets opened and made available for use right away. There are instances where you may want to
fine-tune the engine. Tuning the engine has to be performed before the document store is opened. Another method is
available in NSFNanoStore for this purpose:
+ (NSFNanoStore *)createStoreWithType:(NSFNanoStoreType)theType path:(NSString *)thePath.
// Instantiate a file-based document store but don't open it right away. The path parameter must be specified.
NSString *thePath = @"~/Desktop/myDatabase.database";
NSFNanoStore *nanoStore = [NSFNanoStore createStoreWithType:NSFPersistentStoreType path:thePath error:nil];
// Obtain the engine
NSFNanoEngine *nanoStoreEngine = [nanoStore nanoStoreEngine];
// Set the synchronous mode setting
[nanoStoreEngine setSynchronousMode:SynchronousModeOff];
[nanoStoreEngine setEncodingType:NSFEncodingUTF16];
// Open the document store
[nanoStore openWithError:nil];
Check the section Performance Tips below for important information about how to get the most out of NanoStore.
NanoStore可以对NanoObject执行三种基本操作
要添加对象,实例化一个NSFNanoObject
,填充它并将其添加到文档存储。
// Instantiate a NanoStore and open it
NSFNanoStore *nanoStore = [NSFNanoStore createAndOpenStoreWithType:NSFMemoryStoreType path:nil error:nil];
// Generate an empty NanoObject
NSFNanoObject *object = [NSFNanoObject nanoObject];
// Add some data
[object setObject:@"Doe" forKey:@"kLastName"];
[object setObject:@"John" forKey:@"kFirstName"];
[object setObject:[NSArray arrayWithObjects:@"[email protected]", @"[email protected]", nil] forKey:@"kEmails"];
// Add it to the document store
[nanoStore addObject:object error:nil];
// Close the document store
[nanoStore closeWithError:nil];
或者,您可以通过提供字典来实例化NanoObject
+ (NSFNanoObject*)nanoObjectWithDictionary:(NSDictionary *)theDictionary
NanoStore将在实例化NanoObject时自动分配UUID。这意味着从NanoObject请求数据时的键将返回有效的UUID。对于从NSFNanoObject
继承的对象也相同。然而,实现NSFNanoObjectProtocol
协议的类应确保通过返回有效的键
- (NSString *)nanoObjectKey
If an attempt is made to add or remove an object without a valid key, an exception of type NSFNanoObjectBehaviorException
will be raised. To update an object, simply modify the object and add it to the document store. NanoStore will replace
the existing object with the one being added.
// Instantiate and open a NanoStore
NSFNanoStore *nanoStore = [NSFNanoStore createAndOpenStoreWithType:NSFMemoryStoreType path:nil error:nil];
// Assuming the dictionary exists, instantiate a NanoObject
NSDictionary *info = ...;
NSFNanoObject *object = [NSFNanoObject nanoObjectWithDictionary:info];
// Add the NanoObject to the document store
[nanoStore addObject:object error:nil];
// Update the NanoObject with new data
[object setObject:@"foo" forKey:@"SomeKey"];
// Update the NanoObject in the document store
[nanoStore addObject:object error:nil];
要删除对象,有几种选项可用。最常用的方法可以在NSFNanoStore中找到
- (BOOL)removeObject:(id <NSFNanoObjectProtocol>)theObject error:(out NSError **)outError
- (BOOL)removeObjectsWithKeysInArray:(NSArray *)theKeys error:(out NSError **)outError
- (BOOL)removeObjectsInArray:(NSArray *)theObjects error:(out NSError **)outError
// Instantiate and open a NanoStore
NSFNanoStore *nanoStore = [NSFNanoStore createAndOpenStoreWithType:NSFMemoryStoreType path:nil error:nil];
// Assuming the dictionary exists, instantiate a NanoObject
NSDictionary *info = ...;
NSFNanoObject *object = [NSFNanoObject nanoObjectWithDictionary:info];
// Add the NanoObject to the document store
[nanoStore addObject:object error:nil];
// Remove the object
[nanoStore removeObject:object error:nil];
// ... or you could pass the key instead
[nanoStore removeObjectsWithKeysInArray:[NSArray arrayWithObject:[object nanoObjectKey]] error:nil];
大多数数据库解决方案强制开发者在一维空间(行和列)中思考,迫使开发者提前规划架构。这种情况并不理想,因为在大多数情况下,架构优化可能是必要的,这通常也会影响到代码。
NanoStore超越了这个限制,允许开发者以对象自然形式存储对象。这些对象必须遵循NSFNanoObjectProtocol
协议,为NanoStore提供要存储的NSDictionary
。通过使用字典,可以非常快速地检查数据,并且由于它包括对嵌套集合(类型为NSDictionary
和NSArray
)的支持,它还可以以分层方式定义结构。每个内部对象都会自动索引,因此可以快速找到包含特定键和/或值的对象。
默认情况下,NanoStore允许对象在没有与其他对象关联感的情况下进行存储。虽然这种简单格式功能强大,但由于开发者必须跟踪对象之间的关系,因此存在局限性。某些应用程序可能需要关联对象,其中一些可能具有不同的性质或类类型。这正是NanoBag(通过NSFNanoBag
类表示)所做的工作:它允许任何遵循NSFNanoObjectProtocol
协议的对象被添加到包中。通过一个单一的调用保存包,无缝照顾到新和/或修改后的Nano对象。
NSFNanoBag
API丰富,允许开发者添加、删除、重新加载和撤销其更改,当需要时将其压缩(从而节省内存)并对其进行扩展。此外,它提供了获取所有包、匹配某些密钥的特定包以及包含特定对象的包的方法(有关更多信息,请参阅NSFNanoStore
)。
虽然NSFNanoStore
提供了获取标准对象(如包)的一些便利方法,但大部分搜索机制是由NSFNanoSearch
处理的。执行搜索涉及的步骤相当简单
1) Instantiate a search object
2) Configure the search via its accessors
3) Obtain the results specifying whether objects or keys should be returned (*)
(*) If introspecting the data is needed, request objects. You should request keys if you need to feed the result to
another method, such as the following method in NSFNanoStore:
-(BOOL)removeObjectsWithKeysInArray:(NSArray *)theKeys error:(out NSError **)outError
NSFNanoSearch *search = [NSFNanoSearch searchWithStore:nanoStore];
search.attribute = @"LastName";
search.match = NSFEqualTo;
search.value = @"Doe";
// Returns a dictionary with the UUID of the object (key) and the NanoObject (value).
NSDictionary *searchResults = [search searchObjectsWithReturnType:NSFReturnObjects error:nil];
NSFNanoSearch *search = [NSFNanoSearch searchWithStore:nanoStore];
search.attribute = @"LastName";
search.match = NSFEqualTo;
search.value = @"Doe";
// Returns an array of matching UUIDs
NSArray *matchingKeys = [search searchObjectsWithReturnType:NSFReturnKeys error:nil];
// Remove the NanoObjects matching the selected UUIDs
NSError *outError = nil;
if (YES == [nanoStore removeObjectsWithKeysInArray:matchingKeys error:&outError]) {
NSLog(@"The matching objects have been removed.");
} else {
NSLog(@"An error has occurred while removing the matching objects. Reason: %@", [outError localizedDescription]);
}
另一个酷炫的功能是能够在搜索结果上调用聚合函数(计数、平均值、最小值、最大值和总和)。使用上面提供的搜索代码片段,计算所有姓'Doe'的人的平均工资非常简单。
NSFNanoSearch *search = [NSFNanoSearch searchWithStore:nanoStore];
search.attribute = @"LastName";
search.match = NSFEqualTo;
search.value = @"Doe";
float averageSalary = [[search aggregateOperation:NSFAverage onAttribute:@"Salary"]floatValue];
将搜索和排序结合起来是一个非常简单的操作。有两个简单的步骤
1) Preparing your classes for sorting
2) Setup a search operation and set its sort descriptors
由于NanoStore依赖于KVC(键值编码)来进行排序,因此需要了解数据在对象中的位置的一个提示。由于KVC使用一个键路径来定位正在排序的元素,我们需要一种方法来“指向”它。大多数自定义类会返回self,就像NSFNanoBag
所做的那样。
- (id)rootObject
{
return self;
}
自我在此情况下表示最高层,其中变量名称、键和hasUnsavedChanges所在的位置
@interface NSFNanoBag : NSObject <NSFNanoObjectProtocol, NSCopying>
{
NSFNanoStore *store;
NSString *name;
NSString *key;
BOOL hasUnsavedChanges;
}
假设我们有一个表示人的对象,并且其根对象设置为self,就像上述演示的那样
@interface Person : NSFNanoObject
{
NSString *firstName;
NSString *lastName;
NSString *email;
}
如果我们想要检索所有现有的人名firstName等于John并按lastName排序的人,我们将执行以下操作
// Assume NanoStore has been opened elsewhere
NSFNanoStore *nanoStore = ...;
// Prepare the search
NSFNanoSearch *search = [NSFNanoSearch searchWithStore:nanoStore];
search.attribute = @"firstName";
search.match = NSFEqualTo;
search.value = @"John";
// Prepare and set the sort descriptor
NSFNanoSortDescriptor *sortByLastName = [[NSFNanoSortDescriptor alloc]initWithAttribute:@"lastName" ascending:YES];
search.sort = [NSArray arrayWithObject:sortByLastName];
// Perform the search
NSArray *searchResults = [search searchObjectsWithReturnType:NSFReturnObjects error:nil];
// Cleanup
[sortByLastName release];
SQLite提供了一个非常酷的功能称为OFFSET,通常与LIMIT子句一起使用。
LIMIT子句用于限制SQL语句返回的结果数量。所以如果有1000行在表中,但只想返回前10行,可以这样操作
SELECT column FROM table LIMIT 10
现在假设你想要显示第11到20个结果。使用OFFSET关键字也很简单。以下查询将完成此操作
SELECT column FROM table LIMIT 10 OFFSET 10
使用分页在NanoStore中也非常简单。以下示例基于NanoStore发行版中提供的单元测试之一
NSFNanoStore *nanoStore = [NSFNanoStore createAndOpenStoreWithType:NSFMemoryStoreType path:nil error:nil];
// Assume we have added objects to the store
NSFNanoSearch *search = [NSFNanoSearch searchWithStore:nanoStore];
search.value = @"Barcelona";
search.match = NSFEqualTo;
search.limit = 5;
search.offset = 3;
NSDictionary *searchResults = [search searchObjectsWithReturnType:NSFReturnObjects error:nil];
// Assuming the query matches some results, NanoStore should have retrieved
// the first 5 records right after the 3rd one from the result set.
默认情况下,NanoStore会逐个保存每个对象到磁盘。为了加快插入和编辑对象的操作,请增加NSFNanoStore的saveInterval
属性。
// Instantiate and open a NanoStore
NSFNanoStore *nanoStore = [NSFNanoStore createAndOpenStoreWithType:NSFMemoryStoreType path:nil error:nil];
// Increase the save interval
[nanoStore setSaveInterval:1000];
// Do a bunch of inserts and/or edits
// Don't forget that some objects could be lingering in memory. Force a save.
[nanoStore saveStoreAndReturnError:nil];
If you set the saveInterval value to anything other one, keep in mind that some objects may still be left unsaved after
being added or modified. To make sure they're saved properly, call:
- (BOOL)saveStoreAndReturnError:(out NSError **)outError .
Choosing a good saveInterval value is more art than science. While testing NanoStore using a medium-sized dictionary
(iTunes MP3 dictionary) setting saveInterval to 1000 resulted in the best performance. You may want to test with
different numbers and fine-tune it for your data set.
Setting saveInterval to a large number could result in decreased performance because SQLite's would have to spend more
time reading the journal file and writing the changes to the store.
有两种快速方法可以找到答案:阅读文档和浏览单元测试。
尽管已经尽力使文档易于阅读和理解,但它仍然远非完美。如果您发现文档不完整、错误或者需要澄清,请提交一个错误。我将非常感谢您,并尽快进行更正
NanoStore的官方库托管在GitHub上:https://github.com/tciuro/NanoStore