I have a question. I have a DataModel from Class A passing to Class B. I would like Class B to retrieve the changed value from Class A. And They both can edit and share the same data. However, now Class B cannot get any value from Class A. How can i make it
struct DataModel {
var firstName: String = ""
var lastName: String = ""
}
class ClassA {
var dataModel: DataModel
ClassB(dataModel: dataModel)
dataModel.firstName = "ABC"
}
class ClassB {
var dataModel: DataModel
init(dataModel: dataModel) {
self.dataModel = dataModel
dataModel.firstName <--- Print Null
}
}
As struct is a value type when you pass A.dataModel to B, a copy of dataModel is passed to B, not the original instance.
So just use class for your data modal instead of struct if you want to modify A.dataModel in A as well as B
in other words you need to pass reference of A.dataModel to B
class DataModel {
var firstName: String = ""
var lastName: String = ""
}
Related
I need to save a struct to CoreData. I found and example to wrap the struct in NSManagedObject such as:
struct Salary {
let amount: Double
}
class Employee: NSManagedObject {
#NSManaged private var salaryAmount: Double
var salary: Salary {
get {
return Salary(amount: self.salaryAmount)
}
set {
self.salaryAmount = newValue.amount
}
}
}
What I am not sure is how to add the struct to the NSManagedObject. I am creating the NSManagedObject and then modifying its variable.
let employee = Employee() //NSManagedObject
employee.salary = Salary(amount: 1000.00)
Doing so causes a crash - unrecognized selector sent to instance
I also need this to be saved as an array inside of CoreData of Employee because I want to save multiple structs.
What is the proper way to add the struct to the NSManagedObject?
Thanks.
The crash is caused by assigning a value to an attribute of an incorrectly instantiated object.
You must instantiate a NSManagedObject by using one of the official initialisers, for example:
let employee = Employee(context: yourManagedObjectContext)
I'm confused when using get set in protocols. Using only get works fine, but the set part doesnt'.
protocol MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject { get set }
}
extension MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject {
get { return MainViewModel.instance.localDoor }
set { localDoor = newValue }
}
}
final class MainViewModel: MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject = LocalDoorCoreDataObject()
...
Then when I use it in the viewController
self.mainViewModel.localDoor = $0
But this gives me the error
Cannot assign to property: 'mainViewModel' is a get-only property
How do I set it up properly?
EDIT
Initiation of the viewModel is done with factory based dependency injection
protocol MainViewModelInjected {
var mainViewModel: MainViewModelProtocol { get }
}
extension MainViewModelInjected {
var mainViewModel: MainViewModelProtocol { return MainViewModel.instance }
}
It is totally depends on how you create object for mainViewModel.
Let's create some cases with your code:
import UIKit
typealias LocalDoorCoreDataObject = String
protocol MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject { get set }
}
extension MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject {
get { return MainViewModel.instance.localDoor }
set { localDoor = newValue }
}
}
final class MainViewModel: MainViewModelProtocol {
static let instance = MainViewModel()
var localDoor: LocalDoorCoreDataObject = LocalDoorCoreDataObject()
}
protocol MainViewModelInjected {
var mainViewModel: MainViewModelProtocol { get }
}
extension MainViewModelInjected {
var mainViewModel: MainViewModelProtocol { return MainViewModel.instance }
}
Case 1
Here we are creating an object and assigning object through getter as a closure.
So, here mainViewModel has only getter not setter i.e. it'a get-only property.
var mainViewModel: MainViewModelProtocol { MainViewModel.instance }
mainViewModel.localDoor = "assign some thing" // Error: Cannot assign to property: 'mainViewModel' is a get-only property
Case 2
Here we are directly assigning object to mainViewModelOther. So, this will be a normal property and you can make changes in properties of model.
var mainViewModelOther: MainViewModelProtocol = MainViewModel.instance
mainViewModelOther.localDoor = "assign some thing"
Case 3
You can also create a class that will hold your model object, and created another object of your class. You can make changes in properties of model.
class MyClass {
var mainViewModel: MainViewModelProtocol = MainViewModel.instance
}
let myClassObj = MyClass()
myClassObj.mainViewModel.localDoor = "assign some thing"
TL;DR
Mark your MainViewModelProtocol as being class-only (i.e. protocol MainViewModelProtocol: class { ... }) to solve the issue.
The long answer
To understand why marking your MainViewModelProtocol as class-only fixes the problem, we need to take couple steps back and look at how structs and classes are stored internally.
Case 1: MainViewModelProtocol is a reference-type (i.e. class)
First, let's consider the case where MainViewModel is a class: Classes are reference-types, which means that after you retrieve the your view model through the mainViewModel property, you have a pointer to the same view model that is stored inside your view controller. Modifying the referenced type will also modify the view model of the view itself (since they both point to the same object). As an example
/* ... */
class MainViewModel: MainViewModelProtocol { /* ... */ }
var viewModel = myViewController.mainViewModel
viewModel.localDoor = /* something */
modifies the view model that's shared between the local variable viewModel and the view controller. This is exactly what you want.
Case 2: MainViewModelProtocol is a value type (i.e. struct)
Now let's consider if the MainViewModel was a struct: structs are value-types, so retrieving the view model through the mainViewModel computed property essentially clones the view model. Now you might modify the retrieved view model as much as you like locally, but there is no way assign it back to your view controller
/* ... */
struct MainViewModel: MainViewModelProtocol { /* ... */ }
var viewModel = myViewController.mainViewModel
viewModel.localDoor = /* something */
just modifies the local copy of the view model stored in the viewModel variable. There is no way to assign the local variable back to myViewController.
Conclusion
I hope this illustrates why your pattern only works with reference-types and not value types.
Now the Swift compiler needs to be conservative and consider both cases since it doesn't know if all types conforming to MainViewModelProtocol will be classes or structs (consider public protocols vended as a library to which library-users can conform). If you add the class-constraint to the protocol, you tell the compiler that using the pattern from Case 1 is totally fine – just grab a shared instance and modify it – and that there is no need for a setter to modify the view model.
No need to mark MainViewModelProtocol as class only, when the compiler says :
Cannot assign to property: 'mainViewModel' is a get-only property
it's actually complaining about your view controller implementation. I assume mainViewModel is a computed property so you can't assign it.
I managed to reproduce your error with the following playground :
typealias LocalDoorCoreDataObject = String
protocol MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject { get set }
}
extension MainViewModelProtocol {
var localDoor: LocalDoorCoreDataObject {
get { return MainViewModel.instance.localDoor }
set { localDoor = newValue }
}
}
final class MainViewModel: MainViewModelProtocol {
static let instance = MainViewModel()
var localDoor: LocalDoorCoreDataObject = LocalDoorCoreDataObject()
}
final class FakeVC {
var mainViewModel: MainViewModelProtocol {
MainViewModel.instance
}
}
var viewController = FakeVC()
viewController.mainViewModel.localDoor = "foo" // Cannot assign to property: 'mainViewModel' is a get-only property
I got rid of the error by changing FakeVC implementation to :
final class FakeVC {
var mainViewModel: MainViewModelProtocol = MainViewModel()
}
How to use generics in own created model class?
I have one FeatureListModel class and other have FavoriteModel class. Both store the same properties, the only difference is the different class model name.
I need to display model properties value in ProductDetail controller.
How could I manage this stuff using generics?
Here is my code (Swift 4.2):
1st Model: FavoriteListModel
class FavoriteListModel {
var categoryID: Int?
var item_name: String?
var MRP: String?
}
2nd Model: FeatureListModel
class FeatureListModel {
var categoryID: Int?
var item_name: String?
var MRP: String?
}
I have 8-10 more properties, but this is just some stuff in my code.
Controller - ProductDetailTableViewController
class ProductDetailTableViewController : UITableViewController {
var productDetails: FavoriteListModel!
var productFeatureList: FeatureListModel!
fileprivate func displayProduct() {
if productDetails != nil {
title = productDetails.item_name
categoryID = productDetails.categoryID!
}else if productFeatureList != nil {
categoryID = productFeatureList.categoryID!
title = productFeatureList.item_name
}
}
and in my Product Detail Table Controller, I am accessing model objects and display on the screen.
I don't want if-else check.
You are mixing up generics and protocols. In your case a protocol is preferable.
In ProductDetailTableViewController there is an object which responds to the getter of item_name (by the way please conform to the camelCased naming convention itemName) and categoryID. The type of the object as well as the existence of other properties and functions is not significant.
Create a protocol
protocol Listable {
var itemName : String { get }
var categoryID : Int { get }
}
Then adopt the protocol in your classes (do you really need a class?) and declare at least categoryID as non-optional since you are force unwrapping the value later anyway. Don't use optionals as an alibi not to write an initializer.
class FavoriteListModel : Listable { ...
class FeatureListModel : Listable { ...
In ProductDetailTableViewController rather than two properties declare one property as Listable and instead of objective-c-ish nil checking use optional binding:
var details: Listable!
fileprivate func displayProduct() {
if let productDetails = details {
title = productDetails.itemName
categoryID = productDetails.categoryID
}
}
What you have here is not a use case for the Generics. Generics are used when you have for example a function that does exact same thing but can be used with two different parameter types. That's when you use generics.
Another concept is super class (parent class or base class) which is used when you have a class with common properties and then other classes with those properties and then extra and different unique properties which in this case, each class subclasses the parent class.
What you have here is neither of them. A good architecture for this case is just a single model type (class or struct) and using two different collections (array or set) in your view controller.
You can also create a favorite class or featured class which holds an array with your models.
I am trying to create a singleton class. For this I have tried to use two different approaches i.e.
1.First approach - Employee class contains two instance properties, a class property that contains the shared instance of the class and a private initializer, i.e.
class Employee
{
var firstName : String
var lastName : String
static let sharedInstance = Employee(firstName: "Payal", lastName: "Gupta")
private init(firstName : String, lastName : String)
{
self.firstName = firstName
self.lastName = lastName
}
}
2.Second approach - Employee2 class contains two class properties, i.e.
class Employee2
{
static var firstName : String = "SomeFirsrName"
static var lastName : String = "SomeLastName"
}
Are these two approaches of making the singleton equivalent? If yes, which one should I use and what are the differences between each of them in respect of singleton?
To make a simple singletone class in Swift you could write:
class SomeManager {
static let sharedInstance = SomeManager()
}
Usage:
SomeManager.sharedInstance
What does it mean?
Since Swift 1.2 it's possible do declare static class properties. So you can implement the singleton like this. There can only be one instance for the lifetime of the application it exists in. Singletons exist to give us singular global state.
The first approach create a singletone having a class with this initialization: Employee(firstName: "Payal", lastName: "Gupta")
The second approach don't create a singletone, is a simple class with two static declared properties.
These two approaches are not equivalent:
The first approach creates an instance of Employee object
The second approach defines two class fields; it does not create any object instances.
In other words, only the first approach creates a singleton. The second approach makes a collection of related fields at the class level.
Try this...
class Employee : NSObject
{
var firstName : String
var lastName : String
class var sharedInstance: Employee {
struct Static {
static let instance: Employee = Employee(firstName: "Payal", lastName: "Gupta")
}
return Static.instance
}
private init(firstName : String, lastName : String)
{
self.firstName = firstName
self.lastName = lastName
}
}
I've created the following classes:
class Person {
private var name: String = ""
private init(name thisName: String) {
name = thisName
}
class func CreatePerson(#type: String, name: String) -> Person! {
if type == "girl" {
return Female(name: name)
}
else if type == "boy" {
return Male(name: name)
}
return nil
}
func PrintName() {
println(name)
}
}
class Male: Person {
override init(name thisName: String) {
super.init(name: thisName)
}
deinit {
println("Deleting a male.")
}
}
class Female: Person {
deinit {
println("Deleting a female.")
}
}
class AnotherClass {
var secondPeopleArray: [Person]! = nil
func DoSomething(#people: [Person]) {
secondPeopleArray = [Person]()
for person: Person! in people {
let male: Male! = person as? Male
if male != nil {
secondPeopleArray.append(male)
}
}
}
}
As you can see, I've created a class called Person with two subclasses (Male and Female). I've also created a class (AnotherClass) that takes an array of Person classes and builds a new array of Male classes. As you can see, the
I've created the following code that builds the Person class arrays and calls the Another class function DoSomething. DoSomething allocates the array and filters the people array and appends only the Male classes to the secondPeopleArray member.
var firstPeopleArray = [Person]()
var firstPerson: Person = Person.CreatePerson(type: "boy", name: "Bill")
var secondPerson: Person = Person.CreatePerson(type: "boy", name: "Ted")
var thirdPerson: Person = Person.CreatePerson(type: "girl", name: "Nancy")
var fourthPerson: Person = Person.CreatePerson(type: "girl", name: "Diane")
firstPeopleArray.append(firstPerson)
firstPeopleArray.append(secondPerson)
firstPeopleArray.append(thirdPerson)
firstPeopleArray.append(fourthPerson)
var anotherClass: AnotherClass = AnotherClass()
anotherClass.DoSomething(people: firstPeopleArray)
anotherClass.DoSomething(people: firstPeopleArray)
for person in firstPeopleArray {
person.PrintName()
}
As you can see, the DoSomething function is called twice. This is intentional. The first time DoSomething is called, the array in the AnotherClass is created properly. The second time the DoSomething function is called, a new array is allocated (which deallocates the old array). The strange thing is the members of the array are also deallocated, which results in firstPeopleArray to have deallocated references as members of the array. Accessing the member in the for loop results in a crash.
I've found the following line is the culprit:
for person: Person! in people {
Changing this line to the following fixes the issue:
for person: Person in people {
The question is, Why? Why would the unwrapping symbol (the !) affect the references in a way in which having the symbol there results in the system thinking it needs to deallocate the member of the array when the array is deallocated and the reference clearly is used in the firstPeopleArray?