persist data in a class for reuse by another function - ios

Here is a swift class file:
import Foundation
class DataPreparation {
// Variables
var userCountries = [String]() //Just 1 or 2 countries
var correspondingFullArrays = [[String]]()
//Get and set raw user countries from current image
func getUserCountries(countries: [String]) -> [String] {
userCountries = countries
return userCountries
}
func getCorrespondingFullArraysToUserCountries() {
println(userCountries) //Is empty
}
On a separate file viewController, the 2 methods lisOfCountries and getCorrespondingFullArraysToUserCountries are invoked independently one after the other:
DataPreparation().getUserCountries(otherArray[0])
DataPreparation().getCorrespondingFullArraysToUserCountries()
Problem is that second method can't access userCountries data set by first method because it is empty.
My guess, and you will correct me if i'm wrong, is that variables get destroyed after each method call.
So how are we supposed to handle small data persistance from one function to another, when they are called separatly ? I could of course regroup all in a single function, but is this the good way of doing ?

In each line of this code:
DataPreparation().getUserCountries(otherArray[0])
DataPreparation().getCorrespondingFullArraysToUserCountries()
you are creating a new instance of DatePreparation, and then calling a method on it. Since you do not assign the instance to a variable, the instance is destroyed as soon as it goes out of scope, which in this case corresponds to the same line where it is instantiated.
You should create an instance (and not 2), and assign to a variable. Then, you can call methods on that instance:
var dataPreparation = DataPreparation()
dataPreparation.getUserCountries(otherArray[0])
dataPreparation.getCorrespondingFullArraysToUserCountries()

When you are calling the function getCorrespondingFullArraysToUserCountries on DataPreparation() you are creating another instance each time.You are creating DataPreparation() instance each time. instead use
//create instance only first time
var dataPreparation:DataPreparation = DataPreparation()
//call methods on same instance
dataPreparation.getUserCountries(otherArray[0])
dataPreparation.getCorrespondingFullArraysToUserCountries()

Related

Why can't I call .send on a publisher in a class from a second class and have it update the subscription in third class?

I have this class in LRVDetails.swift
class LRVDetails {
var heading = CurrentValueSubject<String, Never>(K.LRV.Register)
// other publishers
}
I have this assign in LRVViewController.swift
private var details = LRVDetails()
private var subs = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
details
.heading
.assign(to: \.text!, on: heading)
.store(in: &subs)
}
and in LRVCoordinator.swift I have
private var details = LRVDetails()
func UserDidPressButton() { // this is a delegate method from the VC
details.heading.send("New Heading")
}
I thought that because LRVDetails is a class, it only stores a reference. so if I simply instantiate it in each file and then call send() on it, the publisher should update, and then emit the value to the LRVViewController subscription and update the heading.
But it does nothing. The subscriber never receives a value ( I checked with a .print() operator ). When i call lrvController?.details.send() -- without the private tag -- in the coordinator, it works fine.
Is this because the subscriber is stored in the 'subs' variable in LRVCoordinator, and thus it has to be updated with the subscriber in LRVViewController? That's my best bet.
If not, why doesn't this work?.
Thanks!
Edit:
If they are different instances, why does this code print two?
class myClass {
var int = 1
}
let test1 = myClass()
let test2 = myClass()
test1.int = 2
print(test2.int)
// prints 2
I thought that because LRVDetails is a class, it only stores a reference. so if I simply instantiate it in each file and then call send() on it, the publisher should update
Your understanding is incorrect. You have created two different LRVDetails objects. (The syntax LRVDetails() creates a new object) One in the VC, and the other in the coordinator. You are correct that it stores references though, so it would work if you actually make the two details variable refer to the same LRVDetails.
But actually, you don't seem to need a details variable in the coordinator. Since the coordinator is the delegate of the VC, you could just pass the LRVDetails via the delegate method:
func UserDidPressButton(_ viewController: LRVViewController) {
viewController.details.heading.send("New Heading")
}
Needless to say, you should also change the caller of UserDidPressButton to pass self as the parameter.

It seems like each View Controller is creating a unique instance of my struct - which I don't want?

