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

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.

Related

How to access/modify parent view variable in subviews?

I have parent view container which holds multiple subviews.
struct Person {
var name: String
var lastname: String
}
class ParentContainer: UIView {
var person = Person(name: "Vish", lastname: "Deshmukh")
let child1 = ChildView1(person)
let child2 = ChildView2(person)
let child3 = ChildView3(person)
}
How should I make sure if any of the child change person object, reflect that value everywhere including other child views and parent Container view?
This actually a fairly involved topic. If Person was a reference object, you could have your child views watch for changes to the Person. However, that's not really following best practices.
Modern design is moving to UDF (unidirectional data flow) where you have a value object that describes the current state of your UI and it gets passed around when it changes. With UDF, the child wouldn't change the person object - instead they would create a new, immutable person object containing changes, and notify subscribers that there is an updated person object. The children would be observing changes to the Person object.
The specifics depend on how you're implementing your app.

Best way to store and refrence lots data in Swift for use in a UITableView

This is a bit confusing so I apologise. But:
I am trying to make an app where the user has the ability to add items to a list of individual items that will be displayed back to them(Something along the lines of a todo list app) as a table view.
However, I have hit a roadblock I need to store several different bits of data for each item(Some Strings, Some ints and a date) in the list.
I think that a class(or struct) would be the best way to do this where an instance of the class holds the information need for each item and then the name of that instance is stored in a list so it can be accessed via the indexPath in the table view.
However, I don't know how I am going to make a new instance of the class for every item because the app could have hundreds of individual items.
I'm sorry, this is so confusing and any help would be appreciated! Feel free to ask for more info
Edit: what I am looking for and I'm sure there's a stupidly easy way of doing it but I'm try to work out how to create an instance of a class when the name of the class is stored in a variable. Ecencialy I want the instance of the class to store the item. To be created when the user inputs the item to be added to the table.
Eg. They enter an item. item1 and the other data that goes along with then I want to be able to store that in instance of the item class but I don't know how to make the name of that instance because the name I want which is item 1 is stored in a variable.
Sorry that's so confusing that's the reason I need help
So first: You can't store the Name of a Class in a Variable and use this variable to get a new instance of a class.
What you need is an Array containing all the different Items. This array is unlimited so you can store as many items in it as you like and you don't need to set a name for each of these Instances.
First create an empty array containing all Items as a property:
var items : [Item] = []
Second call the function populateItemArray() in your viewDidLoad():
private func populateItemArray() {
// Populate the items Array (Are you using CoreData, Realm, ...?)
}
Third, use the Item Array to populate the TableView.
REMEMBER: If your only using one section in your tableView, indexPath.row is always equal to the corresponding item in the array.
E.G. items[indexPath.row]
Hope this helps you!
UPDATE:
Look at this example struct Item. As you can see you can also store a date in it:
struct Item {
// First create all the properties for your struct.
var data1: String
var data2: Int
var data3: String
// You can create properties of any type you want, also of type date.
var dateOfCreation : Date
// Then implement all the methods of your custom Struct Item.
func setDate(with date : Date) {
self.dateOfCreation = date
}
func returnDate() -> Date {
return self.dateOfCreation
}
}

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.

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.

Loading data and singleton for IOS

I am making an instagram clone but I am having abit of question about what would be the best way to pass data arround.
I have 3 post arrays and three VCs as shown below
myPosts = [Post]()
nearbyPosts = [Post]()
followingPosts = [Post]()
myPostVC
nearbyPostVC
followingPostVC
The interesting part is when I download a post that is close by and it happens to be one of my post that I am also following. So when I download that post, I will need to put it in all three arrays so they are the same reference. This is so that if the user "likes" that post, it will be reflected in all three array because they point to the same post. (If they dont have the same reference, and the user likes or perform any sort of action on a post in myPosts array, I will have to manually loop through each nearbyPosts and followingPosts to update their "like count" individually)
Initially, my code looks this
myPost -> Initiated inside myPostVC
nearbyPost -> Initiated inside nearByVC
followingPostVC -> Initiated inside followingPostVC
So in myPostVC, I would need to create an instance of nearbyVC and followingPostVC so that everytime I download a post inside myPostVC, I loop through nearbyVC.nearbyPosts and followingPostVC.followingPosts and update anyting that has the same poast as the one that was just downloded. And I do this for all three VC. Imagine if I have moreVC, this gets very messy as each VC is linked to other VC (In my case, the threeVC as three tabs)
This is when I changed to Singleton... I made myPosts, nearbyPosts and followingPosts to be all inside a singleton as shown below. This allows the three VC to be disconnected and they just need to access whichever array they wish to by calling something like PostService.ps.myPosts (Without the need to create a refrence to other VC)
My question is...
I know it "might" be good to use the first method because it is not singleton, but is that actually a method people use in this case? (It makes all VC linked together. Very messy)
Is singleton the right way to solve this problem or is there a much better way? (Note that similar to instagram, I dont really need to store these posts on the phone to look at in future, so no coredata?)
class PostService {
static let ps = PostService()
private var _myPosts = [Post]()
private var _nearbyPosts = [Post]()
private var _followingPosts = [Post]()
var myPosts: [Post] {
return _myPosts
}
var nearbyPosts: [Post] {
return _nearbyPosts
}
var followingPosts: [Post] {
return _followingPosts
}
So many ways to approach this.
Create a new class (such as your PostService) to represent your data model, outside of any view controller. This data model class could be a singleton. I would put all of the posts into a single Set (not an array) named allPosts. Using a Set rather than an array will prevent any duplicates. Each Post object could have boolean vars indicating if it belongs to specific subsets or not. This could be stored or calculated depending on your data.
Then to access a specific subset, use the filter function on the set, such as:
var allPosts: Set<Post> = ...
var myPosts: [Post] {
return allPosts.filter { $0.isMine }
}
var nearbyPosts: [Post] {
return allPosts.filter { $0.isNearby }
}
The advantage here is no duplication of objects and a shared resource for each of your view controllers.
Note: Since this is a Set rather than an Array, it will not have any order to the items it contains. But you can always use the sort() function to do that.

Resources