TestsTested | ✓ |
LangLanguage | Obj-CObjective C |
License | MIT |
ReleasedLast Release | May 2014 |
Maintained by Unclaimed.
Although Apple has an existing full text search solution in the form of Search Kit there are several major drawbacks with it.
There are a few other solutions to this problem out there but they aren't perfect:
This is where LevelSearch comes into play. It is designed with the goal of being dead simple to integrate while being very low touch (no objc/runtime or associated objects) while providing performance that can meet the needs of full text search for use cases as latency sensitive as auto-complete.
LevelSearch as the name suggests is built on the LevelDB key/value database developed by Google under the BSD license. It's a high performance key/value store like Redis but built for embedded client purposes like the Chrome browser which makes it really nice for this usecase. For more info you can take a look at the Google documentation.
What this means is that LevelSearch utilizes a LevelDB instance for each index which is file backed and handles its own persistence. LevelSearch supports a single shared convenience index or multiple named indexes--its totally up to your use case as to how you want to most efficiently utilize it.
Beyond the technology though LevelSearch is really easy to use because it manages all of the index changes for you using Core Data save notifications. Once it starts watching a given NSManagedObjectContext
the index will happily continue indexing and updating the index whenever changes are detected from Core Data. This tight coupling makes Level Search really streamlined and the implementation is pretty lean at ~500 LOC (not counting the LevelDB library and Objective-C wrapper).
It's also REALLY DAMN FAST, see the benchmarks below for more on that.
After that just import the shared header and you are good to go.
#import <LevelSearch/LevelSearch.h>
Using LevelSearch is pretty simple, you just need to configure the index and it takes care of the rest.
If you only need a single index then the shared index works well. To set it up just do the following after your Core Data stack has been setup:
[[LSIndex sharedIndex] addIndexingToEntity:[NSEntityDescription entityForName:@"ExampleEntity" inManagedObjectContext:ExampleContext] forAttributes:@[@"attribute1", @"attribute2"]];
[[LSIndex sharedIndex] startWatchingManagedObjectContext:ExampleContext];
It really is that easy. An important caveat to be aware of though is that because LevelSearch does not use any runtime tricks it does not have the ability to add any kind of primary key to your managed objects. This is a GOOD thing for the most part but since the only real primary key Core Data provides is the NSManagedObjectID it requires objects to be saved to the persistent store before they can be indexed (since objectIDs are temporary and can change until the object is saved).
What this means to you is that you want to watch an NSManagedObjectContext that is connected directly to the NSPersistentStoreCoordinator and not a child context. If you are using only a single context like Apple's default Core Data code then you are fine, if you use MagicalRecord (or a similar stack that uses a main-thread context and a parent saving context) then you want to do something like:
[[LSIndex sharedIndex] startWatchingManagedObjectContext:[NSManagedObjectContext MR_rootSavingContext]];
After the index is configured you really don't need to do anything else, the index will register for Core Data save notifications and automatically index objects as they are saved. If you want more granularity into when indexing is occuring you can use either the LSIndexDelegate
protocol or the notifications. Both are fully documented.
There are two sets of query methods: synchronous and async query methods. For most everyone the async methods are VASTLY preferred as you really don't want any latency on the main thread while a user is typing a search term. The sync methods are useful for testing and for implementing your own async searches if you want to own the search queue but please do us all a favor and don't call them from the main thread.
Otherwise the query interface is identical between the sync and async queries, a simple query looks like this:
[[LSIndex sharedIndex] queryInBackgroundWithString:@"example query"
withResults:^(NSSet *results) {
// Put your completion code here...
}];
The return from the query is an NSSet
of objects from Core Data, you don't even need to perform your own NSFetchRequest
...the query takes care of all of that for you. You will probably want to sort the results into an NSArray
using an NSSortDescriptor
before presenting it but that's pretty straightforward.
To run the example project; clone the repo, and run pod install
from the root directory first. There are two sample applications: one that demonstrates how to integrate the framework and another that is used for performance and integration testing.
John Tumminaro, [email protected]
LevelSearch is available under the MIT license. See the LICENSE file for more info.