Pass array by reference between viewcontrollers in swift - ios

When using Objective-C I would pass a NSMutableArray from one view controller VC_A to another VC_B by simply assigning a property in VC_B as
VC_B.list = self.list
where self is VC_A
It allows the changes done in VC_B on the list to be seen in the list in VC_A when the view controller was say popped off the navigation stack.
However in Swift as arrays are passed by value, assigning as above does not work so I am stuck how to solve this. What would be the correct way to handle this now?

You can still do this in Swift by making the property an NSMutableArray, just as before. Foundation types still exist if you ask for them. But this is bad design in both ObjC and Swift. It creates spooky action at a distance (when things magically change values that were not part of the call) and likely breaks MVC (if the data is persistent, it should live in the model, not in the view controllers).
There are two common patterns in Cocoa: store the data in the model, or pass it via delegation.
If this array represents some kind of persistent state (such as a list of items in the system), then that belongs in the model layer, and both view controllers should read and manipulate it there rather than by communicating with each other. (See Model-View-Controller.)
If this array is a transient piece of data (such as selections from a list), then the calling VC should set itself as the delegate to the receiving VC, and when the receiving VC finishes, it should pass the data back to its delegate. (See Delegates and Data Sources.)

If you use the standard Swift Array which is a value type you have to use a wrapper or a untyped NSArray.
// a generic wrapper class
class Reference<T> {
var value: T
init(_ val: T) { value = val }
}
// Usage
class ViewController1 {
static var list = Reference<[Int]>([])
}
class ViewController2 {
static var list = Reference([3, 5, 7, 9, 11])
func passToOtherVC() {
ViewController1.list = self.list
}
}
If you want to mutate the array you should always change the value property of the Reference object.

In Swift, objects are automatically passed by reference. NSArray is an Objective C class (pass by reference), where as Array is a struct (pass by value).
So if you are working with NSMutableArray the array is already being passed by reference.

Just as a potential proof of concept that complements my comment on the question - it is possible to use the Objective-C NSMutableArray to accomplish this task:
class A {
var x: NSMutableArray = NSMutableArray(capacity: 12)
}
class B {
var y: NSMutableArray!
}
let a = A()
let b = B()
b.y = a.x
b.y[0] = 123
assert(a.x[0] === b.y[0])
Still, this is approach is not following the Swift style of handling data structures IMO.

Related

I need to reorganize my solution for my iOS app due to redundant data