I'm creating an app in Swift 2.0 xCode7 using the Tabbed-Application template, with each screen having a separate ViewController. I have a struct to manage a variable I want to be accessed by all view controllers. I created the instance of the struct in the first view controller. I'm able to access the struct data and methods in the other views, but if update the data in one view, it doesn't change for all... It's acting as if each View Controller is creating its own instance on its own. I don't want that. I want each ViewController to share the same updated data in the struct. Does this mean that I should be creating a Singleton Pattern? Or, something else? I'm quite new at this, so thanks for your patience.
I'm not sure how exactly you access the structure but it might be that you only need to change struct to class because structs are value types so if you assign it or pass into a method it is copied whereas an instance of a class will avoid copying
Because you didn't give me any code, this is just my guess.
Structs are different from classes. The former stores values and the latter stores references. Let's look at this code:
var obj = SomethingCool()
obj.somethingCooler = 20
var obj2 = obj
obj2.somethingCooler = 10
If SomethingCool were a struct, obj.somethingCooler would still be 20 but obj2.somethingCooler would be 10. On the other hand, if SomethingCool were a class, both obj.somethingCooler and obj2.somethingCooler would be 20.
This is because the third line. The third line is VERY important. If SomethingCool were a struct, the values stored in obj will be copied to obj2. i.e. Two set of independent values would be created. If it were a class, the object that obj will also be referenced by obj2. i.e. There would still be just one object.
Now that you know the difference, I can tell you that you must have done something like the third line in your view controllers, haven't you?
To solve this problem, you can change from a struct to a class. Or you can create something like this:
public class SomeName {
static var myData: SomeTypeOfStruct {
return something
}
}
If you are so hellbent on keeping it as a struct you could do something that swift actually helps u out with.....AppDelegate!
The appdelegate.swift is a single instance object for any application. So in case you want to save a value that you need to access throughout the application or update throughtout the application, you might want to use AppDelegate.
E.g.
In FirstViewController.swift set the AppDelegate variable that you want to reflect on the remaining screens:
(UIApplication.sharedApplication().delegate as! AppDelegate).commonVariableName = NewValueYouWant;
In the SecondViewController.swift, take up that value from the AppDelegate
var updatedValue = (UIApplication.sharedApplication().delegate as! AppDelegate).commonVariableName;
Again...as #Sweeper said, you can always switch to class which is more reliable and used to achieve something like this.
It's acting as if each View Controller is creating its own instance on
its own.
It's all explained in Apple's Swift guide:
Structs:
struct Dog {
var name: String
}
var d1 = Dog(name: "Rover")
var d2 = d1
d2.name = "Sally"
print(d1.name)
print(d2.name)
--output:--
Rover
Sally
Classes:
class Cat {
var name: String = ""
}
var c1 = Cat()
c1.name = "Kitty"
var c2 = c1
c2.name = "Gerald"
print(c1.name)
print(c2.name)
--output:--
Gerald
Gerald
See the difference?

Sharing data between VIewControllers - iOS

For any object created I generally use two two scopes 1) Singleton 2) {local scope}. I am looking for something in between.
Say I have one object that 5 view controllers are editing. I want to share an object between view controllers without having to pass it between view controllers. But it should not also live throughout application since once I am done editing the object i don't need it anymore.
I don't want to inherit all view controller from another class an create a variable there. Since view controller are reusable for different objects. I want to create an object that comes to life before launch of first view controller, lives throughout the scope of 5 view controllers and then dies after I have saved it someway. Is there anyways I could do this in iOS.
An alternative is to use your AppDelegate. Within it you can declare a global var than 2 functions, a first one to get the current value and another one to set the value.
It might give something like this:
// Get AppDelegate instance
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate;
// Use your getter to get the current value
var something = appDelegate.getYourStuff();
// Or use a setter to set it, a modifier to modify oit
appDelegate.setYourStuff(yourStuff);
appDelegate.modifiyYourStuffAttribute(newAttributeValue);
Don't realize if such a method is a bad practice or not, but it works for me.
Open to other suggestions!
As Mat said you can do is in that what. For me better is to create specific class for that that will do one particular job.
class EditingSession {
class Factory {
private static let session = EditingSession() //do it lazy
static func create() -> EditingSession {
return session
}
}
func openSession() {
}
func endSession {
}
func getData () -> AnyObject {
...
}
}
In editing session create private initializer. Factory should give the shared instance.
You can pass this class to your ViewControllers and manipulate with the data. You can inject it or just set as property in your VC or VM.
If you are done with editing you should end session and clear data or other stuff.

