How do I make model in MVC for iOS app dynamic based on changes in REST-ful API? - ios

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.

Related

Is there a way in Realm to implement order_by for nested objects

I am trying to retrieve nested objects sorted in a specific order when I retrieve the parent object using realm.objects(). And equally importantly, maintain the sort order if any nested object is modified.
I have searched related issues but they are either dated or not quite the issue I am describing
Here's an example: I have 'user's in Realm, each of which have 'task's (with extraneous fields omitted for clarity)
class task: Object {
#objc dynamic var priority = 10
}
class user: Object {
#objc dynamic var name = ""
let tasks = List<task>()
}
I create a few users and then append tasks for any given user (a table in my UI with section for each User and Tasks for that User as rows in that user's section, SORTED by priority).
Default priority starts at 10, and can be changed in the range from 1-10 at any time.
When I retrieve users:
// (1)
users = realm.objects(user.self).sorted(byKeyPath: "name")
I want to retrieve their tasks sorted by priority.
Note that the priority is modified after the initial retrieval of users (meaning Results's task object's priority is changed under a realm.write()). To clarify, somewhere in my code, for a given user's task, I do the following:
realm.write() {
task1.priority = newPriority
?
Which means that the user's task list should always be sorted by priority and not require repeating (1) above. I can't sort the user.tasks property because it is a "let" variable.
Note that both user and task objects have a sort order.
I could do index(remove/add) on tasks after I update 'priority' above but have not tried this yet. However, rather than do this manually (and assuming it works, and assuming there isn't a delete/add of 'task' happening alongside the reshuflle, ...), isn't that the whole idea behind ORMs, to make such things straightforward?
Anyway to do this? With Extensions on Realm? ??? Open to suggestions on alternative approaches vs nested objects.
Great question and an answer that really shows off Realms live updating capability.
Restating the question
A user has tasks and we want to work with those tasks as an ordered
list, and if there's a change to the order, keep them ordered by the new order
Using the two classes presented in the question, we have a button that calls a function to query realm for a user and store that user in a class var
var myUser: UserClass!
func loadUser() {
if let realm = gGetRealm() { //my code to connect to Realm
let userResults = realm.objects(UserClass.self)
if userResults.count > 0 {
let user = userResults[0]
self.myUser = user
}
}
}
then a button that calls a function to simply print out that users tasks, ordered by priority
func printOrderedTasks() {
let sortedTasks = self.myUser.tasks.sorted(byKeyPath: "priority")
for task in sortedTasks {
print(task)
}
}
So for this example, I created 4 tasks, and added them to a users tasks list and wrote the user to realm, the priority initial order is: 10, 0, 1, 4
Then loadUser loads in and store the first user available and assigned it to the class var.
The printOrderedTasks outputs the tasks, in ascending order. So after loading the user, clicking printOrderedTasks the output is
0
1
4
10
then, using Realm Studio, change the 1 to a 6 and click the printOrderedTasks again and the output is
0
4
6
10
without having to reload anything.
Realm objects are live updating so as long as you have a reference to them in your code, any changes are reflected live.
You could expand upon this by adding an observer to that object and Realm will notify the app of the event, to which you could reload a tableView or let the user know of the change.
Edit:
To take this a step further, the users tasks are also live updating objects and if you set a sort on them, those results maintain their sort.
For example, let's re-write the above code to keep track of a users tasks that maintain a live sort. I've re-written the above code and eliminated the user class var and added a tasks class var. Note that we never need to re-sort the tasks, the sort order is set initially and they will stay sorted from that point forward.
var myUserTasks: Results<TaskClass>!
func loadUserAndGetTasks() {
if let realm = gGetRealm() {
let userResults = realm.objects(UserClass.self)
if userResults.count > 0 {
let user = userResults[0]
self.myUserTasks = user.tasks.sorted(byKeyPath: "priority")
}
}
}
func printTasks() {
for t in self.myUserTasks {
print(t)
}
}
The initial order was 10, 0, 1, 4 as above. If we then change the 1 to a six using Realm Studio, and then run the printTasks function, you'll see the ordering was automagically done because the Results are live updating.
0
4
6
10
The cool thing here is that you don't need to keep resorting the tasks - they maintain their sort order.
#Jay's answer is great as always, but I'd take his suggestion and make it intrinsic to the user object. This is my 'usual' way of handling requirements such as this.
As you say, the user object tasks property is a let, but this is how a Realm object is defined. That's not to say you can't add your own computed properties to control access to the object.
If you only want to see the tasks as an ordered list, then add a property that gives you that, e.g.
extension user
{
var orderedTasks: Results<task>
{
return tasks.sorted(byKeyPath: "priority")
}
}
Then always use that property to access the tasks. Using an extension there is my own style - I just keep the data declarations in the class declaration, and then add an extension with any computed properties or functions - but you can just add the declaration straight into the class definition.

Value type design pattern to replace class

We are a looking for a value type design pattern in swift that will allow us to create a shopping cart to hold Products. We are currently using a class but that is a reference type and when we try to add two different version of the same product (i.e. with a different colors or sizes), the first item we added gets changed to the second item we added because it points to the same object in memory.
The design pattern needs to be “global” so we can access it from any page in the app. Right now this is our Cart class that stores all the items in the cart. What do we need to do to make this a value type or how does it need to be reengineered to use a struct without a class?
class Cart : NSObject {
var allProductsInCart = [MainProduct]()
override init() {
super.init()
}
class var sharedCart: Cart {
struct Static {
static let instance = Cart()
}
return Static.instance
}
}
The problem we are getting is that we need the products in the cart to be of custom class “MainProduct.” Right now as you can see, they are stored as “MainProduct.” Do we need to switch the products to a struct or other design pattern as well? How would we do that?
Yes, given the desired behavior between a value type vs. reference type you should use a Struct.
A commonly used "pattern" for doing this is called "Redux".
The idea is that you have one, immutable version of the "state" of your app.
It can be accessed from anywhere and the only way to update it is through "actions". These will reconstruct the entire state with the required updates.
ViewControllers and views, etc... subscribe to updates of various parts of the state.
So you could have an AppState that contains a ShoppingCartState. When a product is added to it your CartViewController will be informed of this update and can update its view etc...
There are many different frameworks that are built to use Redux so I won't recommend one as you should find the one that is right for you. But I believe this pattern best suits the usage you are after.

Swift - SharkORM ignore and encrypt property

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
}

Realm creation and population sequence

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.

Class or Struct for a model similar to a relational database?

The application in question is built as follows:
A user selects a job
the job can have many components
each component can have many lineitems.
I am not clear on how this should be structured - should this be class or structs? Seeing that only one job is being processed at a time, I am fairly confident that jobs should be a class. However, when there are multiples of a certain object type, I am not exactly clear on how to form them, like the components and lineitem objects.
The application consists of ViewControllers and TableViewControllers. All the data is fetched from a server in JSON and populated into the appropriate view as needed. Here are the object types as they are currently setup:
A job object:
// Job Object
//
public struct Job {
static var globalId : String?
static var name : String?
static var status : String?
static var client = Client()
static var components = Array<Component>()
// etc..
}
A Component like so:
// JobComponent Object
//
public struct Component {
var name:String? = ""
var fmRecordId : String?
var startTS:NSDate?
var endTS:NSDate?
var notes:String? = ""
var items = Array<Lineitem>()
// etc...
}
and finally, a lineitem:
// Lineitem Object
//
public struct Lineitem {
var fmRecordId = String()
var itemName = String()
var itemNumber = String()
// etc...
}
All of these object are built within a public class called "PL".
When a user selects lineitem and edits it's values, the values are not available outside the VC in which they are edited because the VC isn't referencing the lineitem that is was passed, it is simply copying it. The same happens with components.
A workaround I found was to use the the Job struct PL.Job.self and always modify the components and lineitems like so where i = a desired index in the array:
PL.Job.components[i] to access a component
PL.Job.components[i].items[i] to access a specific item within that component.
However, this doesn't scale very well.
The desired behavior is to be able to pass a reference to a particular instance of an object around rather than pass around the index path of those objects in the PL.Job object.
I am well aware there is something wrong with how this is currently structured, but could someone please point me in the right direction?
A couple of points:
You can only pass class instances by reference. If you want to be able to pass a reference to a particular LineItem or Component or Job, and you want to be able to make changes to that object that are effective everywhere, then you need to define them as classes and not structs. Instances of struct types are always passed by value and not be reference. And when a value type is passed, it is copied, meaning that you create an entirely new copy of the object, and mutating the copy has no effect on the original.
Your Job struct only has static properties - i.e., there will only ever be one globalId, name, status etc. throughout your entire application. If you want to have multiple instances of Job, then these should not be static properties. You say that only one Job will be processed at a time, so maybe that was intentional. Either way, it is still often preferable to create an instance of a Job class that has those properties. It certainly would give you more flexibility later if you decide to make it possible to hold references to multiple jobs in memory, or to allow the user to select between different jobs, or switch between jobs, etc. For example, you may want to allow a user to switch to the Job they were processing earlier without necessarily destroying the Job that they are working on now.
But I think the main point is that you will need to define your objects as classes if you want to be able to pass them by reference. If you modify an object that is passed by reference, all other references to the same object will show the same changes (because, after all, they are just references to the same object). That doesn't work with value types, like structs.

Resources