I realized I'm not approaching this correctly when I have 3 copies of the same object, so I need some guidance towards structuring this problem. I'll do my best to explain the model at the moment. For simplicity VC = View Controller
Tl;dr:
MainVC -> MapVC -> DetailsVC <- FavoritesVC
Tab1 Tab2
MyClass objects consist of a 'favorite' bool flag and a unique id String
MainVC makes array of MyClass, gives array to MapVC after construction
MapVC constructs dict mapping Pins to MyClass, user selects pin, corresponding
MyClass sent to DetailsVC in segue
DetailsVC gets copy of MyClass object, displays its details, can mark as favorite
Since marking as favorite occurs on the copied object, MapVC doesn't realize it's
marked as favorite
It starts with the MainVC. This has a container view, which switches between a MapVC and a TableVC (we'll focus on the MapVC). The MainVC uses an XML parser object to sort through a response and create custom MyClass objects and appends them to
myList //(an array of MyClass)
The MapVC also has an array of MyClass called mapList. When MainVC is done updating myList, it also sets
mapPage.mapList = myList
MapVC also has a dictionary that maps Pins to MyClass, so that when a user selects a pin I can see which MyClass it corresponds to.
The map sends the selected MyClass to the DetailsVC in a segue as such
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let dest = segue.destinationViewController as? DetailsTableViewController{
dest.myobject = mapList[selectedRow]
// selectedRow is correctly set elsewhere, no bugs
}
}
The DetailsVC (which only knows about the single selected myObject) displays the passed (or rather copied) object's details. You can then mark it as a favorite, which sets the object's favorite flag to true. To save favorites, I store a dictionary of MyClass objects that have been marked as favorite to UserDefaults. I use a dictionary instead of an array so I can look up and remove myObjects when a user unfavorites them
var faveArray = [String : MyClass]()
if let unarchivedObject = NSUserDefaults.standardUserDefaults().objectForKey(Constants.PreferenceKeys.favorites) as? NSData {
faveArray = NSKeyedUnarchiver.unarchiveObjectWithData(unarchivedObject) as! [String : MyClass]
}
if isFavorite{
faveArray[myobject.id] = myobject
}
else{
faveArray.removeValueForKey(myobject.id)
}
defaults.setObject(NSKeyedArchiver.archivedDataWithRootObject(faveArray as NSDictionary), forKey: Constants.PreferenceKeys.favorites)
The FavoritesVC loads the UserDefault dictionary and shows each myObject in a table view. You can also select a myObject here, which will segue to the same DetailsVC where they can unfavorite/refavorite continuously (the favorite button updates instantly, so if you favorite, it turns into unfavorite).
The problem is that when they unfavorite something from the FavoritesVC, it's a different copy MyClass object than the one sitting in the MapVC (same object, different copies), so when you go back to the map it still says "Unfavorite". The broader issue is that I have 3 of the same MyClass lists, 2 dictionaries, and 3 copies of the same MyClass object (1 in MapVC, 1 in ListVC, 1 in FavoritesVC) and I realize this isn't good
I'm thinking of moving all of the data objects to a static class (is that even possible?) so that all controllers have access to the same MyClass array and objects. Is there a better or standard approach?
What you want is a singleton. This is an instance of a class, that is initialized once and, when accessed, is the same instance (stored in the same block of memory, and the equivalent of a static instance). There are multiple singleton patterns, and as I mentioned in my comment, if you want to not accept my answer and simply ask about singletons in Swift, that is okay! It's an open question. Here is an example of the singleton pattern I use, for a MyClass, class:
private let mySingleton = MyClass()
class MyClass {
class var shared : MyClass {
return mySingleton
}
}
Add properties, etc... to this class, of course. To access the SAME instance of this class in any VC, use the following code:
let globalMyClass = MyClass.shared
That's it! Obviously, name your class and var whatever you like. And again, there is another solid approach to global vars in Swift. Use this for now, but learn when you have time. Good luck!

Swift Generics - Changing Values and Passing Between View Controllers

I'd like to use a variable of type Any in order to pass different classes to a child view controller. For example, I might have Table, Chair and Plate objects. In my child view controller, I'd like to change the value of one of their properties (e.g. Table.legs was 4, change that to 3), and for the parent view controller to be able to read that from the child VC. I'll use a Protocol to update the parent VC that can pop the child after reading the updated object.
In order to work out how the passing of generics might work, I wrote this code in a playground:
class Table {
var legs: Int
var material: String
init(legs: Int, material: String) {
self.legs = legs
self.material = material
}
}
var anObject: Any?
// set up the Table
let aTable = Table(legs: 4, material: "Oak")
// set anObject to be the Table
anObject = aTable
// get the object and change it
let bTable = anObject as! Table
bTable.legs = 3
// get the original object and cast it as a Table
let cTable = anObject as! Table
print(cTable.legs) // prints 3
I believe from this, I should be able to do what I describe above without any issues, because the original object reference (anObject) is updated whenever I update a variable referencing it.
My question is this - are there any pitfalls I should be aware of when adopting this approach? It appears that rather than creating a copy of an object, swift will always create a pointer to the original object; are there any situations when that does not hold true?
Appologies if this is seen as a fairly basic question, but this is all fairly new to me - many thanks in advance!
Class are reference types as you noticed, if you assign an instance of the class to a variable, it keep the reference (the pointer in memory) to the instance and not the value copy.
Struct are value types, if you copy the instance of the structure to another variable, it's just copied to the variable.

