I'm using SharkORM to create a SQLite database but I have the following question.
How can I encrypt and ignore a property in sharkORM?
class Example: SRKObject {
dynamic var birthdate : NSDate?
dynamic var age : NSNumber?
}
I'm trying to calculate the age from the birthdate, and I don't want to have a column in the table for the age.
Also, my data should be secure so I want to encrypt the birthdate, how can this be implemented?
Thanks for your support.
It appears that I might be wrong about ignoreEntities - that's not what you need. It appears that their documentation is not updated to reflect this but what you actually need is ignoredProperties :)
The actual Swift code you need to ignore a property on an object would look like this - I am using an example Person object to illustrate the code:
class Person: SRKObject {
dynamic var name : String?
dynamic var age : NSNumber?
dynamic var payrollNumber : NSNumber?
override class func ignoredProperties() -> [Any] {
return ["age"]
}
}
Since I have not worked with SharkORM before, I tested the code to make sure that the above does indeed work correctly :)
On the subject of the implementation for ignoredProperties, generally, the unit tests for a project (if they exist) are a good place to start to see how to use a certain method. But strangely enough, SharkORM does not seem to implement any tests to see if ignoredProperties works as it should. Hopefully, somebody from the development team sees this and fixes this oversight :)
With regards to encrypting a specific property, I believe all you need to do is implement encryptedPropertiesForClass. Since the implementation will be similar to the above one for ignoredProperties, I will leave the actual implementation to you :)
From the documentation:
In Objective-C properties need to be implemented using #dynamic, this is to indicate to the ORM that it will control the fetching and setting of these values from the database, and in Swift the property is implemented as var dynamic
So, if you don't want age to be a column in the database, don't mark it as dynamic. Since you want age to be a calculated property, you would have something like:
var age: Int? {
if let birthdate = birthdate {
return // whole years from birthdate to today
}
return nil
}
Related
I'm currently writing an NSManagedObject and am looking for naming convention ideas. Here's my code:
final class StrokeSample: NSManagedObject {
#NSManaged private var location: String
var _location: CGPoint {
get {
return NSCoder.cgPoint(for: location)
}
set {
location = NSCoder.string(for: newValue)
}
}
}
As you can see, I have a CGPoint object that is stored into Core Data as a String. The issue I have here, is how should I name those two variables that describe the exact same property in an elegant way?
What I've already considered:
In my xcdatamodel object, naming the attributes string[PropertyName]. Pros: resolves the ambiguity of, for example, having a property named rect that is described as String in the data model. Also, outside the data model, everything is very clear. Cons: Writing string before every attribute looks messy in the model.
Putting a _ before the computed properties' names in my NSManagedObject. This is what is highlighted in my example. Pros: This leaves the xcdatamodel clean. Cons: forces me to use underscores everywhere in my Swift code. 🤢
Putting a _ before the attribute name in the Data Model. Xcode prevents it.
I think option 1 is the better one since the mess is restricted at only one place. But if you have better ideas on this particular issue, they are welcome. Thank you 🙂
The convention seems to be to put an underscore after a property name when you want to make it "private" as in the data model it must start with a letter.
We're building an iOS app using Realm as our model / database but we want to design the client so it can easily accommodate changes in the REST-ful API that may occur in the future. Lets say we're developing an app for sports competition organizations that accommodates different events. Each event has different types of event types based on what sports are being played. Right now the API only returns Soccer, Baseball, and Football but in the future it might expand to include Basketball too. Later it might eliminate Baseball. I've designed the Realm objects so that Events are decoupled from Event Types using a one-to many relationship like this:
class EventTypeGroup: Object {
dynamic var name = ""
let eventTypes = List<EventType>()
}
class EventType: Object {
dynamic var name = ""
dynamic var descriptionText = ""
}
An EventTypeGroup is the class describing the event types (in this case which sports) will be played at the event. I used this design because dictionaries aren't supported in Realm where we could store an event type with an associated set of properties.
In order to make the model adaptable to future changes in the API in case sports for a particular organization are added or removed, I used an abstract factory pattern like below. This way an event cannot be created without using an enum in keeping with modern Swift design principles. The problem I'm having is, assuming we only check for changes in the API to the event types (sports) once upon the user opening the app, how do we change the model with the app already open? Will the database need to be migrated if these fields change?
protocol EventTypeGroupFactory {
func createEventTypeGroup(List<EventType>) -> EventTypeGroup
}
protocol EventTypeFactory {
func createEventTypes() -> List<EventType>
}
class SportEventGroupFactory: EventTypeGroupFactory {
func createEventTypeGroup(withEventTypes: List<EventType>) ->
EventTypeGroup {
//implement logic to create an EventTypeGroup for the SportEventGroup
}
}
class SportEventTypeFactory: EventTypeFactory {
EventTypeGroup {
func createEventType() -> EventType {
//implement logic to create an EventType for the SportEventType
}
}
class EventTypeGroup: Object {
let eventTypes = List<Int>
enum EventType {
}
}
class EventType: Object {
var type: Int?
name: String?
description: String?
}
class Event: Object {
static enum EventType
init(eventTypeWithRawValue:) {
}
}
Also, how will I refer to the different variations of the classes in the code I write now if I don't know how they'll be defined. I'm guessing the abstract factory pattern may not be the best way to deal with this but am not sure what other options I should consider or how to approach the issue of making types easily extensible in a model based on API changes.
You are overcomplicating it, I think. Just add a string property called "eventType" to your Event model.
For example, normally, if you didn't need to keep things dynamic, you might do something like this:
enum EventType {
case soccer
case baseball
case football
}
// Your Event model
struct Event {
var date: Date
var eventType: EventType // a static type :)
}
But in your case, instead you can do something like this:
// Your Event model without any enums
struct Event {
var date: Date
var eventType: String // a dynamic type :(
}
Property eventType can then be "soccer" or "baseball" or "football". (But the compiler cannot help you catch errors now.) As for your persistent storage, just have a field there of type eventType and store the string.
Dynamic types make me sad given how nicely static Swift is, but it gets you what you want. Just make sure to think about edge cases. To not end up with undefined behavior, think ahead about what your app is supposed to do if, for example, you end up with event types on disk that are no longer being supported by your REST API.
For example, say you have an /eventTypes endpoint, so that your app's users can add events and categorize them accordingly, and it's been returning "soccer", "baseball" and "football" and your users have been adding these types of events and you have been storing them on disk (in Realm or CoreData or whatever). But then one day someone on the backend (or through the backend) renames "football" to "american football", and let's hope no one renames "soccer" to "football" too. (And so now you can't tell if a thing was renamed or removed and another added.) Do you then take the union of the event types your /eventTypes endpoint returns and what you find on disk? Do you let users add old event types that still live on disk but are not longer supported by your REST API or only display them?
With active users, you will likely end up with these kinds of edge cases if your backend folks rename event types or remove event types (as opposed to simply adding them). Just discuss with your stakeholders what the behavior should be.
I'm trying to build up a Realm of a bunch of data. This wouldn't be a problem but I've hit a wall - a gap in experience shall we say.
Creating one record is fine. However, one of the fields of that record is an array (<List>) of records from another table. Now my 2 questions are:
Does Realm support that? A list or array of Objects as one of the fields for a record... Answering no here will leed me on to an answer of my question - I will simply need to make an array of "primary keys" and query with those when I need to. If the answer is yes, proceed to question 2.
How would I go about creating those lists, bearing in mind that those tables might be created at a fraction of a second later than the current one, meaning those records don't yet exist and therefore can't be added to the list...
Example:
class baseRLMObject: Object {
// Creates an id used as the primary key. Also includes a few methods.
}
class Film: baseRLMObject {
var name: String!
var episodeId: Int!
var characters = List<Character>()
}
class Character: baseRLMObject {
var name: String!
var films = List<Film>()
}
See how all the film objects need to be created first before the character objects? Otherwise I could try add a film which does not yet exist and then it all crashes and burns :( Reason I want to try find a better way is, I'm dealing with 10 tables, a few hundred records and variable connection speeds. It would be too long to wait for each data retrieval to finish before the next one starts. AND since they are all suffering from the same problem (inter-connections), regardless of which I start with, it won't work...
Thank you :)
As discussed, for the object that haven't been created, you should create an empty object with only the primary key, then re-fetch and add value after the other network request called
For Many-to-many relationship, you can use Realm's Inverse Relationships to create linking between these objects like:
class Character: baseRLMObject {
var name: String!
var films = LinkingObjects(fromType: Film.self, property: "characters")
}
Like it's being discussed in the comments, you should be able to use Realm's inverse relationships feature to automate most of this process:
class baseRLMObject: Object {
// Creates an id used as the primary key. Also includes a few methods.
}
class Film: baseRLMObject {
var name: String!
var episodeId: Int!
var characters = List<Character>()
}
class Character: baseRLMObject {
var name: String!
let films = LinkingObjects(fromType: Film.self, property: "characters")
}
When calling character.films, a List will be returned of all of the Film objects whose characters property contains that object.
This is a lot easier and error-free than trying to maintain two separate relational lists between object types.
I have a Book class and a ReadingSession class, with relationships declared like so:
public class Book: Object {
// (…)
var readingSessions: [ReadingSession] {
return linkingObjects(ReadingSession.self, forProperty: "book")
}
}
public class ReadingSession: Object {
// (…)
dynamic var book: Book?
}
Now I wanted to use Realm’s Results’ methods like filter() and sum() on that readingSessions property, but I can't, because it's a regular array.
So I added another computed property to my Book class:
var sessions: Results<ReadingSession> {
let realm = try! Realm()
return realm.objects(ReadingSession).filter("book == %#", self)
}
Now when I need to use those methods I go for the sessions porperty, and when I don't I use the readingSessions array.
So my question is: why is the first way, using the linkingObjects() method, recommended in Realm’s documentation? Is there a reason why I shouldn't completely replace that property with my latest one, using Realm’s Results? In my experience, working with Realm’s Results is usually a lot faster, besides the extra methods, and even if I need a regular array I can just convert the Results then. Is there any disadvantage to doing that?
Thanks in advance,
Daniel
For now, there is no better way than what you're currently doing. Using linkingObjects would be likely faster as the relationship can be navigated internally bidirectionally and the query doesn't need to be evaluated for the full dataset, so it also depends on the size of that. We plan to change that behavior as seen in issue #1508.
I am developing a game in Swift using UIKit. I want to save/load the changes each time the game is closed and opened. Data need to be saved/loaded are classes containing String and Int's.
Sample class:
class UserData {
var username: String = String()
var gamepoints: String = String()
var items: [UserItem] = []
}
class UserItem {
var name: String = String()
var quantity: Int = 0
var price: Int = 0
}
I only need to save classes similar to ones above. What method should I use? How do I retrieve the updated values of the instance of the UserData class when the game is closed and opened again? Is there a method which I can save this class values to somewhere and initialize them after restarting the app?
The answer is yes. You're going to want to look into NSCoding and NSKeyedArchiver. Basically you will need to do the following steps:
Have your UserData and UserItem classes implement the NSCoding protocol.
Write them to disk when you need to using NSKeyedArchiver
Read them from disk when you need to using NSKeyedUnarchiver
Checkout this article about it: http://nshipster.com/nscoding/
However you have many other options for storing user data - you could also store user data using NSUserDefaults, CoreData, SQLite, etc. Your question is really quite broad, and without knowing the specifics of your game: how often it needs to read/output this data, how much of it there is, what data structures you're using to collect it... it's hard to give you a definite right answer.