Methods in Swift

I have 2 classes in Swift in different Swift files, and I'm trying to call a method from the other class, and pass an integer as an argument. I have the following:
Class 1:
Inschatting.wentBack(Punten)
Class 2:
class Inschatting : UIViewController {
var Punten:Int = 0;
#IBOutlet var inschattingAdvies: UILabel!
func wentBack(Punten:Int) {
self.inschattingAdvies.text = Punten
}
}
Given error: Cannot convert value of type "Int" to expected argument type 'Inschatting'
Bonus question: Class 2 also complaints about the fact I want to put down a String, but it's clearly an Int
When you want to call your wentBack()-Func like above you should declare it as a class function... otherwise you should create an instance of Inschatting.
the problem is that Inschatting should declare the function the following way:
static func wentBack(Punten:Int) {
self.inschattingAdvies.text = Punten
}
But the problem is that, that you have an instance value the wentBack function.
self.inschattingAdvies
Now what you need to do is to make a decision:
1. you should call the wentback function on a Inschatting instance.
In Class A:
let instance = Inschatting()
instance.wentBack(5)
2. You should remove the self.inschattingAdvies from wentBack.
Answer to your bonus question:
"Punten" does not seem to be an integer, or an enum value at all to me.
Since your Inschatting class is a UIViewController, I suspect you want to display the data from your 1st class in your UIViewController. If this is indeed true your mistake is that you are using a Class instead of an instance of the class.
If you indeed want to display the data from your 1st class in your 2nd class (The VC), you will need to create a class instance of your second class.
let inschattingVC = Inschatting()
this way you can use inschattingVC.wentBack(someRandInt) to call your function and set the text accordingly.
Secondly your Punten in Inschatting.wentBack(Punten) is also probably not a variable but again an instance (or you should rename it. Variables should start with lowercase letters!!!).
So if you are creating a class instance using Inschatting() you should be able to use .wentBack(12345) to set the text in your class instance.
First you say :
#IBOutlet var inschattingAdvies: UILabel!
so inschattingAdvies is a UILabel
Then you have a function :
func wentBack(Punten:Int) {
self.inschattingAdvies.text = Punten
}
where you have Punten which is an integer. And then you try to set the text property of your label to punten.
Your code should be :
func wentBack(Punten:Int) {
self.inschattingAdvies.text = String(Punten)
}
note the cast to a string.
Not sure if that's the error you were getting, but it's definitely also an error.

Using one variable as a reference to another in swift

I keep all of my users data in a User object and access it when I need it, as demonstrated in the example below on line 1. The inconvenience I encounter when using this method of data storing is when I need to edit these objects. Below, I have a Time object which belongs to the current user. Ideally, I would set the current user's times to a variable to make it more manageable, and then edit the times using that variable. The problem is, the time variable doesn't refer to the User.current().times[0] variable. Is there a way of using a variable essentially as a shortcut?
let time = User.current().times[0]
time.name = titleLabel.text
time.start = start
time.end = end
time.save()
User.current().times is of type [Time] which is a subclass of NSObject.
The declaration of the User class as requested with the current() function.
var this_user: User?
class User: NSObject {
var times = [Time]()
class func current() -> User {
if this_user == nil {
this_user = User()
}
return this_user!
}
}
Classes Are Reference Types
Unlike value types, reference types are not copied when they are assigned to a variable or constant, or when they are passed to a function. Rather than a copy, a reference to the same existing instance is used instead.
Just check User().current().times[0]. Is it a class or only a value? If you make an user object and holds that reference then you can get those.
For more Info see this.
Your code does work. The fault is in your testing procedure.
To prove this, I set up mock versions of your classes at the top level of a file, like this:
var this_user : User?
class Time : NSObject {
var start : String!
}
class User: NSObject {
var times = [Time]()
class func current() -> User {
if this_user == nil {
this_user = User()
}
return this_user!
}
}
Then, elsewhere, I ran this code:
let cur = User.current()
cur.times.append(Time()) // to give us a first Time in times
let time = User.current().times[0]
time.start = "Testing" // so let's see if we can write into it!
println(User.current().times[0].start) // Testing
This proves that when I set time.start it does reflect back into the Time that is in the times array of the current User. Therefore, I conclude that there is something wrong with the way you are testing your code. (Either that, or your description of your objects is inaccurate.)

Resources