Assign Array type reference in Swift [duplicate]

When using Objective-C I would pass a NSMutableArray from one view controller VC_A to another VC_B by simply assigning a property in VC_B as
VC_B.list = self.list
where self is VC_A
It allows the changes done in VC_B on the list to be seen in the list in VC_A when the view controller was say popped off the navigation stack.
However in Swift as arrays are passed by value, assigning as above does not work so I am stuck how to solve this. What would be the correct way to handle this now?
You can still do this in Swift by making the property an NSMutableArray, just as before. Foundation types still exist if you ask for them. But this is bad design in both ObjC and Swift. It creates spooky action at a distance (when things magically change values that were not part of the call) and likely breaks MVC (if the data is persistent, it should live in the model, not in the view controllers).
There are two common patterns in Cocoa: store the data in the model, or pass it via delegation.
If this array represents some kind of persistent state (such as a list of items in the system), then that belongs in the model layer, and both view controllers should read and manipulate it there rather than by communicating with each other. (See Model-View-Controller.)
If this array is a transient piece of data (such as selections from a list), then the calling VC should set itself as the delegate to the receiving VC, and when the receiving VC finishes, it should pass the data back to its delegate. (See Delegates and Data Sources.)
If you use the standard Swift Array which is a value type you have to use a wrapper or a untyped NSArray.
// a generic wrapper class
class Reference<T> {
var value: T
init(_ val: T) { value = val }
}
// Usage
class ViewController1 {
static var list = Reference<[Int]>([])
}
class ViewController2 {
static var list = Reference([3, 5, 7, 9, 11])
func passToOtherVC() {
ViewController1.list = self.list
}
}
If you want to mutate the array you should always change the value property of the Reference object.
In Swift, objects are automatically passed by reference. NSArray is an Objective C class (pass by reference), where as Array is a struct (pass by value).
So if you are working with NSMutableArray the array is already being passed by reference.
Just as a potential proof of concept that complements my comment on the question - it is possible to use the Objective-C NSMutableArray to accomplish this task:
class A {
var x: NSMutableArray = NSMutableArray(capacity: 12)
}
class B {
var y: NSMutableArray!
}
let a = A()
let b = B()
b.y = a.x
b.y[0] = 123
assert(a.x[0] === b.y[0])
Still, this is approach is not following the Swift style of handling data structures IMO.

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?

When to cast in swift

So this is probably a super simple question, but I have looked and cannot find the answer.
Let's say I have made a class and I fill an NSMutableArray with instances of this class and that is it. I want to use a for in loop to loop through. What is the easiest way to cast so I don't have to everytime I want to call the current instance in the loop?
Here it is shown:
for m in objects {
m.randomVar = "hello"
}
where m is my custom object that has a randomVar as a String and objects is an array of custom objects.
Of course the code above will not execute because xcode assumes m is an [AnyObject?]. Where and how would be the best was to get m in its own class. Let's assume we are changing more than one variable so casting everytime would not be optimal.
Sorry if I am unclear. Thank you
Avoid the problem.
Don't use NSArray/NSMutableArray — just use let/var variables of pure Swift arrays, such as [MyClass].
Or, if your array is coming from Objective-C, in Xcode 7+ you can use the NSArray<MyClass *> * syntax to expose it to Swift as [MyClass] rather than [AnyObject].
Cast the array.
Use objects as! [MyClass] (unsafe) if you are absolutely sure it contains instances of MyClass.
Cast the variable.
for m in objects {
if let m = m as? MyClass { ...
// or
let myobj = m as! MyClass // unsafe
or in Swift 2 (Xcode 7)
for case let m as MyClass in objects {
// this is executed only for objects which are instances of MyClass,
// ignoring other objects in the array.
}

Resources