Shark is a new open-source ORM framework for iOS that aims to be an easy-to-use replacement for Core Data by providing high-performance and thread-safety. InfoQ has spoken with Adrian Herridge, creator of Shark.
As Herridge recounts, Shark was born from the experience gained with DBAccess, his previous ORM for iOS that InfoQ covered at the time of its launch. Shark “meets exactly the same specifications as” DBAccess, Herridge says, but its code base has been largely rewritten due to copyright reasons. Despite being closed-source, DBAccess has had a significant adoption, which Herridge rates at 700 apps currently, based on developer contacts, CocoaPod metrics, and Stack Overflow questions, with about 12k downloads overall.
One of the strengths of Shark should be is ease of use, according to its creator. The following code snippet shows how you can create an object, execute a query, and update and object through the ORM, which also exemplifies Shark’s FLUENT API :
class Employee: SRKObject {
var name: String?
var age: Int?
}
var newEmployee = Employee()
newEmployee.name = "Steve"
newEmployee.age = 54
newEmployee.commit()
let youngest = Employee.query()
.whereWithFormat("age < %@", withParameters: [21])
.orderBy("age")
.limit(10)
.fetch()
youngest?.lastModified = nil
youngest?.commit()
Shark uses SQLite as its backing DB engine, so developers can execute SQL queries that include joins and subqueries. This is a key feature that allows developers to optimise their applications for performance, says Herridge.
Another feature that will be welcome to Core Data developers is thread-safety, which makes it possible for objects returned by Shark to be used cross-thread “in every scenario”.
Other distinctive features that Shark provides are:
- transactions
- event models
- column level encryption
- support for batch operations
- object domains allowing to control how object are managed
InfoQ has spoken with Herridge to learn more about Shark.
How did you go from DBAccess to Shark?
Our motivations for wanting to open source DBAccess were purely around time constraints. More and more developers were contacting us and asking for what we considered perfectly reasonable features, which we had simply not thought of initially. And as the major development had been completed to a stage where it was usable within all our projects, there was little time allocated to making improvements that would not have any clear benefits to our products. With a big enough community, willing to contribute we hope to advance the product well beyond the original. Since release, I have been able to spend at least 8 hours a week on the project, now support queries have reduced markedly.
We were unable to gain approval to release what we had already written, so after discussions with our CEO we decided that re-writing the ORM and even giving it a very different name would be an appropriate workaround. So the re-write started, initially we took the original code and re-factored it into separate source files to make it more modular. Then went about stripping out lots of the more complicated parts, with the intention to backfill them later (such as a query cache manager and shared memory system). The result, worryingly, being very little change in performance.
Then, once we had re-organized the code base, we re-wrote every function to be different, which felt like a waste of everyone’s personal time, but in this case was necessary to change it enough to be completely different to the original project, to avoid any problems in the future. But when we reviewed the work, it was still very similar to the original code base. Not the same, but similar. So a more concerted effort was made to modularize the code, and segment it for easy re-development. Almost a year later, and despite being similar, it was a very different code base. But we had the ability to continually test the input/output of the new methods to ensure integrity and compatibility with DBAccess.
Why not Swift?
That is tricky to answer, as obviously it is the future and it is feature-rich enough to achieve the task. But ultimately, DBAccess was a static library, which could be used back to iOS 6 if need be.
At the time we started the port, and as far as I know still, Swift cannot be included in a static library. But that is irrelevant as we also made the decision to not “hack” a framework object together, and just use the project templates provided by XCode 7, which produces a dynamic framework instead, targeting iOS 8+.
The problem still is that if it was written in Swift, then Objective-C objects could not inherit from a Swift base class, and this is essentially the model we have stuck with.
Now that the code base is nicely split out, we envisage writing large amounts of it in Swift, and eventually the entire thing, especially as this is clearly a desirable trend. And we have already started work on a top level Swift object to enable persistence of all the standard Swift data types.
How mature is Shark in comparison with DBAccess?
Because of the function by function port and modularization of Shark we were able to test as we went, and the code base was seldom in a state where it could not be compiled. So although it is new, it is also based on well-trodden code, and in many ways more reliable given the far better test coverage. We have also gone much farther with the documentation than before, and we are giving far more examples of all the features and far clearer wording wherever possible.
Are there any advanced features in Shark that you would like to highlight?
One advanced feature of Shark is the managed object domain system. By default, all objects are individual and exist in separate memory spaces, therefore changes made to one instance of an entity are only made on that single object and changes are not common across other instances. This was a deliberate design decision, as we found that often this was very useful in our application development. But with the simple addition of a managed object domain, it is then updated in line with other instances in the same domain. The domain is just a string value and can be changed at any time, the power of this being that you could have a network domain and a UI domain, with objects, when completed on a network thread having their domain changed to the UI domain for updates to bound controls.
Optimization is another key feature of the ORM; we have the usual tools available for optimizing your queries. Indexes can be added to any class as well as COUNT, SUM, and DISTINCT operations for faster data set retrieval. There is also the ability to restrict the properties that are retrieved from a query, reducing time and lowering memory usage, with the values of the remaining properties lazily loaded when required. Delegate methods can be added to allow you to intercept slow queries during development and have a look at the query plan, so you can both make adjustments and measure any improvements that have been made.